diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index a5a05d74..e300e987 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -23,6 +23,8 @@ 1842B1E12BECE7B70021AFCA /* CalendarHeaderReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1842B1E02BECE7B70021AFCA /* CalendarHeaderReusableView.swift */; }; 1842B1E32BECF0A20021AFCA /* CalendarFooterReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1842B1E22BECF0A10021AFCA /* CalendarFooterReusableView.swift */; }; 1855EC662BAABF2A002ACAC2 /* BreadcrumbItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1855EC652BAABF2A002ACAC2 /* BreadcrumbItemModel.swift */; }; + 1859B30F2CBF6FEB0031CD70 /* ListUnordered.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1859B30E2CBF6FDD0031CD70 /* ListUnordered.swift */; }; + 1859B31B2CBFA0180031CD70 /* ListUnorderedItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1859B31A2CBFA0180031CD70 /* ListUnorderedItemModel.swift */; }; 186D13CB2BBA8B1500986B53 /* DropdownSelect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 186D13CA2BBA8B1500986B53 /* DropdownSelect.swift */; }; 18792A902B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18792A8F2B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift */; }; 18926F5B2C7616A500C55BF6 /* FootnoteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18926F5A2C7616A500C55BF6 /* FootnoteItem.swift */; }; @@ -241,6 +243,9 @@ 1842B1E22BECF0A10021AFCA /* CalendarFooterReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarFooterReusableView.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 = ""; }; + 1859B30E2CBF6FDD0031CD70 /* ListUnordered.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListUnordered.swift; sourceTree = ""; }; + 1859B3122CBF70AB0031CD70 /* ListUnordered.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = ListUnordered.txt; sourceTree = ""; }; + 1859B31A2CBFA0180031CD70 /* ListUnorderedItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListUnorderedItemModel.swift; 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 = ""; }; @@ -508,6 +513,16 @@ path = PriceLockup; sourceTree = ""; }; + 1859B30D2CBF6EF80031CD70 /* ListUnordered */ = { + isa = PBXGroup; + children = ( + 1859B30E2CBF6FDD0031CD70 /* ListUnordered.swift */, + 1859B31A2CBFA0180031CD70 /* ListUnorderedItemModel.swift */, + 1859B3122CBF70AB0031CD70 /* ListUnordered.txt */, + ); + path = ListUnordered; + sourceTree = ""; + }; 186D13C92BBA8A3500986B53 /* DropdownSelect */ = { isa = PBXGroup; children = ( @@ -759,6 +774,7 @@ 180636C52C29B06200C92D86 /* InputStepper */, EA3362412892EF700071C351 /* Label */, 44604AD529CE195300E62B51 /* Line */, + 1859B30D2CBF6EF80031CD70 /* ListUnordered */, EAD0688C2A55F801002E3A2D /* Loader */, 18C0F9442C980CE500E1DD71 /* Modal */, 445BA07629C07ABA0036A7C5 /* Notification */, @@ -1347,6 +1363,7 @@ EA6642952BCEBF9500D81DC4 /* TextLinkModel.swift in Sources */, 71FC86E22B97483000700965 /* Clamping.swift in Sources */, EAF7F0B3289B1ADC00B287F5 /* ActionLabelAttribute.swift in Sources */, + 1859B30F2CBF6FEB0031CD70 /* ListUnordered.swift in Sources */, 1855EC662BAABF2A002ACAC2 /* BreadcrumbItemModel.swift in Sources */, EAC925832911B35400091998 /* TextLinkCaret.swift in Sources */, EA33622E2891EA3C0071C351 /* DispatchQueue+Once.swift in Sources */, @@ -1414,6 +1431,7 @@ EAD8D2C128BFDE8B006EB6A6 /* UIGestureRecognizer+Publisher.swift in Sources */, 18B42AC62C09D197008D6262 /* CarouselSlotAlignmentModel.swift in Sources */, 71B23C2D2B91FA690027F7D9 /* Pagination.swift in Sources */, + 1859B31B2CBFA0180031CD70 /* ListUnorderedItemModel.swift in Sources */, EA0D1C372A681CCE00E5C127 /* ToggleView.swift in Sources */, EAF7F0B9289C139800B287F5 /* ColorConfiguration.swift in Sources */, EA3361BD288B2C760071C351 /* TypeAlias.swift in Sources */, diff --git a/VDS/Components/ListUnordered/ListUnordered.swift b/VDS/Components/ListUnordered/ListUnordered.swift new file mode 100644 index 00000000..e9349a0b --- /dev/null +++ b/VDS/Components/ListUnordered/ListUnordered.swift @@ -0,0 +1,239 @@ +// +// ListUnordered.swift +// VDS +// +// Created by Vasavi Kanamarlapudi on 16/10/24. +// + +import Foundation +import UIKit +import VDSCoreTokens + +/// List unordered breaks up related content into distinct phrases or sentences, which improves scannability. +/// This component should be used when the text items don’t need to be in numeric order. +@objcMembers +@objc(VDSListUnordered) +open class ListUnordered: View { + + //-------------------------------------------------- + // 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: - Enums + //-------------------------------------------------- + /// Enum that represents the size availble for the component. + public enum Size: String, DefaultValuing, CaseIterable { + case large + case medium + case small + case micro + + public static var defaultValue: Self { .large } + + /// TextStyle relative to Size. + public var textStyle: TextStyle.StandardStyle { + switch self { + case .large: + return .bodyLarge + case .medium: + return .bodyMedium + case .small: + return .bodySmall + case .micro: + return .micro + } + } + } + + /// Enum that represents the type of spacing available for the component. + public enum Spacing: String, CaseIterable { + case standard, compact + } + + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + /// Size of the component. The default size is Large. + open var size: Size = .defaultValue { didSet { setNeedsUpdate() } } + + /// Spacing type of the component. + open var spacing: Spacing = .standard { didSet { setNeedsUpdate() } } + + /// Lead-in text that shows as the top text for the component. This is optional. + open var leadInText: String? = nil { didSet { setNeedsUpdate() } } + + /// Array of unordered list items to show for the component. + open var unorderedList: [ListUnorderedItemModel] = [] { didSet { setNeedsUpdate() }} + + //-------------------------------------------------- + // MARK: - Configuration Properties + //-------------------------------------------------- + // It can be used for Glyph level 1. + private var disc = "•" + + // It can be used for Glyph Level 2. + private var endash = "–" + + // Spacing between the list items. + private var spaceBetweenItems: CGFloat { + switch (size, spacing) { + case (.large, .standard): + return VDSLayout.space4X + case (.medium, .standard), (.small, .standard), (.micro, .standard): + return VDSLayout.space3X + case (.large, .compact): + return VDSLayout.space2X + case (.medium, .compact), (.small, .compact), (.micro, .compact): + return VDSLayout.space1X + } + } + + // Padding that can be used in an item between the glyph and the item text. + private var padding: CGFloat { + switch (size, spacing) { + case (.large, .standard), (.large, .compact): + return VDSLayout.space3X + case (.medium, .standard), (.small, .standard), (.micro, .standard), (.medium, .compact), (.small, .compact), (.micro, .compact): + return VDSLayout.space2X + } + } + + private let textColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark) + + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + private lazy var listStackView = UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.axis = .vertical + $0.distribution = .fill + $0.spacing = spaceBetweenItems + $0.backgroundColor = .clear + } + + //-------------------------------------------------- + // MARK: - Overrides + //-------------------------------------------------- + /// Called once when a view is initialized and is used to Setup additional UI or other constants and config.texturations. + open override func setup() { + super.setup() + + // add stackview + addSubview(listStackView) + listStackView.heightGreaterThanEqualTo(constant:0) + listStackView.pinToSuperView() + } + + open override func setDefaults() { + super.setDefaults() + leadInText = nil + unorderedList = [] + } + + /// Resets to default settings. + open override func reset() { + super.reset() + } + + /// Used to make changes to the View based off a change events or from local properties. + open override func updateView() { + super.updateView() + listStackView.removeArrangedSubviews() + listStackView.subviews.forEach { $0.removeFromSuperview() } + listStackView.spacing = spaceBetweenItems + + if leadInText != nil { + let listItem = getListItem(with:self.leadInText, surface: surface) + listStackView.addArrangedSubview(listItem) + } + + unorderedList.forEach { item in + let listItem = getListItem(levelOneText: item.levelOneText, surface: surface) + listStackView.addArrangedSubview(listItem) + + item.levelTwoText?.forEach { text in + let listItem = getListItem(levelTwoText: text, surface: surface) + listStackView.addArrangedSubview(listItem) + } + } + } + + //-------------------------------------------------- + // MARK: - Private Methods + //-------------------------------------------------- + // Get Label with the required text and text formats. + func getLabel(with text: String?, surface: Surface) -> Label { + let textLabel = Label().with { + $0.isAccessibilityElement = true + $0.lineBreakMode = .byWordWrapping + $0.text = text + $0.textStyle = size.textStyle.regular + $0.textColor = textColorConfiguration.getColor(surface) + $0.surface = surface + } + return textLabel + } + + // Get the list item with the required text (LeadInText, Level 1 Text, Level 2 Text). + func getListItem(with leadInText:String? = nil, levelOneText: String? = nil, levelTwoText: String? = nil, surface:Surface) -> UIView { + let itemStackView = UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.axis = .horizontal + $0.alignment = .leading + $0.distribution = .fill + $0.spacing = padding + $0.backgroundColor = .clear + } + + // StackView with LeadIntext if provided. + if leadInText != nil { + let leadTextLabel = getLabel(with: leadInText, surface: surface) + itemStackView.addArrangedSubview(leadTextLabel) + } + + // StackView with Level 1 Text if provided. + if levelOneText != nil { + + // Add level 1 glyph: 'disc, bold' + let discLabel = getLabel(with: disc, surface: surface) + discLabel.widthAnchor.constraint(equalToConstant: discLabel.intrinsicContentSize.width).activate() + itemStackView.addArrangedSubview(discLabel) + + // Add level 1 Text + let levelOneLabel = getLabel(with: levelOneText, surface: surface) + itemStackView.addArrangedSubview(levelOneLabel) + } + + // StackView with Level 2 Text if provided. + if levelTwoText != nil { + + // Set level 2 leading space as needed for alignment. + let discSpaceView = View() + let discLabel = getLabel(with: disc, surface: surface) + discSpaceView.widthAnchor.constraint(equalToConstant: discLabel.intrinsicContentSize.width).activate() + itemStackView.addArrangedSubview(discSpaceView) + + // Add level 2 glyph: 'en dash, regular' + let endashLabel = getLabel(with: endash, surface: surface) + endashLabel.widthAnchor.constraint(equalToConstant: endashLabel.intrinsicContentSize.width).activate() + itemStackView.addArrangedSubview(endashLabel) + + // Add level 2 Text + let levelTwoLabel = getLabel(with: levelTwoText, surface: surface) + itemStackView.addArrangedSubview(levelTwoLabel) + } + return itemStackView + } +} diff --git a/VDS/Components/ListUnordered/ListUnordered.txt b/VDS/Components/ListUnordered/ListUnordered.txt new file mode 100644 index 00000000..001ac437 --- /dev/null +++ b/VDS/Components/ListUnordered/ListUnordered.txt @@ -0,0 +1,41 @@ +MM/DD/YYYY +---------------- +- Initial Brand 3.0 handoff + +05/2/2022 +---------------- +- Added Body Medium to size configuration + +05/5/2022 +---------------- +- Added Spacing configuration (Standard, Compact) Web handoff + +08/2/2022 +---------------- +- Included a VDS Note about the Spacing prop naming rationale + +08/10/2022 +---------------- +- Updated default and inverted prop to light and dark surface. + +12/13/2022 +---------------- +- Replaced focus border pixel and style & spacing values with tokens. + +01/10/2023 +---------------- +- Removed from Anatomy section: “List item text” +- Updated “Glyph level 1” to “List Item Level 1” +- Updated “Glyph level 2” to “List Item Level 2” +- Updated image markers to reflect changes + +02/02/2023 +---------------- +- Reduced left padding for all Level 2 sizes so that the Glyph aligns with the text in Level 1. +- Added dashed line on all sizes to indicate Level 2 alignment under Level 1. +- Changed “endash” to “endash, regular” under Size section. +- Updated all Level 1 and Level 2 glyph widths to “Hug” + +12/26/23 +---------------- +- Deleted Decisions log diff --git a/VDS/Components/ListUnordered/ListUnorderedItemModel.swift b/VDS/Components/ListUnordered/ListUnorderedItemModel.swift new file mode 100644 index 00000000..9726e92b --- /dev/null +++ b/VDS/Components/ListUnordered/ListUnorderedItemModel.swift @@ -0,0 +1,23 @@ +// +// ListUnorderedItemModel.swift +// VDS +// +// Created by Vasavi Kanamarlapudi on 16/10/24. +// + +import Foundation +extension ListUnordered { + public struct ListUnorderedItemModel: Equatable { + + /// Item Level 1 that shows text with glyph - disc, bold. + public var levelOneText: String + + /// Item Level 2 that shows text (one or many) with glyph - en dash. This is optional. + public var levelTwoText: [String?]? + + public init(itemLevelOneText: String, itemLevelTwoTexts: [String?]? = nil) { + self.levelOneText = itemLevelOneText + self.levelTwoText = itemLevelTwoTexts + } + } +} diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index 74fadbcd..57f8873e 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -1,5 +1,6 @@ 1.0.76 ---------------- +- ONEAPP-11355 - ListUnordered - Finished Development - CXTDT-630735 - PriceLockup - Accessibility - CXTDT-626164 - FootnoteGroup - Dark mode - CXTDT-626180 - FootnoteItem - Symbol type padding diff --git a/VDS/VDS.docc/VDS.md b/VDS/VDS.docc/VDS.md index e1fda159..07db49b5 100755 --- a/VDS/VDS.docc/VDS.md +++ b/VDS/VDS.docc/VDS.md @@ -39,6 +39,7 @@ Using the system allows designers and developers to collaborate more easily and - ``InputField`` - ``Label`` - ``Line`` +- ``ListUnordered`` - ``Loader`` - ``Modal`` - ``Notification``