diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 6db040ae..a7c2e6b4 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -7,15 +7,18 @@ objects = { /* Begin PBXBuildFile section */ + 1808BEBC2BA41C3200129230 /* CarouselScrollbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1808BEBB2BA41C3200129230 /* CarouselScrollbar.swift */; }; + 1808BEC02BA456B700129230 /* CarouselScrollbarChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 1808BEBF2BA456B700129230 /* CarouselScrollbarChangeLog.txt */; }; 1832AC572BA0791D008AE476 /* BreadcrumbCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1832AC562BA0791D008AE476 /* BreadcrumbCellItem.swift */; }; 18450CF12BA1B19C009FDF2A /* BreadcrumbsChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 18450CF02BA1B19C009FDF2A /* BreadcrumbsChangeLog.txt */; }; 1855EC662BAABF2A002ACAC2 /* BreadcrumbItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1855EC652BAABF2A002ACAC2 /* BreadcrumbItemModel.swift */; }; - 1808BEBC2BA41C3200129230 /* CarouselScrollbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1808BEBB2BA41C3200129230 /* CarouselScrollbar.swift */; }; - 1808BEC02BA456B700129230 /* CarouselScrollbarChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 1808BEBF2BA456B700129230 /* CarouselScrollbarChangeLog.txt */; }; 186B2A8A2B88DA7F001AB71F /* TextAreaChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 186B2A892B88DA7F001AB71F /* TextAreaChangeLog.txt */; }; + 186D13CB2BBA8B1500986B53 /* DropdownSelect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 186D13CA2BBA8B1500986B53 /* DropdownSelect.swift */; }; + 186D13CF2BBC36EF00986B53 /* DropdownSelectChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 186D13CE2BBC36EE00986B53 /* DropdownSelectChangeLog.txt */; }; 18792A902B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18792A8F2B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift */; }; 18A65A022B96E848006602CC /* Breadcrumbs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A65A012B96E848006602CC /* Breadcrumbs.swift */; }; 18A65A042B96F050006602CC /* BreadcrumbItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A65A032B96F050006602CC /* BreadcrumbItem.swift */; }; + 18B463A42BBD3C46005C4528 /* DropdownOptionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B463A32BBD3C46005C4528 /* DropdownOptionModel.swift */; }; 18BDEE822B75316E00452358 /* ButtonIconChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 18BDEE812B75316E00452358 /* ButtonIconChangeLog.txt */; }; 445BA07829C07B3D0036A7C5 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 445BA07729C07B3D0036A7C5 /* Notification.swift */; }; 44604AD429CE186A00E62B51 /* NotificationButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44604AD329CE186A00E62B51 /* NotificationButtonModel.swift */; }; @@ -192,15 +195,18 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 1808BEBB2BA41C3200129230 /* CarouselScrollbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselScrollbar.swift; sourceTree = ""; }; + 1808BEBF2BA456B700129230 /* CarouselScrollbarChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CarouselScrollbarChangeLog.txt; sourceTree = ""; }; 1832AC562BA0791D008AE476 /* BreadcrumbCellItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbCellItem.swift; sourceTree = ""; }; 18450CF02BA1B19C009FDF2A /* BreadcrumbsChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = BreadcrumbsChangeLog.txt; sourceTree = ""; }; 1855EC652BAABF2A002ACAC2 /* BreadcrumbItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbItemModel.swift; sourceTree = ""; }; - 1808BEBB2BA41C3200129230 /* CarouselScrollbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselScrollbar.swift; sourceTree = ""; }; - 1808BEBF2BA456B700129230 /* CarouselScrollbarChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CarouselScrollbarChangeLog.txt; sourceTree = ""; }; 186B2A892B88DA7F001AB71F /* TextAreaChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = TextAreaChangeLog.txt; sourceTree = ""; }; + 186D13CA2BBA8B1500986B53 /* DropdownSelect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropdownSelect.swift; sourceTree = ""; }; + 186D13CE2BBC36EE00986B53 /* DropdownSelectChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = DropdownSelectChangeLog.txt; sourceTree = ""; }; 18792A8F2B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonIconBadgeIndicatorModel.swift; sourceTree = ""; }; 18A65A012B96E848006602CC /* Breadcrumbs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Breadcrumbs.swift; sourceTree = ""; }; 18A65A032B96F050006602CC /* BreadcrumbItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbItem.swift; sourceTree = ""; }; + 18B463A32BBD3C46005C4528 /* DropdownOptionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropdownOptionModel.swift; sourceTree = ""; }; 18BDEE812B75316E00452358 /* ButtonIconChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = ButtonIconChangeLog.txt; sourceTree = ""; }; 445BA07729C07B3D0036A7C5 /* Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = ""; }; 44604AD329CE186A00E62B51 /* NotificationButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationButtonModel.swift; sourceTree = ""; }; @@ -388,6 +394,25 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 1808BEBA2BA41B1D00129230 /* CarouselScrollbar */ = { + isa = PBXGroup; + children = ( + 1808BEBB2BA41C3200129230 /* CarouselScrollbar.swift */, + 1808BEBF2BA456B700129230 /* CarouselScrollbarChangeLog.txt */, + ); + path = CarouselScrollbar; + sourceTree = ""; + }; + 186D13C92BBA8A3500986B53 /* DropdownSelect */ = { + isa = PBXGroup; + children = ( + 186D13CA2BBA8B1500986B53 /* DropdownSelect.swift */, + 18B463A32BBD3C46005C4528 /* DropdownOptionModel.swift */, + 186D13CE2BBC36EE00986B53 /* DropdownSelectChangeLog.txt */, + ); + path = DropdownSelect; + sourceTree = ""; + }; 18A65A002B96E7E1006602CC /* Breadcrumbs */ = { isa = PBXGroup; children = ( @@ -400,15 +425,6 @@ path = Breadcrumbs; sourceTree = ""; }; - 1808BEBA2BA41B1D00129230 /* CarouselScrollbar */ = { - isa = PBXGroup; - children = ( - 1808BEBB2BA41C3200129230 /* CarouselScrollbar.swift */, - 1808BEBF2BA456B700129230 /* CarouselScrollbarChangeLog.txt */, - ); - path = CarouselScrollbar; - sourceTree = ""; - }; 445BA07629C07ABA0036A7C5 /* Notification */ = { isa = PBXGroup; children = ( @@ -557,6 +573,7 @@ EA0FC2BE2912D18200DF80B4 /* Buttons */, 1808BEBA2BA41B1D00129230 /* CarouselScrollbar */, EAF7F092289985E200B287F5 /* Checkbox */, + 186D13C92BBA8A3500986B53 /* DropdownSelect */, EA985BF3296C609E00F2FF2E /* Icon */, EA3362412892EF700071C351 /* Label */, 44604AD529CE195300E62B51 /* Line */, @@ -1044,6 +1061,7 @@ EAEEECA02B1F908200531FC2 /* BadgeIndicatorChangeLog.txt in Resources */, EAA5EEB928ECD24B003B3210 /* Icons.xcassets in Resources */, EAEEECA92B1F969700531FC2 /* TooltipChangeLog.txt in Resources */, + 186D13CF2BBC36EF00986B53 /* DropdownSelectChangeLog.txt in Resources */, EAEEEC9C2B1F8F0700531FC2 /* TextLinkCaretChangeLog.txt in Resources */, EAA5EEE428F5B855003B3210 /* VerizonNHGDS-Light.otf in Resources */, 71B5FCBB2B95A0CA00269BCC /* PaginationChangeLog.txt in Resources */, @@ -1102,6 +1120,7 @@ EAF7F0AF289B144C00B287F5 /* UnderlineLabelAttribute.swift in Sources */, EA0D1C412A6AD61C00E5C127 /* Typography+Additional.swift in Sources */, EAC925842911C63100091998 /* Colorable.swift in Sources */, + 18B463A42BBD3C46005C4528 /* DropdownOptionModel.swift in Sources */, EAACB89A2B927108006A3869 /* Valuing.swift in Sources */, EAE785312BA0A438009428EA /* UIImage+Helper.swift in Sources */, EAB5FEF5292D371F00998C17 /* ButtonBase.swift in Sources */, @@ -1165,6 +1184,7 @@ 1808BEBC2BA41C3200129230 /* CarouselScrollbar.swift in Sources */, EAF978212A99035B00C2FEA9 /* Enabling.swift in Sources */, EA5E3058295105A40082B959 /* Tilelet.swift in Sources */, + 186D13CB2BBA8B1500986B53 /* DropdownSelect.swift in Sources */, EA89201528B56CF4006B9984 /* RadioBoxGroup.swift in Sources */, EA0B180A2AA78F9000F2D0CD /* UIEdgeInsets.swift in Sources */, EA985C1D296CD13600F2FF2E /* BundleManager.swift in Sources */, @@ -1353,7 +1373,7 @@ BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 55; + CURRENT_PROJECT_VERSION = 58; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1390,7 +1410,7 @@ BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 55; + CURRENT_PROJECT_VERSION = 58; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; diff --git a/VDS/Components/BadgeIndicator/BadgeIndicator.swift b/VDS/Components/BadgeIndicator/BadgeIndicator.swift index 027af45f..b74aec3b 100644 --- a/VDS/Components/BadgeIndicator/BadgeIndicator.swift +++ b/VDS/Components/BadgeIndicator/BadgeIndicator.swift @@ -82,14 +82,14 @@ open class BadgeIndicator: View { /// Dynamic TextStyle for the size. public var textStyle: TextStyle { - let style = TextStyle.bodySmall + var font: Font = .edsRegular var pointSize: CGFloat = VDSTypography.fontSizeBody12 var letterSpacing: CGFloat = 0.0 + switch self { case .xxlarge: pointSize = VDSTypography.fontSizeTitle24 - letterSpacing = VDSTypography.letterSpacingWide - + case .xlarge: pointSize = VDSTypography.fontSizeTitle20 @@ -100,12 +100,14 @@ open class BadgeIndicator: View { case .medium: pointSize = VDSTypography.fontSizeBody14 letterSpacing = VDSTypography.letterSpacingWide - case .small: - pointSize = VDSTypography.fontSizeBody12 + case .small: + font = .etxRegular + pointSize = VDSTypography.fontSizeBody12 } + return TextStyle(rawValue: "\(self.rawValue)BadgeIndicator", - fontFace: style.fontFace, + fontFace: font, pointSize: pointSize, lineHeight: 0, letterSpacing: letterSpacing) @@ -113,16 +115,16 @@ open class BadgeIndicator: View { //EdgeInsets for the label. public var edgeInset: UIEdgeInsets { - var horizontalPadding: CGFloat = VDSLayout.space1X - let verticalPadding: CGFloat = 0 + var horizontalPadding: CGFloat = 0.0 + let verticalPadding: CGFloat = 0.0 switch self { case .xxlarge: horizontalPadding = VDSLayout.space2X - case .xlarge, .large,.medium: + case .xlarge, .large, .medium: horizontalPadding = 6.0 case .small: - break + horizontalPadding = VDSLayout.Spacing.space1X.value } return .axis(horizontal: horizontalPadding, vertical: verticalPadding) } @@ -266,10 +268,10 @@ open class BadgeIndicator: View { /// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations. open override func setup() { super.setup() - + isAccessibilityElement = true + addSubview(badgeView) badgeView.addSubview(label) - accessibilityElements = [label] heightConstraint = badgeView.heightGreaterThanEqualTo(constant: badgeSize) widthConstraint = badgeView.widthGreaterThanEqualTo(constant: badgeSize) @@ -345,6 +347,15 @@ open class BadgeIndicator: View { layoutIfNeeded() } + open override func updateAccessibility() { + super.updateAccessibility() + if kind == .numbered { + accessibilityLabel = label.text + } else { + accessibilityLabel = "Simple" + } + } + open override func layoutSubviews() { super.layoutSubviews() @@ -354,10 +365,9 @@ open class BadgeIndicator: View { //border if hideBorder { - layer.borderWidth = 0 + removeBezierPathBorder() } else { - layer.borderWidth = borderWidth - layer.borderColor = borderColorConfiguration.getColor(surface).cgColor + bezierPathBorder(borderColorConfiguration.getColor(surface), width: borderWidth) } //dot @@ -442,5 +452,4 @@ open class BadgeIndicator: View { let maxNumber = Int(pow(10.0, Double(maxDigits))) - 1 return min(number, maxNumber) } - } diff --git a/VDS/Components/DropdownSelect/DropdownOptionModel.swift b/VDS/Components/DropdownSelect/DropdownOptionModel.swift new file mode 100644 index 00000000..c82519eb --- /dev/null +++ b/VDS/Components/DropdownSelect/DropdownOptionModel.swift @@ -0,0 +1,22 @@ +// +// DropdownOptionModel.swift +// VDS +// +// Created by Kanamarlapudi, Vasavi on 03/04/24. +// + +import Foundation + +extension DropdownSelect { + public struct DropdownOptionModel { + + /// Text that goes as option to DropdownSelect + public var text: String + public var value: String + + public init(text: String, value: String? = nil) { + self.text = text + self.value = value ?? text + } + } +} diff --git a/VDS/Components/DropdownSelect/DropdownSelect.swift b/VDS/Components/DropdownSelect/DropdownSelect.swift new file mode 100644 index 00000000..a56eb783 --- /dev/null +++ b/VDS/Components/DropdownSelect/DropdownSelect.swift @@ -0,0 +1,290 @@ +// +// DropdownSelect.swift +// VDS +// +// Created by Kanamarlapudi, Vasavi on 28/03/24. +// + +import Foundation +import UIKit +import VDSColorTokens +import VDSFormControlsTokens +import Combine + +/// A dropdown select is an expandable menu of predefined options that allows a customer to make a single selection. +@objc(VDSDropdownSelect) +open class DropdownSelect: EntryFieldBase { + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + required public init() { + super.init(frame: .zero) + } + + public override init(frame: CGRect) { + super.init(frame: .zero) + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + } + + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + /// If true, the label will be displayed inside the dropdown containerView. Otherwise, the label will be above the dropdown containerView like a normal text input. + open var showInlineLabel: Bool = false { didSet { setNeedsUpdate() }} + + /// Allows unique ID to be passed to the element. + open var selectId: Int? { didSet { setNeedsUpdate() }} + + /// Current SelectedItem + open var selectedItem: DropdownOptionModel? { + guard let selectId else { return nil } + return options[selectId] + } + + /// Array of options to show + open var options: [DropdownOptionModel] = [] { didSet { setNeedsUpdate() }} + + /// A callback when the selected option changes. Passes parameters (option). + open var onItemSelected: ((Int, DropdownOptionModel) -> Void)? + + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + internal var minWidthDefault = 66.0 + internal var minWidthInlineLabel = 102.0 + + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + open var inlineDisplayLabel = Label().with { + $0.textAlignment = .left + $0.textStyle = .boldBodyLarge + $0.lineBreakMode = .byCharWrapping + $0.sizeToFit() + } + + open var selectedOptionLabel = Label().with { + $0.setContentCompressionResistancePriority(.required, for: .vertical) + $0.textAlignment = .left + $0.textStyle = .bodyLarge + $0.lineBreakMode = .byCharWrapping + } + + open var dropdownField = UITextField().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.tintColor = UIColor.clear + $0.font = TextStyle.bodyLarge.font + } + + open var optionsPicker = UIPickerView() + + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + internal var inlineWidthConstraint: NSLayoutConstraint? + + //-------------------------------------------------- + // MARK: - Configuration Properties + //-------------------------------------------------- + internal override var containerSize: CGSize { CGSize(width: showInlineLabel ? minWidthInlineLabel : width ?? minWidthDefault, height: 44) } + + internal let iconColorConfiguration = ControlColorConfiguration().with { + $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .normal) + $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled) + $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .error) + } + + //-------------------------------------------------- + // MARK: - Overrides + //-------------------------------------------------- + + /// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations. + open override func setup() { + super.setup() + + accessibilityLabel = "Dropdown Select" + + // stackview for controls in EntryFieldBase.controlContainerView + let controlStackView = UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.axis = .horizontal + $0.spacing = VDSFormControls.spaceInset + } + controlContainerView.addSubview(controlStackView) + controlStackView.pinToSuperView() + + controlStackView.addArrangedSubview(dropdownField) + controlStackView.addArrangedSubview(inlineDisplayLabel) + controlStackView.addArrangedSubview(selectedOptionLabel) + + controlStackView.setCustomSpacing(0, after: dropdownField) + controlStackView.setCustomSpacing(VDSLayout.Spacing.space1X.value, after: inlineDisplayLabel) + controlStackView.setCustomSpacing(VDSLayout.Spacing.space3X.value, after: selectedOptionLabel) + dropdownField.width(0) + inlineWidthConstraint = inlineDisplayLabel.widthAnchor.constraint(greaterThanOrEqualToConstant: 0) + inlineWidthConstraint?.isActive = true + + // setting color config + inlineDisplayLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable() + selectedOptionLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable() + + // Options PickerView + optionsPicker.delegate = self + optionsPicker.dataSource = self + optionsPicker.isHidden = true + dropdownField.inputView = optionsPicker + dropdownField.inputAccessoryView = { + let inputToolbar = UIToolbar().with { + $0.barStyle = .default + $0.isTranslucent = true + $0.items=[ + UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: self, action: nil), + UIBarButtonItem(title: "Done", style: UIBarButtonItem.Style.done, target: self, action: #selector(pickerDoneClicked)) + ] + } + inputToolbar.sizeToFit() + return inputToolbar + }() + + // tap gesture + containerStackView + .publisher(for: UITapGestureRecognizer()) + .sink { [weak self] _ in + self?.launchPicker() + } + .store(in: &subscribers) + } + + /// Used to make changes to the View based off a change events or from local properties. + open override func updateView() { + super.updateView() + + updateInlineLabel() + + dropdownField.isUserInteractionEnabled = readOnly ? false : true + selectedOptionLabel.surface = surface + selectedOptionLabel.isEnabled = isEnabled + } + + /// Resets to default settings. + open override func reset() { + super.reset() + + inlineDisplayLabel.textStyle = .boldBodyLarge + selectedOptionLabel.textStyle = .bodyLarge + showInlineLabel = false + options = [] + selectId = nil + } + + //-------------------------------------------------- + // MARK: - Public Methods + //-------------------------------------------------- + + open override func updateTitleLabel() { + + //update the local vars for the label since we no long have a model + var attributes: [any LabelAttributeModel] = [] + var updatedLabelText = labelText + + updatedLabelText = showInlineLabel ? "" : updatedLabelText + + if let oldText = updatedLabelText, !required, !oldText.hasSuffix("Optional") { + let optionColorAttr = ColorLabelAttribute(location: oldText.count + 2, + length: 8, + color: secondaryColorConfiguration.getColor(self)) + + updatedLabelText = showInlineLabel ? "Optional" : "\(oldText) Optional" + attributes.append(optionColorAttr) + } + + if let tooltipModel { + attributes.append(TooltipLabelAttribute(surface: surface, model: tooltipModel, presenter: self)) + } + + titleLabel.text = updatedLabelText + titleLabel.attributes = attributes + titleLabel.surface = surface + titleLabel.isEnabled = isEnabled + } + + open func updateInlineLabel() { + + inlineWidthConstraint?.isActive = false + + /// inline label text and selected option text separated by ':' + if let labelText, !labelText.isEmpty { + inlineDisplayLabel.text = showInlineLabel ? (labelText + ":") : "" + } else { + inlineDisplayLabel.text = showInlineLabel ? labelText : "" + } + inlineDisplayLabel.surface = surface + inlineDisplayLabel.isEnabled = isEnabled + + /// Update width as per updated text size + inlineWidthConstraint = inlineDisplayLabel.widthAnchor.constraint(equalToConstant: inlineDisplayLabel.intrinsicContentSize.width) + inlineWidthConstraint?.isActive = true + + if let selectId, selectId < options.count { + updateSelectedOptionLabel(option: options[selectId]) + } + } + + open func updateSelectedOptionLabel(option: DropdownOptionModel? = nil) { + selectedOptionLabel.text = option?.text ?? "" + value = option?.value + } + + open override func updateErrorLabel() { + super.updateErrorLabel() + if !showError && !hasInternalError { + icon.name = .downCaret + } + icon.surface = surface + icon.isHidden = readOnly ? true : false + icon.color = iconColorConfiguration.getColor(self) + } + + @objc open func pickerDoneClicked() { + optionsPicker.isHidden = true + dropdownField.resignFirstResponder() + } +} + +//-------------------------------------------------- +// MARK: - UIPickerView Delegate & Datasource +//-------------------------------------------------- +extension DropdownSelect: UIPickerViewDelegate, UIPickerViewDataSource { + + internal func launchPicker() { + if optionsPicker.isHidden { + dropdownField.becomeFirstResponder() + } else { + dropdownField.resignFirstResponder() + } + optionsPicker.isHidden = !optionsPicker.isHidden + } + + public func numberOfComponents(in pickerView: UIPickerView) -> Int { + return 1 + } + + public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + return options.count + } + + public func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { + guard options.count > row else { return nil } + return options[row].text + } + + public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + guard options.count > row else { return } + selectId = row + updateSelectedOptionLabel(option: options[row]) + self.onItemSelected?(row, options[row]) + } +} diff --git a/VDS/Components/DropdownSelect/DropdownSelectChangeLog.txt b/VDS/Components/DropdownSelect/DropdownSelectChangeLog.txt new file mode 100644 index 00000000..537b6532 --- /dev/null +++ b/VDS/Components/DropdownSelect/DropdownSelectChangeLog.txt @@ -0,0 +1,45 @@ +MM/DD/YYYY +---------------- +Initial Brand 3.0 handoff + +12/27/2021 +---------------- +- Removed Max Width, Increased Min width. +- Updated the tokens with FormControl tokens + +02/24/2022 +---------------- +- Replaced Error and Caret Down Non-Scaling icons with VDS Icon. + +05/02/2022 +---------------- +- Anatomy: Noted that Label and Inline Label are optional within the code + +07/27/2022 +---------------- +- Updated Dropdown Select Configurations to include Background Transparent Boolean. + +08/10/2022 +---------------- +- Updated surface prop from inverted to light and dark + +11/30/2022 +---------------- +- Added "(web only)" to any instance of "keyboard focus" + +12/13/2022 +---------------- +- Replaced form border and focus border pixel values and styles & spacing with tokens. + +01/19/2023 +---------------- +- Added Tooltip and Optional Indicator to Anatomy Updated Specs to use new SPEC Templates and SPEC DOC Components. +- Added “Option List” and artwork to Anatomy. + +04/12/2023 +---------------- +- Updated hex colors for updated feedback tokens in error states. + +12/27/23 +---------------- +- Removed Web/App Parity Discrepancies section diff --git a/VDS/Components/Icon/ButtonIcon/ButtonIcon.swift b/VDS/Components/Icon/ButtonIcon/ButtonIcon.swift index 1267000a..b108e519 100644 --- a/VDS/Components/Icon/ButtonIcon/ButtonIcon.swift +++ b/VDS/Components/Icon/ButtonIcon/ButtonIcon.swift @@ -108,7 +108,10 @@ open class ButtonIcon: Control, Changeable, FormFieldable { open var badgeIndicatorModel: BadgeIndicatorModel? { didSet { setNeedsUpdate() } } /// Icon object used to render out the Icon for this ButtonIcon. - open var icon = Icon().with { $0.isUserInteractionEnabled = false } + open var icon = Icon().with { + $0.isUserInteractionEnabled = false + $0.accessibilityTraits = .button + } /// Determines the type of button based on the contrast. open var kind: Kind = .ghost { didSet { setNeedsUpdate() } } @@ -342,9 +345,8 @@ open class ButtonIcon: Control, Changeable, FormFieldable { /// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations. open override func setup() { super.setup() - isAccessibilityElement = true - accessibilityTraits = .button - accessibilityElements = [badgeIndicator] + isAccessibilityElement = false + accessibilityElements = [icon, badgeIndicator] //create a layoutGuide for the icon to key off of let iconLayoutGuide = UILayoutGuide() @@ -430,6 +432,7 @@ open class ButtonIcon: Control, Changeable, FormFieldable { icon.color = color icon.size = size.value icon.customSize = customSize + icon.isEnabled = isEnabled } else { icon.reset() } @@ -500,7 +503,6 @@ open class ButtonIcon: Control, Changeable, FormFieldable { badgeIndicator.isHidden = true return } - badgeIndicator.surface = surface badgeIndicator.kind = badgeIndicatorModel.kind badgeIndicator.fillColor = badgeIndicatorModel.fillColor @@ -536,11 +538,6 @@ open class ButtonIcon: Control, Changeable, FormFieldable { } } - /// Used to update any Accessibility properties. - open override func updateAccessibility() { - super.updateAccessibility() - setAccessibilityLabel(for: [icon, badgeIndicator.label]) - } } // MARK: AppleGuidelinesTouchable diff --git a/VDS/Components/Icon/IconName.swift b/VDS/Components/Icon/IconName.swift index 825d7e30..3a17f686 100644 --- a/VDS/Components/Icon/IconName.swift +++ b/VDS/Components/Icon/IconName.swift @@ -51,6 +51,8 @@ extension Icon { public static let checkmark = Name(name: "checkmark") public static let checkmarkAlt = Name(name: "checkmark-alt") public static let close = Name(name: "close") + public static let downCaret = Name(name: "down-caret") + public static let downCaretBold = Name(name: "down-caret-bold") public static let error = Name(name: "error") public static let info = Name(name: "info") public static let multipleDocuments = Name(name: "multiple-documents") diff --git a/VDS/Components/Label/Attributes/LabelAttributeModel.swift b/VDS/Components/Label/Attributes/LabelAttributeModel.swift index 01dfec08..578a9e15 100644 --- a/VDS/Components/Label/Attributes/LabelAttributeModel.swift +++ b/VDS/Components/Label/Attributes/LabelAttributeModel.swift @@ -71,7 +71,6 @@ public extension NSAttributedString { extension NSMutableAttributedString { public func apply(attribute: any LabelAttributeModel) { - guard isValid(range: attribute.range) else { return } attribute.setAttribute(on: self) } diff --git a/VDS/Components/Label/Attributes/TooltipLabelAttribute.swift b/VDS/Components/Label/Attributes/TooltipLabelAttribute.swift index 76efcec7..187d4eda 100644 --- a/VDS/Components/Label/Attributes/TooltipLabelAttribute.swift +++ b/VDS/Components/Label/Attributes/TooltipLabelAttribute.swift @@ -24,8 +24,6 @@ public class TooltipLabelAttribute: ActionLabelAttributeModel, TooltipLaunchable public var presenter: UIView? public func setAttribute(on attributedString: NSMutableAttributedString) { - guard isValidRange(on: attributedString) else { return } - //update the location location = attributedString.string.count - 1 diff --git a/VDS/Components/Notification/Notification.swift b/VDS/Components/Notification/Notification.swift index 2d41f4a5..f91d45ef 100644 --- a/VDS/Components/Notification/Notification.swift +++ b/VDS/Components/Notification/Notification.swift @@ -39,18 +39,32 @@ open class Notification: View { public enum Style: String, CaseIterable { case info, success, warning, error - func iconName() -> Icon.Name { + var iconName: Icon.Name { switch self { case .info: - return .infoBold + .infoBold case .success: - return .checkmarkAltBold + .checkmarkAltBold case .warning: - return .warningBold + .warningBold case .error: - return .errorBold + .errorBold } } + + var accessibilityText: String { + switch self { + case .info: + "Information Message" + case .success: + "Success Message" + case .warning: + "Warning Message" + case .error: + "Catastrophic Warning Alert" + } + } + } //-------------------------------------------------- @@ -247,6 +261,11 @@ open class Notification: View { subTitleLabel.textColorConfiguration = textColorConfiguration.eraseToAnyColorable() //TODO: Need to add setup animation for displaying the Notification view for iOS. + isAccessibilityElement = false + accessibilityElements = [closeButton, typeIcon, titleLabel, subTitleLabel, buttonGroup] + closeButton.accessibilityTraits = [.button] + closeButton.accessibilityLabel = "Close Notification" + } /// Resets to default settings. @@ -298,24 +317,15 @@ open class Notification: View { super.layoutSubviews() layer.cornerRadius = UIScreen.main.bounds.width == bounds.width ? 0 : 4.0 } - - ///Updating the accessiblity values i.e elements, label, value other items for the component. - open override func updateAccessibility() { - super.updateAccessibility() - accessibilityElements = [closeButton, typeIcon, titleLabel, subTitleLabel, buttonGroup] - typeIcon.accessibilityLabel = style.rawValue - typeIcon.imageView.image?.isAccessibilityElement = false - closeButton.accessibilityTraits = [.button] - closeButton.accessibilityLabel = "Close Notification" - } - + //-------------------------------------------------- // MARK: - Private Methods //-------------------------------------------------- private func updateIcons() { let iconColor = surface == .dark ? VDSColor.paletteWhite : VDSColor.paletteBlack - typeIcon.name = style.iconName() + typeIcon.name = style.iconName typeIcon.color = iconColor + typeIcon.accessibilityLabel = style.accessibilityText closeButton.color = iconColor closeButton.isHidden = hideCloseButton } diff --git a/VDS/Components/RadioBox/RadioBoxItem.swift b/VDS/Components/RadioBox/RadioBoxItem.swift index ca32516f..de6223e1 100644 --- a/VDS/Components/RadioBox/RadioBoxItem.swift +++ b/VDS/Components/RadioBox/RadioBoxItem.swift @@ -33,16 +33,10 @@ open class RadioBoxItem: Control, Changeable, FormFieldable { //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- - private var mainStackView = UIStackView().with { - $0.translatesAutoresizingMaskIntoConstraints = false - $0.alignment = .top - $0.axis = .vertical - $0.spacing = 0 - } - private var selectorStackView = UIStackView().with { $0.translatesAutoresizingMaskIntoConstraints = false $0.alignment = .top + $0.distribution = .fill $0.axis = .horizontal $0.spacing = 12 } @@ -159,6 +153,7 @@ open class RadioBoxItem: Control, Changeable, FormFieldable { $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled) $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .selected) $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .highlighted) + $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: [.selected, .disabled]) } //-------------------------------------------------- @@ -181,10 +176,8 @@ open class RadioBoxItem: Control, Changeable, FormFieldable { addSubview(selectorView) selectorView.isUserInteractionEnabled = false - selectorView.addSubview(mainStackView) - - mainStackView.addArrangedSubview(selectorStackView) - + selectorView.addSubview(selectorStackView) + selectorStackView.addArrangedSubview(selectorLeftLabelStackView) selectorStackView.addArrangedSubview(subTextRightLabel) selectorLeftLabelStackView.addArrangedSubview(textLabel) @@ -196,7 +189,7 @@ open class RadioBoxItem: Control, Changeable, FormFieldable { .pinTrailing(0, .defaultHigh) .pinBottom(0, .defaultHigh) - mainStackView.pinToSuperView(.uniform(16)) + selectorStackView.pinToSuperView(.uniform(16)) } /// Resets to default settings. @@ -322,7 +315,7 @@ open class RadioBoxItem: Control, Changeable, FormFieldable { //get the colors let backgroundColor = backgroundColorConfiguration.getColor(self) let borderColor = borderColorConfiguration.getColor(self) - let borderWidth = isSelected || isHighlighted ? selectorBorderWidthSelected : selectorBorderWidth + let borderWidth = (isSelected || isHighlighted) && isEnabled ? selectorBorderWidthSelected : selectorBorderWidth selectorView.backgroundColor = backgroundColor selectorView.layer.borderColor = borderColor.cgColor diff --git a/VDS/Components/TextFields/EntryFieldBase.swift b/VDS/Components/TextFields/EntryFieldBase.swift index a1c247e0..076ff6c1 100644 --- a/VDS/Components/TextFields/EntryFieldBase.swift +++ b/VDS/Components/TextFields/EntryFieldBase.swift @@ -109,6 +109,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { $0.setSurfaceColors(VDSFormControlsColor.borderOnlight, VDSFormControlsColor.borderOndark, forState: .normal) $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled) $0.setSurfaceColors(VDSColor.feedbackErrorOnlight, VDSColor.feedbackErrorOndark, forState: .error) + $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: [.disabled,.error]) } internal var readOnlyBorderColorConfiguration = ControlColorConfiguration().with { @@ -184,33 +185,24 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { setNeedsUpdate() } } - - /// Override this to conveniently get/set the textfield(s). - open var text: String? { - get { nil } - set { fatalError("You MUST override EntryField's 'text' variable in your subclass.") } - } - + open var tooltipModel: Tooltip.TooltipModel? { didSet { setNeedsUpdate() } } open var transparentBackground: Bool = false { didSet { setNeedsUpdate() } } open var width: CGFloat? { didSet { setNeedsUpdate() } } - - open var maxLength: Int? { didSet { setNeedsUpdate() } } - + open var inputId: String? { didSet { setNeedsUpdate() } } /// The text of this textField. - private var _value: String? + internal var _value: String? open var value: String? { get { _value } set { if let newValue, newValue != _value { _value = newValue - text = newValue + sendActions(for: .valueChanged) } - setNeedsUpdate() } } @@ -307,7 +299,6 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { tooltipModel = nil transparentBackground = false width = nil - maxLength = nil inputId = nil value = nil defaultValue = nil @@ -334,7 +325,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { //-------------------------------------------------- private func updateContainerView() { containerView.backgroundColor = backgroundColorConfiguration.getColor(self) - containerView.layer.borderColor = borderColorConfiguration.getColor(self).cgColor + containerView.layer.borderColor = readOnly ? readOnlyBorderColorConfiguration.getColor(self).cgColor : borderColorConfiguration.getColor(self).cgColor containerView.layer.borderWidth = VDSFormControls.borderWidth containerView.layer.cornerRadius = VDSFormControls.borderRadius } diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index 3e545410..cb9501e8 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -77,17 +77,28 @@ open class InputField: EntryFieldBase, UITextFieldDelegate { /// Representing the type of input. open var fieldType: FieldType = .text { didSet { setNeedsUpdate() } } - /// The text of this textField. - open override var text: String? { - get { textField.text } + /// The text of this TextField. + private var _text: String? + open var text: String? { + get { _text } set { - if let newValue, newValue != text { + if let newValue, newValue != _text { + _text = newValue textField.text = newValue value = newValue } setNeedsUpdate() } } + + /// The value of this textField. + open override var value: String? { + didSet { + if text != value { + text = value + } + } + } var _showError: Bool = false /// Whether not to show the error. diff --git a/VDS/Components/TextFields/TextArea/TextArea.swift b/VDS/Components/TextFields/TextArea/TextArea.swift index ee4db48e..c02caa48 100644 --- a/VDS/Components/TextFields/TextArea/TextArea.swift +++ b/VDS/Components/TextFields/TextArea/TextArea.swift @@ -104,11 +104,11 @@ open class TextArea: EntryFieldBase { } } } - - /// The text of this textView + + /// The text of this TextArea. private var _text: String? - open override var text: String? { - get { textView.text } + open var text: String? { + get { _text } set { if let newValue, newValue != _text { _text = newValue @@ -118,6 +118,15 @@ open class TextArea: EntryFieldBase { setNeedsUpdate() } } + + /// The value of this textField. + open override var value: String? { + didSet { + if text != value { + text = value + } + } + } /// UITextView shown in the TextArea. open var textView = TextView().with { @@ -126,7 +135,15 @@ open class TextArea: EntryFieldBase { $0.isScrollEnabled = false } - open override var maxLength: Int? { willSet { countRule.maxLength = newValue }} + open var maxLength: Int? { + willSet { + countRule.maxLength = newValue + } + + didSet { + setNeedsUpdate() + } + } /// Color configuration for error icon. internal var iconColorConfiguration = ControlColorConfiguration().with { diff --git a/VDS/Extensions/NSAttributedString.swift b/VDS/Extensions/NSAttributedString.swift index 4f758762..6d168182 100644 --- a/VDS/Extensions/NSAttributedString.swift +++ b/VDS/Extensions/NSAttributedString.swift @@ -68,9 +68,7 @@ extension NSMutableAttributedString { .paragraphStyle: paragraph] //set letterSpacing - if textStyle.letterSpacing > 0.0 { - attributes[.kern] = textStyle.letterSpacing - } + attributes[.kern] = textStyle.letterSpacing return NSMutableAttributedString(string: text, attributes: attributes) diff --git a/VDS/Extensions/UIView+CALayer.swift b/VDS/Extensions/UIView+CALayer.swift index 16e92919..18c19e49 100644 --- a/VDS/Extensions/UIView+CALayer.swift +++ b/VDS/Extensions/UIView+CALayer.swift @@ -148,3 +148,44 @@ extension UIView { } } } + +extension UIView { + + fileprivate var bezierPathIdentifier: String { return "bezierPathBorderLayer" } + + fileprivate var bezierPathBorder: CAShapeLayer? { + return (self.layer.sublayers?.filter({ (layer) -> Bool in + return layer.name == self.bezierPathIdentifier && (layer as? CAShapeLayer) != nil + }) as? [CAShapeLayer])?.first + } + + public func bezierPathBorder(_ color: UIColor = .white, width: CGFloat = 1) { + + var border = self.bezierPathBorder + let path = UIBezierPath(roundedRect: self.bounds, cornerRadius:self.layer.cornerRadius) + let mask = CAShapeLayer() + mask.path = path.cgPath + self.layer.mask = mask + + if (border == nil) { + border = CAShapeLayer() + border!.name = self.bezierPathIdentifier + self.layer.addSublayer(border!) + } + + border!.frame = self.bounds + let pathUsingCorrectInsetIfAny = + UIBezierPath(roundedRect: border!.bounds, cornerRadius:self.layer.cornerRadius) + + border!.path = pathUsingCorrectInsetIfAny.cgPath + border!.fillColor = UIColor.clear.cgColor + border!.strokeColor = color.cgColor + border!.lineWidth = width * 2 + } + + public func removeBezierPathBorder() { + self.layer.mask = nil + self.bezierPathBorder?.removeFromSuperlayer() + } + +} diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index e1cd12fd..62650dfc 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -1,3 +1,27 @@ +1.0.59 +---------------- +- CXTDT-540077 - BadgeIndicator Font + +1.0.58 +---------------- +- CXTDT-542341 - RadioButtonItem - disabled state color/borderWidth +- CXTDT-542333 - RadioButtonItem - padding fix +- CXTDT-542295 - ButtonIcon - Disabled state +- ONEAPP-6360 - Notification - Accessiblity issues + +1.0.57 +---------------- +- CXTDT-540077 - BadgeIndicator Font +- CXTDT-540084 - BadgeIndicator Border + +1.0.56 +---------------- +- ONEAPP-7190 - Breadcrumbs - Finished Development +- ONEAPP-6830 - CarouselScrollbar - Finished Development +- ONEAPP-6978 - Pagination - Finished Development +- ONEAPP-6984 - Tilelet - Finished Development +- ONEAPP-6682 - TextArea - Finished Development + 1.0.55 ---------------- - ONEAPP-6305 - BadgeIndicator - Finished Development diff --git a/VDS/VDS.docc/VDS.md b/VDS/VDS.docc/VDS.md index 63956f83..607e09f5 100755 --- a/VDS/VDS.docc/VDS.md +++ b/VDS/VDS.docc/VDS.md @@ -29,6 +29,7 @@ Using the system allows designers and developers to collaborate more easily and - ``Checkbox`` - ``CheckboxItem`` - ``CheckboxGroup`` +- ``DropdownSelect`` - ``Icon`` - ``InputField`` - ``Label``