// // 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 } }