Merge branch 'develop' of https://gitlab.verizon.com/BPHV_MIPS/vds_ios.git into vasavk/carouselScrollbar
This commit is contained in:
commit
c0d31cf3db
@ -22,8 +22,10 @@
|
||||
44604AD729CE196600E62B51 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44604AD629CE196600E62B51 /* Line.swift */; };
|
||||
5F21D7BF28DCEB3D003E7CD6 /* Useable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F21D7BE28DCEB3D003E7CD6 /* Useable.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 */; };
|
||||
71ACE89C2BA0451200FB6ADC /* PaginationContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71ACE89B2BA0451200FB6ADC /* PaginationContainer.swift */; };
|
||||
71ACE89E2BA1CC1700FB6ADC /* TiletEyebrowModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71ACE89D2BA1CC1700FB6ADC /* TiletEyebrowModel.swift */; };
|
||||
71B23C2D2B91FA690027F7D9 /* Pagination.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71B23C2C2B91FA690027F7D9 /* Pagination.swift */; };
|
||||
71B5FCBB2B95A0CA00269BCC /* PaginationChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 71B5FCBA2B95A0CA00269BCC /* PaginationChangeLog.txt */; };
|
||||
71BFA70A2B7F70E6000DCE33 /* DropShadowable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71BFA7092B7F70E6000DCE33 /* DropShadowable.swift */; };
|
||||
@ -116,6 +118,8 @@
|
||||
EAA5EEF128F5C909003B3210 /* VDSColorTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EAA5EEED28F5C908003B3210 /* VDSColorTokens.xcframework */; };
|
||||
EAA5EEF328F5C909003B3210 /* VDSFormControlsTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EAA5EEEE28F5C908003B3210 /* VDSFormControlsTokens.xcframework */; };
|
||||
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 */; };
|
||||
EAB1D2CD28ABE76100DAE764 /* Withable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D2CC28ABE76000DAE764 /* Withable.swift */; };
|
||||
EAB1D2CF28ABEF2B00DAE764 /* Typography+Base.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D2CE28ABEF2B00DAE764 /* Typography+Base.swift */; };
|
||||
@ -163,7 +167,6 @@
|
||||
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 */; };
|
||||
@ -208,8 +211,10 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
71ACE89B2BA0451200FB6ADC /* PaginationContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationContainer.swift; sourceTree = "<group>"; };
|
||||
71ACE89D2BA1CC1700FB6ADC /* TiletEyebrowModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TiletEyebrowModel.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>"; };
|
||||
71BFA7092B7F70E6000DCE33 /* DropShadowable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropShadowable.swift; sourceTree = "<group>"; };
|
||||
@ -304,6 +309,8 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -351,7 +358,6 @@
|
||||
EAEEECAE2B1FC2BA00531FC2 /* ToggleViewChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = ToggleViewChangeLog.txt; sourceTree = "<group>"; };
|
||||
EAF1FE9829D4850E00101452 /* Clickable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Clickable.swift; sourceTree = "<group>"; };
|
||||
EAF1FE9A29DB1A6000101452 /* Changeable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Changeable.swift; sourceTree = "<group>"; };
|
||||
EAF4A6A02BAC8B94006BCC2C /* BreadcrumbsFlowLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbsFlowLayout.swift; sourceTree = "<group>"; };
|
||||
EAF7F0932899861000B287F5 /* CheckboxItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckboxItem.swift; sourceTree = "<group>"; };
|
||||
EAF7F0992899B17200B287F5 /* CATransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CATransaction.swift; sourceTree = "<group>"; };
|
||||
EAF7F09F289AB7EC00B287F5 /* View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = "<group>"; };
|
||||
@ -396,7 +402,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
18A65A012B96E848006602CC /* Breadcrumbs.swift */,
|
||||
EAF4A6A02BAC8B94006BCC2C /* BreadcrumbsFlowLayout.swift */,
|
||||
18A65A032B96F050006602CC /* BreadcrumbItem.swift */,
|
||||
1855EC652BAABF2A002ACAC2 /* BreadcrumbItemModel.swift */,
|
||||
1832AC562BA0791D008AE476 /* BreadcrumbCellItem.swift */,
|
||||
@ -626,6 +631,7 @@
|
||||
EAF1FE9A29DB1A6000101452 /* Changeable.swift */,
|
||||
EAF1FE9829D4850E00101452 /* Clickable.swift */,
|
||||
EAA5EEDF28F49DB3003B3210 /* Colorable.swift */,
|
||||
EAACB8972B92706F006A3869 /* DefaultValuing.swift */,
|
||||
EA3361A9288B25E40071C351 /* Disabling.swift */,
|
||||
EAF978202A99035B00C2FEA9 /* Enabling.swift */,
|
||||
EA5E305929510F8B0082B959 /* EnumSubset.swift */,
|
||||
@ -639,6 +645,7 @@
|
||||
EA3361B7288B2AAA0071C351 /* ViewProtocol.swift */,
|
||||
EAB1D2CC28ABE76000DAE764 /* Withable.swift */,
|
||||
5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */,
|
||||
EAACB8992B927108006A3869 /* Valuing.swift */,
|
||||
71BFA7092B7F70E6000DCE33 /* DropShadowable.swift */,
|
||||
);
|
||||
path = Protocols;
|
||||
@ -755,9 +762,11 @@
|
||||
children = (
|
||||
EA5E3057295105A40082B959 /* Tilelet.swift */,
|
||||
EA985BE529688F6A00F2FF2E /* TileletBadgeModel.swift */,
|
||||
EA985BE929689B6D00F2FF2E /* TileletSubTitleModel.swift */,
|
||||
71ACE89D2BA1CC1700FB6ADC /* TiletEyebrowModel.swift */,
|
||||
EA985BE72968951C00F2FF2E /* TileletTitleModel.swift */,
|
||||
EA985BE929689B6D00F2FF2E /* TileletSubTitleModel.swift */,
|
||||
EA985C2C296F03FE00F2FF2E /* TileletIconModels.swift */,
|
||||
710607942B91A99500F2863F /* TitleletChangeLog.txt */,
|
||||
);
|
||||
path = Tilelet;
|
||||
sourceTree = "<group>";
|
||||
@ -1032,6 +1041,7 @@
|
||||
EAEEECA72B1F952000531FC2 /* TabsChangeLog.txt in Resources */,
|
||||
186B2A8A2B88DA7F001AB71F /* TextAreaChangeLog.txt in Resources */,
|
||||
EAEEEC962B1F893B00531FC2 /* ButtonChangeLog.txt in Resources */,
|
||||
710607952B91A99500F2863F /* TitleletChangeLog.txt in Resources */,
|
||||
EA5F86CC2A1D28B500BC83E4 /* ReleaseNotes.txt in Resources */,
|
||||
EAEEEC982B1F8DD100531FC2 /* LineChangeLog.txt in Resources */,
|
||||
EAEEECA22B1F92AD00531FC2 /* LabelChangeLog.txt in Resources */,
|
||||
@ -1088,6 +1098,7 @@
|
||||
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 */,
|
||||
@ -1106,6 +1117,7 @@
|
||||
EAF7F0AF289B144C00B287F5 /* UnderlineLabelAttribute.swift in Sources */,
|
||||
EA0D1C412A6AD61C00E5C127 /* Typography+Additional.swift in Sources */,
|
||||
EAC925842911C63100091998 /* Colorable.swift in Sources */,
|
||||
EAACB89A2B927108006A3869 /* Valuing.swift in Sources */,
|
||||
EAE785312BA0A438009428EA /* UIImage+Helper.swift in Sources */,
|
||||
EAB5FEF5292D371F00998C17 /* ButtonBase.swift in Sources */,
|
||||
EA978EC5291D6AFE00ACC883 /* AnyLabelAttribute.swift in Sources */,
|
||||
@ -1113,6 +1125,7 @@
|
||||
EAC71A1F2A2E173D00E47A9F /* RadioButton.swift in Sources */,
|
||||
EA33622C2891E73B0071C351 /* FontProtocol.swift in Sources */,
|
||||
EA596ABD2A16B4EC00300C4B /* Tab.swift in Sources */,
|
||||
71ACE89E2BA1CC1700FB6ADC /* TiletEyebrowModel.swift in Sources */,
|
||||
EAF7F11728A1475A00B287F5 /* RadioButtonItem.swift in Sources */,
|
||||
EA985BEE2968A92400F2FF2E /* TitleLockupSubTitleModel.swift in Sources */,
|
||||
EA985BF22968B5BB00F2FF2E /* TitleLockupTextStyle.swift in Sources */,
|
||||
@ -1138,7 +1151,6 @@
|
||||
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 */,
|
||||
|
||||
@ -147,6 +147,7 @@ open class Badge: View {
|
||||
|
||||
label.widthGreaterThanEqualTo(constant: minWidth)
|
||||
maxWidthConstraint = label.widthLessThanEqualTo(constant: 0).with { $0.isActive = false }
|
||||
clipsToBounds = true
|
||||
}
|
||||
|
||||
/// Resets to default settings.
|
||||
|
||||
@ -24,6 +24,8 @@ final class BreadcrumbCellItem: UICollectionViewCell {
|
||||
$0.distribution = .fill
|
||||
$0.alignment = .fill
|
||||
$0.spacing = VDSLayout.Spacing.space1X.value
|
||||
$0.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
|
||||
$0.setContentHuggingPriority(.defaultHigh, for: .horizontal)
|
||||
}
|
||||
}()
|
||||
|
||||
@ -64,13 +66,14 @@ final class BreadcrumbCellItem: UICollectionViewCell {
|
||||
|
||||
///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
|
||||
breadCrumbItem.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
||||
breadCrumbItem.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
||||
|
||||
//remove previous views
|
||||
stackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
||||
|
||||
//add to stack
|
||||
stackView.addArrangedSubview(separator)
|
||||
@ -80,15 +83,8 @@ final class BreadcrumbCellItem: UICollectionViewCell {
|
||||
//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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -44,9 +44,15 @@ open class BreadcrumbItem: ButtonBase {
|
||||
|
||||
/// The natural size for the receiving view, considering only properties of the view itself.
|
||||
open override var intrinsicContentSize: CGSize {
|
||||
return titleLabel?.intrinsicContentSize ?? super.intrinsicContentSize
|
||||
guard let titleLabel else { return super.intrinsicContentSize }
|
||||
// Calculate the titleLabel's intrinsic content size
|
||||
let labelSize = titleLabel.sizeThatFits(CGSize(width: self.frame.width, height: CGFloat.greatestFiniteMagnitude))
|
||||
// Adjust the size if needed (add any additional padding if your design requires)
|
||||
let adjustedSize = CGSize(width: labelSize.width + contentEdgeInsets.left + contentEdgeInsets.right,
|
||||
height: labelSize.height + contentEdgeInsets.top + contentEdgeInsets.bottom)
|
||||
return adjustedSize
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Private Properties
|
||||
//--------------------------------------------------
|
||||
@ -64,8 +70,8 @@ open class BreadcrumbItem: ButtonBase {
|
||||
open override func setup() {
|
||||
super.setup()
|
||||
isAccessibilityElement = true
|
||||
accessibilityTraits = .button
|
||||
accessibilityLabel = "Breadcrumb"
|
||||
accessibilityTraits = .link
|
||||
contentHorizontalAlignment = .leading
|
||||
}
|
||||
|
||||
/// Used to make changes to the View based off a change events or from local properties.
|
||||
@ -86,4 +92,11 @@ open class BreadcrumbItem: ButtonBase {
|
||||
shouldUpdateView = true
|
||||
setNeedsUpdate()
|
||||
}
|
||||
|
||||
/// Used to update any Accessibility properties.
|
||||
open override func updateAccessibility() {
|
||||
super.updateAccessibility()
|
||||
accessibilityLabel = "Breadcrumb \(text ?? "")"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -47,14 +47,17 @@ open class Breadcrumbs: View {
|
||||
//--------------------------------------------------
|
||||
// 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
|
||||
fileprivate lazy var layout = ButtonGroupPositionLayout().with {
|
||||
$0.position = .left
|
||||
$0.delegate = self
|
||||
$0.axisSpacer = { _, _, _ in
|
||||
return VDSLayout.Spacing.space1X.value
|
||||
}
|
||||
$0.verticalSpacer = { _, _ in
|
||||
return VDSLayout.Spacing.space1X.value
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///Collectionview to render Breadcrumb Items
|
||||
private lazy var collectionView: SelfSizingCollectionView = {
|
||||
let collectionView = SelfSizingCollectionView(frame: .zero, collectionViewLayout: layout)
|
||||
@ -134,9 +137,10 @@ open class Breadcrumbs: View {
|
||||
self.collectionView.collectionViewLayout.invalidateLayout()
|
||||
}
|
||||
}
|
||||
private var separatorWidth = Label().with { $0.text = "/"; $0.sizeToFit() }.intrinsicContentSize.width
|
||||
}
|
||||
|
||||
extension Breadcrumbs: UICollectionViewDelegate, UICollectionViewDataSource {
|
||||
extension Breadcrumbs: UICollectionViewDelegate, UICollectionViewDataSource, ButtongGroupPositionLayoutDelegate {
|
||||
//--------------------------------------------------
|
||||
// MARK: - UICollectionView Delegate & Datasource
|
||||
//--------------------------------------------------
|
||||
@ -150,5 +154,16 @@ extension Breadcrumbs: UICollectionViewDelegate, UICollectionViewDataSource {
|
||||
cell.update(surface: surface, hideSlash: hideSlash, breadCrumbItem: breadcrumbs[indexPath.row])
|
||||
return cell
|
||||
}
|
||||
|
||||
|
||||
public func collectionView(_ collectionView: UICollectionView, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize {
|
||||
let breadcrumb = breadcrumbs[indexPath.row]
|
||||
let intrinsicSize = breadcrumb.intrinsicContentSize
|
||||
let separatorFullWidth: CGFloat = indexPath.row == 0 ? 0 : VDSLayout.Spacing.space1X.value + separatorWidth
|
||||
let cellwidth = intrinsicSize.width + separatorFullWidth
|
||||
return .init(width: min(cellwidth, collectionView.frame.width), height: intrinsicSize.height)
|
||||
}
|
||||
|
||||
public func collectionView(_ collectionView: UICollectionView, buttonBaseAtIndexPath indexPath: IndexPath) -> ButtonBase {
|
||||
breadcrumbs[indexPath.row]
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,31 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@ -6,21 +6,18 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
struct ButtonGroupConstants {
|
||||
static let defaultSpace = 12.0
|
||||
|
||||
enum ButtonSpacingAxis {
|
||||
case horizontal, vertical
|
||||
}
|
||||
|
||||
/// This will determine the spacing that will go between 2 ButtonBases either horizontally or vertically
|
||||
/// - Parameters:
|
||||
/// - axis: horizontal/vertical
|
||||
/// - primary: first ButtonBase
|
||||
/// - neighboring: next ButtonBase based off of axis
|
||||
/// - Returns: float value
|
||||
static func getSpacing(for axis: ButtonSpacingAxis, with primary: ButtonBase, neighboring: ButtonBase) -> CGFloat {
|
||||
static func getSpacing(for axis: NSLayoutConstraint.Axis, with primary: ButtonBase, neighboring: ButtonBase) -> CGFloat {
|
||||
|
||||
//large button
|
||||
if let button = primary as? Button, button.size == .large {
|
||||
|
||||
@ -146,6 +146,8 @@ class ButtonLayoutAttributes: UICollectionViewLayoutAttributes{
|
||||
class ButtonGroupPositionLayout: UICollectionViewLayout {
|
||||
|
||||
weak var delegate: ButtongGroupPositionLayoutDelegate?
|
||||
var verticalSpacer: ((ButtonCollectionViewRow, ButtonCollectionViewRow?) -> CGFloat)?
|
||||
var axisSpacer: ((NSLayoutConstraint.Axis, ButtonBase, ButtonBase) -> CGFloat)?
|
||||
|
||||
// Total height of the content. Will be used to configure the scrollview content
|
||||
var layoutHeight: CGFloat = 0.0
|
||||
@ -154,7 +156,7 @@ class ButtonGroupPositionLayout: UICollectionViewLayout {
|
||||
var buttonPercentage: CGFloat?
|
||||
|
||||
private var itemCache: [ButtonLayoutAttributes] = []
|
||||
|
||||
|
||||
override func prepare() {
|
||||
super.prepare()
|
||||
|
||||
@ -226,7 +228,7 @@ class ButtonGroupPositionLayout: UICollectionViewLayout {
|
||||
let neighbor = delegate.collectionView(collectionView, buttonBaseAtIndexPath: IndexPath(item: nextItem, section: section))
|
||||
|
||||
// get the spacing to go between the current and next button
|
||||
itemSpacing = ButtonGroupConstants.getSpacing(for: .horizontal, with: itemButtonBase, neighboring: neighbor)
|
||||
itemSpacing = getAxisSpacing(for: .horizontal, with: itemButtonBase, neighboring: neighbor)
|
||||
}
|
||||
|
||||
// create the custom layout attribute
|
||||
@ -255,7 +257,7 @@ class ButtonGroupPositionLayout: UICollectionViewLayout {
|
||||
|
||||
if item > 0 {
|
||||
let prevRow = rows[item - 1]
|
||||
rowSpacing = ButtonGroupConstants.getVerticalSpacing(for: prevRow, neighboringRow: row)
|
||||
rowSpacing = getVerticalSpacing(for: prevRow, neighboringRow: row)
|
||||
row.rowY = layoutHeight + rowSpacing
|
||||
layoutHeight += rowSpacing
|
||||
}
|
||||
@ -300,5 +302,19 @@ class ButtonGroupPositionLayout: UICollectionViewLayout {
|
||||
}
|
||||
return collectionView.bounds.width
|
||||
}
|
||||
|
||||
private func getAxisSpacing(for axis: NSLayoutConstraint.Axis, with primary: ButtonBase, neighboring: ButtonBase) -> CGFloat {
|
||||
guard let axisSpacer else {
|
||||
return ButtonGroupConstants.getSpacing(for: axis, with: primary, neighboring: neighboring)
|
||||
}
|
||||
return axisSpacer(axis, primary, neighboring)
|
||||
}
|
||||
|
||||
private func getVerticalSpacing(for row: ButtonCollectionViewRow, neighboringRow: ButtonCollectionViewRow?) -> CGFloat {
|
||||
guard let verticalSpacer else {
|
||||
return ButtonGroupConstants.getVerticalSpacing(for: row, neighboringRow: neighboringRow)
|
||||
}
|
||||
return verticalSpacer(row, neighboringRow)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ import Combine
|
||||
|
||||
/// Base Class used to build out a Input controls.
|
||||
@objc(VDSEntryField)
|
||||
open class EntryFieldBase: Control, Changeable, FormFieldable {
|
||||
open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializers
|
||||
@ -153,8 +153,11 @@ open class EntryFieldBase: Control, Changeable, FormFieldable {
|
||||
/// Whether not to show the error.
|
||||
open var showError: Bool = false { didSet { setNeedsUpdate() } }
|
||||
|
||||
/// FormFieldValidator
|
||||
internal var validator: (any FormFieldValidatorable)?
|
||||
|
||||
/// 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.
|
||||
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 {
|
||||
updateContainerView()
|
||||
updateErrorLabel()
|
||||
@ -200,19 +203,18 @@ open class EntryFieldBase: Control, Changeable, FormFieldable {
|
||||
open var inputId: String? { didSet { setNeedsUpdate() } }
|
||||
|
||||
/// The text of this textField.
|
||||
private var _value: AnyHashable?
|
||||
open var value: AnyHashable? {
|
||||
private var _value: String?
|
||||
open var value: String? {
|
||||
get { _value }
|
||||
set {
|
||||
if let newValue, newValue != _value {
|
||||
_value = newValue
|
||||
text = newValue as? String
|
||||
text = newValue
|
||||
}
|
||||
setNeedsUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
open var defaultValue: AnyHashable? { didSet { setNeedsUpdate() } }
|
||||
|
||||
open var required: Bool = false { didSet { setNeedsUpdate() } }
|
||||
@ -324,6 +326,8 @@ open class EntryFieldBase: Control, Changeable, FormFieldable {
|
||||
updateHelperLabel()
|
||||
|
||||
backgroundColor = surface.color
|
||||
validator?.validate()
|
||||
internalErrorText = validator?.errorMessage
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -89,7 +89,7 @@ open class InputField: EntryFieldBase, UITextFieldDelegate {
|
||||
setNeedsUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var _showError: Bool = false
|
||||
/// Whether not to show the error.
|
||||
open override var showError: Bool {
|
||||
|
||||
@ -107,17 +107,19 @@ open class TextArea: EntryFieldBase {
|
||||
}
|
||||
|
||||
/// The text of this textView
|
||||
private var _text: String?
|
||||
open override var text: String? {
|
||||
get { textView.text }
|
||||
set {
|
||||
if let newValue, newValue != text {
|
||||
if let newValue, newValue != _text {
|
||||
_text = newValue
|
||||
textView.text = newValue
|
||||
value = newValue
|
||||
}
|
||||
setNeedsUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// UITextView shown in the TextArea.
|
||||
open var textView = TextView().with {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
@ -125,6 +127,8 @@ open class TextArea: EntryFieldBase {
|
||||
$0.isScrollEnabled = false
|
||||
}
|
||||
|
||||
open override var maxLength: Int? { willSet { countRule.maxLength = newValue }}
|
||||
|
||||
/// Color configuration for error icon.
|
||||
internal var iconColorConfiguration = ControlColorConfiguration().with {
|
||||
$0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .normal)
|
||||
@ -147,6 +151,7 @@ open class TextArea: EntryFieldBase {
|
||||
open override func setup() {
|
||||
super.setup()
|
||||
accessibilityLabel = "TextArea"
|
||||
validator = FormFieldValidator<TextArea>(field: self, rules: [.init(countRule)])
|
||||
|
||||
containerStackView.pinToSuperView(.uniform(VDSFormControls.spaceInset))
|
||||
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.
|
||||
open override func updateView() {
|
||||
super.updateView()
|
||||
|
||||
textView.isEditable = isEnabled
|
||||
textView.isEnabled = isEnabled
|
||||
textView.surface = surface
|
||||
@ -202,13 +206,8 @@ open class TextArea: EntryFieldBase {
|
||||
widthConstraint?.isActive = false
|
||||
minWidthConstraint?.isActive = true
|
||||
}
|
||||
|
||||
let characterError = getCharacterCounterText()
|
||||
if let maxLength, maxLength > 0 {
|
||||
characterCounterLabel.text = characterError
|
||||
} else {
|
||||
characterCounterLabel.text = ""
|
||||
}
|
||||
|
||||
characterCounterLabel.text = getCharacterCounterText()
|
||||
|
||||
icon.size = .medium
|
||||
icon.color = iconColorConfiguration.getColor(self)
|
||||
@ -247,17 +246,11 @@ open class TextArea: EntryFieldBase {
|
||||
let countStr = (count > maxLength ?? 0) ? ("-" + "\(count-(maxLength ?? 0))") : "\(count)"
|
||||
if let maxLength, maxLength > 0 {
|
||||
if count > maxLength {
|
||||
hasInternalError = true
|
||||
internalErrorText = "You have exceeded the character limit."
|
||||
return countStr
|
||||
} else {
|
||||
hasInternalError = false
|
||||
internalErrorText = nil
|
||||
return ("\(countStr)" + "/" + "\(maxLength)")
|
||||
}
|
||||
} else {
|
||||
hasInternalError = false
|
||||
internalErrorText = nil
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@ -277,6 +270,21 @@ open class TextArea: EntryFieldBase {
|
||||
|
||||
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 {
|
||||
|
||||
@ -11,7 +11,39 @@ import VDSFormControlsTokens
|
||||
import UIKit
|
||||
|
||||
@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
|
||||
@ -53,33 +85,6 @@ open class TileContainer: Control {
|
||||
case gradient(String, String)
|
||||
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.
|
||||
public enum AspectRatio: String, CaseIterable {
|
||||
@ -130,7 +135,7 @@ open class TileContainer: Control {
|
||||
open var backgroundEffect: BackgroundEffect = .none { didSet { setNeedsUpdate() } }
|
||||
|
||||
/// 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.
|
||||
open var imageFallbackColor: Surface = .light { didSet { setNeedsUpdate() } }
|
||||
@ -235,7 +240,12 @@ open class TileContainer: Control {
|
||||
|
||||
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.isHidden = true
|
||||
|
||||
@ -252,6 +262,7 @@ open class TileContainer: Control {
|
||||
layer.cornerRadius = cornerRadius
|
||||
backgroundImageView.layer.cornerRadius = cornerRadius
|
||||
highlightView.layer.cornerRadius = cornerRadius
|
||||
clipsToBounds = true
|
||||
}
|
||||
|
||||
/// Resets to default settings.
|
||||
@ -259,7 +270,6 @@ open class TileContainer: Control {
|
||||
super.reset()
|
||||
shouldUpdateView = false
|
||||
color = .white
|
||||
padding = .padding4X
|
||||
aspectRatio = .ratio1x1
|
||||
imageFallbackColor = .light
|
||||
width = nil
|
||||
@ -323,7 +333,14 @@ open class TileContainer: Control {
|
||||
}
|
||||
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
|
||||
//--------------------------------------------------
|
||||
@ -401,11 +418,11 @@ open class TileContainer: Control {
|
||||
|
||||
}
|
||||
|
||||
extension TileContainer {
|
||||
extension TileContainerBase {
|
||||
|
||||
final class BackgroundColorConfiguration: ObjectColorable {
|
||||
|
||||
typealias ObjectType = TileContainer
|
||||
typealias ObjectType = TileContainerBase
|
||||
|
||||
let primaryColorConfig = SurfaceColorConfiguration(VDSColor.backgroundPrimaryLight, VDSColor.backgroundPrimaryDark)
|
||||
let secondaryColorConfig = SurfaceColorConfiguration(VDSColor.backgroundSecondaryLight, VDSColor.backgroundSecondaryDark)
|
||||
@ -415,7 +432,7 @@ extension TileContainer {
|
||||
|
||||
required init() { }
|
||||
|
||||
func getColor(_ object: TileContainer) -> UIColor {
|
||||
func getColor(_ object: ObjectType) -> UIColor {
|
||||
switch object.color {
|
||||
case .primary:
|
||||
primaryColorConfig.getColor(object.surface)
|
||||
|
||||
@ -16,7 +16,38 @@ import Combine
|
||||
/// while it can include an arrow CTA, it does not require one in order to
|
||||
/// function.
|
||||
@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
|
||||
@ -39,6 +70,7 @@ open class Tilelet: TileContainer {
|
||||
/// Enum to represent the Vertical Layout of the Text.
|
||||
public enum TextPosition: String, CaseIterable {
|
||||
case top
|
||||
case middle
|
||||
case bottom
|
||||
}
|
||||
|
||||
@ -72,7 +104,7 @@ open class Tilelet: TileContainer {
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Public Properties
|
||||
//--------------------------------------------------
|
||||
//--------------------------------------------------
|
||||
public override var onClickSubscriber: AnyCancellable? {
|
||||
didSet {
|
||||
isAccessibilityElement = onClickSubscriber != nil
|
||||
@ -82,6 +114,27 @@ open class Tilelet: TileContainer {
|
||||
/// Title lockup positioned in the contentView.
|
||||
open var titleLockup = TitleLockup().with {
|
||||
$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,
|
||||
titleStandardStyles: [.titleSmall],
|
||||
spacingConfigurations: [
|
||||
@ -103,6 +156,27 @@ open class Tilelet: TileContainer {
|
||||
topSpacing: 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,
|
||||
titleStandardStyles: [.titleSmall, .titleMedium],
|
||||
spacingConfigurations: [
|
||||
@ -176,6 +250,9 @@ open class Tilelet: TileContainer {
|
||||
/// If set, this is used to render the subTitleLabel of the TitleLockup.
|
||||
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
|
||||
private var _descriptiveIconModel: DescriptiveIcon?
|
||||
|
||||
@ -206,6 +283,17 @@ open class Tilelet: TileContainer {
|
||||
//--------------------------------------------------
|
||||
internal var titleLockupWidthConstraint: 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
|
||||
@ -230,12 +318,17 @@ open class Tilelet: TileContainer {
|
||||
|
||||
titleLockupContainerView.addSubview(titleLockup)
|
||||
titleLockup
|
||||
.pinTop()
|
||||
.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?.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(directionalIcon)
|
||||
|
||||
@ -249,6 +342,47 @@ open class Tilelet: TileContainer {
|
||||
.pinTop()
|
||||
.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.
|
||||
@ -261,7 +395,7 @@ open class Tilelet: TileContainer {
|
||||
titleModel = nil
|
||||
subTitleModel = nil
|
||||
descriptiveIconModel = nil
|
||||
directionalIconModel = nil
|
||||
directionalIconModel = nil
|
||||
shouldUpdateView = true
|
||||
setNeedsUpdate()
|
||||
}
|
||||
@ -273,7 +407,11 @@ open class Tilelet: TileContainer {
|
||||
updateBadge()
|
||||
updateTitleLockup()
|
||||
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()
|
||||
}
|
||||
|
||||
@ -294,6 +432,7 @@ open class Tilelet: TileContainer {
|
||||
badge.numberOfLines = badgeModel.numberOfLines
|
||||
badge.surface = badgeModel.surface
|
||||
badge.maxWidth = badgeModel.maxWidth
|
||||
badgeLabelHeightGreaterThanConstraint?.constant = badge.label.minimumLineHeight
|
||||
if badgeContainerView.superview == nil {
|
||||
stackView.insertArrangedSubview(badgeContainerView, at: 0)
|
||||
setNeedsLayout()
|
||||
@ -306,7 +445,11 @@ open class Tilelet: TileContainer {
|
||||
private func updateTitleLockup() {
|
||||
|
||||
var showTitleLockup = false
|
||||
|
||||
|
||||
if let eyebrowModel, !eyebrowModel.text.isEmpty {
|
||||
showTitleLockup = true
|
||||
}
|
||||
|
||||
if let titleModel, !titleModel.text.isEmpty {
|
||||
showTitleLockup = true
|
||||
}
|
||||
@ -350,6 +493,7 @@ open class Tilelet: TileContainer {
|
||||
}
|
||||
|
||||
//set models
|
||||
titleLockup.eyebrowModel = eyebrowModel?.toTitleLockupEyebrowModel()
|
||||
titleLockup.titleModel = titleModel?.toTitleLockupTitleModel()
|
||||
titleLockup.subTitleModel = subTitleModel?.toTitleLockupSubTitleModel()
|
||||
|
||||
@ -360,6 +504,9 @@ open class Tilelet: TileContainer {
|
||||
} else {
|
||||
removeFromSuperview(titleLockupContainerView)
|
||||
}
|
||||
titleLockupEyebrowLabelHeightGreaterThanConstraint?.constant = titleLockup.eyebrowLabel.minimumLineHeight
|
||||
titleLockupTitleLabelHeightGreaterThanConstraint?.constant = titleLockup.titleLabel.minimumLineHeight
|
||||
titleLockupSubTitleLabelHeightGreaterThanConstraint?.constant = titleLockup.subTitleLabel.minimumLineHeight
|
||||
}
|
||||
|
||||
private func updateIcons() {
|
||||
@ -392,7 +539,7 @@ open class Tilelet: TileContainer {
|
||||
view = titleLockupContainerView
|
||||
}
|
||||
if let view {
|
||||
stackView.setCustomSpacing(padding.tiletSpacing, after: view)
|
||||
stackView.setCustomSpacing(padding.titleLockupBottomSpacing, after: view)
|
||||
}
|
||||
if iconContainerView.superview == nil {
|
||||
stackView.addArrangedSubview(iconContainerView)
|
||||
@ -402,21 +549,33 @@ open class Tilelet: TileContainer {
|
||||
removeFromSuperview(iconContainerView)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TileContainer.Padding {
|
||||
fileprivate var tiletSpacing: CGFloat {
|
||||
switch self {
|
||||
case .padding2X:
|
||||
return 16
|
||||
case .padding4X:
|
||||
return 24
|
||||
case .padding6X:
|
||||
return 32
|
||||
case .padding8X:
|
||||
return 48
|
||||
default:
|
||||
return 16
|
||||
|
||||
private func updateTextPositionAlignment() {
|
||||
switch textPostion {
|
||||
case .top:
|
||||
titleLockupTopConstraint?.activate()
|
||||
titleLockupTopGreaterThanConstraint?.deactivate()
|
||||
titleLockupBottomConstraint?.deactivate()
|
||||
titleLockupBottomGreaterThanConstraint?.activate()
|
||||
titleLockupCenterYConstraint?.deactivate()
|
||||
case .middle:
|
||||
titleLockupTopConstraint?.deactivate()
|
||||
titleLockupTopGreaterThanConstraint?.activate()
|
||||
titleLockupBottomConstraint?.deactivate()
|
||||
titleLockupBottomGreaterThanConstraint?.activate()
|
||||
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 }
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
extension Tilelet {
|
||||
|
||||
@ -26,12 +27,16 @@ extension Tilelet {
|
||||
/// Max width that will be used for the badge.
|
||||
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.fillColor = fillColor
|
||||
self.surface = surface
|
||||
self.numberOfLines = numberOfLines
|
||||
self.maxWidth = maxWidth
|
||||
self.lineBreakMode = lineBreakMode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
extension Tilelet {
|
||||
/// Model that represents the options available for the sub title label.
|
||||
@ -18,7 +19,8 @@ extension Tilelet {
|
||||
case bodyLarge
|
||||
case bodyMedium
|
||||
case bodySmall
|
||||
|
||||
case titleSmall
|
||||
case titleMedium
|
||||
public var defaultValue: TitleLockup.OtherStandardStyle { .bodySmall }
|
||||
}
|
||||
//--------------------------------------------------
|
||||
@ -36,17 +38,22 @@ extension Tilelet {
|
||||
/// Text color that will be used for the subTitle label.
|
||||
public var textColor: Use = .primary
|
||||
|
||||
/// LineBreakMode used in Badge label.
|
||||
public var lineBreakMode: NSLineBreakMode
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializers
|
||||
//--------------------------------------------------
|
||||
public init(text: String,
|
||||
otherStandardStyle: OtherStandardStyle = .bodySmall,
|
||||
textColor: Use = .primary,
|
||||
textAttributes: [any LabelAttributeModel]? = nil) {
|
||||
textAttributes: [any LabelAttributeModel]? = nil,
|
||||
lineBreakMode: NSLineBreakMode = .byTruncatingTail) {
|
||||
self.text = text
|
||||
self.otherStandardStyle = otherStandardStyle
|
||||
self.textAttributes = textAttributes
|
||||
self.textColor = textColor
|
||||
self.lineBreakMode = lineBreakMode
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
@ -57,7 +64,7 @@ extension Tilelet {
|
||||
TitleLockup.SubTitleModel(text: text,
|
||||
otherStandardStyle: otherStandardStyle.value,
|
||||
textColor: textColor,
|
||||
textAttributes: textAttributes)
|
||||
textAttributes: textAttributes, lineBreakMode: lineBreakMode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
extension Tilelet {
|
||||
/// Model that represents the options available for the title label.
|
||||
@ -19,7 +20,10 @@ extension Tilelet {
|
||||
case titleLarge
|
||||
case titleMedium
|
||||
case titleSmall
|
||||
|
||||
case bodyLarge
|
||||
case bodyMedium
|
||||
case bodySmall
|
||||
|
||||
public var defaultValue: TitleLockup.TitleStandardStyle { .titleSmall }
|
||||
}
|
||||
//--------------------------------------------------
|
||||
@ -28,21 +32,30 @@ extension Tilelet {
|
||||
/// Text that will be used for the title label.
|
||||
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.
|
||||
public var textAttributes: [any LabelAttributeModel]?
|
||||
|
||||
/// Text style that will be used for the title label.
|
||||
public var standardStyle: StandardStyle = .titleSmall
|
||||
|
||||
/// LineBreakMode used in Badge label.
|
||||
public var lineBreakMode: NSLineBreakMode
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializers
|
||||
//--------------------------------------------------
|
||||
public init(text: String,
|
||||
textAttributes: [any LabelAttributeModel]? = nil,
|
||||
standardStyle: StandardStyle = .titleSmall) {
|
||||
isBold: Bool = true,
|
||||
standardStyle: StandardStyle = .titleSmall,
|
||||
lineBreakMode: NSLineBreakMode = .byTruncatingTail) {
|
||||
self.text = text
|
||||
self.textAttributes = textAttributes
|
||||
self.standardStyle = standardStyle
|
||||
self.isBold = isBold
|
||||
self.lineBreakMode = lineBreakMode
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
@ -52,7 +65,7 @@ extension Tilelet {
|
||||
public func toTitleLockupTitleModel() -> TitleLockup.TitleModel {
|
||||
TitleLockup.TitleModel(text: text,
|
||||
textAttributes: textAttributes,
|
||||
standardStyle: standardStyle.value)
|
||||
isBold: isBold, standardStyle: standardStyle.value, lineBreakMode: lineBreakMode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
55
VDS/Components/Tilelet/TiletEyebrowModel.swift
Normal file
55
VDS/Components/Tilelet/TiletEyebrowModel.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
110
VDS/Components/Tilelet/TitleletChangeLog.txt
Normal file
110
VDS/Components/Tilelet/TitleletChangeLog.txt
Normal 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 Lockup’s 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
|
||||
@ -353,7 +353,7 @@ open class TitleLockup: View {
|
||||
titleLabel.attributes = titleModel.textAttributes
|
||||
titleLabel.numberOfLines = titleModel.numberOfLines
|
||||
titleLabel.surface = surface
|
||||
|
||||
titleLabel.lineBreakMode = titleModel.lineBreakMode
|
||||
addSubview(titleLabel)
|
||||
titleLabel
|
||||
.pinTop(previousView?.bottomAnchor ?? self.topAnchor, eyebrowLabel == previousView ? topSpacing : 0)
|
||||
@ -372,7 +372,7 @@ open class TitleLockup: View {
|
||||
subTitleLabel.attributes = subTitleModel.textAttributes
|
||||
subTitleLabel.numberOfLines = subTitleModel.numberOfLines
|
||||
subTitleLabel.surface = surface
|
||||
|
||||
subTitleLabel.lineBreakMode = subTitleModel.lineBreakMode
|
||||
addSubview(subTitleLabel)
|
||||
subTitleLabel
|
||||
.pinTop(previousView?.bottomAnchor ?? self.topAnchor, (eyebrowLabel == previousView || titleLabel == previousView) ? bottomSpacing : 0)
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
extension TitleLockup {
|
||||
/// 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.
|
||||
public var numberOfLines: Int
|
||||
|
||||
/// LineBreakMode used in subtitle label.
|
||||
public var lineBreakMode: NSLineBreakMode
|
||||
|
||||
public init(text: String,
|
||||
otherStandardStyle: OtherStandardStyle = .bodyLarge,
|
||||
textColor: Use = .primary,
|
||||
textAttributes: [any LabelAttributeModel]? = nil,
|
||||
numberOfLines: Int = 0) {
|
||||
numberOfLines: Int = 0,
|
||||
lineBreakMode: NSLineBreakMode = .byWordWrapping) {
|
||||
self.text = text
|
||||
self.otherStandardStyle = otherStandardStyle
|
||||
self.textColor = textColor
|
||||
self.textAttributes = textAttributes
|
||||
self.numberOfLines = numberOfLines
|
||||
self.lineBreakMode = lineBreakMode
|
||||
}
|
||||
|
||||
/// TextStyle used to render the text.
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
extension TitleLockup {
|
||||
/// 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.
|
||||
public var numberOfLines: Int
|
||||
|
||||
/// LineBreakMode used in subtitle label.
|
||||
public var lineBreakMode: NSLineBreakMode
|
||||
|
||||
public init(text: String,
|
||||
textAttributes: [any LabelAttributeModel]? = nil,
|
||||
isBold: Bool = true,
|
||||
standardStyle: TitleStandardStyle = .featureXSmall,
|
||||
numberOfLines: Int = 0) {
|
||||
numberOfLines: Int = 0,
|
||||
lineBreakMode: NSLineBreakMode = .byTruncatingTail) {
|
||||
self.text = text
|
||||
self.isBold = isBold
|
||||
self.textAttributes = textAttributes
|
||||
self.standardStyle = standardStyle
|
||||
self.numberOfLines = numberOfLines
|
||||
self.lineBreakMode = lineBreakMode
|
||||
}
|
||||
|
||||
/// TextStyle used to render the text.
|
||||
|
||||
@ -10,7 +10,8 @@ import Foundation
|
||||
/// Represents constants used that deal with layout.
|
||||
public struct VDSLayout {
|
||||
/// Enum used to describe the spacing constants.
|
||||
public enum Spacing: String, CaseIterable {
|
||||
public enum Spacing: String, CaseIterable, Valuing {
|
||||
|
||||
case space1X
|
||||
case space2X
|
||||
case space3X
|
||||
|
||||
12
VDS/Protocols/DefaultValuing.swift
Normal file
12
VDS/Protocols/DefaultValuing.swift
Normal 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 }
|
||||
}
|
||||
@ -50,7 +50,6 @@ extension ViewProtocol where Self: UIView {
|
||||
shadowLayer.shadowPath = shadowPath.cgPath
|
||||
shadowLayer.frame = bounds
|
||||
shadowLayer.position = .init(x: bounds.midX, y: bounds.midY)
|
||||
shadowLayer.backgroundColor = backgroundColor?.cgColor
|
||||
shadowLayer.cornerRadius = layer.cornerRadius
|
||||
shadowLayer.shadowColor = config.shadowColorConfiguration.getColor(self).cgColor
|
||||
shadowLayer.shadowOpacity = Float(config.shadowOpacityConfiguration.value(for: self))
|
||||
@ -86,4 +85,8 @@ extension ViewProtocol where Self: UIView {
|
||||
func removeGradientLayer() {
|
||||
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] }
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol EnumSubset<T>: RawRepresentable, CaseIterable {
|
||||
public protocol EnumSubset<T>: RawRepresentable, CaseIterable, Valuing {
|
||||
associatedtype T:RawRepresentable
|
||||
var defaultValue: T { get }
|
||||
}
|
||||
|
||||
@ -9,10 +9,91 @@ import Foundation
|
||||
|
||||
/// Protocol used for a FormField object.
|
||||
public protocol FormFieldable {
|
||||
associatedtype ValueType = AnyHashable
|
||||
|
||||
/// Unique Id for the Form Field object within a Form.
|
||||
var inputId: String? { get set }
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
13
VDS/Protocols/Valuing.swift
Normal file
13
VDS/Protocols/Valuing.swift
Normal 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 }
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user