Merge branch 'develop' into feature/breadcrumbs

This commit is contained in:
Matt Bruce 2024-03-27 09:47:32 -05:00
commit 552de21ec0
22 changed files with 632 additions and 155 deletions

View File

@ -20,10 +20,12 @@
44604AD729CE196600E62B51 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44604AD629CE196600E62B51 /* Line.swift */; }; 44604AD729CE196600E62B51 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44604AD629CE196600E62B51 /* Line.swift */; };
5F21D7BF28DCEB3D003E7CD6 /* Useable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */; }; 5F21D7BF28DCEB3D003E7CD6 /* Useable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */; };
5FC35BE328D51405004EBEAC /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FC35BE228D51405004EBEAC /* Button.swift */; }; 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 */; }; 7115BD3C2B84C0C200E0A610 /* TileContainerChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 7115BD3B2B84C0C200E0A610 /* TileContainerChangeLog.txt */; };
71ACE89C2BA0451200FB6ADC /* PaginationContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71ACE89B2BA0451200FB6ADC /* PaginationContainer.swift */; }; 71ACE89C2BA0451200FB6ADC /* PaginationContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71ACE89B2BA0451200FB6ADC /* PaginationContainer.swift */; };
71B23C2D2B91FA690027F7D9 /* Pagination.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71B23C2C2B91FA690027F7D9 /* Pagination.swift */; }; 71B23C2D2B91FA690027F7D9 /* Pagination.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71B23C2C2B91FA690027F7D9 /* Pagination.swift */; };
71B5FCBB2B95A0CA00269BCC /* PaginationChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 71B5FCBA2B95A0CA00269BCC /* PaginationChangeLog.txt */; }; 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 */; }; 71BFA70A2B7F70E6000DCE33 /* DropShadowable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71BFA7092B7F70E6000DCE33 /* DropShadowable.swift */; };
71C02B382B7BD98F00E93E66 /* NotificationChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 71C02B372B7BD98F00E93E66 /* NotificationChangeLog.txt */; }; 71C02B382B7BD98F00E93E66 /* NotificationChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 71C02B372B7BD98F00E93E66 /* NotificationChangeLog.txt */; };
71FC86DA2B96F44C00700965 /* PaginationButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71FC86D92B96F44C00700965 /* PaginationButton.swift */; }; 71FC86DA2B96F44C00700965 /* PaginationButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71FC86D92B96F44C00700965 /* PaginationButton.swift */; };
@ -114,6 +116,8 @@
EAA5EEF128F5C909003B3210 /* VDSColorTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EAA5EEED28F5C908003B3210 /* VDSColorTokens.xcframework */; }; EAA5EEF128F5C909003B3210 /* VDSColorTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EAA5EEED28F5C908003B3210 /* VDSColorTokens.xcframework */; };
EAA5EEF328F5C909003B3210 /* VDSFormControlsTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EAA5EEEE28F5C908003B3210 /* VDSFormControlsTokens.xcframework */; }; EAA5EEF328F5C909003B3210 /* VDSFormControlsTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EAA5EEEE28F5C908003B3210 /* VDSFormControlsTokens.xcframework */; };
EAA7456C2AB23E2000C1841F /* TooltipModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA7456B2AB23E2000C1841F /* TooltipModel.swift */; }; EAA7456C2AB23E2000C1841F /* TooltipModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA7456B2AB23E2000C1841F /* TooltipModel.swift */; };
EAACB8982B92706F006A3869 /* DefaultValuing.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAACB8972B92706F006A3869 /* DefaultValuing.swift */; };
EAACB89A2B927108006A3869 /* Valuing.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAACB8992B927108006A3869 /* Valuing.swift */; };
EAB1D29C28A5618900DAE764 /* RadioButtonGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D29B28A5618900DAE764 /* RadioButtonGroup.swift */; }; EAB1D29C28A5618900DAE764 /* RadioButtonGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D29B28A5618900DAE764 /* RadioButtonGroup.swift */; };
EAB1D2CD28ABE76100DAE764 /* Withable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D2CC28ABE76000DAE764 /* Withable.swift */; }; EAB1D2CD28ABE76100DAE764 /* Withable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D2CC28ABE76000DAE764 /* Withable.swift */; };
EAB1D2CF28ABEF2B00DAE764 /* Typography+Base.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D2CE28ABEF2B00DAE764 /* Typography+Base.swift */; }; EAB1D2CF28ABEF2B00DAE764 /* Typography+Base.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D2CE28ABEF2B00DAE764 /* Typography+Base.swift */; };
@ -204,10 +208,12 @@
44604AD629CE196600E62B51 /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = "<group>"; }; 44604AD629CE196600E62B51 /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = "<group>"; };
5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Useable.swift; sourceTree = "<group>"; }; 5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Useable.swift; sourceTree = "<group>"; };
5FC35BE228D51405004EBEAC /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = "<group>"; }; 5FC35BE228D51405004EBEAC /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = "<group>"; };
710607942B91A99500F2863F /* TitleletChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = TitleletChangeLog.txt; sourceTree = "<group>"; };
7115BD3B2B84C0C200E0A610 /* TileContainerChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = TileContainerChangeLog.txt; sourceTree = "<group>"; }; 7115BD3B2B84C0C200E0A610 /* TileContainerChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = TileContainerChangeLog.txt; sourceTree = "<group>"; };
71ACE89B2BA0451200FB6ADC /* PaginationContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationContainer.swift; sourceTree = "<group>"; }; 71ACE89B2BA0451200FB6ADC /* PaginationContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationContainer.swift; sourceTree = "<group>"; };
71B23C2C2B91FA690027F7D9 /* Pagination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pagination.swift; sourceTree = "<group>"; }; 71B23C2C2B91FA690027F7D9 /* Pagination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pagination.swift; sourceTree = "<group>"; };
71B5FCBA2B95A0CA00269BCC /* PaginationChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = PaginationChangeLog.txt; sourceTree = "<group>"; }; 71B5FCBA2B95A0CA00269BCC /* PaginationChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = PaginationChangeLog.txt; sourceTree = "<group>"; };
71ACE89D2BA1CC1700FB6ADC /* TiletEyebrowModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TiletEyebrowModel.swift; sourceTree = "<group>"; };
71BFA7092B7F70E6000DCE33 /* DropShadowable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropShadowable.swift; sourceTree = "<group>"; }; 71BFA7092B7F70E6000DCE33 /* DropShadowable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropShadowable.swift; sourceTree = "<group>"; };
71C02B372B7BD98F00E93E66 /* NotificationChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = NotificationChangeLog.txt; sourceTree = "<group>"; }; 71C02B372B7BD98F00E93E66 /* NotificationChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = NotificationChangeLog.txt; sourceTree = "<group>"; };
71FC86D92B96F44C00700965 /* PaginationButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationButton.swift; sourceTree = "<group>"; }; 71FC86D92B96F44C00700965 /* PaginationButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationButton.swift; sourceTree = "<group>"; };
@ -300,6 +306,8 @@
EAA5EEED28F5C908003B3210 /* VDSColorTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSColorTokens.xcframework; path = ../SharedFrameworks/VDSColorTokens.xcframework; sourceTree = "<group>"; }; EAA5EEED28F5C908003B3210 /* VDSColorTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSColorTokens.xcframework; path = ../SharedFrameworks/VDSColorTokens.xcframework; sourceTree = "<group>"; };
EAA5EEEE28F5C908003B3210 /* VDSFormControlsTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSFormControlsTokens.xcframework; path = ../SharedFrameworks/VDSFormControlsTokens.xcframework; sourceTree = "<group>"; }; EAA5EEEE28F5C908003B3210 /* VDSFormControlsTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSFormControlsTokens.xcframework; path = ../SharedFrameworks/VDSFormControlsTokens.xcframework; sourceTree = "<group>"; };
EAA7456B2AB23E2000C1841F /* TooltipModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TooltipModel.swift; sourceTree = "<group>"; }; EAA7456B2AB23E2000C1841F /* TooltipModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TooltipModel.swift; sourceTree = "<group>"; };
EAACB8972B92706F006A3869 /* DefaultValuing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultValuing.swift; sourceTree = "<group>"; };
EAACB8992B927108006A3869 /* Valuing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Valuing.swift; sourceTree = "<group>"; };
EAB1D29B28A5618900DAE764 /* RadioButtonGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButtonGroup.swift; sourceTree = "<group>"; }; EAB1D29B28A5618900DAE764 /* RadioButtonGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButtonGroup.swift; sourceTree = "<group>"; };
EAB1D2CC28ABE76000DAE764 /* Withable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Withable.swift; sourceTree = "<group>"; }; EAB1D2CC28ABE76000DAE764 /* Withable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Withable.swift; sourceTree = "<group>"; };
EAB1D2CE28ABEF2B00DAE764 /* Typography+Base.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Typography+Base.swift"; sourceTree = "<group>"; }; EAB1D2CE28ABEF2B00DAE764 /* Typography+Base.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Typography+Base.swift"; sourceTree = "<group>"; };
@ -612,6 +620,7 @@
EAF1FE9A29DB1A6000101452 /* Changeable.swift */, EAF1FE9A29DB1A6000101452 /* Changeable.swift */,
EAF1FE9829D4850E00101452 /* Clickable.swift */, EAF1FE9829D4850E00101452 /* Clickable.swift */,
EAA5EEDF28F49DB3003B3210 /* Colorable.swift */, EAA5EEDF28F49DB3003B3210 /* Colorable.swift */,
EAACB8972B92706F006A3869 /* DefaultValuing.swift */,
EA3361A9288B25E40071C351 /* Disabling.swift */, EA3361A9288B25E40071C351 /* Disabling.swift */,
EAF978202A99035B00C2FEA9 /* Enabling.swift */, EAF978202A99035B00C2FEA9 /* Enabling.swift */,
EA5E305929510F8B0082B959 /* EnumSubset.swift */, EA5E305929510F8B0082B959 /* EnumSubset.swift */,
@ -625,6 +634,7 @@
EA3361B7288B2AAA0071C351 /* ViewProtocol.swift */, EA3361B7288B2AAA0071C351 /* ViewProtocol.swift */,
EAB1D2CC28ABE76000DAE764 /* Withable.swift */, EAB1D2CC28ABE76000DAE764 /* Withable.swift */,
5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */, 5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */,
EAACB8992B927108006A3869 /* Valuing.swift */,
71BFA7092B7F70E6000DCE33 /* DropShadowable.swift */, 71BFA7092B7F70E6000DCE33 /* DropShadowable.swift */,
); );
path = Protocols; path = Protocols;
@ -741,9 +751,11 @@
children = ( children = (
EA5E3057295105A40082B959 /* Tilelet.swift */, EA5E3057295105A40082B959 /* Tilelet.swift */,
EA985BE529688F6A00F2FF2E /* TileletBadgeModel.swift */, EA985BE529688F6A00F2FF2E /* TileletBadgeModel.swift */,
EA985BE929689B6D00F2FF2E /* TileletSubTitleModel.swift */, 71ACE89D2BA1CC1700FB6ADC /* TiletEyebrowModel.swift */,
EA985BE72968951C00F2FF2E /* TileletTitleModel.swift */, EA985BE72968951C00F2FF2E /* TileletTitleModel.swift */,
EA985BE929689B6D00F2FF2E /* TileletSubTitleModel.swift */,
EA985C2C296F03FE00F2FF2E /* TileletIconModels.swift */, EA985C2C296F03FE00F2FF2E /* TileletIconModels.swift */,
710607942B91A99500F2863F /* TitleletChangeLog.txt */,
); );
path = Tilelet; path = Tilelet;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1018,6 +1030,7 @@
EAEEECA72B1F952000531FC2 /* TabsChangeLog.txt in Resources */, EAEEECA72B1F952000531FC2 /* TabsChangeLog.txt in Resources */,
186B2A8A2B88DA7F001AB71F /* TextAreaChangeLog.txt in Resources */, 186B2A8A2B88DA7F001AB71F /* TextAreaChangeLog.txt in Resources */,
EAEEEC962B1F893B00531FC2 /* ButtonChangeLog.txt in Resources */, EAEEEC962B1F893B00531FC2 /* ButtonChangeLog.txt in Resources */,
710607952B91A99500F2863F /* TitleletChangeLog.txt in Resources */,
EA5F86CC2A1D28B500BC83E4 /* ReleaseNotes.txt in Resources */, EA5F86CC2A1D28B500BC83E4 /* ReleaseNotes.txt in Resources */,
EAEEEC982B1F8DD100531FC2 /* LineChangeLog.txt in Resources */, EAEEEC982B1F8DD100531FC2 /* LineChangeLog.txt in Resources */,
EAEEECA22B1F92AD00531FC2 /* LabelChangeLog.txt in Resources */, EAEEECA22B1F92AD00531FC2 /* LabelChangeLog.txt in Resources */,
@ -1073,6 +1086,7 @@
EAC9258C2911C9DE00091998 /* InputField.swift in Sources */, EAC9258C2911C9DE00091998 /* InputField.swift in Sources */,
EA3362402892EF6C0071C351 /* Label.swift in Sources */, EA3362402892EF6C0071C351 /* Label.swift in Sources */,
EAB2376229E9880400AABE9A /* TrailingTooltipLabel.swift in Sources */, EAB2376229E9880400AABE9A /* TrailingTooltipLabel.swift in Sources */,
EAACB8982B92706F006A3869 /* DefaultValuing.swift in Sources */,
EAB2376A29E9E59100AABE9A /* TooltipLaunchable.swift in Sources */, EAB2376A29E9E59100AABE9A /* TooltipLaunchable.swift in Sources */,
18A65A042B96F050006602CC /* BreadcrumbItem.swift in Sources */, 18A65A042B96F050006602CC /* BreadcrumbItem.swift in Sources */,
EAB2375D29E8789100AABE9A /* Tooltip.swift in Sources */, EAB2375D29E8789100AABE9A /* Tooltip.swift in Sources */,
@ -1091,6 +1105,7 @@
EAF7F0AF289B144C00B287F5 /* UnderlineLabelAttribute.swift in Sources */, EAF7F0AF289B144C00B287F5 /* UnderlineLabelAttribute.swift in Sources */,
EA0D1C412A6AD61C00E5C127 /* Typography+Additional.swift in Sources */, EA0D1C412A6AD61C00E5C127 /* Typography+Additional.swift in Sources */,
EAC925842911C63100091998 /* Colorable.swift in Sources */, EAC925842911C63100091998 /* Colorable.swift in Sources */,
EAACB89A2B927108006A3869 /* Valuing.swift in Sources */,
EAE785312BA0A438009428EA /* UIImage+Helper.swift in Sources */, EAE785312BA0A438009428EA /* UIImage+Helper.swift in Sources */,
EAB5FEF5292D371F00998C17 /* ButtonBase.swift in Sources */, EAB5FEF5292D371F00998C17 /* ButtonBase.swift in Sources */,
EA978EC5291D6AFE00ACC883 /* AnyLabelAttribute.swift in Sources */, EA978EC5291D6AFE00ACC883 /* AnyLabelAttribute.swift in Sources */,
@ -1098,6 +1113,7 @@
EAC71A1F2A2E173D00E47A9F /* RadioButton.swift in Sources */, EAC71A1F2A2E173D00E47A9F /* RadioButton.swift in Sources */,
EA33622C2891E73B0071C351 /* FontProtocol.swift in Sources */, EA33622C2891E73B0071C351 /* FontProtocol.swift in Sources */,
EA596ABD2A16B4EC00300C4B /* Tab.swift in Sources */, EA596ABD2A16B4EC00300C4B /* Tab.swift in Sources */,
71ACE89E2BA1CC1700FB6ADC /* TiletEyebrowModel.swift in Sources */,
EAF7F11728A1475A00B287F5 /* RadioButtonItem.swift in Sources */, EAF7F11728A1475A00B287F5 /* RadioButtonItem.swift in Sources */,
EA985BEE2968A92400F2FF2E /* TitleLockupSubTitleModel.swift in Sources */, EA985BEE2968A92400F2FF2E /* TitleLockupSubTitleModel.swift in Sources */,
EA985BF22968B5BB00F2FF2E /* TitleLockupTextStyle.swift in Sources */, EA985BF22968B5BB00F2FF2E /* TitleLockupTextStyle.swift in Sources */,

