diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 63549b92..1454090c 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -7,8 +7,13 @@ objects = { /* Begin PBXBuildFile section */ + 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 */; }; 186B2A8A2B88DA7F001AB71F /* TextAreaChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 186B2A892B88DA7F001AB71F /* TextAreaChangeLog.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 */; }; 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 */; }; @@ -149,6 +154,7 @@ EAEEECAF2B1FC2BA00531FC2 /* ToggleViewChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = EAEEECAE2B1FC2BA00531FC2 /* ToggleViewChangeLog.txt */; }; EAF1FE9929D4850E00101452 /* Clickable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF1FE9829D4850E00101452 /* Clickable.swift */; }; EAF1FE9B29DB1A6000101452 /* Changeable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF1FE9A29DB1A6000101452 /* Changeable.swift */; }; + EAF4A6A12BAC8B94006BCC2C /* BreadcrumbsFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF4A6A02BAC8B94006BCC2C /* BreadcrumbsFlowLayout.swift */; }; EAF7F0952899861000B287F5 /* CheckboxItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F0932899861000B287F5 /* CheckboxItem.swift */; }; EAF7F09A2899B17200B287F5 /* CATransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F0992899B17200B287F5 /* CATransaction.swift */; }; EAF7F0A0289AB7EC00B287F5 /* View.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F09F289AB7EC00B287F5 /* View.swift */; }; @@ -178,8 +184,13 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 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 = ""; }; 186B2A892B88DA7F001AB71F /* TextAreaChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = TextAreaChangeLog.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 = ""; }; 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 = ""; }; @@ -322,6 +333,7 @@ EAEEECAE2B1FC2BA00531FC2 /* ToggleViewChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = ToggleViewChangeLog.txt; sourceTree = ""; }; EAF1FE9829D4850E00101452 /* Clickable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Clickable.swift; sourceTree = ""; }; EAF1FE9A29DB1A6000101452 /* Changeable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Changeable.swift; sourceTree = ""; }; + EAF4A6A02BAC8B94006BCC2C /* BreadcrumbsFlowLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbsFlowLayout.swift; sourceTree = ""; }; EAF7F0932899861000B287F5 /* CheckboxItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckboxItem.swift; sourceTree = ""; }; EAF7F0992899B17200B287F5 /* CATransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CATransaction.swift; sourceTree = ""; }; EAF7F09F289AB7EC00B287F5 /* View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = ""; }; @@ -362,6 +374,19 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 18A65A002B96E7E1006602CC /* Breadcrumbs */ = { + isa = PBXGroup; + children = ( + 18A65A012B96E848006602CC /* Breadcrumbs.swift */, + EAF4A6A02BAC8B94006BCC2C /* BreadcrumbsFlowLayout.swift */, + 18A65A032B96F050006602CC /* BreadcrumbItem.swift */, + 1855EC652BAABF2A002ACAC2 /* BreadcrumbItemModel.swift */, + 1832AC562BA0791D008AE476 /* BreadcrumbCellItem.swift */, + 18450CF02BA1B19C009FDF2A /* BreadcrumbsChangeLog.txt */, + ); + path = Breadcrumbs; + sourceTree = ""; + }; 445BA07629C07ABA0036A7C5 /* Notification */ = { isa = PBXGroup; children = ( @@ -495,6 +520,7 @@ children = ( EA4DB2FE28DCBC1900103EE3 /* Badge */, EAD062AE2A3B87210015965D /* BadgeIndicator */, + 18A65A002B96E7E1006602CC /* Breadcrumbs */, EA0FC2BE2912D18200DF80B4 /* Buttons */, EAF7F092289985E200B287F5 /* Checkbox */, EA985BF3296C609E00F2FF2E /* Icon */, @@ -974,6 +1000,7 @@ 18BDEE822B75316E00452358 /* ButtonIconChangeLog.txt in Resources */, EA3362062891E14D0071C351 /* VerizonNHGeTX-Regular.otf in Resources */, EA3362052891E14D0071C351 /* VerizonNHGeDS-Bold.otf in Resources */, + 18450CF12BA1B19C009FDF2A /* BreadcrumbsChangeLog.txt in Resources */, EAEEECA02B1F908200531FC2 /* BadgeIndicatorChangeLog.txt in Resources */, EAA5EEB928ECD24B003B3210 /* Icons.xcassets in Resources */, EAEEECA92B1F969700531FC2 /* TooltipChangeLog.txt in Resources */, @@ -1003,10 +1030,12 @@ EAF7F0A6289B0CE000B287F5 /* Resetable.swift in Sources */, EA985C2D296F03FE00F2FF2E /* TileletIconModels.swift in Sources */, EA89200428AECF4B006B9984 /* UITextField+Publisher.swift in Sources */, + 18A65A022B96E848006602CC /* Breadcrumbs.swift in Sources */, EA0D1C3F2A6AD5E200E5C127 /* Typography+ContentSizeCategory.swift in Sources */, EA5F86C82A1BD99100BC83E4 /* TabModel.swift in Sources */, EA0D1C432A6AD70900E5C127 /* VDSTypography.swift in Sources */, EA297A5729FB0A360031ED56 /* AppleGuidelinesTouchable.swift in Sources */, + 1832AC572BA0791D008AE476 /* BreadcrumbCellItem.swift in Sources */, EA3361C328902D960071C351 /* Toggle.swift in Sources */, EAF7F0A0289AB7EC00B287F5 /* View.swift in Sources */, EA89201328B568D8006B9984 /* RadioBoxItem.swift in Sources */, @@ -1014,11 +1043,13 @@ EA3362402892EF6C0071C351 /* Label.swift in Sources */, EAB2376229E9880400AABE9A /* TrailingTooltipLabel.swift in Sources */, EAB2376A29E9E59100AABE9A /* TooltipLaunchable.swift in Sources */, + 18A65A042B96F050006602CC /* BreadcrumbItem.swift in Sources */, EAB2375D29E8789100AABE9A /* Tooltip.swift in Sources */, 71BFA70A2B7F70E6000DCE33 /* DropShadowable.swift in Sources */, EA0D1C452A6AD73000E5C127 /* RawRepresentable.swift in Sources */, EA985C23296E033A00F2FF2E /* TextArea.swift in Sources */, EAF7F0B3289B1ADC00B287F5 /* ActionLabelAttribute.swift in Sources */, + 1855EC662BAABF2A002ACAC2 /* BreadcrumbItemModel.swift in Sources */, EAC925832911B35400091998 /* TextLinkCaret.swift in Sources */, EA33622E2891EA3C0071C351 /* DispatchQueue+Once.swift in Sources */, EA4DB2FD28D3D0CA00103EE3 /* AnyEquatable.swift in Sources */, @@ -1057,6 +1088,7 @@ EAD068922A560B65002E3A2D /* LoaderViewController.swift in Sources */, EABFEB642A26473700C4C106 /* NSAttributedString.swift in Sources */, EAF7F13328A2A16500B287F5 /* AttachmentLabelAttributeModel.swift in Sources */, + EAF4A6A12BAC8B94006BCC2C /* BreadcrumbsFlowLayout.swift in Sources */, EA0FC2C62914222900DF80B4 /* ButtonGroup.swift in Sources */, EA89200628B526D6006B9984 /* CheckboxGroup.swift in Sources */, EA8E40932A82889500934ED3 /* TooltipDialog.swift in Sources */, diff --git a/VDS/Components/Breadcrumbs/BreadcrumbCellItem.swift b/VDS/Components/Breadcrumbs/BreadcrumbCellItem.swift new file mode 100644 index 00000000..9f0ea68b --- /dev/null +++ b/VDS/Components/Breadcrumbs/BreadcrumbCellItem.swift @@ -0,0 +1,94 @@ +// +// BreadcrumbCellItem.swift +// VDS +// +// Created by Kanamarlapudi, Vasavi on 11/03/24. +// + +import UIKit +import VDSColorTokens + +///This is customised view for Breadcrumb cell item +final class BreadcrumbCellItem: UICollectionViewCell { + + ///Identifier for the BreadcrumbCellItem + static let identifier: String = String(describing: BreadcrumbCellItem.self) + + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + internal var stackView: UIStackView = { + return UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.axis = .horizontal + $0.distribution = .fill + $0.alignment = .fill + $0.spacing = VDSLayout.Spacing.space1X.value + } + }() + + internal var breadCrumbItem: BreadcrumbItem? + + ///separator label + private var separator: Label = Label().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.textAlignment = .left + $0.numberOfLines = 1 + $0.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) + $0.setContentHuggingPriority(.defaultHigh, for: .horizontal) + $0.text = "/" + } + + private let textColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark) + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + override init(frame: CGRect) { + super.init(frame: frame) + setUp() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setUp() + } + + ///Configuring the cell with default setup + private func setUp() { + separator.textColorConfiguration = textColorConfiguration.eraseToAnyColorable() + contentView.addSubview(stackView) + stackView.pinToSuperView() + separator.backgroundColor = .clear + } + + ///Updating the breadCrumbItem and UI based on the selected flag along with the surface + func update(surface: Surface, hideSlash: Bool, breadCrumbItem: BreadcrumbItem) { + //remove views from stack + separator.removeFromSuperview() + self.breadCrumbItem?.removeFromSuperview() + + //update surface + separator.surface = surface + breadCrumbItem.surface = surface + + //add to stack + stackView.addArrangedSubview(separator) + stackView.addArrangedSubview(breadCrumbItem) + stackView.setCustomSpacing(VDSLayout.Spacing.space1X.value, after: separator) + + //update separator + separator.textColor = textColorConfiguration.getColor(surface) + separator.isHidden = hideSlash + + self.breadCrumbItem = breadCrumbItem + layoutIfNeeded() + } + + /// Remove views from StackView. + override func prepareForReuse() { + super.prepareForReuse() + separator.removeFromSuperview() + breadCrumbItem?.removeFromSuperview() + } +} diff --git a/VDS/Components/Breadcrumbs/BreadcrumbItem.swift b/VDS/Components/Breadcrumbs/BreadcrumbItem.swift new file mode 100644 index 00000000..3b279551 --- /dev/null +++ b/VDS/Components/Breadcrumbs/BreadcrumbItem.swift @@ -0,0 +1,89 @@ +// +// BreadcrumbItem.swift +// VDS +// +// Created by Kanamarlapudi, Vasavi on 05/03/24. +// + +import Foundation +import VDSColorTokens +import VDSFormControlsTokens +import Combine + +/// A Breadcrumb Item contains href(link) and selected flag. +/// Breadcrumb links to its respective page if it is not disabled. +/// Breadcrumb contains text with a separator by default, highlights text in bold without a separator if selected. +@objc (VDSBreadcrumbItem) +open class BreadcrumbItem: ButtonBase { + + //-------------------------------------------------- + // 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 + //-------------------------------------------------- + /// TextStyle used on the titleLabel. + open override var textStyle: TextStyle { isSelected ? TextStyle.boldBodySmall : TextStyle.bodySmall } + + /// UIColor used on the titleLabel text. + open override var textColor: UIColor { + textColorConfiguration.getColor(self) + } + + /// The natural size for the receiving view, considering only properties of the view itself. + open override var intrinsicContentSize: CGSize { + return titleLabel?.intrinsicContentSize ?? super.intrinsicContentSize + } + + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + private var textColorConfiguration = ControlColorConfiguration().with { + $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .normal) + $0.setSurfaceColors(VDSColor.interactiveActiveOnlight, VDSColor.interactiveActiveOndark, forState: .disabled) + $0.setSurfaceColors(VDSColor.interactiveActiveOnlight, VDSColor.interactiveActiveOndark, forState: .highlighted) + $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .selected) + } + + //-------------------------------------------------- + // 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() + isAccessibilityElement = true + accessibilityTraits = .button + accessibilityLabel = "Breadcrumb" + } + + /// Used to make changes to the View based off a change events or from local properties. + open override func updateView() { + //always call last so the label is rendered + super.updateView() + + } + + /// Resets to default settings. + open override func reset() { + super.reset() + shouldUpdateView = false + text = nil + accessibilityCustomActions = [] + isAccessibilityElement = true + accessibilityTraits = .button + shouldUpdateView = true + setNeedsUpdate() + } +} diff --git a/VDS/Components/Breadcrumbs/BreadcrumbItemModel.swift b/VDS/Components/Breadcrumbs/BreadcrumbItemModel.swift new file mode 100644 index 00000000..75c97c86 --- /dev/null +++ b/VDS/Components/Breadcrumbs/BreadcrumbItemModel.swift @@ -0,0 +1,32 @@ +// +// BreadcrumbItemModel.swift +// VDS +// +// Created by Kanamarlapudi, Vasavi on 20/03/24. +// + +import Foundation + +extension Breadcrumbs { + public struct BreadcrumbItemModel { + + ///Text that goes in the breadcrumb item + public var text: String + + /// Whether the Item can be clicked. + public var enabled: Bool + + /// The Breadcrumb link to links to its respective page. + public var selected: Bool + + ///Click event when you click on a breadcrumb item + public var onClick: ((BreadcrumbItem) -> Void)? + + public init(text: String, enabeled: Bool = true, selected: Bool = false, onClick: ((BreadcrumbItem) -> Void)? = nil) { + self.text = text + self.enabled = enabeled + self.selected = selected + self.onClick = onClick + } + } +} diff --git a/VDS/Components/Breadcrumbs/Breadcrumbs.swift b/VDS/Components/Breadcrumbs/Breadcrumbs.swift new file mode 100644 index 00000000..0ed7585d --- /dev/null +++ b/VDS/Components/Breadcrumbs/Breadcrumbs.swift @@ -0,0 +1,154 @@ +// +// Breadcrumbs.swift +// VDS +// +// Created by Kanamarlapudi, Vasavi on 11/03/24. +// + +import Foundation +import VDSColorTokens +import Combine + +/// A Breadcrumbs contains BreadcrumbItems. +/// It contains Breadcrumb Item Default, Breadcrumb Item Selected, Separator. +/// Breadcrumbs are secondary navigation that use a hierarchy of internal links to tell customers where they are in an experience. Each breadcrumb links to its respective page, except for that of current page. +@objc(VDSBreadcrumbs) +open class Breadcrumbs: View { + + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + /// Array of ``BreadcrumbItem`` views for the Breadcrumbs. + open var breadcrumbs: [BreadcrumbItem] = [] { didSet { setNeedsUpdate() } } + + /// Array of ``BreadcurmbItemModel`` you are wanting to show. + open var breadcrumbModels: [BreadcrumbItemModel] = [] { didSet { updateBreadcrumbItems() } } + + /// Whether this object is enabled or not + override open var isEnabled: Bool { + didSet { + breadcrumbs.forEach { $0.isEnabled = isEnabled } + } + } + + /// Current Surface and this is used to pass down to child objects that implement Surfacable + override open var surface: Surface { + didSet { + breadcrumbs.forEach { $0.surface = surface } + } + } + + /// A callback when the selected item changes. Passes parameters (crumb). + open var onBreadcrumbDidSelect: ((BreadcrumbItem) -> Void)? + + /// A callback when the Tab determine if a item should be selected. + open var onBreadcrumbShouldSelect:((BreadcrumbItem) -> Bool)? + + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + let layout: UICollectionViewFlowLayout = BreadcrumbsFlowLayout().with { + $0.estimatedItemSize = UICollectionViewFlowLayout.automaticSize + $0.minimumInteritemSpacing = VDSLayout.Spacing.space1X.value + $0.minimumLineSpacing = VDSLayout.Spacing.space1X.value + $0.sectionInset = .zero + $0.scrollDirection = .vertical + } + + ///Collectionview to render Breadcrumb Items + private lazy var collectionView: SelfSizingCollectionView = { + let collectionView = SelfSizingCollectionView(frame: .zero, collectionViewLayout: layout) + collectionView.isScrollEnabled = false + collectionView.translatesAutoresizingMaskIntoConstraints = false + collectionView.delegate = self + collectionView.dataSource = self + collectionView.showsHorizontalScrollIndicator = false + collectionView.showsVerticalScrollIndicator = false + collectionView.register(BreadcrumbCellItem.self, forCellWithReuseIdentifier: BreadcrumbCellItem.identifier) + collectionView.backgroundColor = .clear + return collectionView + }() + + //-------------------------------------------------- + // MARK: - Private Methods + //-------------------------------------------------- + /// Removes all of the Breadcrumbs and creates new ones from the Breadcrumb Models property. + private func updateBreadcrumbItems() { + // Clear existing breadcrumbs + for breadcrumbItem in breadcrumbs { + breadcrumbItem.removeFromSuperview() + } + + // Create new breadcrumb items from the models + breadcrumbs = breadcrumbModels.compactMap({ model in + let breadcrumbItem = BreadcrumbItem() + breadcrumbItem.text = model.text + breadcrumbItem.isEnabled = model.enabled + breadcrumbItem.isSelected = model.selected + breadcrumbItem.onClick = { [weak self] breadcrumb in + guard let self, breadcrumb.isEnabled else { return } + if self.onBreadcrumbShouldSelect?(breadcrumb) ?? true { + model.onClick?(breadcrumb) + self.onBreadcrumbDidSelect?(breadcrumb) + } + } + return breadcrumbItem + }) + } + + //------------------------------------------s-------- + // MARK: - Overrides + //-------------------------------------------------- + /// Executed on initialization for this View. + open override func initialSetup() { + super.initialSetup() + addSubview(collectionView) + collectionView.pinToSuperView() + } + + /// Resets to default settings. + open override func reset() { + super.reset() + shouldUpdateView = false + breadcrumbs.forEach { $0.reset() } + shouldUpdateView = true + setNeedsUpdate() + } + + /// Used to make changes to the View based off a change events or from local properties. + open override func updateView() { + super.updateView() + collectionView.reloadData() + } + + open override func layoutSubviews() { + //Turn off the ability to execute updateView() in the super + //since we don't want an infinite loop + shouldUpdateView = false + super.layoutSubviews() + shouldUpdateView = true + + // Accounts for any collection size changes + DispatchQueue.main.async { [weak self] in + guard let self else { return } + self.collectionView.collectionViewLayout.invalidateLayout() + } + } +} + +extension Breadcrumbs: UICollectionViewDelegate, UICollectionViewDataSource { + //-------------------------------------------------- + // MARK: - UICollectionView Delegate & Datasource + //-------------------------------------------------- + public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + breadcrumbs.count + } + + public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: BreadcrumbCellItem.identifier, for: indexPath) as? BreadcrumbCellItem else { return UICollectionViewCell() } + let hideSlash = indexPath.row == 0 + cell.update(surface: surface, hideSlash: hideSlash, breadCrumbItem: breadcrumbs[indexPath.row]) + return cell + } + +} diff --git a/VDS/Components/Breadcrumbs/BreadcrumbsChangeLog.txt b/VDS/Components/Breadcrumbs/BreadcrumbsChangeLog.txt new file mode 100644 index 00000000..3fae7754 --- /dev/null +++ b/VDS/Components/Breadcrumbs/BreadcrumbsChangeLog.txt @@ -0,0 +1,36 @@ +MM/DD/YYYY +---------------- +- Initial Brand 3.0 handoff + +12/17/2021 +---------------- +- Replaced focusring colors (previously interactive/onlight/ondark) with accessibility/onlight/ondark colors +- Updated focus border name (previously interactive.focusring.onlight) with focusring.onlight/ondark + +2/28/2022 +---------------- +- Changed Last Breadcrumb Item to Selected Item + +03/08/2022 +---------------- +- Added dev note for Active and Hover states. + +08/04/2022 +---------------- +- Updated default and inverted prop to light and dark surface. + +11/30/2022 +---------------- +- Added "(web only)" to any instance of "keyboard focus" + +12/13/2022 +---------------- +- Replaced focus border pixel and style & spacing values with tokens. + +01/03/2022 +---------------- +- Updated Specs to use new SPEC Templates and SPEC DOC Components. + +01/06/2023 +---------------- +- Tweaked anatomy element naming to align with design doc and dev doc diff --git a/VDS/Components/Breadcrumbs/BreadcrumbsFlowLayout.swift b/VDS/Components/Breadcrumbs/BreadcrumbsFlowLayout.swift new file mode 100644 index 00000000..d6daf151 --- /dev/null +++ b/VDS/Components/Breadcrumbs/BreadcrumbsFlowLayout.swift @@ -0,0 +1,31 @@ +// +// BreadcrumsFlowLayout.swift +// VDS +// +// Created by Matt Bruce on 3/21/24. +// + +import Foundation +import UIKit + +class BreadcrumbsFlowLayout: UICollectionViewFlowLayout { + + override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { + let attributes = super.layoutAttributesForElements(in: rect) + + var leftMargin = sectionInset.left + var maxY: CGFloat = -1.0 + attributes?.forEach { layoutAttribute in + if layoutAttribute.frame.origin.y >= maxY { + leftMargin = sectionInset.left + } + + layoutAttribute.frame.origin.x = leftMargin + + leftMargin += layoutAttribute.frame.width + minimumInteritemSpacing + maxY = max(layoutAttribute.frame.maxY , maxY) + } + + return attributes + } +} diff --git a/VDS/Components/Buttons/ButtonBase.swift b/VDS/Components/Buttons/ButtonBase.swift index 46f72c55..a1cb39dc 100644 --- a/VDS/Components/Buttons/ButtonBase.swift +++ b/VDS/Components/Buttons/ButtonBase.swift @@ -97,6 +97,9 @@ open class ButtonBase: UIButton, ViewProtocol, UserInfoable, Clickable { /// Whether the Control is enabled or not. open override var isEnabled: Bool { didSet { setNeedsUpdate() } } + /// Whether the Control is selected or not. + open override var isSelected: Bool { didSet { setNeedsUpdate() } } + //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- diff --git a/VDS/VDS.docc/VDS.md b/VDS/VDS.docc/VDS.md index 8beb9255..6b6dbeb6 100755 --- a/VDS/VDS.docc/VDS.md +++ b/VDS/VDS.docc/VDS.md @@ -21,6 +21,7 @@ Using the system allows designers and developers to collaborate more easily and ### Components - ``Badge`` - ``BadgeIndicator`` +- ``Breadcrumbs`` - ``Button`` - ``ButtonIcon`` - ``ButtonGroup``