diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 26d6c357..7c65a8b0 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 */; }; @@ -17,11 +22,18 @@ 5FC35BE328D51405004EBEAC /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FC35BE228D51405004EBEAC /* Button.swift */; }; 710607952B91A99500F2863F /* TitleletChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 710607942B91A99500F2863F /* TitleletChangeLog.txt */; }; 7115BD3C2B84C0C200E0A610 /* TileContainerChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 7115BD3B2B84C0C200E0A610 /* TileContainerChangeLog.txt */; }; + 71ACE89C2BA0451200FB6ADC /* PaginationContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71ACE89B2BA0451200FB6ADC /* PaginationContainer.swift */; }; + 71B23C2D2B91FA690027F7D9 /* Pagination.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71B23C2C2B91FA690027F7D9 /* Pagination.swift */; }; + 71B5FCBB2B95A0CA00269BCC /* PaginationChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 71B5FCBA2B95A0CA00269BCC /* PaginationChangeLog.txt */; }; 71ACE89E2BA1CC1700FB6ADC /* TiletEyebrowModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71ACE89D2BA1CC1700FB6ADC /* TiletEyebrowModel.swift */; }; 71BFA70A2B7F70E6000DCE33 /* DropShadowable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71BFA7092B7F70E6000DCE33 /* DropShadowable.swift */; }; 71C02B382B7BD98F00E93E66 /* NotificationChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 71C02B372B7BD98F00E93E66 /* NotificationChangeLog.txt */; }; + 71FC86DA2B96F44C00700965 /* PaginationButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71FC86D92B96F44C00700965 /* PaginationButton.swift */; }; + 71FC86DC2B96F4C800700965 /* PaginationCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71FC86DB2B96F4C800700965 /* PaginationCellItem.swift */; }; 71FC86DE2B9738B900700965 /* SurfaceConfigurationValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71FC86DD2B9738B900700965 /* SurfaceConfigurationValue.swift */; }; 71FC86E02B973AE500700965 /* DropShadowConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71FC86DF2B973AE500700965 /* DropShadowConfiguration.swift */; }; + 71FC86E22B97483000700965 /* Clamping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71FC86E12B97483000700965 /* Clamping.swift */; }; + 71FC86E42B9841AC00700965 /* PaginationFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71FC86E32B9841AC00700965 /* PaginationFlowLayout.swift */; }; EA0B18022A9E236900F2D0CD /* SelectorGroupBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0B18012A9E236900F2D0CD /* SelectorGroupBase.swift */; }; EA0B18052A9E2D2D00F2D0CD /* SelectorBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0B18032A9E2D2D00F2D0CD /* SelectorBase.swift */; }; EA0B18062A9E2D2D00F2D0CD /* SelectorItemBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0B18042A9E2D2D00F2D0CD /* SelectorItemBase.swift */; }; @@ -153,6 +165,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 */; }; @@ -182,8 +195,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 = ""; }; @@ -192,11 +210,18 @@ 5FC35BE228D51405004EBEAC /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; 710607942B91A99500F2863F /* TitleletChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = TitleletChangeLog.txt; sourceTree = ""; }; 7115BD3B2B84C0C200E0A610 /* TileContainerChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = TileContainerChangeLog.txt; sourceTree = ""; }; + 71ACE89B2BA0451200FB6ADC /* PaginationContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationContainer.swift; sourceTree = ""; }; + 71B23C2C2B91FA690027F7D9 /* Pagination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pagination.swift; sourceTree = ""; }; + 71B5FCBA2B95A0CA00269BCC /* PaginationChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = PaginationChangeLog.txt; sourceTree = ""; }; 71ACE89D2BA1CC1700FB6ADC /* TiletEyebrowModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TiletEyebrowModel.swift; sourceTree = ""; }; 71BFA7092B7F70E6000DCE33 /* DropShadowable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropShadowable.swift; sourceTree = ""; }; 71C02B372B7BD98F00E93E66 /* NotificationChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = NotificationChangeLog.txt; sourceTree = ""; }; + 71FC86D92B96F44C00700965 /* PaginationButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationButton.swift; sourceTree = ""; }; + 71FC86DB2B96F4C800700965 /* PaginationCellItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationCellItem.swift; sourceTree = ""; }; 71FC86DD2B9738B900700965 /* SurfaceConfigurationValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurfaceConfigurationValue.swift; sourceTree = ""; }; 71FC86DF2B973AE500700965 /* DropShadowConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropShadowConfiguration.swift; sourceTree = ""; }; + 71FC86E12B97483000700965 /* Clamping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Clamping.swift; sourceTree = ""; }; + 71FC86E32B9841AC00700965 /* PaginationFlowLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationFlowLayout.swift; sourceTree = ""; }; EA0B18012A9E236900F2D0CD /* SelectorGroupBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectorGroupBase.swift; sourceTree = ""; }; EA0B18032A9E2D2D00F2D0CD /* SelectorBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectorBase.swift; sourceTree = ""; }; EA0B18042A9E2D2D00F2D0CD /* SelectorItemBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectorItemBase.swift; sourceTree = ""; }; @@ -330,6 +355,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 = ""; }; @@ -370,6 +396,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 = ( @@ -398,6 +437,19 @@ path = Button; sourceTree = ""; }; + 71B23C2B2B91FA510027F7D9 /* Pagination */ = { + isa = PBXGroup; + children = ( + 71B23C2C2B91FA690027F7D9 /* Pagination.swift */, + 71ACE89B2BA0451200FB6ADC /* PaginationContainer.swift */, + 71FC86D92B96F44C00700965 /* PaginationButton.swift */, + 71FC86DB2B96F4C800700965 /* PaginationCellItem.swift */, + 71FC86E32B9841AC00700965 /* PaginationFlowLayout.swift */, + 71B5FCBA2B95A0CA00269BCC /* PaginationChangeLog.txt */, + ); + path = Pagination; + sourceTree = ""; + }; EA0B17FF2A9E21CA00F2D0CD /* Selector */ = { isa = PBXGroup; children = ( @@ -503,6 +555,7 @@ children = ( EA4DB2FE28DCBC1900103EE3 /* Badge */, EAD062AE2A3B87210015965D /* BadgeIndicator */, + 18A65A002B96E7E1006602CC /* Breadcrumbs */, EA0FC2BE2912D18200DF80B4 /* Buttons */, EAF7F092289985E200B287F5 /* Checkbox */, EA985BF3296C609E00F2FF2E /* Icon */, @@ -510,6 +563,7 @@ 44604AD529CE195300E62B51 /* Line */, EAD0688C2A55F801002E3A2D /* Loader */, 445BA07629C07ABA0036A7C5 /* Notification */, + 71B23C2B2B91FA510027F7D9 /* Pagination */, EA89200B28B530F0006B9984 /* RadioBox */, EAF7F11428A1470D00B287F5 /* RadioButton */, EA596ABB2A16B4D500300C4B /* Tabs */, @@ -600,6 +654,7 @@ isa = PBXGroup; children = ( EA3361BC288B2C760071C351 /* TypeAlias.swift */, + 71FC86E12B97483000700965 /* Clamping.swift */, 71FC86DD2B9738B900700965 /* SurfaceConfigurationValue.swift */, 71FC86DF2B973AE500700965 /* DropShadowConfiguration.swift */, ); @@ -987,11 +1042,13 @@ 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 */, EAEEEC9C2B1F8F0700531FC2 /* TextLinkCaretChangeLog.txt in Resources */, EAA5EEE428F5B855003B3210 /* VerizonNHGDS-Light.otf in Resources */, + 71B5FCBB2B95A0CA00269BCC /* PaginationChangeLog.txt in Resources */, EAEEECAD2B1FC1A600531FC2 /* TitleLockupChangeLog.txt in Resources */, EAEEECAB2B1FBF2A00531FC2 /* ToggleChangeLog.txt in Resources */, ); @@ -1016,23 +1073,29 @@ 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 */, + 71FC86E42B9841AC00700965 /* PaginationFlowLayout.swift in Sources */, EAC9258C2911C9DE00091998 /* InputField.swift in Sources */, EA3362402892EF6C0071C351 /* Label.swift in Sources */, EAB2376229E9880400AABE9A /* TrailingTooltipLabel.swift in Sources */, EAACB8982B92706F006A3869 /* DefaultValuing.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 */, + 71FC86E22B97483000700965 /* Clamping.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 */, @@ -1046,6 +1109,7 @@ EAE785312BA0A438009428EA /* UIImage+Helper.swift in Sources */, EAB5FEF5292D371F00998C17 /* ButtonBase.swift in Sources */, EA978EC5291D6AFE00ACC883 /* AnyLabelAttribute.swift in Sources */, + 71ACE89C2BA0451200FB6ADC /* PaginationContainer.swift in Sources */, EAC71A1F2A2E173D00E47A9F /* RadioButton.swift in Sources */, EA33622C2891E73B0071C351 /* FontProtocol.swift in Sources */, EA596ABD2A16B4EC00300C4B /* Tab.swift in Sources */, @@ -1054,6 +1118,7 @@ EA985BEE2968A92400F2FF2E /* TitleLockupSubTitleModel.swift in Sources */, EA985BF22968B5BB00F2FF2E /* TitleLockupTextStyle.swift in Sources */, EAB1D2CD28ABE76100DAE764 /* Withable.swift in Sources */, + 71FC86DC2B96F4C800700965 /* PaginationCellItem.swift in Sources */, EAC846F3294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift in Sources */, EAF7F0952899861000B287F5 /* CheckboxItem.swift in Sources */, EA985BE82968951C00F2FF2E /* TileletTitleModel.swift in Sources */, @@ -1071,13 +1136,16 @@ EAC9258F2911C9DE00091998 /* EntryFieldBase.swift in Sources */, EAB1D2EA28AE84AA00DAE764 /* UIControlPublisher.swift in Sources */, EAD068922A560B65002E3A2D /* LoaderViewController.swift in Sources */, + 71FC86DA2B96F44C00700965 /* PaginationButton.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 */, 44604AD429CE186A00E62B51 /* NotificationButtonModel.swift in Sources */, EAD8D2C128BFDE8B006EB6A6 /* UIGestureRecognizer+Publisher.swift in Sources */, + 71B23C2D2B91FA690027F7D9 /* Pagination.swift in Sources */, EA0D1C372A681CCE00E5C127 /* ToggleView.swift in Sources */, EAF7F0B9289C139800B287F5 /* ColorConfiguration.swift in Sources */, EA3361BD288B2C760071C351 /* TypeAlias.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/Components/Icon/IconName.swift b/VDS/Components/Icon/IconName.swift index c2d5076b..1234df63 100644 --- a/VDS/Components/Icon/IconName.swift +++ b/VDS/Components/Icon/IconName.swift @@ -48,7 +48,6 @@ extension Icon { internal static let paginationRightCaret = Name(name: "pagination-right-caret") internal static let verizonUp = Name(name: "verizon-up") internal static let warningBold = Name(name: "warning-bold") - public static let checkmark = Name(name: "checkmark") public static let checkmarkAlt = Name(name: "checkmark-alt") public static let close = Name(name: "close") diff --git a/VDS/Components/Label/Attributes/ActionLabelAttribute.swift b/VDS/Components/Label/Attributes/ActionLabelAttribute.swift index acd28f91..6d7a1034 100644 --- a/VDS/Components/Label/Attributes/ActionLabelAttribute.swift +++ b/VDS/Components/Label/Attributes/ActionLabelAttribute.swift @@ -56,6 +56,8 @@ public struct ActionLabelAttribute: ActionLabelAttributeModel { } public func setAttribute(on attributedString: NSMutableAttributedString) { + guard isValidRange(on: attributedString) else { return } + if(shouldUnderline){ UnderlineLabelAttribute(location: location, length: length).setAttribute(on: attributedString) } diff --git a/VDS/Components/Label/Attributes/AttachmentLabelAttributeModel.swift b/VDS/Components/Label/Attributes/AttachmentLabelAttributeModel.swift index 5de6f4b2..c4f9bd79 100644 --- a/VDS/Components/Label/Attributes/AttachmentLabelAttributeModel.swift +++ b/VDS/Components/Label/Attributes/AttachmentLabelAttributeModel.swift @@ -14,6 +14,8 @@ public protocol AttachmentLabelAttributeModel: LabelAttributeModel { extension AttachmentLabelAttributeModel { public func setAttribute(on attributedString: NSMutableAttributedString) { + guard isValidRange(on: attributedString) else { return } + do { let mutableString = NSMutableAttributedString() let attachment = try getAttachment() diff --git a/VDS/Components/Label/Attributes/LabelAttributeModel.swift b/VDS/Components/Label/Attributes/LabelAttributeModel.swift index ac43472f..01dfec08 100644 --- a/VDS/Components/Label/Attributes/LabelAttributeModel.swift +++ b/VDS/Components/Label/Attributes/LabelAttributeModel.swift @@ -31,11 +31,21 @@ extension LabelAttributeModel { } public func isValidRange(on attributedString: NSMutableAttributedString) -> Bool { - range.location + range.length <= attributedString.string.count + attributedString.isValid(range: range) + } +} + +public extension String { + func isValid(range: NSRange) -> Bool { + range.location >= 0 && range.length > 0 && range.location + range.length <= count } } public extension NSAttributedString { + func isValid(range: NSRange) -> Bool { + range.location >= 0 && range.length > 0 && range.location + range.length <= length + } + func createAttributeModels() -> [(any LabelAttributeModel)] { var attributes: [any VDS.LabelAttributeModel] = [] enumerateAttributes(in: NSMakeRange(0, length)) { attributeMap, range, stop in @@ -61,6 +71,7 @@ public extension NSAttributedString { extension NSMutableAttributedString { public func apply(attribute: any LabelAttributeModel) { + guard isValid(range: attribute.range) else { return } attribute.setAttribute(on: self) } diff --git a/VDS/Components/Label/Attributes/TooltipLabelAttribute.swift b/VDS/Components/Label/Attributes/TooltipLabelAttribute.swift index 914a8783..bd02cf87 100644 --- a/VDS/Components/Label/Attributes/TooltipLabelAttribute.swift +++ b/VDS/Components/Label/Attributes/TooltipLabelAttribute.swift @@ -24,7 +24,9 @@ public class TooltipLabelAttribute: ActionLabelAttributeModel, TooltipLaunchable public var presenter: UIView? public func setAttribute(on attributedString: NSMutableAttributedString) { - //update the location + guard isValidRange(on: attributedString) else { return } + + //update the location location = attributedString.string.count - 1 //set the default color off surface for text diff --git a/VDS/Components/Label/Label.swift b/VDS/Components/Label/Label.swift index 955d9604..c0edef0b 100644 --- a/VDS/Components/Label/Label.swift +++ b/VDS/Components/Label/Label.swift @@ -344,7 +344,7 @@ open class Label: UILabel, ViewProtocol, UserInfoable { for attribute in attributes { //see if the attribute is Actionable - if let actionable = attribute as? any ActionLabelAttributeModel{ + if let actionable = attribute as? any ActionLabelAttributeModel, mutableAttributedString.isValid(range: actionable.range) { //create a accessibleAction let customAccessibilityAction = customAccessibilityAction(text: mutableAttributedString.string, range: actionable.range, accessibleText: actionable.accessibleText) @@ -379,7 +379,7 @@ open class Label: UILabel, ViewProtocol, UserInfoable { guard let text = text, let attributedText else { return nil } - let actionText = accessibleText ?? NSString(string:text).substring(with: range) + let actionText = accessibleText ?? (text.isValid(range: range) ? NSString(string:text).substring(with: range) : text) // Calculate the frame of the substring let layoutManager = NSLayoutManager() diff --git a/VDS/Components/Notification/Notification.swift b/VDS/Components/Notification/Notification.swift index 126f83f0..cc011249 100644 --- a/VDS/Components/Notification/Notification.swift +++ b/VDS/Components/Notification/Notification.swift @@ -53,11 +53,6 @@ open class Notification: View { } } - /// Enum used to describe the orientation of Notification. - public enum Layout: String, CaseIterable { - case vertical, horizontal - } - //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- @@ -80,19 +75,9 @@ open class Notification: View { $0.translatesAutoresizingMaskIntoConstraints = false } - private var labelButtonViewSpacing: CGFloat { - let spacing: CGFloat = UIDevice.isIPad ? 20 : 16 - return switch layout { - case .vertical: - 0 - case .horizontal: - spacing - } - } + private var labelButtonViewSpacing: CGFloat { UIDevice.isIPad ? 20 : 16 } internal var onCloseSubscriber: AnyCancellable? - - private var maxWidthConstraint: NSLayoutConstraint? private var leadingConstraint: NSLayoutConstraint? @@ -172,19 +157,6 @@ open class Notification: View { /// Add this attribute determine your type of Notification. open var style: Style = .info { didSet { setNeedsUpdate()}} - - private var _layout: Layout = .vertical - - /// Determines the orientation of buttons and text in the Notification. - open var layout: Layout { - set { - if !UIDevice.isIPad, newValue == .horizontal { return } - _layout = newValue - buttonGroup.alignment = _layout == .horizontal ? .center : .left - setNeedsUpdate() - } - get { _layout } - } //-------------------------------------------------- // MARK: - Configuration @@ -219,18 +191,11 @@ open class Notification: View { return 288 } - private var maxViewWidth: CGFloat { - return 1232 - } - - private var labelViewWidthConstraint: NSLayoutConstraint? private var labelViewBottomConstraint: NSLayoutConstraint? private var labelViewAndButtonViewConstraint: NSLayoutConstraint? private var buttonViewTopConstraint: NSLayoutConstraint? private var typeIconWidthConstraint: NSLayoutConstraint? private var closeIconWidthConstraint: NSLayoutConstraint? - private var buttonGroupCenterYConstraint: NSLayoutConstraint? - private var buttonGroupBottomConstraint: NSLayoutConstraint? //-------------------------------------------------- // MARK: - Overrides @@ -255,21 +220,18 @@ open class Notification: View { mainStackView.heightAnchor.constraint(greaterThanOrEqualToConstant: minContentHeight), layoutGuide.widthAnchor.constraint(greaterThanOrEqualToConstant: minViewWidth) ]) - maxWidthConstraint = layoutGuide.widthAnchor.constraint(lessThanOrEqualToConstant: maxViewWidth) labelButtonView.addSubview(labelsView) labelsView .pinTop() .pinLeading() - labelViewWidthConstraint = labelsView.widthAnchor.constraint(equalTo: labelButtonView.widthAnchor, multiplier: 1.0) - labelViewWidthConstraint?.activate() + labelsView.widthAnchor.constraint(equalTo: labelButtonView.widthAnchor, multiplier: 1.0).activate() labelViewBottomConstraint = labelButtonView.bottomAnchor.constraint(equalTo: labelsView.bottomAnchor) labelButtonView.addSubview(buttonGroup) buttonGroup .pinTrailing() - buttonGroupBottomConstraint = labelButtonView.bottomAnchor.constraint(equalTo: buttonGroup.bottomAnchor) - buttonGroupCenterYConstraint = buttonGroup.centerYAnchor.constraint(equalTo: labelButtonView.centerYAnchor) + labelButtonView.bottomAnchor.constraint(equalTo: buttonGroup.bottomAnchor).activate() labelViewAndButtonViewConstraint = buttonGroup.topAnchor.constraint(equalTo: labelsView.bottomAnchor, constant: VDSLayout.Spacing.space3X.value) buttonGroup.widthAnchor.constraint(equalTo: labelsView.widthAnchor).activate() @@ -314,7 +276,6 @@ open class Notification: View { closeButton.size = UIDevice.isIPad ? .medium : .small closeButton.name = .close - layout = .vertical hideCloseButton = false shouldUpdateView = true @@ -338,6 +299,16 @@ open class Notification: View { layer.cornerRadius = UIScreen.main.bounds.width == bounds.width ? 0 : 4.0 } + ///Updating the accessiblity values i.e elements, label, value other items for the component. + open override func updateAccessibility() { + super.updateAccessibility() + accessibilityElements = [closeButton, typeIcon, titleLabel, subTitleLabel, buttonGroup] + typeIcon.accessibilityLabel = style.rawValue + typeIcon.imageView.image?.isAccessibilityElement = false + closeButton.accessibilityTraits = [.button] + closeButton.accessibilityLabel = "Close Notification" + } + //-------------------------------------------------- // MARK: - Private Methods //-------------------------------------------------- @@ -383,32 +354,21 @@ open class Notification: View { secondaryButton.onClick = secondaryButtonModel.onClick buttons.append(secondaryButton) } - labelViewWidthConstraint?.deactivate() if buttons.isEmpty { buttonGroup.isHidden = true - labelViewWidthConstraint = labelsView.widthAnchor.constraint(equalTo: labelButtonView.widthAnchor) buttonGroup.buttons.removeAll() } else { labelsView.setCustomSpacing(VDSLayout.Spacing.space3X.value, after: subTitleLabel) buttonGroup.buttons = buttons buttonGroup.isHidden = false - labelViewWidthConstraint = labelsView.widthAnchor.constraint(equalTo: labelButtonView.widthAnchor, multiplier: layout == .vertical ? 1.0 : 0.5, constant: layout == .vertical ? 0 : -labelButtonViewSpacing) } - labelViewWidthConstraint?.activate() } private func setConstraints() { - maxWidthConstraint?.deactivate() labelViewAndButtonViewConstraint?.deactivate() labelViewBottomConstraint?.deactivate() - buttonGroupCenterYConstraint?.deactivate() - buttonGroupBottomConstraint?.deactivate() - maxWidthConstraint?.constant = maxViewWidth - maxWidthConstraint?.isActive = UIDevice.isIPad - labelViewAndButtonViewConstraint?.isActive = layout == .vertical && !buttonGroup.buttons.isEmpty - labelViewBottomConstraint?.isActive = layout == .horizontal || buttonGroup.buttons.isEmpty - buttonGroupCenterYConstraint?.isActive = layout == .horizontal - buttonGroupBottomConstraint?.isActive = layout == .vertical + labelViewAndButtonViewConstraint?.isActive = !buttonGroup.buttons.isEmpty + labelViewBottomConstraint?.isActive = buttonGroup.buttons.isEmpty typeIconWidthConstraint?.constant = typeIcon.size.dimensions.width closeIconWidthConstraint?.constant = closeButton.size.dimensions.width } diff --git a/VDS/Components/Pagination/Pagination.swift b/VDS/Components/Pagination/Pagination.swift new file mode 100644 index 00000000..b7adcab3 --- /dev/null +++ b/VDS/Components/Pagination/Pagination.swift @@ -0,0 +1,244 @@ +// +// Pagination.swift +// VDS +// +// Created by Bandaru, Krishna Kishore on 01/03/24. +// + +import Foundation +import VDSColorTokens +import Combine + +///Pagination is a control that enables customers to navigate multiple pages of content by selecting either a specific page or the next or previous set of four pages. +@objc(VDSPagination) +open class Pagination: View { + + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + ///Maximum component width + private let maxWidth: CGFloat = 288.0 + ///Collectionview width anchor + private var collectionViewWidthAnchor: NSLayoutConstraint? + ///Collectionview container Center X constraint + private var collectionContainerViewCenterXConstraint: NSLayoutConstraint? + ///Selected page index + private var _selectedPageIndex: Int = 0 + ///Custom flow layout defined for the Pagination + private let flowLayout = PaginationFlowLayout() + ///A root view for the pagination + public let containerView: View = View().with { + $0.translatesAutoresizingMaskIntoConstraints = false + } + ///Collectionview to render pagination indexes + private lazy var collectionView: UICollectionView = { + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout) + collectionView.isScrollEnabled = false + collectionView.translatesAutoresizingMaskIntoConstraints = false + collectionView.showsHorizontalScrollIndicator = false + collectionView.showsVerticalScrollIndicator = false + collectionView.isAccessibilityElement = true + collectionView.register(PaginationCellItem.self, forCellWithReuseIdentifier: PaginationCellItem.identifier) + collectionView.backgroundColor = .clear + collectionView.delegate = self + collectionView.dataSource = self + return collectionView + }() + ///Container view to hold collectionview to render pagination indexes and to handler accessibility. + private let collectionContainerView = PaginationContainer() + + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + ///Previous button to select previous page + public let previousButton: PaginationButton = .init(type: .previous) + ///Next button to select next page + public let nextButton: PaginationButton = .init(type: .next) + /// A callback when the page changes. Passes parameters (selectedPage). + public var onPageDidSelect: ((Int) -> Void)? + /// Total number of pages, allows limit ranging from 0 to 9999. + @Clamping(range: 0...9999) + public var total: Int { + didSet { + previousButton.isHidden = true + nextButton.isHidden = total <= 1 + _selectedPageIndex = 0 + setNeedsUpdate() + updateSelection() + } + } + ///Selected active page number and clips to total pages if selected index is greater than the total pages. + public var selectedPage: Int { + set { + if newValue >= total { + _selectedPageIndex = total - 1 + } else if newValue < 0 { + _selectedPageIndex = 0 + } else { + _selectedPageIndex = max(newValue - 1, 0) + } + setNeedsUpdate() + updateSelection() + } + get { + _selectedPageIndex + 1 //Returns selected page value not index + } + } + + //-------------------------------------------------- + // MARK: - Overrides + //-------------------------------------------------- + /// Executed on initialization for this View. + open override func initialSetup() { + super.initialSetup() + + collectionContainerView.addSubview(collectionView) + containerView.addSubview(previousButton) + containerView.addSubview(collectionContainerView) + containerView.addSubview(nextButton) + addSubview(containerView) + + containerView + .pinTop() + .pinBottom() + .pinLeadingGreaterThanOrEqualTo() + .pinTrailingLessThanOrEqualTo() + .pinCenterX() + .width(maxWidth) + .height(44) + + previousButton + .pinTop() + .pinBottom() + .pinLeading() + .pinTrailingGreaterThanOrEqualTo(collectionContainerView.leadingAnchor) + + collectionContainerView + .pinTrailingGreaterThanOrEqualTo(nextButton.leadingAnchor) + .pinTop() + .pinBottom() + + collectionView + .height(VDSLayout.Spacing.space4X.value) + .pinCenterY() + .pinCenterX() + + collectionViewWidthAnchor = collectionView.width(constant: 92) + + nextButton + .pinTop() + .pinBottom() + .pinTrailing() + + nextButton.onClick = onbuttonTapped + previousButton.onClick = onbuttonTapped + previousButton.isHidden = true + + flowLayout.$collectionViewWidth + .receive(on: RunLoop.main) + .sink { [weak self] value in + self?.collectionViewWidthAnchor?.constant = value //As cell width is dynamic i.e cell may contain 2 or 3 or 4 charcters. Make sure that all the visible cells are displayed. + }.store(in: &subscribers) + collectionContainerView.onAccessibilityIncrement = { [weak self] in + guard let self else { return } + self.selectedPage = max(0, self.selectedPage + 1) + } + collectionContainerView.onAccessibilityDecrement = { [weak self] in + guard let self else { return } + self.selectedPage = max(0, self.selectedPage - 1) + } + } + + ///Updating the accessiblity values i.e elements, label, value other items for the component. + open override func updateAccessibility() { + super.updateAccessibility() + accessibilityElements = [previousButton, collectionContainerView, nextButton] + collectionContainerView.accessibilityLabel = "Pagination containing \(total) pages" + collectionContainerView.accessibilityValue = "Page \(selectedPage) of \(total) selected" + } + + /// Used to make changes to the View based off a change events or from local properties. + open override func updateView() { + super.updateView() + nextButton.surface = surface + previousButton.surface = surface + collectionView.reloadData() + } + + //-------------------------------------------------- + // MARK: - Private Methods + //-------------------------------------------------- + ///When previous/next button is tapped + private func onbuttonTapped(_ sender: UIButton) { + let isNextAction = sender == nextButton + _selectedPageIndex = if isNextAction { _selectedPageIndex + 1 } else { _selectedPageIndex - 1 } + updateSelection() + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) { [weak self] in + guard let self else { return } + UIAccessibility.post(notification: .announcement, argument: "Page \(self.selectedPage) of \(self.total) selected") + } + } + + ///Refreshing the UI based on the selected page + private func updateSelection() { + guard _selectedPageIndex < total else { return } + //Need to make selected page as second element so scrolling previous index of the selected page to left + collectionView.scrollToItem(at: IndexPath(row: max(_selectedPageIndex - 1, 0), section: 0), at: .left, animated: false) + previousButton.isHidden = _selectedPageIndex == 0 + nextButton.isHidden = _selectedPageIndex == total - 1 + collectionView.reloadData() + verifyIfMaxDigitChanged() + } + + ///Identifying if there is any change in the digits of upcoming page + private func verifyIfMaxDigitChanged() { + let upperLimitPage = _selectedPageIndex + flowLayout.maxNumberOfColumns + let upperLimitDigits = upperLimitPage.digitCount //future value digits + switch (flowLayout.numberOfColumns, upperLimitDigits) { + case (_, 1), (_, 2): + flowLayout.numberOfColumns = 4 + default: + flowLayout.numberOfColumns = 3 + } + if upperLimitDigits != flowLayout.upperLimitDigits { + flowLayout.upperLimitDigits = upperLimitDigits + flowLayout.invalidateLayout() + collectionView.reloadData() + //Need to make selected page as second element so scrolling previous index of the selected page to left + collectionView.scrollToItem(at: IndexPath(row: max(_selectedPageIndex - 1, 0), section: 0), at: .left, animated: false) + } + } +} + +extension Pagination: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { + //-------------------------------------------------- + // MARK: - UICollectionView Delegate & Datasource + //-------------------------------------------------- + public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { total } + + public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PaginationCellItem.identifier, for: indexPath) as? PaginationCellItem else { return UICollectionViewCell() } + cell.update(_selectedPageIndex, currentIndex: indexPath.row, surface: surface) + return cell + } + + public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + guard _selectedPageIndex != indexPath.row else { return } + _selectedPageIndex = indexPath.row + updateSelection() + onPageDidSelect?(selectedPage) + } +} + +fileprivate extension Int { + //-------------------------------------------------- + // MARK: - Extension on Int to identify number of digits in given number. + //-------------------------------------------------- + var digitCount: Int { + numberOfDigits(in: self) + } + + private func numberOfDigits(in number: Int) -> Int { + number < 10 && number >= 0 ? 1 : 1 + numberOfDigits(in: number/10) + } +} diff --git a/VDS/Components/Pagination/PaginationButton.swift b/VDS/Components/Pagination/PaginationButton.swift new file mode 100644 index 00000000..290bbef5 --- /dev/null +++ b/VDS/Components/Pagination/PaginationButton.swift @@ -0,0 +1,111 @@ +// +// PaginationButton.swift +// VDS +// +// Created by Bandaru, Krishna Kishore on 05/03/24. +// + +import UIKit +import VDSColorTokens + +///This is customised button for Pagination view +@objc(PaginationButton) +open class PaginationButton: ButtonBase { + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + /// Type of the PaginationButton + private var type: Type = .next + /// Button tint color configuration + private let buttonTintColorConfiguration = SurfaceColorConfiguration(VDSColor.paletteBlack, VDSColor.paletteWhite) + /// Button title color configuration + private let buttonTextColorConfiguration = SurfaceColorConfiguration(VDSColor.paletteBlack, VDSColor.paletteWhite) + /// Button configuration for iOS 15+ + @available(iOS 15.0, *) + private var buttonConfiguration: Button.Configuration { + var configuration = ButtonBase.Configuration.plain() + configuration.imagePadding = VDSLayout.Spacing.space2X.value + configuration.imagePlacement = type == .next ? .trailing : .leading + configuration.titleAlignment = type == .next ? .trailing : .leading + configuration.contentInsets = .zero + return configuration + } + + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + /// TextStyle used on the titleLabel. + open override var textStyle: TextStyle { TextStyle.boldBodySmall } + /// UIColor used on the titleLabel text. + open override var textColor: UIColor { buttonTextColorConfiguration.getColor(surface) } + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + init(type: Type) { + self.type = type + super.init() + } + + required public init() { + super.init() + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + } + + //-------------------------------------------------- + // MARK: - Overrides + //-------------------------------------------------- + /// Executed on initialization for this View. + open override func initialSetup() { + super.initialSetup() + if #available(iOS 15.0, *) { + configuration = buttonConfiguration + } else { + semanticContentAttribute = type == .next ? .forceRightToLeft : .forceLeftToRight + imageEdgeInsets = .init(top: 0, left: 0, bottom: 0, right: VDSLayout.Spacing.space2X.value) + } + contentHorizontalAlignment = type == .next ? .trailing : .leading + } + + /// Used to make changes to the View based off a change events or from local properties. + open override func updateView() { + text = type.title + let color = buttonTintColorConfiguration.getColor(surface) + setImage(type.image(color), for: .normal) + tintColor = color + super.updateView() + } +} + +extension PaginationButton { + //-------------------------------------------------- + // MARK: - Enum to configure PaginationButton + //-------------------------------------------------- + enum `Type` { + case previous, next + + var title: String { + switch self { + case .next: + "Next" + case .previous: + "Previous" + } + } + + private var imageSize: CGSize { Icon.Size.xsmall.dimensions } + + ///Image for the configuration type + func image(_ color: UIColor) -> UIImage? { + switch self { + case .previous: + UIImage.image(for: .paginationLeftArrow, color: color, renderingMode: .alwaysTemplate)?.resized(to: imageSize) + case .next: + UIImage.image(for: .paginationRightArrow, color: color, renderingMode: .alwaysTemplate)?.resized(to: imageSize) + } + } + } +} diff --git a/VDS/Components/Pagination/PaginationCellItem.swift b/VDS/Components/Pagination/PaginationCellItem.swift new file mode 100644 index 00000000..24edc3a4 --- /dev/null +++ b/VDS/Components/Pagination/PaginationCellItem.swift @@ -0,0 +1,63 @@ +// +// PaginationCellItem.swift +// VDS +// +// Created by Bandaru, Krishna Kishore on 05/03/24. +// + +import UIKit +import VDSColorTokens + +///This is customised view for Pagination cell item +final class PaginationCellItem: UICollectionViewCell { + + ///Identifier for the PaginationCellItem + static let identifier: String = String(describing: PaginationCellItem.self) + + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + ///Text color configuration for the element + private let textColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark) + ///Pagination index label + private var indexLabel: Label = Label().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.textAlignment = .center + $0.isAccessibilityElement = false + $0.numberOfLines = 1 + } + + //-------------------------------------------------- + // 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() { + let containerView = View() + containerView.translatesAutoresizingMaskIntoConstraints = false + containerView.addSubview(indexLabel) + contentView.addSubview(containerView) + containerView.pinToSuperView() + indexLabel.pinToSuperView() + indexLabel.widthGreaterThanEqualTo(VDSLayout.Spacing.space5X.value) + contentView.backgroundColor = .clear + containerView.backgroundColor = .clear + indexLabel.backgroundColor = .clear + } + + ///Updating UI based on selected index, current index along with surface + func update(_ selectedIndex: Int, currentIndex: Int, surface: Surface) { + indexLabel.textStyle = selectedIndex == currentIndex ? .boldBodySmall : .bodySmall + indexLabel.text = "\(currentIndex + 1)" + indexLabel.textColor = textColorConfiguration.getColor(surface) + } +} diff --git a/VDS/Components/Pagination/PaginationChangeLog.txt b/VDS/Components/Pagination/PaginationChangeLog.txt new file mode 100644 index 00000000..66f0ac14 --- /dev/null +++ b/VDS/Components/Pagination/PaginationChangeLog.txt @@ -0,0 +1,34 @@ +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 + +02/28/2022 +---------------- +- Change Page Item Active to Page Item Selected. All Active references changed to Selected. + +03/01/2022 +---------------- +- Replaced Left and Right Arrow Non-Scaling icons with VDS Icon. +- Removed “weight” and “vector effect” from Anatomy frame. + +08/10/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/12/2023 +---------------- +- Removed “Page Item Selected” from Anatomy. diff --git a/VDS/Components/Pagination/PaginationContainer.swift b/VDS/Components/Pagination/PaginationContainer.swift new file mode 100644 index 00000000..2172e32a --- /dev/null +++ b/VDS/Components/Pagination/PaginationContainer.swift @@ -0,0 +1,45 @@ +// +// PaginationContainerView.swift +// VDS +// +// Created by Bandaru, Krishna Kishore on 12/03/24. +// + +import UIKit + +///PaginationCollectionView is a container view that holds collectionview for displaying page indexes +final class PaginationContainer: View { + + //-------------------------------------------------- + // MARK: - Internal Properties + //-------------------------------------------------- + ///Notifies when accessibility increment is happend when user swipes up + var onAccessibilityIncrement: (() -> Void)? + ///Notifies when accessibility decrement is happend when user swipes down + var onAccessibilityDecrement: (() -> Void)? + + //-------------------------------------------------- + // MARK: - Overrides + //-------------------------------------------------- + ///Accessibilty traits for the Pagination view + override var accessibilityTraits: UIAccessibilityTraits { + get { [.adjustable] } + set { } + } + + ///Accessibilty increment + override func accessibilityIncrement() { + onAccessibilityIncrement?() + } + + ///Accessibilty decrement + override func accessibilityDecrement() { + onAccessibilityDecrement?() + } + + /// Executed on initialization for this View. + override func setup() { + super.setup() + isAccessibilityElement = true + } +} diff --git a/VDS/Components/Pagination/PaginationFlowLayout.swift b/VDS/Components/Pagination/PaginationFlowLayout.swift new file mode 100644 index 00000000..c6bd02c3 --- /dev/null +++ b/VDS/Components/Pagination/PaginationFlowLayout.swift @@ -0,0 +1,91 @@ +// +// PaginationFlowLayout.swift +// VDS +// +// Created by Bandaru, Krishna Kishore on 06/03/24. +// + +import Foundation +import UIKit + +///Customised flow layout for Pagination view +final class PaginationFlowLayout : UICollectionViewLayout { + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + ///Spacing between the pagination cells + private let spacingBetweenCell: CGFloat = VDSLayout.Spacing.space1X.value + ///Pre-defined sizes of the pagination cell based on number of digits. + private var upperLimitSize: CGSize { + switch upperLimitDigits { + case 1, 2: .init(width: 20, height: 16) + case 3: .init(width: 28, height: 16) + default: .init(width: 34, height: 16) + } + } + ///Property to store the defined layout attributes. + private var itemCache : [UICollectionViewLayoutAttributes] = [] + + //-------------------------------------------------- + // MARK: - Internal Properties + //-------------------------------------------------- + ///Maximum number of page indexes shown on UI + let maxNumberOfColumns: Int = 4 + ///Number of digits of the maximum page index. + var upperLimitDigits: Int = 0 + ///Number of page indexes shown on UI. + var numberOfColumns: Int = 4 + ///A property that publishes when there is change in collection view width. + @Published var collectionViewWidth: CGFloat = 0 + + //-------------------------------------------------- + // MARK: - Overrides + //-------------------------------------------------- + ///Preparing the layout collection attributes for pagination and updating the collectionview width. + override func prepare() { + + guard let collectionView else { return } + + itemCache.removeAll() + var xPos : CGFloat = 0 + for item in 0.. [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[indexPath.row] + } + + ///Returns the collectionview content size + override var collectionViewContentSize: CGSize { + guard let lastAttribute = itemCache.last else { return super.collectionViewContentSize } + return .init(width: lastAttribute.frame.width + lastAttribute.frame.origin.x, height: 16) + } +} diff --git a/VDS/Components/Tabs/Tabs.swift b/VDS/Components/Tabs/Tabs.swift index 0aa81bdf..647a74a2 100644 --- a/VDS/Components/Tabs/Tabs.swift +++ b/VDS/Components/Tabs/Tabs.swift @@ -264,7 +264,6 @@ open class Tabs: View { model.onClick?(tab.index) self.selectedIndex = tab.index self.onTabDidSelect?(tab.index) - let t = tabViews[tab.index] } } } diff --git a/VDS/Extensions/UIImage+Helper.swift b/VDS/Extensions/UIImage+Helper.swift index c8f103dc..41f25a42 100644 --- a/VDS/Extensions/UIImage+Helper.swift +++ b/VDS/Extensions/UIImage+Helper.swift @@ -23,4 +23,17 @@ extension UIImage { return image.withTintColor(color, renderingMode: renderingMode) } + + /// Resizes image to a specific Size + /// - Parameter size: Size to resize + /// - Returns: Image that is resized + public func resized(to size: CGSize) -> UIImage? { + UIGraphicsBeginImageContextWithOptions(size, false, 0.0) + + defer { UIGraphicsEndImageContext() } + draw(in: .init(origin: .zero, size: size)) + + return UIGraphicsGetImageFromCurrentImageContext() + } + } diff --git a/VDS/Protocols/LayoutConstraintable.swift b/VDS/Protocols/LayoutConstraintable.swift index 3c05bbc7..457b5fa5 100644 --- a/VDS/Protocols/LayoutConstraintable.swift +++ b/VDS/Protocols/LayoutConstraintable.swift @@ -478,7 +478,159 @@ extension LayoutConstraintable { } } +//-------------------------------------------------- +// MARK: - Center X Constraints +//-------------------------------------------------- +extension LayoutConstraintable { + + @discardableResult + /// Adds a centerXAnchor. + /// - Parameter constant: Constant size. + /// - Returns: Yourself. + public func pinCenterX(_ constant: CGFloat = 0.0, _ priority: UILayoutPriority = .required) -> Self { + pinCenterX(nil, constant) + } + @discardableResult + /// Adds a centerXAnchor to a specific XAxisAnchor. + /// - Parameter anchor:The anchor in which to attach the centerXAnchor. + /// - constant: Constant size. + /// - Returns: Yourself. + public func pinCenterX(_ anchor: NSLayoutXAxisAnchor? = nil, _ constant: CGFloat = 0.0, _ priority: UILayoutPriority = .required) -> Self { + pinCenterX(anchor: anchor, constant: constant) + return self + } + + @discardableResult + /// Adds a centerXAnchor to a specific XAxisAnchor passed in using a lessThanOrEqualTo Constraint + /// - Parameter anchor:The anchor in which to attach the centerXAnchor + /// - constant: Constant size. + /// - Returns: Yourself. + public func pinCenterXLessThanOrEqualTo(_ anchor: NSLayoutXAxisAnchor? = nil, _ constant: CGFloat = 0.0, _ priority: UILayoutPriority = .required) -> Self { + pinCenterXLessThanOrEqualTo(anchor: anchor, constant: constant) + return self + } + + @discardableResult + /// Adds a centerXAnchor to a specific XAxisAnchor passed in using a greaterThanOrEqualTo Constraint + /// - Parameter anchor:The anchor in which to attach the centerXAnchor + /// - constant: Constant size. + /// - Returns: Yourself. + public func pinCenterXGreaterThanOrEqualTo(_ anchor: NSLayoutXAxisAnchor? = nil, _ constant: CGFloat = 0.0) -> Self { + pinCenterXGreaterThanOrEqualTo(anchor: anchor, constant: constant) + return self + } + + @discardableResult + /// Adds a centerXAnchor for the constant passed into the method. + /// - Parameter anchor:The anchor in which to attach the centerXAnchor + /// - constant: Constant size. + /// - Returns: The Constraint that was created. + public func pinCenterX(anchor: NSLayoutXAxisAnchor?, constant: CGFloat = 0.0, priority: UILayoutPriority = .required) -> NSLayoutConstraint? { + let found: NSLayoutXAxisAnchor? = anchor ?? superview?.centerXAnchor + guard let found else { return nil } + return centerXAnchor.constraint(equalTo: found, constant: -constant).with { $0.priority = priority; $0.isActive = true } + } + + @discardableResult + /// Adds a centerXAnchor with the constant passed in using a lessThanOrEqualTo Constraint. + /// - Parameter anchor:The anchor in which to attach the centerXAnchor + /// - constant: Constant size. + /// - Returns: The Constraint that was created. + public func pinCenterXLessThanOrEqualTo(anchor: NSLayoutXAxisAnchor?, constant: CGFloat = 0.0, priority: UILayoutPriority = .required) -> NSLayoutConstraint? { + let found: NSLayoutXAxisAnchor? = anchor ?? superview?.centerXAnchor + guard let found else { return nil } + return centerXAnchor.constraint(lessThanOrEqualTo: found, constant: -constant).with { $0.priority = priority; $0.isActive = true } + } + + @discardableResult + /// Adds a centerXAnchor with the constant passed in using a greaterThanOrEqualTo Constraint. + /// - Parameter anchor:The anchor in which to attach the centerXAnchor + /// - constant: Constant size. + /// - Returns: The Constraint that was created. + public func pinCenterXGreaterThanOrEqualTo(anchor: NSLayoutXAxisAnchor?, constant: CGFloat = 0.0, priority: UILayoutPriority = .required) -> NSLayoutConstraint? { + let found: NSLayoutXAxisAnchor? = anchor ?? superview?.centerXAnchor + guard let found else { return nil } + return centerXAnchor.constraint(greaterThanOrEqualTo: found, constant: -constant).with { $0.priority = priority; $0.isActive = true } + } +} + +//-------------------------------------------------- +// MARK: - Center Y Constraints +//-------------------------------------------------- +extension LayoutConstraintable { + + @discardableResult + /// Adds a centerYAnchor. + /// - Parameter constant: Constant size. + /// - Returns: Yourself. + public func pinCenterY(_ constant: CGFloat = 0.0, _ priority: UILayoutPriority = .required) -> Self { + pinCenterY(nil, constant) + } + + @discardableResult + /// Adds a centerYAnchor to a specific YAxisAnchor. + /// - Parameter anchor:The anchor in which to attach the centerYAnchor. + /// - constant: Constant size. + /// - Returns: Yourself. + public func pinCenterY(_ anchor: NSLayoutYAxisAnchor? = nil, _ constant: CGFloat = 0.0, _ priority: UILayoutPriority = .required) -> Self { + pinCenterY(anchor: anchor, constant: constant) + return self + } + + @discardableResult + /// Adds a centerYAnchor to a specific YAxisAnchor passed in using a lessThanOrEqualTo Constraint + /// - Parameter anchor:The anchor in which to attach the centerYAnchor + /// - constant: Constant size. + /// - Returns: Yourself. + public func pinCenterYLessThanOrEqualTo(_ anchor: NSLayoutYAxisAnchor? = nil, _ constant: CGFloat = 0.0, _ priority: UILayoutPriority = .required) -> Self { + pinCenterYLessThanOrEqualTo(anchor: anchor, constant: constant) + return self + } + + @discardableResult + /// Adds a centerYAnchor to a specific YAxisAnchor passed in using a greaterThanOrEqualTo Constraint + /// - Parameter anchor:The anchor in which to attach the centerYAnchor + /// - constant: Constant size. + /// - Returns: Yourself. + public func pinCenterYGreaterThanOrEqualTo(_ anchor: NSLayoutXAxisAnchor? = nil, _ constant: CGFloat = 0.0) -> Self { + pinCenterXGreaterThanOrEqualTo(anchor: anchor, constant: constant) + return self + } + + @discardableResult + /// Adds a centerYAnchor for the constant passed into the method. + /// - Parameter anchor:The anchor in which to attach the centerYAnchor + /// - constant: Constant size. + /// - Returns: The Constraint that was created. + public func pinCenterY(anchor: NSLayoutYAxisAnchor?, constant: CGFloat = 0.0, priority: UILayoutPriority = .required) -> NSLayoutConstraint? { + let found: NSLayoutYAxisAnchor? = anchor ?? superview?.centerYAnchor + guard let found else { return nil } + return centerYAnchor.constraint(equalTo: found, constant: -constant).with { $0.priority = priority; $0.isActive = true } + } + + @discardableResult + /// Adds a centerYAnchor with the constant passed in using a lessThanOrEqualTo Constraint. + /// - Parameter anchor:The anchor in which to attach the centerYAnchor + /// - constant: Constant size. + /// - Returns: The Constraint that was created. + public func pinCenterYLessThanOrEqualTo(anchor: NSLayoutYAxisAnchor?, constant: CGFloat = 0.0, priority: UILayoutPriority = .required) -> NSLayoutConstraint? { + let found: NSLayoutYAxisAnchor? = anchor ?? superview?.centerYAnchor + guard let found else { return nil } + return centerYAnchor.constraint(lessThanOrEqualTo: found, constant: -constant).with { $0.priority = priority; $0.isActive = true } + } + + @discardableResult + /// Adds a centerYAnchor with the constant passed in using a greaterThanOrEqualTo Constraint. + /// - Parameter anchor:The anchor in which to attach the centerYAnchor + /// - constant: Constant size. + /// - Returns: The Constraint that was created. + public func pinCenterYGreaterThanOrEqualTo(anchor: NSLayoutYAxisAnchor?, constant: CGFloat = 0.0, priority: UILayoutPriority = .required) -> NSLayoutConstraint? { + let found: NSLayoutYAxisAnchor? = anchor ?? superview?.centerYAnchor + guard let found else { return nil } + return centerYAnchor.constraint(greaterThanOrEqualTo: found, constant: -constant).with { $0.priority = priority; $0.isActive = true } + } +} //-------------------------------------------------- // MARK: - Implementations //-------------------------------------------------- diff --git a/VDS/Utilities/Clamping.swift b/VDS/Utilities/Clamping.swift new file mode 100644 index 00000000..c8213828 --- /dev/null +++ b/VDS/Utilities/Clamping.swift @@ -0,0 +1,24 @@ +// +// Clamping.swift +// VDS +// +// Created by Bandaru, Krishna Kishore on 05/03/24. +// + +import Foundation + +@propertyWrapper public struct Clamping { + + private var value: Value + private let range: ClosedRange + + public init(range: ClosedRange) { + self.value = range.lowerBound + self.range = range + } + + public var wrappedValue: Value { + get { value } + set { value = min(max(range.lowerBound, newValue), range.upperBound) } + } +} diff --git a/VDS/VDS.docc/VDS.md b/VDS/VDS.docc/VDS.md index 8beb9255..551a835c 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`` @@ -33,6 +34,7 @@ Using the system allows designers and developers to collaborate more easily and - ``Line`` - ``Loader`` - ``Notification`` +- ``Pagination`` - ``RadioBoxItem`` - ``RadioBoxGroup`` - ``RadioButton``