View File

@ -147,6 +147,7 @@ open class Badge: View {
label.widthGreaterThanEqualTo(constant: minWidth) label.widthGreaterThanEqualTo(constant: minWidth)
maxWidthConstraint = label.widthLessThanEqualTo(constant: 0).with { $0.isActive = false } maxWidthConstraint = label.widthLessThanEqualTo(constant: 0).with { $0.isActive = false }
clipsToBounds = true
} }
/// Resets to default settings. /// Resets to default settings.

View File

@ -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 // MARK: - Private Properties
//-------------------------------------------------- //--------------------------------------------------
@ -80,19 +75,9 @@ open class Notification: View {
$0.translatesAutoresizingMaskIntoConstraints = false $0.translatesAutoresizingMaskIntoConstraints = false
} }
private var labelButtonViewSpacing: CGFloat { private var labelButtonViewSpacing: CGFloat { UIDevice.isIPad ? 20 : 16 }
let spacing: CGFloat = UIDevice.isIPad ? 20 : 16
return switch layout {
case .vertical:
0
case .horizontal:
spacing
}
}
internal var onCloseSubscriber: AnyCancellable? internal var onCloseSubscriber: AnyCancellable?
private var maxWidthConstraint: NSLayoutConstraint?
private var leadingConstraint: NSLayoutConstraint? private var leadingConstraint: NSLayoutConstraint?
@ -172,19 +157,6 @@ open class Notification: View {
/// Add this attribute determine your type of Notification. /// Add this attribute determine your type of Notification.
open var style: Style = .info { didSet { setNeedsUpdate()}} 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 // MARK: - Configuration
@ -219,18 +191,11 @@ open class Notification: View {
return 288 return 288
} }
private var maxViewWidth: CGFloat {
return 1232
}
private var labelViewWidthConstraint: NSLayoutConstraint?
private var labelViewBottomConstraint: NSLayoutConstraint? private var labelViewBottomConstraint: NSLayoutConstraint?
private var labelViewAndButtonViewConstraint: NSLayoutConstraint? private var labelViewAndButtonViewConstraint: NSLayoutConstraint?
private var buttonViewTopConstraint: NSLayoutConstraint? private var buttonViewTopConstraint: NSLayoutConstraint?
private var typeIconWidthConstraint: NSLayoutConstraint? private var typeIconWidthConstraint: NSLayoutConstraint?
private var closeIconWidthConstraint: NSLayoutConstraint? private var closeIconWidthConstraint: NSLayoutConstraint?
private var buttonGroupCenterYConstraint: NSLayoutConstraint?
private var buttonGroupBottomConstraint: NSLayoutConstraint?
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Overrides // MARK: - Overrides
@ -255,21 +220,18 @@ open class Notification: View {
mainStackView.heightAnchor.constraint(greaterThanOrEqualToConstant: minContentHeight), mainStackView.heightAnchor.constraint(greaterThanOrEqualToConstant: minContentHeight),
layoutGuide.widthAnchor.constraint(greaterThanOrEqualToConstant: minViewWidth) layoutGuide.widthAnchor.constraint(greaterThanOrEqualToConstant: minViewWidth)
]) ])
maxWidthConstraint = layoutGuide.widthAnchor.constraint(lessThanOrEqualToConstant: maxViewWidth)
labelButtonView.addSubview(labelsView) labelButtonView.addSubview(labelsView)
labelsView labelsView
.pinTop() .pinTop()
.pinLeading() .pinLeading()
labelViewWidthConstraint = labelsView.widthAnchor.constraint(equalTo: labelButtonView.widthAnchor, multiplier: 1.0) labelsView.widthAnchor.constraint(equalTo: labelButtonView.widthAnchor, multiplier: 1.0).activate()
labelViewWidthConstraint?.activate()
labelViewBottomConstraint = labelButtonView.bottomAnchor.constraint(equalTo: labelsView.bottomAnchor) labelViewBottomConstraint = labelButtonView.bottomAnchor.constraint(equalTo: labelsView.bottomAnchor)
labelButtonView.addSubview(buttonGroup) labelButtonView.addSubview(buttonGroup)
buttonGroup buttonGroup
.pinTrailing() .pinTrailing()
buttonGroupBottomConstraint = labelButtonView.bottomAnchor.constraint(equalTo: buttonGroup.bottomAnchor) labelButtonView.bottomAnchor.constraint(equalTo: buttonGroup.bottomAnchor).activate()
buttonGroupCenterYConstraint = buttonGroup.centerYAnchor.constraint(equalTo: labelButtonView.centerYAnchor)
labelViewAndButtonViewConstraint = buttonGroup.topAnchor.constraint(equalTo: labelsView.bottomAnchor, constant: VDSLayout.Spacing.space3X.value) labelViewAndButtonViewConstraint = buttonGroup.topAnchor.constraint(equalTo: labelsView.bottomAnchor, constant: VDSLayout.Spacing.space3X.value)
buttonGroup.widthAnchor.constraint(equalTo: labelsView.widthAnchor).activate() buttonGroup.widthAnchor.constraint(equalTo: labelsView.widthAnchor).activate()
@ -314,7 +276,6 @@ open class Notification: View {
closeButton.size = UIDevice.isIPad ? .medium : .small closeButton.size = UIDevice.isIPad ? .medium : .small
closeButton.name = .close closeButton.name = .close
layout = .vertical
hideCloseButton = false hideCloseButton = false
shouldUpdateView = true shouldUpdateView = true
@ -338,6 +299,16 @@ open class Notification: View {
layer.cornerRadius = UIScreen.main.bounds.width == bounds.width ? 0 : 4.0 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 // MARK: - Private Methods
//-------------------------------------------------- //--------------------------------------------------
@ -383,32 +354,21 @@ open class Notification: View {
secondaryButton.onClick = secondaryButtonModel.onClick secondaryButton.onClick = secondaryButtonModel.onClick
buttons.append(secondaryButton) buttons.append(secondaryButton)
} }
labelViewWidthConstraint?.deactivate()
if buttons.isEmpty { if buttons.isEmpty {
buttonGroup.isHidden = true buttonGroup.isHidden = true
labelViewWidthConstraint = labelsView.widthAnchor.constraint(equalTo: labelButtonView.widthAnchor)
buttonGroup.buttons.removeAll() buttonGroup.buttons.removeAll()
} else { } else {
labelsView.setCustomSpacing(VDSLayout.Spacing.space3X.value, after: subTitleLabel) labelsView.setCustomSpacing(VDSLayout.Spacing.space3X.value, after: subTitleLabel)
buttonGroup.buttons = buttons buttonGroup.buttons = buttons
buttonGroup.isHidden = false 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() { private func setConstraints() {
maxWidthConstraint?.deactivate()
labelViewAndButtonViewConstraint?.deactivate() labelViewAndButtonViewConstraint?.deactivate()
labelViewBottomConstraint?.deactivate() labelViewBottomConstraint?.deactivate()
buttonGroupCenterYConstraint?.deactivate() labelViewAndButtonViewConstraint?.isActive = !buttonGroup.buttons.isEmpty
buttonGroupBottomConstraint?.deactivate() labelViewBottomConstraint?.isActive = buttonGroup.buttons.isEmpty
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
typeIconWidthConstraint?.constant = typeIcon.size.dimensions.width typeIconWidthConstraint?.constant = typeIcon.size.dimensions.width
closeIconWidthConstraint?.constant = closeButton.size.dimensions.width closeIconWidthConstraint?.constant = closeButton.size.dimensions.width
} }

View File

@ -13,7 +13,7 @@ import Combine
/// Base Class used to build out a Input controls. /// Base Class used to build out a Input controls.
@objc(VDSEntryField) @objc(VDSEntryField)
open class EntryFieldBase: Control, Changeable, FormFieldable { open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Initializers // MARK: - Initializers
@ -153,8 +153,11 @@ open class EntryFieldBase: Control, Changeable, FormFieldable {
/// Whether not to show the error. /// Whether not to show the error.
open var showError: Bool = false { didSet { setNeedsUpdate() } } open var showError: Bool = false { didSet { setNeedsUpdate() } }
/// FormFieldValidator
internal var validator: (any FormFieldValidatorable)?
/// Whether or not to show the internal error /// Whether or not to show the internal error
open internal(set) var hasInternalError: Bool = false { didSet { setNeedsUpdate() } } open var hasInternalError: Bool { !(validator?.isValid ?? true) }
/// Override UIControl state to add the .error state if showError is true. /// Override UIControl state to add the .error state if showError is true.
open override var state: UIControl.State { open override var state: UIControl.State {
@ -175,7 +178,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldable {
} }
} }
internal var internalErrorText: String? { open var internalErrorText: String? {
didSet { didSet {
updateContainerView() updateContainerView()
updateErrorLabel() updateErrorLabel()
@ -200,19 +203,18 @@ open class EntryFieldBase: Control, Changeable, FormFieldable {
open var inputId: String? { didSet { setNeedsUpdate() } } open var inputId: String? { didSet { setNeedsUpdate() } }
/// The text of this textField. /// The text of this textField.
private var _value: AnyHashable? private var _value: String?
open var value: AnyHashable? { open var value: String? {
get { _value } get { _value }
set { set {
if let newValue, newValue != _value { if let newValue, newValue != _value {
_value = newValue _value = newValue
text = newValue as? String text = newValue
} }
setNeedsUpdate() setNeedsUpdate()
} }
} }
open var defaultValue: AnyHashable? { didSet { setNeedsUpdate() } } open var defaultValue: AnyHashable? { didSet { setNeedsUpdate() } }
open var required: Bool = false { didSet { setNeedsUpdate() } } open var required: Bool = false { didSet { setNeedsUpdate() } }
@ -324,6 +326,8 @@ open class EntryFieldBase: Control, Changeable, FormFieldable {
updateHelperLabel() updateHelperLabel()
backgroundColor = surface.color backgroundColor = surface.color
validator?.validate()
internalErrorText = validator?.errorMessage
} }
//-------------------------------------------------- //--------------------------------------------------

View File

@ -89,7 +89,7 @@ open class InputField: EntryFieldBase, UITextFieldDelegate {
setNeedsUpdate() setNeedsUpdate()
} }
} }
var _showError: Bool = false var _showError: Bool = false
/// Whether not to show the error. /// Whether not to show the error.
open override var showError: Bool { open override var showError: Bool {

View File

@ -107,17 +107,19 @@ open class TextArea: EntryFieldBase {
} }
/// The text of this textView /// The text of this textView
private var _text: String?
open override var text: String? { open override var text: String? {
get { textView.text } get { textView.text }
set { set {
if let newValue, newValue != text { if let newValue, newValue != _text {
_text = newValue
textView.text = newValue textView.text = newValue
value = newValue value = newValue
} }
setNeedsUpdate() setNeedsUpdate()
} }
} }
/// UITextView shown in the TextArea. /// UITextView shown in the TextArea.
open var textView = TextView().with { open var textView = TextView().with {
$0.translatesAutoresizingMaskIntoConstraints = false $0.translatesAutoresizingMaskIntoConstraints = false
@ -125,6 +127,8 @@ open class TextArea: EntryFieldBase {
$0.isScrollEnabled = false $0.isScrollEnabled = false
} }
open override var maxLength: Int? { willSet { countRule.maxLength = newValue }}
/// Color configuration for error icon. /// Color configuration for error icon.
internal var iconColorConfiguration = ControlColorConfiguration().with { internal var iconColorConfiguration = ControlColorConfiguration().with {
$0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .normal) $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .normal)
@ -147,6 +151,7 @@ open class TextArea: EntryFieldBase {
open override func setup() { open override func setup() {
super.setup() super.setup()
accessibilityLabel = "TextArea" accessibilityLabel = "TextArea"
validator = FormFieldValidator<TextArea>(field: self, rules: [.init(countRule)])
containerStackView.pinToSuperView(.uniform(VDSFormControls.spaceInset)) containerStackView.pinToSuperView(.uniform(VDSFormControls.spaceInset))
minWidthConstraint = containerView.widthAnchor.constraint(greaterThanOrEqualToConstant: containerSize.width) minWidthConstraint = containerView.widthAnchor.constraint(greaterThanOrEqualToConstant: containerSize.width)
@ -187,7 +192,6 @@ open class TextArea: EntryFieldBase {
/// Used to make changes to the View based off a change events or from local properties. /// Used to make changes to the View based off a change events or from local properties.
open override func updateView() { open override func updateView() {
super.updateView() super.updateView()
textView.isEditable = isEnabled textView.isEditable = isEnabled
textView.isEnabled = isEnabled textView.isEnabled = isEnabled
textView.surface = surface textView.surface = surface
@ -202,13 +206,8 @@ open class TextArea: EntryFieldBase {
widthConstraint?.isActive = false widthConstraint?.isActive = false
minWidthConstraint?.isActive = true minWidthConstraint?.isActive = true
} }
let characterError = getCharacterCounterText() characterCounterLabel.text = getCharacterCounterText()
if let maxLength, maxLength > 0 {
characterCounterLabel.text = characterError
} else {
characterCounterLabel.text = ""
}
icon.size = .medium icon.size = .medium
icon.color = iconColorConfiguration.getColor(self) icon.color = iconColorConfiguration.getColor(self)
@ -247,17 +246,11 @@ open class TextArea: EntryFieldBase {
let countStr = (count > maxLength ?? 0) ? ("-" + "\(count-(maxLength ?? 0))") : "\(count)" let countStr = (count > maxLength ?? 0) ? ("-" + "\(count-(maxLength ?? 0))") : "\(count)"
if let maxLength, maxLength > 0 { if let maxLength, maxLength > 0 {
if count > maxLength { if count > maxLength {
hasInternalError = true
internalErrorText = "You have exceeded the character limit."
return countStr return countStr
} else { } else {
hasInternalError = false
internalErrorText = nil
return ("\(countStr)" + "/" + "\(maxLength)") return ("\(countStr)" + "/" + "\(maxLength)")
} }
} else { } else {
hasInternalError = false
internalErrorText = nil
return nil return nil
} }
} }
@ -277,6 +270,21 @@ open class TextArea: EntryFieldBase {
textView.textAttributes = textAttributes textView.textAttributes = textAttributes
} }
//--------------------------------------------------
// MARK: - Validation
//--------------------------------------------------
var countRule = CharacterCountRule()
class CharacterCountRule: Rule {
var maxLength: Int?
var errorMessage: String = "You have exceeded the character limit."
func isValid(value: String?) -> Bool {
guard let text = value, let maxLength, maxLength > 0 else { return true }
return text.count <= maxLength
}
}
} }
extension TextArea: UITextViewDelegate { extension TextArea: UITextViewDelegate {

View File

@ -11,7 +11,39 @@ import VDSFormControlsTokens
import UIKit import UIKit
@objc(VDSTileContainer) @objc(VDSTileContainer)
open class TileContainer: Control { open class TileContainer: TileContainerBase<TileContainer.Padding> {
/// Enum used to describe the padding choices used for this component.
public enum Padding: DefaultValuing {
case padding2X
case padding4X
case padding6X
case padding8X
case padding12X
case custom(CGFloat)
public static var defaultValue: Self { .padding4X }
public var value: CGFloat {
switch self {
case .padding2X:
return VDSLayout.Spacing.space2X.value
case .padding4X:
return VDSLayout.Spacing.space4X.value
case .padding6X:
return VDSLayout.Spacing.space6X.value
case .padding8X:
return VDSLayout.Spacing.space8X.value
case .padding12X:
return VDSLayout.Spacing.space12X.value
case .custom(let padding):
return padding
}
}
}
}
open class TileContainerBase<PaddingType: DefaultValuing>: Control where PaddingType.ValueType == CGFloat {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Initializers // MARK: - Initializers
@ -53,33 +85,6 @@ open class TileContainer: Control {
case gradient(String, String) case gradient(String, String)
case none case none
} }
/// Enum used to describe the padding choices used for this component.
public enum Padding {
case padding2X
case padding4X
case padding6X
case padding8X
case padding12X
case custom(CGFloat)
public var value: CGFloat {
switch self {
case .padding2X:
return VDSLayout.Spacing.space2X.value
case .padding4X:
return VDSLayout.Spacing.space4X.value
case .padding6X:
return VDSLayout.Spacing.space6X.value
case .padding8X:
return VDSLayout.Spacing.space8X.value
case .padding12X:
return VDSLayout.Spacing.space12X.value
case .custom(let padding):
return padding
}
}
}
/// Enum used to describe the aspect ratios used for this component. /// Enum used to describe the aspect ratios used for this component.
public enum AspectRatio: String, CaseIterable { public enum AspectRatio: String, CaseIterable {
@ -130,7 +135,7 @@ open class TileContainer: Control {
open var backgroundEffect: BackgroundEffect = .none { didSet { setNeedsUpdate() } } open var backgroundEffect: BackgroundEffect = .none { didSet { setNeedsUpdate() } }
/// Sets the inside padding for the component /// Sets the inside padding for the component
open var padding: Padding = .padding4X { didSet { setNeedsUpdate() } } open var padding: PaddingType = PaddingType.defaultValue { didSet { setNeedsUpdate() } }
/// Applies a background color if backgroundImage prop fails or has trouble loading. /// Applies a background color if backgroundImage prop fails or has trouble loading.
open var imageFallbackColor: Surface = .light { didSet { setNeedsUpdate() } } open var imageFallbackColor: Surface = .light { didSet { setNeedsUpdate() } }
@ -235,7 +240,12 @@ open class TileContainer: Control {
heightConstraint = layoutGuide.heightAnchor.constraint(equalToConstant: 0) heightConstraint = layoutGuide.heightAnchor.constraint(equalToConstant: 0)
backgroundImageView.pin(layoutGuide) backgroundImageView
.pinTop(layoutGuide.topAnchor)
.pinLeading(layoutGuide.leadingAnchor)
.pinTrailing(layoutGuide.trailingAnchor)
.pinBottom(layoutGuide.bottomAnchor, 0, .defaultLow)
backgroundImageView.isUserInteractionEnabled = false backgroundImageView.isUserInteractionEnabled = false
backgroundImageView.isHidden = true backgroundImageView.isHidden = true
@ -252,6 +262,7 @@ open class TileContainer: Control {
layer.cornerRadius = cornerRadius layer.cornerRadius = cornerRadius
backgroundImageView.layer.cornerRadius = cornerRadius backgroundImageView.layer.cornerRadius = cornerRadius
highlightView.layer.cornerRadius = cornerRadius highlightView.layer.cornerRadius = cornerRadius
clipsToBounds = true
} }
/// Resets to default settings. /// Resets to default settings.
@ -259,7 +270,6 @@ open class TileContainer: Control {
super.reset() super.reset()
shouldUpdateView = false shouldUpdateView = false
color = .white color = .white
padding = .padding4X
aspectRatio = .ratio1x1 aspectRatio = .ratio1x1
imageFallbackColor = .light imageFallbackColor = .light
width = nil width = nil
@ -323,7 +333,14 @@ open class TileContainer: Control {
} }
applyBackgroundEffects() applyBackgroundEffects()
} }
/// Used to update frames for the added CAlayers to our view
open override func layoutSubviews() {
super.layoutSubviews()
dropShadowLayers?.forEach { $0.frame = bounds }
gradientLayers?.forEach { $0.frame = bounds }
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Public Methods // MARK: - Public Methods
//-------------------------------------------------- //--------------------------------------------------
@ -401,11 +418,11 @@ open class TileContainer: Control {
} }
extension TileContainer { extension TileContainerBase {
final class BackgroundColorConfiguration: ObjectColorable { final class BackgroundColorConfiguration: ObjectColorable {
typealias ObjectType = TileContainer typealias ObjectType = TileContainerBase
let primaryColorConfig = SurfaceColorConfiguration(VDSColor.backgroundPrimaryLight, VDSColor.backgroundPrimaryDark) let primaryColorConfig = SurfaceColorConfiguration(VDSColor.backgroundPrimaryLight, VDSColor.backgroundPrimaryDark)
let secondaryColorConfig = SurfaceColorConfiguration(VDSColor.backgroundSecondaryLight, VDSColor.backgroundSecondaryDark) let secondaryColorConfig = SurfaceColorConfiguration(VDSColor.backgroundSecondaryLight, VDSColor.backgroundSecondaryDark)
@ -415,7 +432,7 @@ extension TileContainer {
required init() { } required init() { }
func getColor(_ object: TileContainer) -> UIColor { func getColor(_ object: ObjectType) -> UIColor {
switch object.color { switch object.color {
case .primary: case .primary:
primaryColorConfig.getColor(object.surface) primaryColorConfig.getColor(object.surface)

View File

@ -16,7 +16,38 @@ import Combine
/// while it can include an arrow CTA, it does not require one in order to /// while it can include an arrow CTA, it does not require one in order to
/// function. /// function.
@objc(VDSTilelet) @objc(VDSTilelet)
open class Tilelet: TileContainer { open class Tilelet: TileContainerBase<Tilelet.Padding> {
/// Enum used to describe the padding choices used for this component.
public enum Padding: String, DefaultValuing, CaseIterable {
case small
case large
public static var defaultValue: Self { .large }
public var value: CGFloat {
switch self {
case .small:
return UIDevice.isIPad ? VDSLayout.Spacing.space3X.value : VDSLayout.Spacing.space4X.value
case .large:
return UIDevice.isIPad ? VDSLayout.Spacing.space4X.value : VDSLayout.Spacing.space6X.value
}
}
fileprivate var titleLockupBottomSpacing: CGFloat {
switch self.value {
case VDSLayout.Spacing.space3X.value:
return VDSLayout.Spacing.space4X.value
case VDSLayout.Spacing.space4X.value:
return VDSLayout.Spacing.space6X.value
case VDSLayout.Spacing.space4X.value:
return VDSLayout.Spacing.space8X.value
default:
return VDSLayout.Spacing.space4X.value
}
}
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Initializers // MARK: - Initializers
@ -39,6 +70,7 @@ open class Tilelet: TileContainer {
/// Enum to represent the Vertical Layout of the Text. /// Enum to represent the Vertical Layout of the Text.
public enum TextPosition: String, CaseIterable { public enum TextPosition: String, CaseIterable {
case top case top
case middle
case bottom case bottom
} }
@ -72,7 +104,7 @@ open class Tilelet: TileContainer {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Public Properties // MARK: - Public Properties
//-------------------------------------------------- //--------------------------------------------------
public override var onClickSubscriber: AnyCancellable? { public override var onClickSubscriber: AnyCancellable? {
didSet { didSet {
isAccessibilityElement = onClickSubscriber != nil isAccessibilityElement = onClickSubscriber != nil
@ -82,6 +114,27 @@ open class Tilelet: TileContainer {
/// Title lockup positioned in the contentView. /// Title lockup positioned in the contentView.
open var titleLockup = TitleLockup().with { open var titleLockup = TitleLockup().with {
$0.standardStyleConfiguration = .init(styleConfigurations: [ $0.standardStyleConfiguration = .init(styleConfigurations: [
.init(deviceType: .iPhone,
titleStandardStyles: [.bodySmall],
spacingConfigurations: [
.init(otherStandardStyles: [.bodySmall],
topSpacing: VDSLayout.Spacing.space1X.value,
bottomSpacing: VDSLayout.Spacing.space1X.value)
]),
.init(deviceType: .iPhone,
titleStandardStyles: [.bodyMedium],
spacingConfigurations: [
.init(otherStandardStyles: [.bodyMedium],
topSpacing: VDSLayout.Spacing.space1X.value,
bottomSpacing: VDSLayout.Spacing.space1X.value)
]),
.init(deviceType: .iPhone,
titleStandardStyles: [.bodyLarge],
spacingConfigurations: [
.init(otherStandardStyles: [.bodyLarge],
topSpacing: VDSLayout.Spacing.space1X.value,
bottomSpacing: VDSLayout.Spacing.space1X.value)
]),
.init(deviceType: .iPhone, .init(deviceType: .iPhone,
titleStandardStyles: [.titleSmall], titleStandardStyles: [.titleSmall],
spacingConfigurations: [ spacingConfigurations: [
@ -103,6 +156,27 @@ open class Tilelet: TileContainer {
topSpacing: VDSLayout.Spacing.space3X.value, topSpacing: VDSLayout.Spacing.space3X.value,
bottomSpacing: VDSLayout.Spacing.space3X.value) bottomSpacing: VDSLayout.Spacing.space3X.value)
]), ]),
.init(deviceType: .iPad,
titleStandardStyles: [.bodySmall],
spacingConfigurations: [
.init(otherStandardStyles: [.bodySmall],
topSpacing: VDSLayout.Spacing.space2X.value,
bottomSpacing: VDSLayout.Spacing.space2X.value)
]),
.init(deviceType: .iPad,
titleStandardStyles: [.bodyMedium],
spacingConfigurations: [
.init(otherStandardStyles: [.bodyMedium],
topSpacing: VDSLayout.Spacing.space1X.value,
bottomSpacing: VDSLayout.Spacing.space1X.value)
]),
.init(deviceType: .iPad,
titleStandardStyles: [.bodyLarge],
spacingConfigurations: [
.init(otherStandardStyles: [.bodyLarge],
topSpacing: VDSLayout.Spacing.space1X.value,
bottomSpacing: VDSLayout.Spacing.space1X.value)
]),
.init(deviceType: .iPad, .init(deviceType: .iPad,
titleStandardStyles: [.titleSmall, .titleMedium], titleStandardStyles: [.titleSmall, .titleMedium],
spacingConfigurations: [ spacingConfigurations: [
@ -176,6 +250,9 @@ open class Tilelet: TileContainer {
/// If set, this is used to render the subTitleLabel of the TitleLockup. /// If set, this is used to render the subTitleLabel of the TitleLockup.
open var subTitleModel: SubTitleModel? { didSet { setNeedsUpdate() } } open var subTitleModel: SubTitleModel? { didSet { setNeedsUpdate() } }
/// If set, this is used to render the eyebrowLabel of the TitleLockup.
open var eyebrowModel: EyebrowModel? { didSet { setNeedsUpdate() } }
//only 1 Icon can be active //only 1 Icon can be active
private var _descriptiveIconModel: DescriptiveIcon? private var _descriptiveIconModel: DescriptiveIcon?
@ -206,6 +283,17 @@ open class Tilelet: TileContainer {
//-------------------------------------------------- //--------------------------------------------------
internal var titleLockupWidthConstraint: NSLayoutConstraint? internal var titleLockupWidthConstraint: NSLayoutConstraint?
internal var titleLockupTrailingConstraint: NSLayoutConstraint? internal var titleLockupTrailingConstraint: NSLayoutConstraint?
internal var titleLockupTopConstraint: NSLayoutConstraint?
internal var titleLockupBottomConstraint: NSLayoutConstraint?
internal var titleLockupTopGreaterThanConstraint: NSLayoutConstraint?
internal var titleLockupBottomGreaterThanConstraint: NSLayoutConstraint?
internal var titleLockupCenterYConstraint: NSLayoutConstraint?
internal var titleLockupTitleLabelBottomConstraint: NSLayoutConstraint?
//Truncation constraints
internal var badgeLabelHeightGreaterThanConstraint: NSLayoutConstraint?
internal var titleLockupEyebrowLabelHeightGreaterThanConstraint: NSLayoutConstraint?
internal var titleLockupTitleLabelHeightGreaterThanConstraint: NSLayoutConstraint?
internal var titleLockupSubTitleLabelHeightGreaterThanConstraint: NSLayoutConstraint?
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Overrides // MARK: - Overrides
@ -230,12 +318,17 @@ open class Tilelet: TileContainer {
titleLockupContainerView.addSubview(titleLockup) titleLockupContainerView.addSubview(titleLockup)
titleLockup titleLockup
.pinTop()
.pinLeading() .pinLeading()
.pinBottom() titleLockupTopConstraint = titleLockup.topAnchor.constraint(equalTo: titleLockupContainerView.topAnchor)
titleLockupTopConstraint?.activate()
titleLockupBottomConstraint = titleLockupContainerView.bottomAnchor.constraint(equalTo: titleLockup.bottomAnchor)
titleLockupBottomConstraint?.activate()
titleLockupTrailingConstraint = titleLockup.trailingAnchor.constraint(equalTo: titleLockupContainerView.trailingAnchor) titleLockupTrailingConstraint = titleLockup.trailingAnchor.constraint(equalTo: titleLockupContainerView.trailingAnchor)
titleLockupTrailingConstraint?.isActive = true titleLockupTrailingConstraint?.activate()
titleLockupBottomGreaterThanConstraint = titleLockupContainerView.bottomAnchor.constraint(greaterThanOrEqualTo: titleLockup.bottomAnchor)
titleLockupTopGreaterThanConstraint = titleLockup.topAnchor.constraint(greaterThanOrEqualTo: titleLockupContainerView.topAnchor)
titleLockupCenterYConstraint = titleLockup.centerYAnchor.constraint(equalTo: titleLockupContainerView.centerYAnchor)
iconContainerView.addSubview(descriptiveIcon) iconContainerView.addSubview(descriptiveIcon)
iconContainerView.addSubview(directionalIcon) iconContainerView.addSubview(directionalIcon)
@ -249,6 +342,47 @@ open class Tilelet: TileContainer {
.pinTop() .pinTop()
.pinBottom() .pinBottom()
badge.bottomAnchor.constraint(equalTo: badge.label.bottomAnchor, constant: 2).activate()
/**
Truncation:
If a Tilelet has only a Title or a Subtitle, then the Title or Subtitle is truncated and appended with an ellipsis when there is not enough space to display the full text.
If a Tilelet has both Title and Subtitle, then only Subtitle will be truncated.
If a Tilelet has Badge, Title and Subtitle, then Subtitle will be truncated first and Badge will be truncated second. Title will be truncated last (lowest priority).
Atleast one line text based on priority
Minimum bottom space below Badge is 4px; less than 4px results in truncation.
*/
let labelPriority = UILayoutPriority.defaultHigh.rawValue
titleLockup.titleLabel.setContentCompressionResistancePriority(UILayoutPriority(labelPriority), for: .vertical)
badge.label.setContentCompressionResistancePriority(UILayoutPriority(labelPriority-1), for: .vertical)
titleLockup.subTitleLabel.setContentCompressionResistancePriority(UILayoutPriority(labelPriority-2), for: .vertical)
titleLockup.eyebrowLabel.setContentCompressionResistancePriority(UILayoutPriority(labelPriority-3), for: .vertical)
titleLockup.titleLabel.setContentHuggingPriority(UILayoutPriority(labelPriority), for: .vertical)
badge.label.setContentHuggingPriority(UILayoutPriority(labelPriority-1), for: .vertical)
titleLockup.subTitleLabel.setContentHuggingPriority(UILayoutPriority(labelPriority-2), for: .vertical)
titleLockup.eyebrowLabel.setContentHuggingPriority(UILayoutPriority(labelPriority-3), for: .vertical)
/**
Added these constraints for:
At fixed width & height if all the labels(Badge, Eyebrow, Title, Subtitle) are having more number of lines then we should display atleast one line of content per label instead of pushing labels out of bounds.
So adding minimum single line height constraint
*/
badgeLabelHeightGreaterThanConstraint = badge.label.heightGreaterThanEqualTo(constant: badge.label.minimumLineHeight)
badgeLabelHeightGreaterThanConstraint?.priority = .defaultHigh
badgeLabelHeightGreaterThanConstraint?.activate()
titleLockupEyebrowLabelHeightGreaterThanConstraint = titleLockup.eyebrowLabel.heightGreaterThanEqualTo(constant: titleLockup.eyebrowLabel.minimumLineHeight)
titleLockupEyebrowLabelHeightGreaterThanConstraint?.priority = .defaultHigh
titleLockupEyebrowLabelHeightGreaterThanConstraint?.activate()
titleLockupTitleLabelHeightGreaterThanConstraint = titleLockup.titleLabel.heightGreaterThanEqualTo(constant: titleLockup.titleLabel.minimumLineHeight)
titleLockupTitleLabelHeightGreaterThanConstraint?.priority = .defaultHigh
titleLockupTitleLabelHeightGreaterThanConstraint?.activate()
titleLockupSubTitleLabelHeightGreaterThanConstraint = titleLockup.subTitleLabel.heightGreaterThanEqualTo(constant: titleLockup.subTitleLabel.minimumLineHeight)
titleLockupSubTitleLabelHeightGreaterThanConstraint?.priority = .defaultHigh
titleLockupSubTitleLabelHeightGreaterThanConstraint?.activate()
} }
/// Resets to default settings. /// Resets to default settings.
@ -261,7 +395,7 @@ open class Tilelet: TileContainer {
titleModel = nil titleModel = nil
subTitleModel = nil subTitleModel = nil
descriptiveIconModel = nil descriptiveIconModel = nil
directionalIconModel = nil directionalIconModel = nil
shouldUpdateView = true shouldUpdateView = true
setNeedsUpdate() setNeedsUpdate()
} }
@ -273,7 +407,11 @@ open class Tilelet: TileContainer {
updateBadge() updateBadge()
updateTitleLockup() updateTitleLockup()
updateIcons() updateIcons()
///Content-driven height Tilelets - Minimum height is configurable.
///if width != nil && (aspectRatio != .none || height != nil) then tilelet is not self growing, so we can apply text position alignments.
if width != nil && (aspectRatio != .none || height != nil) {
updateTextPositionAlignment()
}
layoutIfNeeded() layoutIfNeeded()
} }
@ -294,6 +432,7 @@ open class Tilelet: TileContainer {
badge.numberOfLines = badgeModel.numberOfLines badge.numberOfLines = badgeModel.numberOfLines
badge.surface = badgeModel.surface badge.surface = badgeModel.surface
badge.maxWidth = badgeModel.maxWidth badge.maxWidth = badgeModel.maxWidth
badgeLabelHeightGreaterThanConstraint?.constant = badge.label.minimumLineHeight
if badgeContainerView.superview == nil { if badgeContainerView.superview == nil {
stackView.insertArrangedSubview(badgeContainerView, at: 0) stackView.insertArrangedSubview(badgeContainerView, at: 0)
setNeedsLayout() setNeedsLayout()
@ -306,7 +445,11 @@ open class Tilelet: TileContainer {
private func updateTitleLockup() { private func updateTitleLockup() {
var showTitleLockup = false var showTitleLockup = false
if let eyebrowModel, !eyebrowModel.text.isEmpty {
showTitleLockup = true
}
if let titleModel, !titleModel.text.isEmpty { if let titleModel, !titleModel.text.isEmpty {
showTitleLockup = true showTitleLockup = true
} }
@ -350,6 +493,7 @@ open class Tilelet: TileContainer {
} }
//set models //set models
titleLockup.eyebrowModel = eyebrowModel?.toTitleLockupEyebrowModel()
titleLockup.titleModel = titleModel?.toTitleLockupTitleModel() titleLockup.titleModel = titleModel?.toTitleLockupTitleModel()
titleLockup.subTitleModel = subTitleModel?.toTitleLockupSubTitleModel() titleLockup.subTitleModel = subTitleModel?.toTitleLockupSubTitleModel()
@ -360,6 +504,9 @@ open class Tilelet: TileContainer {
} else { } else {
removeFromSuperview(titleLockupContainerView) removeFromSuperview(titleLockupContainerView)
} }
titleLockupEyebrowLabelHeightGreaterThanConstraint?.constant = titleLockup.eyebrowLabel.minimumLineHeight
titleLockupTitleLabelHeightGreaterThanConstraint?.constant = titleLockup.titleLabel.minimumLineHeight
titleLockupSubTitleLabelHeightGreaterThanConstraint?.constant = titleLockup.subTitleLabel.minimumLineHeight
} }
private func updateIcons() { private func updateIcons() {
@ -392,7 +539,7 @@ open class Tilelet: TileContainer {
view = titleLockupContainerView view = titleLockupContainerView
} }
if let view { if let view {
stackView.setCustomSpacing(padding.tiletSpacing, after: view) stackView.setCustomSpacing(padding.titleLockupBottomSpacing, after: view)
} }
if iconContainerView.superview == nil { if iconContainerView.superview == nil {
stackView.addArrangedSubview(iconContainerView) stackView.addArrangedSubview(iconContainerView)
@ -402,21 +549,33 @@ open class Tilelet: TileContainer {
removeFromSuperview(iconContainerView) removeFromSuperview(iconContainerView)
} }
} }
}
private func updateTextPositionAlignment() {
extension TileContainer.Padding { switch textPostion {
fileprivate var tiletSpacing: CGFloat { case .top:
switch self { titleLockupTopConstraint?.activate()
case .padding2X: titleLockupTopGreaterThanConstraint?.deactivate()
return 16 titleLockupBottomConstraint?.deactivate()
case .padding4X: titleLockupBottomGreaterThanConstraint?.activate()
return 24 titleLockupCenterYConstraint?.deactivate()
case .padding6X: case .middle:
return 32 titleLockupTopConstraint?.deactivate()
case .padding8X: titleLockupTopGreaterThanConstraint?.activate()
return 48 titleLockupBottomConstraint?.deactivate()
default: titleLockupBottomGreaterThanConstraint?.activate()
return 16 titleLockupCenterYConstraint?.activate()
case .bottom:
titleLockupTopConstraint?.deactivate()
titleLockupTopGreaterThanConstraint?.activate()
titleLockupBottomConstraint?.activate()
titleLockupBottomGreaterThanConstraint?.deactivate()
titleLockupCenterYConstraint?.deactivate()
} }
} }
} }
extension Label {
///To calculate label single line height
fileprivate var minimumLineHeight: CGFloat { textStyle.lineHeight }
}

View File

@ -6,6 +6,7 @@
// //
import Foundation import Foundation
import UIKit
extension Tilelet { extension Tilelet {
@ -26,12 +27,16 @@ extension Tilelet {
/// Max width that will be used for the badge. /// Max width that will be used for the badge.
public var maxWidth: CGFloat? public var maxWidth: CGFloat?
public init(text: String, fillColor: Badge.FillColor = .red, surface: Surface = .light, numberOfLines: Int = 0, maxWidth: CGFloat? = nil) { /// LineBreakMode used in Badge label.
public var lineBreakMode: NSLineBreakMode
public init(text: String, fillColor: Badge.FillColor = .red, surface: Surface = .light, numberOfLines: Int = 0, maxWidth: CGFloat? = nil, lineBreakMode: NSLineBreakMode = .byTruncatingTail) {
self.text = text self.text = text
self.fillColor = fillColor self.fillColor = fillColor
self.surface = surface self.surface = surface
self.numberOfLines = numberOfLines self.numberOfLines = numberOfLines
self.maxWidth = maxWidth self.maxWidth = maxWidth
self.lineBreakMode = lineBreakMode
} }
} }
} }

View File

@ -6,6 +6,7 @@
// //
import Foundation import Foundation
import UIKit
extension Tilelet { extension Tilelet {
/// Model that represents the options available for the sub title label. /// Model that represents the options available for the sub title label.
@ -18,7 +19,8 @@ extension Tilelet {
case bodyLarge case bodyLarge
case bodyMedium case bodyMedium
case bodySmall case bodySmall
case titleSmall
case titleMedium
public var defaultValue: TitleLockup.OtherStandardStyle { .bodySmall } public var defaultValue: TitleLockup.OtherStandardStyle { .bodySmall }
} }
//-------------------------------------------------- //--------------------------------------------------
@ -36,17 +38,22 @@ extension Tilelet {
/// Text color that will be used for the subTitle label. /// Text color that will be used for the subTitle label.
public var textColor: Use = .primary public var textColor: Use = .primary
/// LineBreakMode used in Badge label.
public var lineBreakMode: NSLineBreakMode
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Initializers // MARK: - Initializers
//-------------------------------------------------- //--------------------------------------------------
public init(text: String, public init(text: String,
otherStandardStyle: OtherStandardStyle = .bodySmall, otherStandardStyle: OtherStandardStyle = .bodySmall,
textColor: Use = .primary, textColor: Use = .primary,
textAttributes: [any LabelAttributeModel]? = nil) { textAttributes: [any LabelAttributeModel]? = nil,
lineBreakMode: NSLineBreakMode = .byTruncatingTail) {
self.text = text self.text = text
self.otherStandardStyle = otherStandardStyle self.otherStandardStyle = otherStandardStyle
self.textAttributes = textAttributes self.textAttributes = textAttributes
self.textColor = textColor self.textColor = textColor
self.lineBreakMode = lineBreakMode
} }
//-------------------------------------------------- //--------------------------------------------------
@ -57,7 +64,7 @@ extension Tilelet {
TitleLockup.SubTitleModel(text: text, TitleLockup.SubTitleModel(text: text,
otherStandardStyle: otherStandardStyle.value, otherStandardStyle: otherStandardStyle.value,
textColor: textColor, textColor: textColor,
textAttributes: textAttributes) textAttributes: textAttributes, lineBreakMode: lineBreakMode)
} }
} }
} }

View File

@ -6,6 +6,7 @@
// //
import Foundation import Foundation
import UIKit
extension Tilelet { extension Tilelet {
/// Model that represents the options available for the title label. /// Model that represents the options available for the title label.
@ -19,7 +20,10 @@ extension Tilelet {
case titleLarge case titleLarge
case titleMedium case titleMedium
case titleSmall case titleSmall
case bodyLarge
case bodyMedium
case bodySmall
public var defaultValue: TitleLockup.TitleStandardStyle { .titleSmall } public var defaultValue: TitleLockup.TitleStandardStyle { .titleSmall }
} }
//-------------------------------------------------- //--------------------------------------------------
@ -28,21 +32,30 @@ extension Tilelet {
/// Text that will be used for the title label. /// Text that will be used for the title label.
public var text: String = "" public var text: String = ""
/// Used in combination with standardStyle to set the textStyle that will be used for the title label.
public var isBold: Bool = false
/// Text attributes that will be used for the title label. /// Text attributes that will be used for the title label.
public var textAttributes: [any LabelAttributeModel]? public var textAttributes: [any LabelAttributeModel]?
/// Text style that will be used for the title label. /// Text style that will be used for the title label.
public var standardStyle: StandardStyle = .titleSmall public var standardStyle: StandardStyle = .titleSmall
/// LineBreakMode used in Badge label.
public var lineBreakMode: NSLineBreakMode
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Initializers // MARK: - Initializers
//-------------------------------------------------- //--------------------------------------------------
public init(text: String, public init(text: String,
textAttributes: [any LabelAttributeModel]? = nil, textAttributes: [any LabelAttributeModel]? = nil,
standardStyle: StandardStyle = .titleSmall) { isBold: Bool = true,
standardStyle: StandardStyle = .titleSmall,
lineBreakMode: NSLineBreakMode = .byTruncatingTail) {
self.text = text self.text = text
self.textAttributes = textAttributes self.textAttributes = textAttributes
self.standardStyle = standardStyle self.standardStyle = standardStyle
self.isBold = isBold
self.lineBreakMode = lineBreakMode
} }
//-------------------------------------------------- //--------------------------------------------------
@ -52,7 +65,7 @@ extension Tilelet {
public func toTitleLockupTitleModel() -> TitleLockup.TitleModel { public func toTitleLockupTitleModel() -> TitleLockup.TitleModel {
TitleLockup.TitleModel(text: text, TitleLockup.TitleModel(text: text,
textAttributes: textAttributes, textAttributes: textAttributes,
standardStyle: standardStyle.value) isBold: isBold, standardStyle: standardStyle.value, lineBreakMode: lineBreakMode)
} }
} }
} }

View File

@ -0,0 +1,55 @@
//
// TiletEyebrowModel.swift
// VDS
//
// Created by Bandaru, Krishna Kishore on 13/03/24.
//
import Foundation
import UIKit
extension Tilelet {
/// Model that represents the options available for the eyebrow label.
public struct EyebrowModel {
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
/// Text that will be used for the eyebrow label.
public var text: String = ""
/// Used in combination with standardStyle to set the textStyle that will be used for the eyebrow label.
public var isBold: Bool = false
/// Text attributes that will be used for the eyebrow label.
public var textAttributes: [any LabelAttributeModel]?
/// Text style that will be used for the eyebrow label. If subtitle standard style
public var standardStyle: Tilelet.SubTitleModel.OtherStandardStyle = .titleSmall
/// LineBreakMode used in Badge label.
public var lineBreakMode: NSLineBreakMode
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
public init(text: String,
textAttributes: [any LabelAttributeModel]? = nil,
isBold: Bool = true,
standardStyle: Tilelet.SubTitleModel.OtherStandardStyle = .bodySmall,
lineBreakMode: NSLineBreakMode = .byTruncatingTail) {
self.text = text
self.textAttributes = textAttributes
self.standardStyle = standardStyle
self.isBold = isBold
self.lineBreakMode = lineBreakMode
}
//--------------------------------------------------
// MARK: - Public Methods
//--------------------------------------------------
/// Converts this type of model to a TitleLockup.TitleModel.
public func toTitleLockupEyebrowModel() -> TitleLockup.EyebrowModel {
TitleLockup.EyebrowModel(text: text, isBold: isBold, standardStyle: standardStyle.value, textAttributes: textAttributes)
}
}
}

View File

@ -0,0 +1,110 @@
MM/DD/YYYY
----------------
08/31/2022
----------------
- Updates to Typography and default Title Lockup configurations:
- Default
- Regular title updated to bold weight
- Subtitle updated to primary color
- Inverted
- Regular title updated to bold weight
- Subtitle updated to primary color
07/15/2022
----------------
- Updates to Typography and default Title Lockup configurations:
- Default
- Bold Title updated to Regular weight
- Subtitle updated to secondary color
- Inverted
- Bold Title updated to Regular weight
- Subtitle updated to secondary color
04/27/2022
----------------
- Updates to Title Lockup configurations.
04/14/2022
----------------
- Updates to Tilelet Group spec based on 4/14 handoff feedback.
04/04/2022
----------------
- Visuals added to Tilelet Group spec.
04/01/2022
----------------
- Updates based on 3/31 handoff feedback.
03/30/2022
----------------
- Tilelet added to Test App.
03/29/2022
----------------
- Interactive states updated.
03/22/2022
----------------
- Layout and spacing page added.
03/21/2022
----------------
- Updated anatomy, configuration, and element pages.
02/21/2022
----------------
- Title Lockups width properties added.
09/14/2022
----------------
- States section updated to reflect states inherited from Tile Container
- Dev note added to Layouts.
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.
06/19/2023
----------------
- Updated anatomy element names to be local to Tilelet and indicated the core component that powers them under the hood.
09/21/2023
----------------
- Updated Anatomy to include Title Lockup with Eyebrow and Tooltip
- Updated Configurations to include Title Lockup section
- Updated Layout and spacing to include Alignment (Left, Center) and textPosition (Middle)
- Updated Elements to include Eyebrow and uniform 1:1 pairings
10/09/2023
----------------
- Updated Configurations VDS Title Lockup and VDS Tile Container sections.
10/13/2023
----------------
- SPEC format changes to Anatomy to include nested component configurations
- Removed Configurations page
11/20/2023
----------------
- Updated visuals to reflect new corner radius value - 12px
- Updated focus border corner radius to 14px
- View changes
12/14/2023
----------------
- Added secondary, primary backgroundColor options
- Relabeled configurations section headings to use property names
- Relabeled surface sections to use “surface:” labeling convention
- Deprecated gray backgroundColor
- View changes
02/08/2024
----------------
- Added external-icon option to Descriptive Icon in Configurations
- View changes

View File

@ -353,7 +353,7 @@ open class TitleLockup: View {
titleLabel.attributes = titleModel.textAttributes titleLabel.attributes = titleModel.textAttributes
titleLabel.numberOfLines = titleModel.numberOfLines titleLabel.numberOfLines = titleModel.numberOfLines
titleLabel.surface = surface titleLabel.surface = surface
titleLabel.lineBreakMode = titleModel.lineBreakMode
addSubview(titleLabel) addSubview(titleLabel)
titleLabel titleLabel
.pinTop(previousView?.bottomAnchor ?? self.topAnchor, eyebrowLabel == previousView ? topSpacing : 0) .pinTop(previousView?.bottomAnchor ?? self.topAnchor, eyebrowLabel == previousView ? topSpacing : 0)
@ -372,7 +372,7 @@ open class TitleLockup: View {
subTitleLabel.attributes = subTitleModel.textAttributes subTitleLabel.attributes = subTitleModel.textAttributes
subTitleLabel.numberOfLines = subTitleModel.numberOfLines subTitleLabel.numberOfLines = subTitleModel.numberOfLines
subTitleLabel.surface = surface subTitleLabel.surface = surface
subTitleLabel.lineBreakMode = subTitleModel.lineBreakMode
addSubview(subTitleLabel) addSubview(subTitleLabel)
subTitleLabel subTitleLabel
.pinTop(previousView?.bottomAnchor ?? self.topAnchor, (eyebrowLabel == previousView || titleLabel == previousView) ? bottomSpacing : 0) .pinTop(previousView?.bottomAnchor ?? self.topAnchor, (eyebrowLabel == previousView || titleLabel == previousView) ? bottomSpacing : 0)

View File

@ -6,6 +6,7 @@
// //
import Foundation import Foundation
import UIKit
extension TitleLockup { extension TitleLockup {
/// Model that represents the options available for the sub title label. /// Model that represents the options available for the sub title label.
@ -25,16 +26,21 @@ extension TitleLockup {
/// Number of lines used in the subtitle label. /// Number of lines used in the subtitle label.
public var numberOfLines: Int public var numberOfLines: Int
/// LineBreakMode used in subtitle label.
public var lineBreakMode: NSLineBreakMode
public init(text: String, public init(text: String,
otherStandardStyle: OtherStandardStyle = .bodyLarge, otherStandardStyle: OtherStandardStyle = .bodyLarge,
textColor: Use = .primary, textColor: Use = .primary,
textAttributes: [any LabelAttributeModel]? = nil, textAttributes: [any LabelAttributeModel]? = nil,
numberOfLines: Int = 0) { numberOfLines: Int = 0,
lineBreakMode: NSLineBreakMode = .byWordWrapping) {
self.text = text self.text = text
self.otherStandardStyle = otherStandardStyle self.otherStandardStyle = otherStandardStyle
self.textColor = textColor self.textColor = textColor
self.textAttributes = textAttributes self.textAttributes = textAttributes
self.numberOfLines = numberOfLines self.numberOfLines = numberOfLines
self.lineBreakMode = lineBreakMode
} }
/// TextStyle used to render the text. /// TextStyle used to render the text.

View File

@ -6,6 +6,7 @@
// //
import Foundation import Foundation
import UIKit
extension TitleLockup { extension TitleLockup {
/// Model that represents the options available for the sub title label. /// Model that represents the options available for the sub title label.
@ -25,16 +26,21 @@ extension TitleLockup {
/// Number of lines that will be used for the title label. /// Number of lines that will be used for the title label.
public var numberOfLines: Int public var numberOfLines: Int
/// LineBreakMode used in subtitle label.
public var lineBreakMode: NSLineBreakMode
public init(text: String, public init(text: String,
textAttributes: [any LabelAttributeModel]? = nil, textAttributes: [any LabelAttributeModel]? = nil,
isBold: Bool = true, isBold: Bool = true,
standardStyle: TitleStandardStyle = .featureXSmall, standardStyle: TitleStandardStyle = .featureXSmall,
numberOfLines: Int = 0) { numberOfLines: Int = 0,
lineBreakMode: NSLineBreakMode = .byTruncatingTail) {
self.text = text self.text = text
self.isBold = isBold self.isBold = isBold
self.textAttributes = textAttributes self.textAttributes = textAttributes
self.standardStyle = standardStyle self.standardStyle = standardStyle
self.numberOfLines = numberOfLines self.numberOfLines = numberOfLines
self.lineBreakMode = lineBreakMode
} }
/// TextStyle used to render the text. /// TextStyle used to render the text.

View File

@ -10,7 +10,8 @@ import Foundation
/// Represents constants used that deal with layout. /// Represents constants used that deal with layout.
public struct VDSLayout { public struct VDSLayout {
/// Enum used to describe the spacing constants. /// Enum used to describe the spacing constants.
public enum Spacing: String, CaseIterable { public enum Spacing: String, CaseIterable, Valuing {
case space1X case space1X
case space2X case space2X
case space3X case space3X

View File

@ -0,0 +1,12 @@
//
// EnumValuing.swift
// VDS
//
// Created by Matt Bruce on 3/1/24.
//
import Foundation
public protocol DefaultValuing: Valuing {
static var defaultValue: Self { get }
}

View File

@ -50,7 +50,6 @@ extension ViewProtocol where Self: UIView {
shadowLayer.shadowPath = shadowPath.cgPath shadowLayer.shadowPath = shadowPath.cgPath
shadowLayer.frame = bounds shadowLayer.frame = bounds
shadowLayer.position = .init(x: bounds.midX, y: bounds.midY) shadowLayer.position = .init(x: bounds.midX, y: bounds.midY)
shadowLayer.backgroundColor = backgroundColor?.cgColor
shadowLayer.cornerRadius = layer.cornerRadius shadowLayer.cornerRadius = layer.cornerRadius
shadowLayer.shadowColor = config.shadowColorConfiguration.getColor(self).cgColor shadowLayer.shadowColor = config.shadowColorConfiguration.getColor(self).cgColor
shadowLayer.shadowOpacity = Float(config.shadowOpacityConfiguration.value(for: self)) shadowLayer.shadowOpacity = Float(config.shadowOpacityConfiguration.value(for: self))
@ -86,4 +85,8 @@ extension ViewProtocol where Self: UIView {
func removeGradientLayer() { func removeGradientLayer() {
layer.sublayers?.removeAll { $0.name == "gradientLayer" } layer.sublayers?.removeAll { $0.name == "gradientLayer" }
} }
//Helper variables to get gradient & shadow layers. These are exposed to update frame of layers when view's frame is updated.
var dropShadowLayers: [CALayer]? { layer.sublayers?.filter { $0.name == "dropShadowLayer" } }
var gradientLayers: [CAGradientLayer]? { layer.sublayers?.filter { $0.name == "gradientLayer" } as? [CAGradientLayer] }
} }

View File

@ -7,7 +7,7 @@
import Foundation import Foundation
public protocol EnumSubset<T>: RawRepresentable, CaseIterable { public protocol EnumSubset<T>: RawRepresentable, CaseIterable, Valuing {
associatedtype T:RawRepresentable associatedtype T:RawRepresentable
var defaultValue: T { get } var defaultValue: T { get }
} }

View File

@ -9,10 +9,91 @@ import Foundation
/// Protocol used for a FormField object. /// Protocol used for a FormField object.
public protocol FormFieldable { public protocol FormFieldable {
associatedtype ValueType = AnyHashable
/// Unique Id for the Form Field object within a Form. /// Unique Id for the Form Field object within a Form.
var inputId: String? { get set } var inputId: String? { get set }
/// Value for the Form Field. /// Value for the Form Field.
var value: AnyHashable? { get set } var value: ValueType? { get set }
}
/// Protocol for FormFieldable that require internal validation.
public protocol FormFieldInternalValidatable: FormFieldable {
/// Is there an internalError
var hasInternalError: Bool { get }
/// Internal Error Message that will show.
var internalErrorText: String? { get }
}
/// Struct that will execute the validation.
public protocol FormFieldValidatorable {
associatedtype FieldType: FormFieldable
/// FormFieldable to be validated.
var field: FieldType { get set }
/// Rules that will be applied against the FormFieldable
var rules: [AnyRule<FieldType.ValueType>] { get set }
/// Error Message that will show.
var errorMessage: String? { get }
/// Is the FormField valid.
var isValid: Bool { get }
/// Run the rules against the FormFieldable.
func validate()
}
/// Rule that will be executed against a specific ValueType.
public protocol Rule<ValueType> {
associatedtype ValueType
/// Determines if this rule valid for the value passed.
func isValid(value: ValueType?) -> Bool
/// Error Message to be show if the value is invalid.
var errorMessage: String { get }
}
/// Type Erased Rule for a specific ValueType.
public struct AnyRule<ValueType>: Rule {
private let _isValid: (ValueType?) -> Bool
public let errorMessage: String
public init<R: Rule>(_ rule: R) where R.ValueType == ValueType {
self._isValid = rule.isValid
self.errorMessage = rule.errorMessage
}
public func isValid(value: ValueType?) -> Bool {
return _isValid(value)
}
}
/// Generic Validator for a specific FormFieldable.
public class FormFieldValidator<Field:FormFieldable>: FormFieldValidatorable{
public var field: Field
public var rules: [AnyRule<Field.ValueType>]
public var errorMessages = [String]()
public var isValid: Bool = true
public init(field: Field, rules: [AnyRule<Field.ValueType>]) {
self.field = field
self.rules = rules
}
public var errorMessage: String? {
guard errorMessages.count > 0 else { return nil }
return errorMessages.joined(separator: "\r")
}
public func validate() {
errorMessages.removeAll()
for rule in rules {
if !rule.isValid(value: field.value) {
errorMessages.append(rule.errorMessage)
isValid = false
return
}
}
isValid = true
}
} }

View File

@ -0,0 +1,13 @@
//
// Valuing.swift
// VDS
//
// Created by Matt Bruce on 3/1/24.
//
import Foundation
public protocol Valuing {
associatedtype ValueType
var value: ValueType { get }
}