diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 1b710f50..fdfe2b15 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -24,6 +24,9 @@ 445BA07829C07B3D0036A7C5 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 445BA07729C07B3D0036A7C5 /* Notification.swift */; }; 44604AD429CE186A00E62B51 /* NotificationButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44604AD329CE186A00E62B51 /* NotificationButtonModel.swift */; }; 44604AD729CE196600E62B51 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44604AD629CE196600E62B51 /* Line.swift */; }; + 44A952D92BE384C40009F874 /* TableItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A952D82BE384C40009F874 /* TableItemModel.swift */; }; + 44A952DD2BE3DA820009F874 /* TableFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A952DC2BE3DA820009F874 /* TableFlowLayout.swift */; }; + 44BD43B62C04866600644F87 /* TableRowModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44BD43B52C04866600644F87 /* TableRowModel.swift */; }; 5F21D7BF28DCEB3D003E7CD6 /* Useable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */; }; 5FC35BE328D51405004EBEAC /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FC35BE228D51405004EBEAC /* Button.swift */; }; 71ACE89C2BA0451200FB6ADC /* PaginationContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71ACE89B2BA0451200FB6ADC /* PaginationContainer.swift */; }; @@ -165,6 +168,8 @@ EAD068942A560C13002E3A2D /* LoaderLaunchable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAD068932A560C13002E3A2D /* LoaderLaunchable.swift */; }; EAD8D2C128BFDE8B006EB6A6 /* UIGestureRecognizer+Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAD8D2C028BFDE8B006EB6A6 /* UIGestureRecognizer+Publisher.swift */; }; EAE785312BA0A438009428EA /* UIImage+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAE785302BA0A438009428EA /* UIImage+Helper.swift */; }; + EAF193422C134F3400C68D18 /* Table.swift in Sources */ = {isa = PBXBuildFile; fileRef = 440B84C92BD8E0E9004A732A /* Table.swift */; }; + EAF193432C134F3800C68D18 /* TableCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 443DBAF92BDA303F0021497E /* TableCellItem.swift */; }; EAF1FE9929D4850E00101452 /* Clickable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF1FE9829D4850E00101452 /* Clickable.swift */; }; EAF1FE9B29DB1A6000101452 /* Changeable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF1FE9A29DB1A6000101452 /* Changeable.swift */; }; EAF7F0952899861000B287F5 /* CheckboxItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F0932899861000B287F5 /* CheckboxItem.swift */; }; @@ -216,9 +221,15 @@ 18FEA1AC2BDD137500A56439 /* CalendarIndicatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarIndicatorModel.swift; sourceTree = ""; }; 18FEA1B42BE0E63600A56439 /* Date+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extension.swift"; sourceTree = ""; }; 18FEA1B82BE1301700A56439 /* CalendarChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CalendarChangeLog.txt; sourceTree = ""; }; + 440B84C92BD8E0E9004A732A /* Table.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Table.swift; sourceTree = ""; }; + 443DBAF92BDA303F0021497E /* TableCellItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableCellItem.swift; 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 = ""; }; 44604AD629CE196600E62B51 /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = ""; }; + 44A952D82BE384C40009F874 /* TableItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableItemModel.swift; sourceTree = ""; }; + 44A952DC2BE3DA820009F874 /* TableFlowLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableFlowLayout.swift; sourceTree = ""; }; + 44BD43B52C04866600644F87 /* TableRowModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableRowModel.swift; sourceTree = ""; }; + 44CCF4942C0493A1005C9C5E /* TableChangeLog.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = TableChangeLog.txt; sourceTree = ""; }; 5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Useable.swift; sourceTree = ""; }; 5FC35BE228D51405004EBEAC /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; 710607942B91A99500F2863F /* TitleletChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = TitleletChangeLog.txt; sourceTree = ""; }; @@ -470,6 +481,19 @@ path = Breadcrumbs; sourceTree = ""; }; + 440B84C82BD8E0CE004A732A /* Table */ = { + isa = PBXGroup; + children = ( + 440B84C92BD8E0E9004A732A /* Table.swift */, + 443DBAF92BDA303F0021497E /* TableCellItem.swift */, + 44A952DC2BE3DA820009F874 /* TableFlowLayout.swift */, + 44BD43B52C04866600644F87 /* TableRowModel.swift */, + 44A952D82BE384C40009F874 /* TableItemModel.swift */, + 44CCF4942C0493A1005C9C5E /* TableChangeLog.txt */, + ); + path = Table; + sourceTree = ""; + }; 445BA07629C07ABA0036A7C5 /* Notification */ = { isa = PBXGroup; children = ( @@ -639,6 +663,7 @@ 71B23C2B2B91FA510027F7D9 /* Pagination */, EA89200B28B530F0006B9984 /* RadioBox */, EAF7F11428A1470D00B287F5 /* RadioButton */, + 440B84C82BD8E0CE004A732A /* Table */, EA596ABB2A16B4D500300C4B /* Tabs */, EAC925852911C9DE00091998 /* TextFields */, EA5E304A294CBDBB0082B959 /* TileContainer */, @@ -1222,7 +1247,9 @@ EAC58BFD2BE935C300BA39FA /* TitleLockupTextColor.swift in Sources */, EAACB89A2B927108006A3869 /* Valuing.swift in Sources */, EAE785312BA0A438009428EA /* UIImage+Helper.swift in Sources */, + EAF193422C134F3400C68D18 /* Table.swift in Sources */, EAB5FEF5292D371F00998C17 /* ButtonBase.swift in Sources */, + 44A952D92BE384C40009F874 /* TableItemModel.swift in Sources */, EA978EC5291D6AFE00ACC883 /* AnyLabelAttribute.swift in Sources */, 71ACE89C2BA0451200FB6ADC /* PaginationContainer.swift in Sources */, EAC71A1F2A2E173D00E47A9F /* RadioButton.swift in Sources */, @@ -1254,6 +1281,7 @@ EAC9258F2911C9DE00091998 /* EntryFieldBase.swift in Sources */, EAB1D2EA28AE84AA00DAE764 /* UIControlPublisher.swift in Sources */, EAD068922A560B65002E3A2D /* LoaderViewController.swift in Sources */, + 44BD43B62C04866600644F87 /* TableRowModel.swift in Sources */, 71FC86DA2B96F44C00700965 /* PaginationButton.swift in Sources */, EABFEB642A26473700C4C106 /* NSAttributedString.swift in Sources */, EAF7F13328A2A16500B287F5 /* AttachmentLabelAttributeModel.swift in Sources */, @@ -1269,6 +1297,7 @@ EAC58C0A2BED004E00BA39FA /* FieldType.swift in Sources */, EA471F3A2A95587500CE9E58 /* LayoutConstraintable.swift in Sources */, EAC58C292BF4118C00BA39FA /* DatePickerViewController.swift in Sources */, + EAF193432C134F3800C68D18 /* TableCellItem.swift in Sources */, EAB1D2CF28ABEF2B00DAE764 /* Typography+Base.swift in Sources */, EA0D1C3B2A6AD51B00E5C127 /* Typogprahy+Styles.swift in Sources */, EAF7F09A2899B17200B287F5 /* CATransaction.swift in Sources */, @@ -1305,6 +1334,7 @@ EA985BF02968A93600F2FF2E /* TitleLockupEyebrowModel.swift in Sources */, EA5E30532950DDA60082B959 /* TitleLockup.swift in Sources */, EAD062B02A3B873E0015965D /* BadgeIndicator.swift in Sources */, + 44A952DD2BE3DA820009F874 /* TableFlowLayout.swift in Sources */, EAA5EEB528ECBFB4003B3210 /* ImageLabelAttribute.swift in Sources */, 18792A902B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift in Sources */, EA0B18062A9E2D2D00F2D0CD /* SelectorItemBase.swift in Sources */, @@ -1493,7 +1523,7 @@ BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 65; + CURRENT_PROJECT_VERSION = 66; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1531,7 +1561,7 @@ BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 65; + CURRENT_PROJECT_VERSION = 66; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; diff --git a/VDS/Components/Calendar/Calendar.swift b/VDS/Components/Calendar/Calendar.swift index 65d77bc6..2b0ba57b 100644 --- a/VDS/Components/Calendar/Calendar.swift +++ b/VDS/Components/Calendar/Calendar.swift @@ -67,15 +67,20 @@ open class CalendarBase: Control, Changeable { //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- - internal var containerSize: CGSize { CGSize(width: 328, height: 336) } + internal var containerSize: CGSize { CGSize(width: widthDefault, height: 336) } internal var calendar = Calendar.current private let cellItemSize = CGSize(width: 40, height: 40) private let headerHeight = 88.0 private let footerHeight = 40.0 private let calendarWidth = 304.0 + private let screenThreeSixty = 360.0 + private let widthDefault = 328.0 + private let widthTight = 320.0 - private var heightConstraint: NSLayoutConstraint? + private var collectionViewLeadingConstraint: NSLayoutConstraint? + private var collectionViewHeightConstraint: NSLayoutConstraint? + private var containerWidthConstraint: NSLayoutConstraint? private var containerHeightConstraint: NSLayoutConstraint? private var selectedIndexPath : IndexPath? private var dates: [Date] = [] @@ -115,7 +120,7 @@ open class CalendarBase: Control, Changeable { //-------------------------------------------------- internal var containerBorderColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight , VDSColor.elementsPrimaryOndark) internal var backgroundColorConfiguration = SurfaceColorConfiguration(VDSFormControlsColor.backgroundOnlight, VDSFormControlsColor.backgroundOndark) - + //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- @@ -133,21 +138,16 @@ open class CalendarBase: Control, Changeable { .pinTop() .pinBottom() .pinLeadingGreaterThanOrEqualTo() - .pinTrailingLessThanOrEqualTo() - .width(containerSize.width) .heightGreaterThanEqualTo(containerSize.height) containerView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() // Calendar View containerView.addSubview(collectionView) let calendarHeight = containerSize.height - (2 * VDSLayout.space4X) - let spacing = (containerSize.width - calendarWidth) / 2 collectionView .pinTop(VDSLayout.space4X) .pinBottom(VDSLayout.space4X) - .pinLeading(spacing) - .pinTrailing(spacing) .width(calendarWidth) .heightGreaterThanEqualTo(calendarHeight) @@ -163,16 +163,14 @@ open class CalendarBase: Control, Changeable { displayDate = fallsBetween ? displayDate : minDate fetchDates(with: displayDate) } - - containerView.layer.backgroundColor = backgroundColorConfiguration.getColor(self).cgColor + containerView.backgroundColor = transparentBackground ? .clear : backgroundColorConfiguration.getColor(self) + containerView.layer.cornerRadius = VDSFormControls.borderRadius if hideContainerBorder { containerView.layer.borderColor = nil containerView.layer.borderWidth = 0 - containerView.layer.cornerRadius = 0 } else { containerView.layer.borderColor = containerBorderColorConfiguration.getColor(self).cgColor containerView.layer.borderWidth = VDSFormControls.borderWidth - containerView.layer.cornerRadius = VDSFormControls.borderRadius } } @@ -191,8 +189,6 @@ open class CalendarBase: Control, Changeable { // MARK: - Private Methods //-------------------------------------------------- func fetchDates(with aDate: Date) { - heightConstraint?.isActive = false - containerHeightConstraint?.isActive = false days.removeAll() dates = aDate.calendarDisplayDays @@ -204,17 +200,35 @@ open class CalendarBase: Control, Changeable { days.append(date.getDay()) } } - + updateViewConstraints() + } + + func updateViewConstraints() { collectionView.reloadData() + // container width && collection view leading + collectionViewLeadingConstraint?.isActive = false + containerWidthConstraint?.isActive = false + var width = containerView.frame.size.width + width = ((width > 0) && (width < screenThreeSixty)) ? ((width > widthTight) && (width < screenThreeSixty)) ? widthTight : containerView.frame.size.width : widthDefault + let spacing = (width - calendarWidth) / 2 + collectionViewLeadingConstraint = collectionView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: spacing) + containerWidthConstraint = containerView.widthAnchor.constraint(equalToConstant: calendarWidth + ( 2 * spacing)) + collectionViewLeadingConstraint?.isActive = true + containerWidthConstraint?.isActive = true + + + // container height && collection view height + collectionViewHeightConstraint?.isActive = false + containerHeightConstraint?.isActive = false var height = collectionView.collectionViewLayout.collectionViewContentSize.height height = height > 0 ? height : containerSize.height - heightConstraint = collectionView.heightAnchor.constraint(equalToConstant: height) containerHeightConstraint = containerView.heightAnchor.constraint(equalToConstant: height + (2 * VDSLayout.space4X)) - heightConstraint?.isActive = true + collectionViewHeightConstraint = collectionView.heightAnchor.constraint(equalToConstant: height) containerHeightConstraint?.isActive = true + collectionViewHeightConstraint?.isActive = true layoutIfNeeded() - } + } } extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { diff --git a/VDS/Components/DatePicker/DatePicker.swift b/VDS/Components/DatePicker/DatePicker.swift index aea9d828..a61b70a5 100644 --- a/VDS/Components/DatePicker/DatePicker.swift +++ b/VDS/Components/DatePicker/DatePicker.swift @@ -147,12 +147,7 @@ open class DatePicker: EntryFieldBase, DatePickerViewControllerDelegate, UIPopov open override func updateAccessibility() { super.updateAccessibility() - let label = "Date Picker, \(isReadOnly ? ", read only" : "")" - if let errorText, showError { - fieldStackView.accessibilityLabel = "\(label) ,error, \(errorText)" - } else { - fieldStackView.accessibilityLabel = label - } + fieldStackView.accessibilityLabel = "Date Picker, \(accessibilityLabelText)" fieldStackView.accessibilityHint = isReadOnly || !isEnabled ? "" : "Double tap to open." fieldStackView.accessibilityValue = value } diff --git a/VDS/Components/DropdownSelect/DropdownSelect.swift b/VDS/Components/DropdownSelect/DropdownSelect.swift index b91ef3c8..6ba1035d 100644 --- a/VDS/Components/DropdownSelect/DropdownSelect.swift +++ b/VDS/Components/DropdownSelect/DropdownSelect.swift @@ -278,12 +278,7 @@ open class DropdownSelect: EntryFieldBase { open override func updateAccessibility() { super.updateAccessibility() - let label = "Dropdown Select, \(isReadOnly ? ", read only" : "")" - if let errorText, showError { - fieldStackView.accessibilityLabel = "\(label) ,error, \(errorText)" - } else { - fieldStackView.accessibilityLabel = label - } + fieldStackView.accessibilityLabel = "Dropdown Select, \(accessibilityLabelText)" fieldStackView.accessibilityHint = isReadOnly || !isEnabled ? "" : "Double tap to open." fieldStackView.accessibilityValue = value } diff --git a/VDS/Components/Label/Label.swift b/VDS/Components/Label/Label.swift index 3177814e..00b76206 100644 --- a/VDS/Components/Label/Label.swift +++ b/VDS/Components/Label/Label.swift @@ -314,8 +314,11 @@ open class Label: UILabel, ViewProtocol, UserInfoable { super.text = newValue return } - + + //clear out accessibility + accessibilityElements?.removeAll() accessibilityCustomActions = [] + //create the primary string let mutableText = NSMutableAttributedString.mutableText(for: newValue, textStyle: textStyle, @@ -337,6 +340,10 @@ open class Label: UILabel, ViewProtocol, UserInfoable { return } + //clear out accessibility + accessibilityElements?.removeAll() + accessibilityCustomActions = [] + let mutableText = NSMutableAttributedString(attributedString: newValue) applyAttributes(mutableText) @@ -348,7 +355,7 @@ open class Label: UILabel, ViewProtocol, UserInfoable { private func applyAttributes(_ mutableAttributedString: NSMutableAttributedString) { actions = [] - if let attributes = attributes { + if let attributes { mutableAttributedString.apply(attributes: attributes) } } @@ -359,7 +366,7 @@ open class Label: UILabel, ViewProtocol, UserInfoable { let mutableAttributedString = NSMutableAttributedString(attributedString: attributedText) - if let attributes = attributes { + if let attributes { //loop through the models attributes for attribute in attributes { diff --git a/VDS/Components/Table/Table.swift b/VDS/Components/Table/Table.swift new file mode 100644 index 00000000..97452db9 --- /dev/null +++ b/VDS/Components/Table/Table.swift @@ -0,0 +1,165 @@ +// +// Table.swift +// VDS +// +// Created by Nadigadda, Sumanth on 24/04/24. +// + +import Foundation +import UIKit +import VDSTokens + +///Table is view composed of rows and columns, which takes any view into each cell and resizes based on the highest cell height. +@objc(VDSTable) +open class Table: View { + + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + + /// CollectionView to show the rows and columns + private lazy var matrixView = SelfSizingCollectionView(frame: .zero, collectionViewLayout: flowLayout).with { + $0.register(TableCellItem.self, forCellWithReuseIdentifier: TableCellItem.Identifier) + $0.dataSource = self + $0.delegate = self + $0.translatesAutoresizingMaskIntoConstraints = false + $0.allowsSelection = false + $0.showsVerticalScrollIndicator = false + $0.showsHorizontalScrollIndicator = false + $0.isAccessibilityElement = true + $0.backgroundColor = .clear + } + + /// Custom flow layout to manage the height of the cells + private lazy var flowLayout = MatrixFlowLayout().with { + $0.delegate = self + $0.scrollDirection = .horizontal + } + + /// Array of ``TableItemModel`` by combining Header & Row items + private var tableData: [TableRowModel] { + return tableHeader + tableRows + } + + //-------------------------------------------------- + // MARK: - Enums + //-------------------------------------------------- + + /// Enums used to define the padding for the cell edge spacing. + public enum Padding: String, CaseIterable { + case standard, compact + + func horizontalValue() -> CGFloat { + switch self { + case .standard: + return UIDevice.isIPad ? VDSLayout.space8X : VDSLayout.space6X + case .compact: + return UIDevice.isIPad ? VDSLayout.space8X : VDSLayout.space6X + } + } + + func verticalValue() -> CGFloat { + switch self { + case .standard: + return UIDevice.isIPad ? VDSLayout.space8X : VDSLayout.space6X + case .compact: + return UIDevice.isIPad ? VDSLayout.space4X : VDSLayout.space3X + } + } + } + + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + + /// Parameter to set striped status for the table + open var striped: Bool = false { didSet { setNeedsUpdate() } } + + /// Parameter to set the padding for the cell + open var padding: Padding = .standard { didSet { setNeedsUpdate() } } + + /// Parameter to show the table header row + open var tableHeader: [TableRowModel] = [] { didSet { setNeedsUpdate() } } + + /// Parameter to show the all table rows + open var tableRows: [TableRowModel] = [] { didSet { setNeedsUpdate() } } + + open var fillContainer: Bool = true { didSet { setNeedsUpdate() } } + + open var columnWidths: [CGFloat]? { didSet { setNeedsUpdate() } } + + //-------------------------------------------------- + // MARK: - Overrides + //-------------------------------------------------- + + ///Called upon initializing the table view + open override func initialSetup() { + super.initialSetup() + addSubview(matrixView) + matrixView.pinToSuperView() + } + + /// Will update the table view, when called becasue of any changes in component parameters + open override func updateView() { + super.updateView() + if fillContainer == true || (fillContainer == false && columnWidths == nil) { + columnWidths = calculateColumnWidths() + } + flowLayout.layoutPadding = padding + matrixView.reloadData() + matrixView.collectionViewLayout.invalidateLayout() + } + + /// Resets to default settings. + open override func reset() { + super.reset() + striped = false + padding = .standard + tableHeader = [] + tableRows = [] + fillContainer = true + columnWidths = nil + setNeedsUpdate() + } + + func calculateColumnWidths() -> [CGFloat] { + guard let noOfColumns = tableData.first?.columnsCount else { return [] } + let itemWidth = floor(matrixView.safeAreaLayoutGuide.layoutFrame.width / CGFloat(noOfColumns)) + return Array(repeating: itemWidth, count: noOfColumns) + } + +} + +extension Table: UICollectionViewDelegate, UICollectionViewDataSource, TableCollectionViewLayoutDataDelegate { + + //-------------------------------------------------- + // MARK: - UICollectionViewDelegate & UICollectionViewDataSource + //-------------------------------------------------- + + public func numberOfSections(in collectionView: UICollectionView) -> Int { + return tableData.count + } + public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return tableData[section].columnsCount + } + + public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TableCellItem.Identifier, for: indexPath) as? TableCellItem else { return UICollectionViewCell() } + let currentItem = tableData[indexPath.section].columns[indexPath.row] + let shouldStrip = striped ? (indexPath.section % 2 != 0) : false + cell.updateCell(content: currentItem, surface: surface, striped: shouldStrip, padding: padding) + return cell + } + + //-------------------------------------------------- + // MARK: - TableCollectionViewLayoutDataDelegate + //-------------------------------------------------- + + func collectionView(_ collectionView: UICollectionView, dataForItemAt indexPath: IndexPath) -> TableItemModel { + return tableData[indexPath.section].columns[indexPath.row] + } + + func collectionView(_ collectionView: UICollectionView, widthForItemAt indexPath: IndexPath) -> CGFloat { + return columnWidths?[indexPath.row] ?? 0.0 + } +} diff --git a/VDS/Components/Table/TableCellItem.swift b/VDS/Components/Table/TableCellItem.swift new file mode 100644 index 00000000..88b6c2d0 --- /dev/null +++ b/VDS/Components/Table/TableCellItem.swift @@ -0,0 +1,92 @@ +// +// TableCellItem.swift +// VDS +// +// Created by Nadigadda, Sumanth on 25/04/24. +// + +import Foundation +import UIKit +import VDSTokens + +final class TableCellItem: UICollectionViewCell { + + /// Identifier for TableCellItem + static let Identifier: String = String(describing: TableCellItem.self) + + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + + /// Main view which holds the content of the cell + private let containerView = View() + + /// Line seperator for cell + private let separator: Line = Line() + + /// Color configuration for default background color + private let backgroundColorConfiguration = SurfaceColorConfiguration(VDSColor.backgroundPrimaryLight, VDSColor.backgroundPrimaryDark) + + /// Color configuration for striped background color + private let stripedColorConfiguration = SurfaceColorConfiguration(VDSColor.backgroundSecondaryLight, VDSColor.backgroundSecondaryDark) + + /// Padding parameter to maintain the edge spacing of the containerView + private var padding: Table.Padding = .standard + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + override init(frame: CGRect) { + super.init(frame: frame) + setupCell() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setupCell() + } + + private func setupCell() { + contentView.backgroundColor = .clear + + addSubview(containerView) + containerView.pinToSuperView() + } + + //-------------------------------------------------- + // MARK: - Public Methods + //-------------------------------------------------- + + /// Updates the cell content with ``TableItemModel`` and styling/padding attributes from other parameters + public func updateCell(content: TableItemModel, surface: Surface, striped: Bool = false, padding: Table.Padding = .standard) { + + containerView.subviews.forEach({ $0.removeFromSuperview() }) + self.padding = padding + containerView.surface = surface + containerView.backgroundColor = striped ? stripedColorConfiguration.getColor(surface) : backgroundColorConfiguration.getColor(surface) + + + containerView.addSubview(separator) + separator.pinLeading().pinTrailing().pinBottom() + + separator.style = content.bottomLine ?? .primary + separator.isHidden = content.bottomLine == nil + separator.surface = surface + + guard let component = content.component else { return } + + containerView.addSubview(component) + + if var surfacedView = component as? Surfaceable { + surfacedView.surface = surface + } + + NSLayoutConstraint.activate([ + component.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: VDSLayout.space1X), + component.topAnchor.constraint(greaterThanOrEqualTo: containerView.topAnchor, constant: padding.verticalValue()), + containerView.bottomAnchor.constraint(greaterThanOrEqualTo: component.bottomAnchor, constant: padding.verticalValue()), + containerView.trailingAnchor.constraint(greaterThanOrEqualTo: component.trailingAnchor, constant: padding.horizontalValue()), + containerView.centerYAnchor.constraint(equalTo: component.centerYAnchor) + ]) + } +} diff --git a/VDS/Components/Table/TableChangeLog.txt b/VDS/Components/Table/TableChangeLog.txt new file mode 100644 index 00000000..36f168af --- /dev/null +++ b/VDS/Components/Table/TableChangeLog.txt @@ -0,0 +1,33 @@ +03/31/2022 +---------------- +- Initial Brand 3.0 handoff + +08/02/2022 +---------------- +- Included a VDS Note about the Padding prop name rationale + +08/10/2022 +---------------- +- Updated default and inverted prop to light and dark surface. + +08/29/2022 +---------------- +- Noted that Striped style is set to false as default. Clarified Anatomy description of line elements to say that both are configurable at both group and item level via bottomLine prop. + +09/02/2022 +---------------- +- Added dev note enhancment to fix vertical 1px height jumping + +09/13/2022 +---------------- +- Updated Anatomy element names per decisions made for design/dev docs. + +10/04/2022 +---------------- +- Added dev note to Viewport > Striped > Compact padding to specify that auto-indent also applies to striped tables with default padding. + +12/16/2022 +---------------- +- Updated border color values to use element tokens. +- Removed Line section from first position of Configurations. +- Replaced spacing values with tokens. diff --git a/VDS/Components/Table/TableFlowLayout.swift b/VDS/Components/Table/TableFlowLayout.swift new file mode 100644 index 00000000..2e447187 --- /dev/null +++ b/VDS/Components/Table/TableFlowLayout.swift @@ -0,0 +1,139 @@ +// +// TableFlowLayout.swift +// VDS +// +// Created by Nadigadda, Sumanth on 02/05/24. +// + +import UIKit +import VDSTokens + +protocol TableCollectionViewLayoutDataDelegate: AnyObject { + func collectionView(_ collectionView: UICollectionView, dataForItemAt indexPath: IndexPath) -> TableItemModel + func collectionView(_ collectionView: UICollectionView, widthForItemAt indexPath: IndexPath) -> CGFloat +} + +class MatrixFlowLayout : UICollectionViewFlowLayout { + + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + + ///Spacing between the pagination cells + private let defaultLeadingPadding: CGFloat = VDSLayout.space1X + + /// Parameter to store the layout attributes of cell, while calculate the size & position of the cell + private var itemCache: [UICollectionViewLayoutAttributes] = [] + + /// Parameter to store the total height of the collectionView + private var layoutHeight: CGFloat = 0.0 + + /// Parameter to store the total width of the collectionView + private var layoutWidth: CGFloat = 0.0 + + //-------------------------------------------------- + // MARK: - Internal Properties + //-------------------------------------------------- + + weak var delegate: TableCollectionViewLayoutDataDelegate? + + ///padding type to be set from Table component, which is used to calculate the size & position of the cell. + var layoutPadding: Table.Padding = .standard + + //-------------------------------------------------- + // MARK: - Overrides + //-------------------------------------------------- + + /// Calculates the layout attribute properties & total height of the collectionView + override func prepare() { + super.prepare() + + itemCache.removeAll() + + layoutHeight = 0.0 + + guard let collectionView, let delegate else { return } + + let sections = collectionView.numberOfSections + + var yPos: CGFloat = 0.0 + + ///Looping through all the sections of the collectionView, visually these are rows + for currentSection in 0.. $1.frame.size.height }).first?.frame.size.height ?? 0.0 + + ///Set the highest height as height to all the cells in the row to make the row in uniform height. + itemCache.filter({$0.indexPath.section == currentSection}).forEach { attributes in + attributes.frame.size.height = highestHeightForSection + } + + ///Adds the height to y position for the next section + yPos += highestHeightForSection + } + + layoutHeight = yPos + } + + /// Fetches estimated height by calling the cell's component estimated height and adding padding + private func estimateHeightFor(item: TableItemModel, with width: CGFloat) -> CGFloat { + + let itemWidth = width - layoutPadding.horizontalValue() - defaultLeadingPadding + let maxSize = CGSize(width: itemWidth, height: CGFloat.greatestFiniteMagnitude) + let estItemSize = item.component?.systemLayoutSizeFitting(maxSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel) ?? CGSize(width: itemWidth, height: item.defaultHeight) + return estItemSize.height + (2 * layoutPadding.verticalValue()) + } + + ///This will return the layout attributes for the elements in the defined rect + override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { + var visibleLayoutAttributes: [UICollectionViewLayoutAttributes] = [] + for attributes in itemCache { + if attributes.frame.intersects(rect) { + visibleLayoutAttributes.append(attributes) + } + } + return visibleLayoutAttributes + } + + ///This will return the layout attributes at particular indexPath + override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { + return itemCache.filter({ $0.indexPath == indexPath}).first + } + + ///Returns the collectionview content size + override var collectionViewContentSize: CGSize { + return CGSize(width: layoutWidth, height: layoutHeight) + } +} diff --git a/VDS/Components/Table/TableItemModel.swift b/VDS/Components/Table/TableItemModel.swift new file mode 100644 index 00000000..bd299d1b --- /dev/null +++ b/VDS/Components/Table/TableItemModel.swift @@ -0,0 +1,26 @@ +// +// TableItemModel.swift +// VDS +// +// Created by Nadigadda, Sumanth on 02/05/24. +// + +import Foundation +import UIKit +import VDSTokens + +/// Model that represent the content of each cell of Table component +public struct TableItemModel { + + public let defaultHeight: CGFloat = 50.0 + + public var bottomLine: Line.Style? + + /// Component to be show in the Table cell + public var component: UIView? + + public init(bottomLine: Line.Style? = nil, component: UIView? = nil) { + self.bottomLine = bottomLine + self.component = component + } +} diff --git a/VDS/Components/Table/TableRowModel.swift b/VDS/Components/Table/TableRowModel.swift new file mode 100644 index 00000000..a838438f --- /dev/null +++ b/VDS/Components/Table/TableRowModel.swift @@ -0,0 +1,21 @@ +// +// TableRowModel.swift +// VDS +// +// Created by Sumanth Nadigadda on 27/05/24. +// + +import Foundation + +public struct TableRowModel { + + public var columns: [TableItemModel] + + public var columnsCount: Int { + return columns.count + } + + public init(columns: [TableItemModel]) { + self.columns = columns + } +} diff --git a/VDS/Components/TextFields/EntryFieldBase.swift b/VDS/Components/TextFields/EntryFieldBase.swift index 61e8af55..2111a7f0 100644 --- a/VDS/Components/TextFields/EntryFieldBase.swift +++ b/VDS/Components/TextFields/EntryFieldBase.swift @@ -241,6 +241,23 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { open var rules = [AnyRule]() + open var accessibilityLabelText: String { + var accessibilityLabels = [String]() + if let text = titleLabel.text { + accessibilityLabels.append(text) + } + if isReadOnly { + accessibilityLabels.append("read only") + } + if !isEnabled { + accessibilityLabels.append("dimmed") + } + if let errorText, showError { + accessibilityLabels.append("error, \(errorText)") + } + return accessibilityLabels.joined(separator: ", ") + } + //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- diff --git a/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift b/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift index bd512b50..bc3a289a 100644 --- a/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift +++ b/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift @@ -9,7 +9,20 @@ import Foundation import UIKit extension InputField { - + public class CreditCardNumberRule: Rule, Withable { + public var cardType: CreditCardType? + public var errorMessage: String = "You have exceeded the character limit." + + public func isValid(value: String?) -> Bool { + guard let count = value?.count, let min = cardType?.minLength, let max = cardType?.maxLength else { return true } + if min == max { + return count == max + } else { + return count >= min && count <= max + } + } + } + public enum CreditCardType: String, CaseIterable { case generic case visa @@ -38,15 +51,22 @@ extension InputField { } } - var separatorIndices: [Int] { + func separatorIndices(_ length: Int) -> [Int] { + var indices: [Int] = [4, 8, 12] switch self { - case .dinersClub: - return [4, 10] + case .amex, .dinersClub: + indices = [4, 10] + case .unionPay: + if length == 19 { + indices = [5] + } default: - return [4, 8, 12] + break } + + return indices } - + var securityCodeLength: Int { if self == .amex { return 4 @@ -55,9 +75,21 @@ extension InputField { } } + var minLength: Int { + switch self { + case .visa: return 13 + case .amex: return 15 + case .dinersClub: return 14 + default: return 16 + } + } + var maxLength: Int { switch self { + case .visa: return 19 + case .amex: return 15 case .dinersClub: return 14 + case .unionPay: return 19 default: return 16 } } @@ -131,9 +163,8 @@ extension InputField { override func appendRules(_ inputField: InputField) { if let text = inputField.textField.text, text.count > 0 { - let rule = CharacterCountRule().copyWith { - $0.maxLength = inputField.cardType.maxLength - $0.compareType = .equals + let rule = CreditCardNumberRule().copyWith { + $0.cardType = inputField.cardType $0.errorMessage = "Enter a valid credit card." } inputField.rules.append(.init(rule)) @@ -205,8 +236,8 @@ extension InputField { /// Private internal func formatCreditCardNumber(_ cardType: CreditCardType, number: String) -> String { - let formattedInput = number.filter { $0.isNumber } // Remove any existing slashes - return String.format(formattedInput, indices: cardType.separatorIndices, with: " ") + let rawNumber = number.filter { $0.isNumber } // Remove any existing slashes + return String.format(rawNumber, indices: cardType.separatorIndices(rawNumber.count), with: " ") } internal func updateCardTypeIcon(_ inputField: InputField, rawNumber: String) { @@ -224,9 +255,8 @@ extension InputField { guard rawNumber.count == cardType.maxLength else { return formatCreditCardNumber(cardType, number: number) } let lastFourDigits = rawNumber.suffix(4) let maskedSection = String(repeating: "•", count: 12) - let formattedMaskSection = String.format(maskedSection, indices: cardType.separatorIndices, with: " ") + let formattedMaskSection = String.format(maskedSection, indices: cardType.separatorIndices(rawNumber.count), with: " ") return formattedMaskSection + " " + lastFourDigits } } - } diff --git a/VDS/Components/TextFields/InputField/FieldTypes/Date.swift b/VDS/Components/TextFields/InputField/FieldTypes/Date.swift index 19656745..17decc96 100644 --- a/VDS/Components/TextFields/InputField/FieldTypes/Date.swift +++ b/VDS/Components/TextFields/InputField/FieldTypes/Date.swift @@ -10,6 +10,18 @@ import UIKit extension InputField { + public class DateRule: Rule, Withable { + public var dateFormat: DateFormat? + public var errorMessage: String = "Enter a valid date" + private let dateFormatter = DateFormatter() + + public func isValid(value: String?) -> Bool { + guard let value, let dateFormat, !value.isEmpty else { return true } + dateFormatter.dateFormat = dateFormat.formatString + return dateFormatter.date(from: value) != nil + } + } + public enum DateFormat: String, CaseIterable { case mmyy case mmddyy @@ -46,6 +58,102 @@ extension InputField { case .mmddyyyy: [2,4] } } + + public func isValid(_ date: String) -> Bool { + let allowedCharacters = CharacterSet(charactersIn: "0123456789/") + + // Check if the input contains only allowed characters + if date.rangeOfCharacter(from: allowedCharacters.inverted) != nil || date.isEmpty { + return false + } + + let components = date.split(separator: "/") + + + func isMonth(_ month: String) -> Bool { + switch month.count { + case 1: + guard let month = Int(month), (0...1).contains(month) else { return false } + return true + case 2: + guard let month = Int(month), (1...12).contains(month) else { return false } + return true + default: + return false + } + } + + func isDay(_ day: String) -> Bool { + switch day.count { + case 1: + guard let day = Int(day),(1...3).contains(day) else { return false } + return true + case 2: + guard let day = Int(day), (1...31).contains(day) else { return false } + return true + default: + return false + } + } + + func isYear(_ year: String, max: Int) -> Bool { + guard year.count <= max else { + return false + } + return true + } + + switch self { + case .mmyy: + if components.count > 2 { + return false + } + + // Validate month part + if components.count > 0, let monthPart = components.first { + if !isMonth(String(monthPart)) { + return false + } + } + + // Validate year part + if components.count > 1, let yearPart = components.last { + if !isYear(String(yearPart), max: 2) { + return false + } + } + + case .mmddyy, .mmddyyyy: + if components.count > 3 { + return false + } + + // Validate month part + if components.count > 0, let monthPart = components.first { + if !isMonth(String(monthPart)) { + return false + } + } + + // Validate day part + if components.count > 1 { + let dayPart = components[1] + if !isDay(String(dayPart)) { + return false + } + } + + // Validate year part + if components.count > 2, let yearPart = components.last { + if !isYear(String(yearPart), max: self == .mmddyy ? 2 : 4) { + return false + } + } + } + + return true + } + } class DateHandler: FieldTypeHandler { @@ -58,16 +166,15 @@ extension InputField { override func updateView(_ inputField: InputField) { minWidth = 114.0 - placeholderText = inputField.dateFormat.placeholderText - + //placeholderText = inputField.dateFormat.placeholderText + inputField.textField.formatText = inputField.dateFormat.placeholderText super.updateView(inputField) } override func appendRules(_ inputField: InputField) { if let text = inputField.textField.text, text.count > 0 { - let rule = CharacterCountRule().copyWith { - $0.maxLength = inputField.dateFormat.maxLength - $0.compareType = .equals + let rule = DateRule().copyWith { + $0.dateFormat = inputField.dateFormat $0.errorMessage = "Enter a valid date." } inputField.rules.append(.init(rule)) @@ -86,9 +193,13 @@ extension InputField { if newText.count > inputField.dateFormat.maxLength { return false } - + if newText.count <= inputField.dateFormat.maxLength { - textField.text = String.format(newText, indices: inputField.dateFormat.separatorIndices, with: "/") + let rawNumber = newText.filter { $0.isNumber } + let formatted = String.format(rawNumber, indices: inputField.dateFormat.separatorIndices, with: "/") + if inputField.dateFormat.isValid(formatted) || formatted.isEmpty { + textField.text = formatted + } return false } else { return true diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index a1a78cf7..3764c0c3 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -190,9 +190,11 @@ open class InputField: EntryFieldBase { successLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable() backgroundColorConfiguration.setSurfaceColors(VDSColor.feedbackSuccessBackgroundOnlight, VDSColor.feedbackSuccessBackgroundOndark, forState: .success) - - borderColorConfiguration.setSurfaceColors(VDSColor.feedbackSuccessOnlight, VDSColor.feedbackSuccessOndark, forState: .success) + backgroundColorConfiguration.setSurfaceColors(VDSColor.feedbackSuccessBackgroundOnlight, VDSColor.feedbackSuccessBackgroundOndark, forState: [.success, .focused]) + borderColorConfiguration.setSurfaceColors(VDSColor.feedbackSuccessOnlight, VDSColor.feedbackSuccessOndark, forState: .success) + + textField.textColorConfiguration = textFieldTextColorConfiguration } open override func getFieldContainer() -> UIView { @@ -221,19 +223,14 @@ open class InputField: EntryFieldBase { super.updateView() + textField.surface = surface textField.isEnabled = isEnabled textField.isUserInteractionEnabled = isEnabled && !isReadOnly - textField.textColor = textFieldTextColorConfiguration.getColor(self) } - + open override func updateAccessibility() { super.updateAccessibility() - let label = "\(isReadOnly ? "read only" : "")" - if let errorText, showError { - textField.accessibilityLabel = "\(label) ,error, \(errorText)" - } else { - textField.accessibilityLabel = label - } + textField.accessibilityLabel = accessibilityLabelText textField.accessibilityHint = isReadOnly || !isEnabled ? "" : "Double tap to open." } @@ -253,7 +250,7 @@ open class InputField: EntryFieldBase { statusIcon.name = .checkmarkAlt statusIcon.color = iconColorConfiguration.getColor(self) statusIcon.surface = surface - statusIcon.isHidden = !isEnabled + statusIcon.isHidden = !isEnabled || state.contains(.focused) } else { successLabel.isHidden = true } @@ -308,6 +305,7 @@ extension InputField: UITextFieldDelegate { public func textFieldDidBeginEditing(_ textField: UITextField) { fieldType.handler().textFieldDidBeginEditing(self, textField: textField) updateContainerView() + updateErrorLabel() } public func textFieldDidEndEditing(_ textField: UITextField) { diff --git a/VDS/Components/TextFields/InputField/TextField.swift b/VDS/Components/TextFields/InputField/TextField.swift index eff54f33..e3d1d312 100644 --- a/VDS/Components/TextFields/InputField/TextField.swift +++ b/VDS/Components/TextFields/InputField/TextField.swift @@ -47,6 +47,17 @@ open class TextField: UITextField, ViewProtocol, Errorable { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- + private var formatLabel = Label().with { + $0.tag = 999 + $0.textColorConfiguration = ViewColorConfiguration().with { + $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forDisabled: true) + $0.setSurfaceColors(VDSColor.elementsSecondaryOnlight, VDSColor.elementsSecondaryOndark, forDisabled: false) + }.eraseToAnyColorable() + } + + /// Format String similar to placeholder + open var formatText: String? + /// TextStyle used on the titleLabel. open var textStyle: TextStyle = .defaultStyle { didSet { setNeedsUpdate() } } @@ -114,6 +125,37 @@ open class TextField: UITextField, ViewProtocol, Errorable { open func updateView() { updateLabel() + updateFormat() + } + + open func updateFormat() { + guard let formatText else { + formatLabel.text = "" + return + } + + if viewWithTag(999) == nil { + addSubview(formatLabel) + formatLabel.pinToSuperView() + } + + var attributes: [any LabelAttributeModel]? + var finalFormatText = formatText + + if let text, !text.isEmpty { + //make the color of the matching text clear + attributes = [ColorLabelAttribute(location: 0, length: text.count, color: .clear)] + + let startIndex = formatText.index(formatText.startIndex, offsetBy: text.count) + if startIndex < formatText.endIndex { + finalFormatText = text + formatText[startIndex...] + } + } + + //set the label + formatLabel.surface = surface + formatLabel.text = finalFormatText + formatLabel.attributes = attributes } open func updateAccessibility() { diff --git a/VDS/Components/TextFields/TextArea/TextArea.swift b/VDS/Components/TextFields/TextArea/TextArea.swift index 5402f055..b1c244e5 100644 --- a/VDS/Components/TextFields/TextArea/TextArea.swift +++ b/VDS/Components/TextFields/TextArea/TextArea.swift @@ -195,12 +195,7 @@ open class TextArea: EntryFieldBase { open override func updateAccessibility() { super.updateAccessibility() - let label = "\(isReadOnly ? "read only" : "")" - if let errorText, showError { - textView.accessibilityLabel = "\(label) ,error, \(errorText)" - } else { - textView.accessibilityLabel = label - } + textView.accessibilityLabel = accessibilityLabelText textView.accessibilityHint = isReadOnly || !isEnabled ? "" : "Double tap to open." } diff --git a/VDS/Components/Tilelet/Tilelet.swift b/VDS/Components/Tilelet/Tilelet.swift index 1afc0ee7..95acf895 100644 --- a/VDS/Components/Tilelet/Tilelet.swift +++ b/VDS/Components/Tilelet/Tilelet.swift @@ -540,7 +540,9 @@ open class Tilelet: TileContainerBase { var showIconContainerView = false if let descriptiveIconModel { descriptiveIcon.name = descriptiveIconModel.name - descriptiveIcon.colorConfiguration = descriptiveIconModel.colorConfiguration + if let color = descriptiveIconModel.iconColor?.uiColor { + descriptiveIcon.color = color + } descriptiveIcon.size = descriptiveIconModel.size descriptiveIcon.surface = backgroundColorSurface showIconContainerView = true @@ -548,7 +550,9 @@ open class Tilelet: TileContainerBase { if let directionalIconModel { directionalIcon.name = directionalIconModel.iconType.iconName - directionalIcon.colorConfiguration = directionalIconModel.colorConfiguration + if let color = directionalIconModel.iconColor?.uiColor { + directionalIcon.color = color + } directionalIcon.size = directionalIconModel.size.value directionalIcon.surface = backgroundColorSurface showIconContainerView = true diff --git a/VDS/Components/Tilelet/TileletIconModels.swift b/VDS/Components/Tilelet/TileletIconModels.swift index 226f44cf..58057942 100644 --- a/VDS/Components/Tilelet/TileletIconModels.swift +++ b/VDS/Components/Tilelet/TileletIconModels.swift @@ -11,13 +11,33 @@ import VDSCoreTokens extension Tilelet { + public enum IconColor: Equatable { + case token(UIColor.VDSColor) + case custom(UIColor) + + private var reflectedValue: String { String(reflecting: self) } + + public static func == (lhs: Self, rhs: Self) -> Bool { + lhs.reflectedValue == rhs.reflectedValue + } + + public var uiColor: UIColor { + switch self { + case .token(let color): + return color.uiColor + case .custom(let color): + return color + } + } + } + /// Model that represents the options available for the descriptive icon. public struct DescriptiveIcon { /// A representation that will be used to render the icon with corresponding name. public var name: Icon.Name /// Color of the icon. - public var colorConfiguration: SurfaceColorConfiguration + public var iconColor: IconColor? /// Enum for a preset height and width for the icon. public var size: Icon.Size @@ -26,12 +46,12 @@ extension Tilelet { public var accessibleText: String public init(name: Icon.Name = .multipleDocuments, - colorConfiguration: SurfaceColorConfiguration = .init(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark), + iconColor: IconColor? = nil, size: Icon.Size = .medium, accessibleText: String? = nil) { self.name = name - self.colorConfiguration = colorConfiguration + self.iconColor = iconColor self.accessibleText = accessibleText ?? name.rawValue self.size = size } @@ -57,7 +77,7 @@ extension Tilelet { } /// Color of the icon. - public var colorConfiguration: SurfaceColorConfiguration + public var iconColor: IconColor? /// Accessible Text for the Icon public var accessibleText: String @@ -68,13 +88,13 @@ extension Tilelet { /// Enum for a preset height and width for the icon. public var size: IconSize - public init(iconType: IconType = .rightArrow, - colorConfiguration: SurfaceColorConfiguration = .init(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark), + public init(iconType: IconType = .rightArrow, + iconColor: IconColor? = nil, size: IconSize = .medium, accessibleText: String? = nil) { self.iconType = iconType - self.colorConfiguration = colorConfiguration + self.iconColor = iconColor self.accessibleText = accessibleText ?? iconType.iconName.rawValue self.size = size } diff --git a/VDS/Components/TitleLockup/TitleLockupTextColor.swift b/VDS/Components/TitleLockup/TitleLockupTextColor.swift index 7ce56952..c3c0b703 100644 --- a/VDS/Components/TitleLockup/TitleLockupTextColor.swift +++ b/VDS/Components/TitleLockup/TitleLockupTextColor.swift @@ -17,7 +17,8 @@ extension TitleLockup { public enum TextColor: Equatable { case primary case secondary - case custom(UIColor, UIColor) + case token(UIColor.VDSColor) + case custom(UIColor) private var reflectedValue: String { String(reflecting: self) } @@ -31,15 +32,18 @@ extension TitleLockup { TitleLockup.textColorPrimaryConfiguration case .secondary: TitleLockup.textColorSecondaryConfiguration - case .custom(let lightColor, let darkColor): - SurfaceColorConfiguration(lightColor, darkColor).eraseToAnyColorable() + case .token(let color): + SurfaceColorConfiguration(color.uiColor, color.uiColor).eraseToAnyColorable() + case .custom(let color): + SurfaceColorConfiguration(color, color).eraseToAnyColorable() } } } public enum TitleTextColor: Equatable { case primary - case custom(UIColor, UIColor) + case token(UIColor.VDSColor) + case custom(UIColor) private var reflectedValue: String { String(reflecting: self) } @@ -51,8 +55,10 @@ extension TitleLockup { switch self { case .primary: TitleLockup.textColorPrimaryConfiguration - case .custom(let lightColor, let darkColor): - SurfaceColorConfiguration(lightColor, darkColor).eraseToAnyColorable() + case .token(let color): + SurfaceColorConfiguration(color.uiColor, color.uiColor).eraseToAnyColorable() + case .custom(let color): + SurfaceColorConfiguration(color, color).eraseToAnyColorable() } } } diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index e39df158..615f10df 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -1,8 +1,19 @@ 1.0.66 ---------------- -- CXTDT-565087 - Input Field - Text - OnDark colors -- CXTDT-565112 - Input Field - Credit Card icons -- CXTDT-565117 - Input Field - Overflow not clipped +- ONEAPP-6325 - Table - Development finished +- CXTDT-565087 - InputField - Text - OnDark colors +- CXTDT-565112 - InputField - Credit Card icons +- CXTDT-565117 - InputField - Overflow not clipped +- CXTDT-565105 - InputField - Date - Typeover text not working +- CXTDT-565115 - InputField - CreditCard - China UnionPay does not allow longer numbers +- CXTDT-560823 – TextArea – Accessibility Labels/Error/ReadyOnly/Disabled +- CXTDT-553663 - DropdownSelect – Accessibility +- CXTDT-544662 - Breadcrumbs - Text Wrapping +- CXTDT-568398 - Calendar - Saturday missing (on smaller screen size devices) +- CXTDT-568402 - Calendar - Extra row (on smaller screen size devices) +- CXTDT-568409 - Calendar - Width control missing +- CXTDT-568419 - Calendar - When hideContainerBorder=true, corner radius disappears +- CXTDT-568413 - Calendar - Missing option for Transparent Background 1.0.65 ----------------