Merge branch 'develop' into refactor/VDSTokens
# Conflicts: # VDS/Components/Buttons/ButtonGroup/ButtonGroup.swift # VDS/Components/Icon/ButtonIcon/ButtonIcon.swift # VDS/Components/Notification/Notification.swift # VDS/Components/TileContainer/TileContainer.swift Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
This commit is contained in:
commit
b0f13de69b
@ -7,11 +7,16 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
18792A902B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18792A8F2B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift */; };
|
||||
18BDEE822B75316E00452358 /* ButtonIconChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 18BDEE812B75316E00452358 /* ButtonIconChangeLog.txt */; };
|
||||
445BA07829C07B3D0036A7C5 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 445BA07729C07B3D0036A7C5 /* Notification.swift */; };
|
||||
44604AD429CE186A00E62B51 /* NotificationButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44604AD329CE186A00E62B51 /* NotificationButtonModel.swift */; };
|
||||
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 */; };
|
||||
7115BD3C2B84C0C200E0A610 /* TileContainerChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 7115BD3B2B84C0C200E0A610 /* TileContainerChangeLog.txt */; };
|
||||
71BFA70A2B7F70E6000DCE33 /* Dropshadowable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71BFA7092B7F70E6000DCE33 /* Dropshadowable.swift */; };
|
||||
71C02B382B7BD98F00E93E66 /* NotificationChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 71C02B372B7BD98F00E93E66 /* NotificationChangeLog.txt */; };
|
||||
EA0B18022A9E236900F2D0CD /* SelectorGroupBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0B18012A9E236900F2D0CD /* SelectorGroupBase.swift */; };
|
||||
EA0B18052A9E2D2D00F2D0CD /* SelectorBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0B18032A9E2D2D00F2D0CD /* SelectorBase.swift */; };
|
||||
EA0B18062A9E2D2D00F2D0CD /* SelectorItemBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0B18042A9E2D2D00F2D0CD /* SelectorItemBase.swift */; };
|
||||
@ -164,11 +169,16 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
18792A8F2B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonIconBadgeIndicatorModel.swift; sourceTree = "<group>"; };
|
||||
18BDEE812B75316E00452358 /* ButtonIconChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = ButtonIconChangeLog.txt; sourceTree = "<group>"; };
|
||||
445BA07729C07B3D0036A7C5 /* Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = "<group>"; };
|
||||
44604AD329CE186A00E62B51 /* NotificationButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationButtonModel.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>"; };
|
||||
5FC35BE228D51405004EBEAC /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = "<group>"; };
|
||||
7115BD3B2B84C0C200E0A610 /* TileContainerChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = TileContainerChangeLog.txt; 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>"; };
|
||||
EA0B18012A9E236900F2D0CD /* SelectorGroupBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectorGroupBase.swift; sourceTree = "<group>"; };
|
||||
EA0B18032A9E2D2D00F2D0CD /* SelectorBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectorBase.swift; sourceTree = "<group>"; };
|
||||
EA0B18042A9E2D2D00F2D0CD /* SelectorItemBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectorItemBase.swift; sourceTree = "<group>"; };
|
||||
@ -337,6 +347,7 @@
|
||||
children = (
|
||||
445BA07729C07B3D0036A7C5 /* Notification.swift */,
|
||||
44604AD329CE186A00E62B51 /* NotificationButtonModel.swift */,
|
||||
71C02B372B7BD98F00E93E66 /* NotificationChangeLog.txt */,
|
||||
);
|
||||
path = Notification;
|
||||
sourceTree = "<group>";
|
||||
@ -535,6 +546,7 @@
|
||||
EA3361B7288B2AAA0071C351 /* ViewProtocol.swift */,
|
||||
EAB1D2CC28ABE76000DAE764 /* Withable.swift */,
|
||||
5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */,
|
||||
71BFA7092B7F70E6000DCE33 /* Dropshadowable.swift */,
|
||||
);
|
||||
path = Protocols;
|
||||
sourceTree = "<group>";
|
||||
@ -623,6 +635,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
EA5E304B294CBDD00082B959 /* TileContainer.swift */,
|
||||
7115BD3B2B84C0C200E0A610 /* TileContainerChangeLog.txt */,
|
||||
);
|
||||
path = TileContainer;
|
||||
sourceTree = "<group>";
|
||||
@ -657,6 +670,8 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
EA81410A2A0E8E3C004F60D2 /* ButtonIcon.swift */,
|
||||
18792A8F2B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift */,
|
||||
18BDEE812B75316E00452358 /* ButtonIconChangeLog.txt */,
|
||||
);
|
||||
path = ButtonIcon;
|
||||
sourceTree = "<group>";
|
||||
@ -913,7 +928,9 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
EAEEECA42B1F934600531FC2 /* IconChangeLog.txt in Resources */,
|
||||
7115BD3C2B84C0C200E0A610 /* TileContainerChangeLog.txt in Resources */,
|
||||
EA3362042891E14D0071C351 /* VerizonNHGeTX-Bold.otf in Resources */,
|
||||
71C02B382B7BD98F00E93E66 /* NotificationChangeLog.txt in Resources */,
|
||||
EAEEECA72B1F952000531FC2 /* TabsChangeLog.txt in Resources */,
|
||||
EAEEEC962B1F893B00531FC2 /* ButtonChangeLog.txt in Resources */,
|
||||
EA5F86CC2A1D28B500BC83E4 /* ReleaseNotes.txt in Resources */,
|
||||
@ -924,6 +941,7 @@
|
||||
EAEEECAF2B1FC2BA00531FC2 /* ToggleViewChangeLog.txt in Resources */,
|
||||
EAEEEC922B1F807300531FC2 /* BadgeChangeLog.txt in Resources */,
|
||||
EAEEEC9E2B1F8F7700531FC2 /* ButtonGroupChangeLog.txt in Resources */,
|
||||
18BDEE822B75316E00452358 /* ButtonIconChangeLog.txt in Resources */,
|
||||
EA3362062891E14D0071C351 /* VerizonNHGeTX-Regular.otf in Resources */,
|
||||
EA3362052891E14D0071C351 /* VerizonNHGeDS-Bold.otf in Resources */,
|
||||
EAEEECA02B1F908200531FC2 /* BadgeIndicatorChangeLog.txt in Resources */,
|
||||
@ -966,6 +984,7 @@
|
||||
EAB2376229E9880400AABE9A /* TrailingTooltipLabel.swift in Sources */,
|
||||
EAB2376A29E9E59100AABE9A /* TooltipLaunchable.swift in Sources */,
|
||||
EAB2375D29E8789100AABE9A /* Tooltip.swift in Sources */,
|
||||
71BFA70A2B7F70E6000DCE33 /* Dropshadowable.swift in Sources */,
|
||||
EA0D1C452A6AD73000E5C127 /* RawRepresentable.swift in Sources */,
|
||||
EA985C23296E033A00F2FF2E /* TextArea.swift in Sources */,
|
||||
EAF7F0B3289B1ADC00B287F5 /* ActionLabelAttribute.swift in Sources */,
|
||||
@ -1045,6 +1064,7 @@
|
||||
EA5E30532950DDA60082B959 /* TitleLockup.swift in Sources */,
|
||||
EAD062B02A3B873E0015965D /* BadgeIndicator.swift in Sources */,
|
||||
EAA5EEB528ECBFB4003B3210 /* ImageLabelAttribute.swift in Sources */,
|
||||
18792A902B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift in Sources */,
|
||||
EA0B18062A9E2D2D00F2D0CD /* SelectorItemBase.swift in Sources */,
|
||||
EAB5FF0129424ACB00998C17 /* UIControl.swift in Sources */,
|
||||
EA985BF5296C60C000F2FF2E /* Icon.swift in Sources */,
|
||||
@ -1217,7 +1237,7 @@
|
||||
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 51;
|
||||
CURRENT_PROJECT_VERSION = 54;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@ -1254,7 +1274,7 @@
|
||||
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 51;
|
||||
CURRENT_PROJECT_VERSION = 54;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Combine
|
||||
|
||||
/// UICollectionView subclassed to deal with Changing the size of itself based on its children and layout and changes of its contentSize.
|
||||
@objc(VDSSelfSizingCollectionView)
|
||||
@ -34,10 +35,13 @@ public final class SelfSizingCollectionView: UICollectionView {
|
||||
// MARK: - Private Properties
|
||||
//--------------------------------------------------
|
||||
private var contentSizeObservation: NSKeyValueObservation?
|
||||
private var collectionViewHeight: NSLayoutConstraint?
|
||||
private var anyCancellable: AnyCancellable?
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Overrides
|
||||
//--------------------------------------------------
|
||||
|
||||
/// The natural size for the receiving view, considering only properties of the view itself.
|
||||
public override var intrinsicContentSize: CGSize {
|
||||
let contentSize = self.contentSize
|
||||
@ -60,11 +64,19 @@ public final class SelfSizingCollectionView: UICollectionView {
|
||||
// MARK: - Private Methods
|
||||
//--------------------------------------------------
|
||||
private func setupContentSizeObservation() {
|
||||
//ensure autoLayout uses intrinsic height
|
||||
setContentHuggingPriority(.required, for: .vertical)
|
||||
setContentCompressionResistancePriority(.required, for: .vertical)
|
||||
collectionViewHeight = heightAnchor.constraint(equalToConstant: 0).activate()
|
||||
|
||||
// Observing the value of contentSize seems to be the only reliable way to get the contentSize after the collection view lays out its subviews.
|
||||
self.contentSizeObservation = self.observe(\.contentSize, options: [.old, .new]) { [weak self] _, change in
|
||||
// If we don't specify `options: [.old, .new]`, the change.oldValue and .newValue will always be `nil`.
|
||||
if change.newValue != change.oldValue {
|
||||
self?.invalidateIntrinsicContentSize()
|
||||
if let height = change.newValue?.height {
|
||||
self?.collectionViewHeight?.constant = height
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,6 +102,7 @@ open class ButtonGroup: View {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Private Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
fileprivate lazy var positionLayout = ButtonGroupPositionLayout().with {
|
||||
$0.position = .center
|
||||
$0.delegate = self
|
||||
@ -162,6 +163,17 @@ open class ButtonGroup: View {
|
||||
collectionView.reloadData()
|
||||
}
|
||||
|
||||
open override func reset() {
|
||||
super.reset()
|
||||
shouldUpdateView = false
|
||||
rowQuantityPhone = 0
|
||||
rowQuantityTablet = 0
|
||||
alignment = .center
|
||||
buttons.forEach { $0.reset() }
|
||||
shouldUpdateView = true
|
||||
setNeedsUpdate()
|
||||
}
|
||||
|
||||
open override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
// Accounts for any collection size changes
|
||||
@ -181,11 +193,11 @@ extension ButtonGroup: UICollectionViewDataSource, UICollectionViewDelegate {
|
||||
return buttons.count
|
||||
}
|
||||
|
||||
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
let button = buttons[indexPath.row]
|
||||
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionViewCell", for: indexPath) as? ButtonGroupCollectionViewCell else { return UICollectionViewCell() }
|
||||
cell.subviews.forEach { $0.removeFromSuperview() }
|
||||
cell.addSubview(button)
|
||||
cell.contentView.subviews.forEach { $0.removeFromSuperview() }
|
||||
cell.contentView.addSubview(button)
|
||||
button.pinToSuperView()
|
||||
if hasDebugBorder {
|
||||
cell.addDebugBorder()
|
||||
|
||||
@ -8,7 +8,6 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
|
||||
class ButtonCollectionViewRow {
|
||||
var attributes = [ButtonLayoutAttributes]()
|
||||
|
||||
@ -193,6 +192,9 @@ class ButtonGroupPositionLayout: UICollectionViewLayout {
|
||||
// get the rect size of the button
|
||||
itemSize = delegate.collectionView(collectionView, sizeForItemAtIndexPath: indexPath)
|
||||
|
||||
// ensure the width is not greater than the collectionViewWidth
|
||||
itemSize.width = min(itemSize.width, collectionViewWidth)
|
||||
|
||||
// determine if the current button will fit in the row
|
||||
let rowItemCount = rows.last?.attributes.count ?? 0
|
||||
|
||||
@ -229,7 +231,7 @@ class ButtonGroupPositionLayout: UICollectionViewLayout {
|
||||
|
||||
// create the custom layout attribute
|
||||
let attributes = ButtonLayoutAttributes(spacing: itemSpacing, button: itemButtonBase, forCellWith: indexPath)
|
||||
attributes.frame = CGRect(x: 0, y: 0, width: itemSize.width, height: itemSize.height)
|
||||
attributes.frame = CGRect(x: 0, y: 0, width: min(itemSize.width, collectionViewWidth), height: itemSize.height)
|
||||
|
||||
// add it to the array
|
||||
rows.last?.add(attribute: attributes)
|
||||
|
||||
@ -79,6 +79,8 @@ open class TextLinkCaret: ButtonBase {
|
||||
open override func setup() {
|
||||
super.setup()
|
||||
accessibilityTraits = .link
|
||||
titleLabel?.numberOfLines = 0
|
||||
titleLabel?.lineBreakMode = .byWordWrapping
|
||||
}
|
||||
|
||||
/// Used to make changes to the View based off a change events or from local properties.
|
||||
|
||||
@ -89,6 +89,11 @@ open class CheckboxGroup: SelectorGroupBase<CheckboxItem>, SelectorGroupMultiSel
|
||||
}
|
||||
valueChanged()
|
||||
}
|
||||
|
||||
open override func reset() {
|
||||
super.reset()
|
||||
showError = false
|
||||
}
|
||||
}
|
||||
|
||||
extension CheckboxGroup {
|
||||
|
||||
@ -8,12 +8,13 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import VDSTokens
|
||||
import Combine
|
||||
|
||||
/// A button icon is an interactive element that visually communicates the action it triggers via an icon.
|
||||
/// It usually represents a supplementary or utilitarian action. A button icon can stand alone, but often
|
||||
/// exists in a group when there are several actions that can be performed.
|
||||
@objc(VDSButtonIcon)
|
||||
open class ButtonIcon: Control {
|
||||
open class ButtonIcon: Control, Changeable, FormFieldable {
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializers
|
||||
@ -67,17 +68,45 @@ open class ButtonIcon: Control {
|
||||
private var centerYConstraint: NSLayoutConstraint?
|
||||
private var layoutGuideWidthConstraint: NSLayoutConstraint?
|
||||
private var layoutGuideHeightConstraint: NSLayoutConstraint?
|
||||
private var badgeIndicatorLeadingConstraint: NSLayoutConstraint?
|
||||
private var badgeIndicatorTrailingConstraint: NSLayoutConstraint?
|
||||
private var badgeIndicatorCenterXConstraint: NSLayoutConstraint?
|
||||
private var badgeIndicatorCenterYConstraint: NSLayoutConstraint?
|
||||
private var currentIconName: Icon.Name? {
|
||||
if let selectedIconName {
|
||||
if let selectedIconName, isSelected {
|
||||
return selectedIconName
|
||||
} else {
|
||||
return iconName
|
||||
}
|
||||
}
|
||||
|
||||
private var badgeIndicatorOffset: CGPoint {
|
||||
switch (size, kind) {
|
||||
case (.small, .ghost):
|
||||
return .init(x: 1, y: 0)
|
||||
case (.large, .ghost):
|
||||
return .init(x: 1, y: 1)
|
||||
case (.small, .lowContrast), (.small, .highContrast):
|
||||
return .init(x: 4, y: 4)
|
||||
case (.large, .lowContrast), (.large, .highContrast):
|
||||
return .init(x: 6, y: 6)
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Public Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
///Badge Indicator object used to render for the ButtonIcon.
|
||||
open var badgeIndicator = BadgeIndicator().with {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
$0.size = .small
|
||||
$0.isHidden = true
|
||||
}
|
||||
|
||||
/// If set, this is used to render the badge indicator.
|
||||
open var badgeIndicatorModel: BadgeIndicatorModel? { didSet { setNeedsUpdate() } }
|
||||
|
||||
/// Icon object used to render out the Icon for this ButtonIcon.
|
||||
open var icon = Icon().with { $0.isUserInteractionEnabled = false }
|
||||
|
||||
@ -108,23 +137,41 @@ open class ButtonIcon: Control {
|
||||
/// If set to true, the button icon will not have a border.
|
||||
open var hideBorder: Bool = true { didSet { setNeedsUpdate() } }
|
||||
|
||||
/// If provided, the badge indicator will present.
|
||||
open var showBadgeIndicator: Bool = false { didSet { setNeedsUpdate() } }
|
||||
|
||||
/// If true, button will be rendered as selected, when it is selectable.
|
||||
open var selectable: Bool = false {
|
||||
didSet {
|
||||
//unselect
|
||||
if !selectable && isSelected {
|
||||
isSelected = false
|
||||
}
|
||||
setNeedsUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to move the icon inside the button in both x and y axis.
|
||||
open var iconOffset: CGPoint = .init(x: 0, y: 0) { didSet { setNeedsUpdate() } }
|
||||
|
||||
open var onChangeSubscriber: AnyCancellable?
|
||||
|
||||
open var inputId: String? { didSet { setNeedsUpdate() } }
|
||||
|
||||
open var value: AnyHashable? { didSet { setNeedsUpdate() } }
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Configuration
|
||||
//--------------------------------------------------
|
||||
private var iconColorConfiguration: AnyColorable {
|
||||
if selectedIconName != nil {
|
||||
return selectedIconColorConfiguration
|
||||
} else {
|
||||
if kind == .highContrast {
|
||||
return highContrastIconColorConfiguration
|
||||
} else if kind == .lowContrast {
|
||||
return (surfaceType == .colorFill) ? lowContrastIconColorConfiguration : (floating ? lowContrastIconColorConfiguration : standardIconColorConfiguration)
|
||||
} else {
|
||||
return standardIconColorConfiguration
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var currentConfiguration: any Configuration {
|
||||
switch kind {
|
||||
@ -148,6 +195,18 @@ open class ButtonIcon: Control {
|
||||
$0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .normal)
|
||||
$0.setSurfaceColors(VDSColor.interactiveActiveOnlight, VDSColor.interactiveActiveOndark, forState: .highlighted)
|
||||
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled)
|
||||
$0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .selected)
|
||||
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: [.selected, .disabled])
|
||||
}.eraseToAnyColorable()
|
||||
}()
|
||||
|
||||
private var lowContrastIconColorConfiguration: AnyColorable = {
|
||||
return ControlColorConfiguration().with {
|
||||
$0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .normal)
|
||||
$0.setSurfaceColors(VDSColor.interactiveActiveOnlight, VDSColor.interactiveActiveOndark, forState: .highlighted)
|
||||
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.paletteBlack.withAlphaComponent(0.70), forState: .disabled)
|
||||
$0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .selected)
|
||||
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.paletteBlack.withAlphaComponent(0.70), forState: [.selected, .disabled])
|
||||
}.eraseToAnyColorable()
|
||||
}()
|
||||
|
||||
@ -155,14 +214,6 @@ open class ButtonIcon: Control {
|
||||
return SurfaceColorConfiguration(VDSColor.elementsPrimaryOndark, VDSColor.elementsPrimaryOnlight).eraseToAnyColorable()
|
||||
}()
|
||||
|
||||
private var selectedIconColorConfiguration: AnyColorable = {
|
||||
return ControlColorConfiguration().with {
|
||||
$0.setSurfaceColors(VDSColor.elementsBrandhighlight, VDSColor.elementsPrimaryOndark, forState: .normal)
|
||||
$0.setSurfaceColors(VDSColor.interactiveActiveOnlight, VDSColor.interactiveActiveOndark, forState: .highlighted)
|
||||
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled)
|
||||
}.eraseToAnyColorable()
|
||||
}()
|
||||
|
||||
private struct GhostConfiguration: Configuration {
|
||||
var kind: Kind = .ghost
|
||||
var surfaceType: SurfaceType = .colorFill
|
||||
@ -181,13 +232,19 @@ open class ButtonIcon: Control {
|
||||
}()
|
||||
}
|
||||
|
||||
private struct LowContrastColorFillFloatingConfiguration: Configuration {
|
||||
private struct LowContrastColorFillFloatingConfiguration: Configuration, Dropshadowable {
|
||||
var kind: Kind = .lowContrast
|
||||
var surfaceType: SurfaceType = .colorFill
|
||||
var floating: Bool = true
|
||||
var backgroundColorConfiguration: AnyColorable = {
|
||||
SurfaceColorConfiguration(VDSColor.backgroundPrimaryLight, .clear).eraseToAnyColorable()
|
||||
SurfaceColorConfiguration(VDSColor.paletteWhite, VDSColor.paletteGray20).eraseToAnyColorable()
|
||||
}()
|
||||
var shadowColorConfiguration: AnyColorable = {
|
||||
SurfaceColorConfiguration(VDSColor.paletteBlack, VDSColor.paletteBlack).eraseToAnyColorable()
|
||||
}()
|
||||
var shadowOpacity: CGFloat = 0.16
|
||||
var shadowOffset: CGSize = .init(width: 0, height: 2)
|
||||
var shadowRadius: CGFloat = 4
|
||||
}
|
||||
|
||||
private struct LowContrastMediaConfiguration: Configuration, Borderable {
|
||||
@ -195,11 +252,11 @@ open class ButtonIcon: Control {
|
||||
var surfaceType: SurfaceType = .media
|
||||
var floating: Bool = false
|
||||
var backgroundColorConfiguration: AnyColorable = {
|
||||
SurfaceColorConfiguration(VDSColor.backgroundPrimaryLight, .clear).eraseToAnyColorable()
|
||||
SurfaceColorConfiguration(VDSColor.backgroundPrimaryLight, VDSColor.backgroundPrimaryDark).eraseToAnyColorable()
|
||||
}()
|
||||
var borderWidth: CGFloat = 1.0
|
||||
var borderColorConfiguration: AnyColorable = {
|
||||
SurfaceColorConfiguration(VDSColor.elementsLowcontrastOnlight, .clear).eraseToAnyColorable()
|
||||
SurfaceColorConfiguration(VDSColor.elementsLowcontrastOnlight, VDSColor.elementsLowcontrastOndark).eraseToAnyColorable()
|
||||
}()
|
||||
}
|
||||
|
||||
@ -208,14 +265,14 @@ open class ButtonIcon: Control {
|
||||
var surfaceType: SurfaceType = .media
|
||||
var floating: Bool = true
|
||||
var backgroundColorConfiguration: AnyColorable = {
|
||||
SurfaceColorConfiguration(VDSColor.backgroundPrimaryLight, .clear).eraseToAnyColorable()
|
||||
SurfaceColorConfiguration(VDSColor.paletteWhite, VDSColor.paletteGray20).eraseToAnyColorable()
|
||||
}()
|
||||
var shadowColorConfiguration: AnyColorable = {
|
||||
SurfaceColorConfiguration(VDSColor.paletteBlack, .clear).eraseToAnyColorable()
|
||||
SurfaceColorConfiguration(VDSColor.paletteBlack, VDSColor.paletteBlack).eraseToAnyColorable()
|
||||
}()
|
||||
var shadowOpacity: CGFloat = 0.16
|
||||
var shadowOffset: CGSize = .init(width: 0, height: 2)
|
||||
var shadowRadius: CGFloat = 2
|
||||
var shadowRadius: CGFloat = 4
|
||||
}
|
||||
|
||||
private struct HighContrastConfiguration: Configuration {
|
||||
@ -225,32 +282,48 @@ open class ButtonIcon: Control {
|
||||
var backgroundColorConfiguration: AnyColorable = {
|
||||
return ControlColorConfiguration().with {
|
||||
$0.setSurfaceColors(VDSColor.backgroundPrimaryDark, VDSColor.backgroundPrimaryLight, forState: .normal)
|
||||
$0.setSurfaceColors(VDSColor.backgroundPrimaryDark, VDSColor.backgroundPrimaryLight, forState: .selected)
|
||||
$0.setSurfaceColors(VDSColor.interactiveActiveOnlight, VDSColor.interactiveActiveOndark, forState: .highlighted)
|
||||
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled)
|
||||
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: [.selected, .disabled])
|
||||
}.eraseToAnyColorable()
|
||||
|
||||
}()
|
||||
}
|
||||
|
||||
private struct HighContrastFloatingConfiguration: Configuration {
|
||||
private struct HighContrastFloatingConfiguration: Configuration, Dropshadowable {
|
||||
var kind: Kind = .highContrast
|
||||
var surfaceType: SurfaceType = .colorFill
|
||||
var floating: Bool = true
|
||||
var backgroundColorConfiguration: AnyColorable = {
|
||||
return ControlColorConfiguration().with {
|
||||
$0.setSurfaceColors(VDSColor.backgroundPrimaryLight, VDSColor.backgroundPrimaryLight, forState: .normal)
|
||||
$0.setSurfaceColors(VDSColor.paletteGray20, VDSColor.paletteWhite, forState: .normal)
|
||||
$0.setSurfaceColors(VDSColor.paletteGray20, VDSColor.paletteWhite, forState: .selected)
|
||||
$0.setSurfaceColors(VDSColor.interactiveActiveOnlight, VDSColor.interactiveActiveOndark, forState: .highlighted)
|
||||
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled)
|
||||
$0.setSurfaceColors(VDSColor.interactiveActiveOnlight, VDSColor.interactiveActiveOndark, forState: [.selected, .highlighted])
|
||||
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: [.selected, .disabled])
|
||||
}.eraseToAnyColorable()
|
||||
}()
|
||||
var shadowColorConfiguration: AnyColorable = {
|
||||
SurfaceColorConfiguration(VDSColor.paletteBlack, VDSColor.paletteBlack).eraseToAnyColorable()
|
||||
}()
|
||||
var shadowOpacity: CGFloat = 0.16
|
||||
var shadowOffset: CGSize = .init(width: 0, height: 2)
|
||||
var shadowRadius: CGFloat = 6
|
||||
}
|
||||
|
||||
private var badgeIndicatorDefaultSize: CGSize = .zero
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Overrides
|
||||
//--------------------------------------------------
|
||||
/// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations.
|
||||
open override func setup() {
|
||||
super.setup()
|
||||
isAccessibilityElement = true
|
||||
accessibilityTraits = .image
|
||||
accessibilityElements = [badgeIndicator]
|
||||
|
||||
//create a layoutGuide for the icon to key off of
|
||||
let iconLayoutGuide = UILayoutGuide()
|
||||
@ -259,10 +332,21 @@ open class ButtonIcon: Control {
|
||||
//add the icon
|
||||
addSubview(icon)
|
||||
|
||||
//add badgeIndicator
|
||||
addSubview(badgeIndicator)
|
||||
badgeIndicator.isHidden = !showBadgeIndicator
|
||||
badgeIndicatorDefaultSize = badgeIndicator.frame.size
|
||||
|
||||
//determines the height/width of the icon
|
||||
layoutGuideWidthConstraint = iconLayoutGuide.width(constant: size.containerSize)
|
||||
layoutGuideHeightConstraint = iconLayoutGuide.height(constant: size.containerSize)
|
||||
badgeIndicatorLeadingConstraint = badgeIndicator.leadingAnchor.constraint(equalTo: icon.centerXAnchor)
|
||||
badgeIndicatorTrailingConstraint = badgeIndicator.trailingAnchor.constraint(equalTo: icon.centerXAnchor)
|
||||
badgeIndicatorCenterXConstraint = badgeIndicator.centerXAnchor.constraint(equalTo: icon.centerXAnchor)
|
||||
badgeIndicatorCenterYConstraint = icon.centerYAnchor.constraint(equalTo: badgeIndicator.centerYAnchor)
|
||||
badgeIndicatorCenterYConstraint?.isActive = true
|
||||
|
||||
badgeIndicatorLeadingConstraint?.isActive = true
|
||||
//pin layout guide
|
||||
iconLayoutGuide
|
||||
.pinTop()
|
||||
@ -277,6 +361,24 @@ open class ButtonIcon: Control {
|
||||
centerYConstraint?.activate()
|
||||
}
|
||||
|
||||
/// Executed on initialization for this View.
|
||||
open override func initialSetup() {
|
||||
super.initialSetup()
|
||||
onClick = { control in
|
||||
guard control.isEnabled else { return }
|
||||
if control.selectedIconName != nil && control.selectable {
|
||||
control.toggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This will change the state of the Selector and execute the actionBlock if provided.
|
||||
open func toggle() {
|
||||
//removed error
|
||||
isSelected.toggle()
|
||||
sendActions(for: .valueChanged)
|
||||
}
|
||||
|
||||
/// Resets to default settings.
|
||||
open override func reset() {
|
||||
super.reset()
|
||||
@ -288,6 +390,10 @@ open class ButtonIcon: Control {
|
||||
hideBorder = true
|
||||
iconOffset = .init(x: 0, y: 0)
|
||||
iconName = nil
|
||||
selectedIconName = nil
|
||||
showBadgeIndicator = false
|
||||
selectable = false
|
||||
badgeIndicatorModel = nil
|
||||
shouldUpdateView = true
|
||||
setNeedsUpdate()
|
||||
}
|
||||
@ -306,6 +412,7 @@ open class ButtonIcon: Control {
|
||||
} else {
|
||||
icon.reset()
|
||||
}
|
||||
updateBadgeIndicator()
|
||||
setNeedsLayout()
|
||||
}
|
||||
|
||||
@ -338,7 +445,7 @@ open class ButtonIcon: Control {
|
||||
layoutGuideHeightConstraint?.constant = iconLayoutSize
|
||||
|
||||
//border
|
||||
if let borderable = currentConfig as? Borderable {
|
||||
if let borderable = currentConfig as? Borderable, hideBorder {
|
||||
layer.borderColor = borderable.borderColorConfiguration.getColor(self).cgColor
|
||||
layer.borderWidth = borderable.borderWidth
|
||||
} else {
|
||||
@ -347,11 +454,71 @@ open class ButtonIcon: Control {
|
||||
}
|
||||
|
||||
if let dropshadowable = currentConfig as? Dropshadowable {
|
||||
addDropShadow(config: dropshadowable)
|
||||
addDropShadow(dropshadowable)
|
||||
} else {
|
||||
removeDropShadow()
|
||||
removeDropShadows()
|
||||
}
|
||||
|
||||
badgeIndicatorCenterXConstraint?.constant = badgeIndicatorOffset.x + badgeIndicatorDefaultSize.width/2
|
||||
badgeIndicatorCenterYConstraint?.constant = badgeIndicatorOffset.y + badgeIndicatorDefaultSize.height/2
|
||||
badgeIndicatorLeadingConstraint?.constant = badgeIndicatorOffset.x
|
||||
badgeIndicatorTrailingConstraint?.constant = badgeIndicatorOffset.x + badgeIndicatorDefaultSize.width
|
||||
|
||||
if showBadgeIndicator {
|
||||
updateExpandDirectionalConstraints()
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Private Methods
|
||||
//--------------------------------------------------
|
||||
private func updateBadgeIndicator() {
|
||||
badgeIndicator.isHidden = !showBadgeIndicator
|
||||
|
||||
guard let badgeIndicatorModel else {
|
||||
badgeIndicator.isHidden = true
|
||||
return
|
||||
}
|
||||
|
||||
badgeIndicator.surface = surface
|
||||
badgeIndicator.kind = badgeIndicatorModel.kind
|
||||
badgeIndicator.fillColor = badgeIndicatorModel.fillColor
|
||||
badgeIndicator.size = badgeIndicatorModel.size
|
||||
badgeIndicator.maximumDigits = badgeIndicatorModel.maximumDigits
|
||||
badgeIndicator.width = badgeIndicatorModel.width
|
||||
badgeIndicator.height = badgeIndicatorModel.height
|
||||
badgeIndicator.number = badgeIndicatorModel.number
|
||||
badgeIndicator.leadingCharacter = badgeIndicatorModel.leadingCharacter
|
||||
badgeIndicator.trailingText = badgeIndicatorModel.trailingText
|
||||
badgeIndicator.dotSize = badgeIndicatorModel.dotSize
|
||||
badgeIndicator.verticalPadding = badgeIndicatorModel.verticalPadding
|
||||
badgeIndicator.horizontalPadding = badgeIndicatorModel.horizontalPadding
|
||||
badgeIndicator.hideDot = badgeIndicatorModel.hideDot
|
||||
badgeIndicator.hideBorder = badgeIndicatorModel.hideBorder
|
||||
}
|
||||
|
||||
private func updateExpandDirectionalConstraints() {
|
||||
guard let badgeIndicatorModel else { return }
|
||||
switch badgeIndicatorModel.expandDirection {
|
||||
case .right:
|
||||
badgeIndicatorLeadingConstraint?.isActive = true
|
||||
badgeIndicatorTrailingConstraint?.isActive = false
|
||||
badgeIndicatorCenterXConstraint?.isActive = false
|
||||
case .center:
|
||||
badgeIndicatorLeadingConstraint?.isActive = false
|
||||
badgeIndicatorTrailingConstraint?.isActive = false
|
||||
badgeIndicatorCenterXConstraint?.isActive = true
|
||||
case .left:
|
||||
badgeIndicatorLeadingConstraint?.isActive = false
|
||||
badgeIndicatorCenterXConstraint?.isActive = false
|
||||
badgeIndicatorTrailingConstraint?.isActive = true
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to update any Accessibility properties.
|
||||
open override func updateAccessibility() {
|
||||
super.updateAccessibility()
|
||||
setAccessibilityLabel(for: [icon, badgeIndicator.label])
|
||||
}
|
||||
}
|
||||
|
||||
@ -363,37 +530,11 @@ extension ButtonIcon: AppleGuidelinesTouchable {
|
||||
}
|
||||
}
|
||||
|
||||
extension UIView {
|
||||
fileprivate func addDropShadow(config: Dropshadowable) {
|
||||
layer.masksToBounds = false
|
||||
layer.shadowColor = config.shadowColorConfiguration.getColor(self).cgColor
|
||||
layer.shadowOpacity = Float(config.shadowOpacity)
|
||||
layer.shadowOffset = config.shadowOffset
|
||||
layer.shadowRadius = config.shadowRadius
|
||||
layer.shouldRasterize = true
|
||||
layer.rasterizationScale = UIScreen.main.scale
|
||||
layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: layer.cornerRadius).cgPath
|
||||
}
|
||||
|
||||
fileprivate func removeDropShadow() {
|
||||
layer.shadowOpacity = 0
|
||||
layer.shadowRadius = 0
|
||||
layer.shadowPath = nil
|
||||
}
|
||||
}
|
||||
|
||||
private protocol Borderable {
|
||||
var borderWidth: CGFloat { get set }
|
||||
var borderColorConfiguration: AnyColorable { get set }
|
||||
}
|
||||
|
||||
private protocol Dropshadowable {
|
||||
var shadowColorConfiguration: AnyColorable { get set }
|
||||
var shadowOpacity: CGFloat { get set }
|
||||
var shadowOffset: CGSize { get set }
|
||||
var shadowRadius: CGFloat { get set }
|
||||
}
|
||||
|
||||
private protocol Configuration {
|
||||
var kind: ButtonIcon.Kind { get set }
|
||||
var surfaceType: ButtonIcon.SurfaceType { get set }
|
||||
|
||||
@ -0,0 +1,82 @@
|
||||
//
|
||||
// ButtonIconBadgeIndicatorModel.swift
|
||||
// VDS
|
||||
//
|
||||
// Created by Kanamarlapudi, Vasavi on 08/02/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension ButtonIcon {
|
||||
|
||||
//Model that represents the options available for the Badge Indicator
|
||||
public struct BadgeIndicatorModel {
|
||||
/// Enum used to describe the badge indicator direction of icon button determining the expand direction.
|
||||
public enum ExpandDirection: String, CaseIterable {
|
||||
case right, center, left
|
||||
}
|
||||
|
||||
/// Applies expand direction to Badge Indicator if shows badge indicator.
|
||||
public var expandDirection: ExpandDirection = .right
|
||||
|
||||
/// Kind that will be used for the badge indicator.
|
||||
public var kind: BadgeIndicator.Kind
|
||||
|
||||
/// Fill color that will be used for the badge indicator.
|
||||
public var fillColor: BadgeIndicator.FillColor
|
||||
|
||||
/// Size that will be used for the badge indicator.
|
||||
public var size: BadgeIndicator.Size
|
||||
|
||||
/// Number of digits that will be used for the badge indicator.
|
||||
public var maximumDigits: BadgeIndicator.MaximumDigits
|
||||
|
||||
/// Max width that will be used for the badge indicator.
|
||||
public var width: CGFloat?
|
||||
|
||||
/// Max height that will be used for the badge indicator.
|
||||
public var height: CGFloat?
|
||||
|
||||
/// Number that will be used for the badge indicator.
|
||||
public var number: Int?
|
||||
|
||||
/// Leading Character that will be used for the badge indicator.
|
||||
public var leadingCharacter: String?
|
||||
|
||||
/// Trailing Text height that will be used for the badge indicator.
|
||||
public var trailingText: String?
|
||||
|
||||
/// Dot Size that will be used for the badge indicator.
|
||||
public var dotSize: CGFloat?
|
||||
|
||||
/// Vertical Padding that will be used for the badge indicator.
|
||||
public var verticalPadding: CGFloat?
|
||||
|
||||
/// Horizontal Padding that will be used for the badge indicator.
|
||||
public var horizontalPadding: CGFloat?
|
||||
|
||||
/// Hide Dot that will be used for the badge indicator.
|
||||
public var hideDot: Bool = false
|
||||
|
||||
/// Hide Border that will be used for the badge indicator.
|
||||
public var hideBorder: Bool = false
|
||||
|
||||
public init(kind: BadgeIndicator.Kind = .simple, fillColor: BadgeIndicator.FillColor = .red, expandDirection: ExpandDirection = .right, size: BadgeIndicator.Size = .xxlarge, maximumDigits: BadgeIndicator.MaximumDigits = .two, width: CGFloat? = nil, height: CGFloat? = nil, number: Int? = nil, leadingCharacter: String = "", trailingText: String = "", dotSize: CGFloat? = nil, verticalPadding: CGFloat? = nil, horizontalPadding: CGFloat? = nil, hideDot: Bool = false, hideBorder: Bool = false) {
|
||||
self.kind = kind
|
||||
self.fillColor = fillColor
|
||||
self.expandDirection = expandDirection
|
||||
self.size = size
|
||||
self.maximumDigits = maximumDigits
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.number = number
|
||||
self.leadingCharacter = leadingCharacter
|
||||
self.trailingText = trailingText
|
||||
self.dotSize = dotSize
|
||||
self.verticalPadding = verticalPadding
|
||||
self.horizontalPadding = horizontalPadding
|
||||
self.hideDot = hideDot
|
||||
self.hideBorder = hideBorder
|
||||
}
|
||||
}
|
||||
}
|
||||
79
VDS/Components/Icon/ButtonIcon/ButtonIconChangeLog.txt
Normal file
79
VDS/Components/Icon/ButtonIcon/ButtonIconChangeLog.txt
Normal file
@ -0,0 +1,79 @@
|
||||
|
||||
03/30/2023
|
||||
----------------
|
||||
- Dev handoff
|
||||
|
||||
04/06/2023
|
||||
----------------
|
||||
- Changed component name to Button Icon.
|
||||
|
||||
04/13/2023
|
||||
----------------
|
||||
- Dev handoff (additional states)
|
||||
|
||||
04/20/2023
|
||||
----------------
|
||||
- Added Selectable and Selected properties in Configuration.
|
||||
|
||||
05/01/2023
|
||||
----------------
|
||||
- Added fitToIcon prop for Ghost variation.
|
||||
|
||||
05/03/2023
|
||||
----------------
|
||||
- Updated drop shadows for light and dark floating buttons (all states)
|
||||
- Removed border from all floating buttons
|
||||
- Updated all dark floating button backgrounds to gray.20
|
||||
- Disabled icons on gray.20 changed to palette.black 70% opacity for floating buttons
|
||||
- Reduce Inside focusborderPosition to 1px
|
||||
|
||||
05/25/2023
|
||||
----------------
|
||||
- Added hideBorder prop back to Configurations
|
||||
- Added showBadgeIndicator prop to Configurations
|
||||
- Added Hit Area support for Button Icon with Badge Indicator
|
||||
- Added Elements page for Badge Indicator with default settings and offset information
|
||||
- Updated Badge Indicator Offset to be based on center of Icon container
|
||||
|
||||
05/30/2023
|
||||
----------------
|
||||
- Added expandDirection prop to Configurations under Badge Indicator section.
|
||||
|
||||
06/02/2023
|
||||
----------------
|
||||
- Added Additional Border Color specification if hideBorder=True on Low Contrast hover states.
|
||||
|
||||
06/12/2023
|
||||
----------------
|
||||
- Ghost Icon Cart position updated from {1,2} to {1,1}.
|
||||
|
||||
06/13/2023
|
||||
----------------
|
||||
- Updated Drop shadow properties layout in States.
|
||||
|
||||
06/29/2023
|
||||
----------------
|
||||
- Updated Badge Indicator simple dot size to 4px.
|
||||
|
||||
08/18/2023
|
||||
----------------
|
||||
- Updated default icon color for all selected Button Icon states to element.primary.onlight for light surfaces and element.primary.ondark for dark surfaces.
|
||||
- Added a dev note that this default color can be set to a custom color (like red).
|
||||
|
||||
11/16/2023
|
||||
----------------
|
||||
- Added component tokens
|
||||
- Applied component tokens throughout states
|
||||
- Applied semantic inverse to primary element, background states on light and dark surfaces
|
||||
|
||||
12/1/2023
|
||||
----------------
|
||||
- Reapplied component token updates to Ghost States
|
||||
|
||||
1/9/2024
|
||||
----------------
|
||||
- Fixed incorrect Low Contrast border token
|
||||
|
||||
1/25/2024
|
||||
----------------
|
||||
- Removed redundant opacity specs in States (dark surface).
|
||||
@ -135,10 +135,10 @@ open class Label: UILabel, ViewProtocol, UserInfoable {
|
||||
override open var text: String? {
|
||||
get { _text }
|
||||
set {
|
||||
if _text != newValue {
|
||||
if _text != newValue || newValue != attributedText?.string {
|
||||
_text = newValue
|
||||
useAttributedText = false
|
||||
attributes = nil
|
||||
attributes?.removeAll()
|
||||
setNeedsUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
@ -108,11 +108,20 @@ open class Loader: View {
|
||||
rotation.repeatCount = .infinity
|
||||
icon.layer.add(rotation, forKey: rotationLayerName)
|
||||
|
||||
// check to make sure VoiceOver is running
|
||||
guard UIAccessibility.isVoiceOverRunning else {
|
||||
loadingTimer?.invalidate()
|
||||
loadingTimer = nil
|
||||
return
|
||||
}
|
||||
|
||||
// Focus VoiceOver on this view
|
||||
UIAccessibility.post(notification: .layoutChanged, argument: self)
|
||||
|
||||
// setup timer for post
|
||||
loadingTimer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true) { [weak self] _ in
|
||||
self?.accessibilityLabel = "Still Loading"
|
||||
guard let self, self.isActive, self.isVisibleOnScreen else { return }
|
||||
self.accessibilityLabel = "Still Loading"
|
||||
UIAccessibility.post(notification: .announcement, argument: "Still Loading")
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,21 +65,29 @@ open class Notification: View {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
$0.alignment = .top
|
||||
$0.axis = .horizontal
|
||||
$0.spacing = VDSLayout.space2X
|
||||
$0.spacing = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space2X
|
||||
}
|
||||
|
||||
private var labelsView = UIStackView().with {
|
||||
$0.spacing = VDSLayout.space1X
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
$0.alignment = .top
|
||||
$0.alignment = .fill
|
||||
$0.distribution = .equalSpacing
|
||||
$0.axis = .vertical
|
||||
}
|
||||
|
||||
private var labelButtonView = UIStackView().with {
|
||||
private var labelButtonView = View().with {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
$0.alignment = .top
|
||||
$0.distribution = .fillEqually
|
||||
$0.axis = .vertical
|
||||
$0.spacing = VDSLayout.space2X
|
||||
}
|
||||
|
||||
private var labelButtonViewSpacing: CGFloat {
|
||||
let spacing: CGFloat = UIDevice.isIPad ? 20 : 16
|
||||
return switch layout {
|
||||
case .vertical:
|
||||
0
|
||||
case .horizontal:
|
||||
spacing
|
||||
}
|
||||
}
|
||||
|
||||
internal var onCloseSubscriber: AnyCancellable?
|
||||
@ -165,7 +173,7 @@ open class Notification: View {
|
||||
/// Add this attribute determine your type of Notification.
|
||||
open var style: Style = .info { didSet { setNeedsUpdate()}}
|
||||
|
||||
var _layout: Layout = .vertical
|
||||
private var _layout: Layout = .vertical
|
||||
|
||||
/// Determines the orientation of buttons and text in the Notification.
|
||||
open var layout: Layout {
|
||||
@ -215,6 +223,15 @@ open class Notification: View {
|
||||
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
|
||||
//--------------------------------------------------
|
||||
@ -240,15 +257,34 @@ open class Notification: View {
|
||||
])
|
||||
maxWidthConstraint = layoutGuide.widthAnchor.constraint(lessThanOrEqualToConstant: maxViewWidth)
|
||||
|
||||
labelButtonView.addArrangedSubview(labelsView)
|
||||
labelButtonView.addSubview(labelsView)
|
||||
labelsView
|
||||
.pinTop()
|
||||
.pinLeading()
|
||||
labelViewWidthConstraint = labelsView.widthAnchor.constraint(equalTo: labelButtonView.widthAnchor, multiplier: 1.0)
|
||||
labelViewWidthConstraint?.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)
|
||||
labelViewAndButtonViewConstraint = buttonGroup.topAnchor.constraint(equalTo: labelsView.bottomAnchor, constant: VDSLayout.space3X)
|
||||
buttonGroup.widthAnchor.constraint(equalTo: labelsView.widthAnchor).activate()
|
||||
|
||||
mainStackView.addArrangedSubview(typeIcon)
|
||||
mainStackView.addArrangedSubview(labelButtonView)
|
||||
mainStackView.addArrangedSubview(closeButton)
|
||||
|
||||
typeIconWidthConstraint = typeIcon.width(constant: typeIcon.size.dimensions.width)
|
||||
closeIconWidthConstraint = closeButton.width(constant: closeButton.size.dimensions.width)
|
||||
|
||||
//labels
|
||||
titleLabel.textColorConfiguration = textColorConfiguration.eraseToAnyColorable()
|
||||
subTitleLabel.textColorConfiguration = textColorConfiguration.eraseToAnyColorable()
|
||||
|
||||
//TODO: Need to add setup animation for displaying the Notification view for iOS.
|
||||
}
|
||||
|
||||
/// Resets to default settings.
|
||||
@ -296,6 +332,12 @@ open class Notification: View {
|
||||
setConstraints()
|
||||
}
|
||||
|
||||
/// Override to check the screen width to determine cornerRadius
|
||||
open override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
layer.cornerRadius = UIScreen.main.bounds.width == bounds.width ? 0 : 4.0
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Private Methods
|
||||
//--------------------------------------------------
|
||||
@ -324,7 +366,6 @@ open class Notification: View {
|
||||
} else {
|
||||
subTitleLabel.removeFromSuperview()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private func updateButtons() {
|
||||
@ -342,28 +383,28 @@ open class Notification: View {
|
||||
secondaryButton.onClick = secondaryButtonModel.onClick
|
||||
buttons.append(secondaryButton)
|
||||
}
|
||||
|
||||
labelViewWidthConstraint?.deactivate()
|
||||
if buttons.isEmpty {
|
||||
labelsView.setCustomSpacing(0, after: subTitleLabel)
|
||||
buttonGroup.removeFromSuperview()
|
||||
buttonGroup.isHidden = true
|
||||
labelViewWidthConstraint = labelsView.widthAnchor.constraint(equalTo: labelButtonView.widthAnchor)
|
||||
buttonGroup.buttons.removeAll()
|
||||
} else {
|
||||
labelsView.setCustomSpacing(VDSLayout.space3X, after: subTitleLabel)
|
||||
|
||||
buttonGroup.buttons = buttons
|
||||
labelButtonView.axis = layout == .vertical ? .vertical : .horizontal
|
||||
labelButtonView.addArrangedSubview(buttonGroup)
|
||||
|
||||
buttonGroup
|
||||
.pinLeading()
|
||||
.pinTrailing()
|
||||
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?.constant = maxViewWidth
|
||||
maxWidthConstraint?.isActive = UIDevice.isIPad
|
||||
|
||||
labelViewAndButtonViewConstraint?.isActive = layout == .vertical && !buttonGroup.buttons.isEmpty
|
||||
typeIconWidthConstraint?.constant = typeIcon.size.dimensions.width
|
||||
closeIconWidthConstraint?.constant = closeButton.size.dimensions.width
|
||||
labelViewBottomConstraint?.isActive = layout == .horizontal || buttonGroup.buttons.isEmpty
|
||||
buttonGroupCenterYConstraint?.isActive = layout == .horizontal
|
||||
buttonGroupBottomConstraint?.isActive = layout == .vertical
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
55
VDS/Components/Notification/NotificationChangeLog.txt
Normal file
55
VDS/Components/Notification/NotificationChangeLog.txt
Normal file
@ -0,0 +1,55 @@
|
||||
MM/DD/YYYY
|
||||
----------------
|
||||
|
||||
12/30/2021
|
||||
----------------
|
||||
- Updated Hover and Active state trigger specs. If triggered by mouse, Active same as Hover. If not, Active same as Default.
|
||||
|
||||
03/03/2022
|
||||
----------------
|
||||
- Initial Brand 3.0 handoff
|
||||
|
||||
03/07/2022
|
||||
----------------
|
||||
- Added Native positioning examples
|
||||
|
||||
05/03/2022
|
||||
----------------
|
||||
- Finalized Native Positioning and Triggers sections.
|
||||
|
||||
07/28/2022
|
||||
----------------
|
||||
- Added note to Anatomy that Border radius is only for Inline Notifications.
|
||||
|
||||
11/30/2022
|
||||
----------------
|
||||
- Added "(web only)" to any instance of "keyboard focus"
|
||||
|
||||
12/13/2022
|
||||
----------------
|
||||
- Replaced focus border pixel and style & spacing values with tokens.
|
||||
|
||||
01/12/2023
|
||||
----------------
|
||||
- Change VDS Button to Button in Anatomy
Moved Button combinations from Anatomy to Elements.
|
||||
|
||||
04/12/2023
|
||||
----------------
|
||||
- Updated hex colors to reflect updated color tokens values.
|
||||
- Updated visuals on the Native frame to reflect new guideline that the notification surface color should be based on whatever it is placed upon, or based on the color of the top navigation bar when displaying above the global nav. Added spec notation about this also.
|
||||
|
||||
05/04/2023
|
||||
----------------
|
||||
- Button Icon Threading - CloseButton
|
||||
- Anatomy section updated with info on Button Icon
|
||||
- CloseButton updated with VDS Button Icon details
|
||||
- Updated Viewport section with Button Icon Config
|
||||
- Removed Elements section. Button combination is now part of Configurations section
|
||||
- Updated visuals in all sections to cater to changes caused by Button Icon threading
|
||||
|
||||
11/21/2023
|
||||
----------------
|
||||
- Added hideCloseButton property to align with React component build
|
||||
- Added dev note to Error variant
|
||||
- Removed close button from Error variant examples
|
||||
- Removed Web/App Discrepancies list
|
||||
@ -73,6 +73,11 @@ open class RadioButtonGroup: SelectorGroupBase<RadioButtonItem>, SelectorGroupSi
|
||||
}
|
||||
}
|
||||
|
||||
open override func reset() {
|
||||
super.reset()
|
||||
showError = false
|
||||
}
|
||||
|
||||
public override func didSelect(_ selectedControl: RadioButtonItem) {
|
||||
if let selectedItem {
|
||||
updateToggle(selectedItem)
|
||||
|
||||
@ -171,6 +171,8 @@ open class Tabs: View {
|
||||
/// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations.
|
||||
open override func setup() {
|
||||
super.setup()
|
||||
isAccessibilityElement = false
|
||||
|
||||
scrollView = UIScrollView()
|
||||
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||
scrollView.showsHorizontalScrollIndicator = false
|
||||
@ -219,6 +221,24 @@ open class Tabs: View {
|
||||
updateContentView()
|
||||
}
|
||||
|
||||
open override func reset() {
|
||||
super.reset()
|
||||
shouldUpdateView = false
|
||||
orientation = .horizontal
|
||||
borderLine = true
|
||||
fillContainer = false
|
||||
indicatorFillTab = false
|
||||
indicatorPosition = .bottom
|
||||
minWidth = 44.0
|
||||
overflow = .scroll
|
||||
selectedIndex = 0
|
||||
size = .medium
|
||||
sticky = false
|
||||
tabViews.forEach{ $0.reset() }
|
||||
shouldUpdateView = true
|
||||
setNeedsUpdate()
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Private Methods
|
||||
//--------------------------------------------------
|
||||
@ -244,6 +264,7 @@ open class Tabs: View {
|
||||
model.onClick?(tab.index)
|
||||
self.selectedIndex = tab.index
|
||||
self.onTabDidSelect?(tab.index)
|
||||
let t = tabViews[tab.index]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -283,7 +304,7 @@ open class Tabs: View {
|
||||
tabItem.orientation = orientation
|
||||
tabItem.surface = surface
|
||||
tabItem.indicatorPosition = indicatorPosition
|
||||
tabItem.accessibilityHint = "\(index+1) of \(tabViews.count) Tabs"
|
||||
tabItem.accessibilityValue = "\(index+1) of \(tabViews.count) Tabs"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -31,20 +31,36 @@ open class TileContainer: Control {
|
||||
// MARK: - Enums
|
||||
//--------------------------------------------------
|
||||
/// Enum used to describe the background color choices used for this component.
|
||||
public enum BackgroundColor: String, CaseIterable {
|
||||
public enum BackgroundColor: Equatable {
|
||||
|
||||
case primary
|
||||
case secondary
|
||||
case white
|
||||
case black
|
||||
case gray
|
||||
case transparent
|
||||
case custom(String)
|
||||
|
||||
private var reflectedValue: String { String(reflecting: self) }
|
||||
|
||||
public static func == (lhs: Self, rhs: Self) -> Bool {
|
||||
lhs.reflectedValue == rhs.reflectedValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Enum used to describe the background effect choices used for this component.
|
||||
public enum BackgroundEffect {
|
||||
case transparency
|
||||
case gradient(String, String)
|
||||
case none
|
||||
}
|
||||
|
||||
/// Enum used to describe the padding choices used for this component.
|
||||
public enum Padding: String, CaseIterable {
|
||||
public enum Padding {
|
||||
case padding2X
|
||||
case padding4X
|
||||
case padding6X
|
||||
case padding8X
|
||||
case padding12X
|
||||
case custom(CGFloat)
|
||||
|
||||
public var value: CGFloat {
|
||||
switch self {
|
||||
@ -58,6 +74,8 @@ open class TileContainer: Control {
|
||||
return VDSLayout.space8X
|
||||
case .padding12X:
|
||||
return VDSLayout.space12X
|
||||
case .custom(let padding):
|
||||
return padding
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -105,7 +123,10 @@ open class TileContainer: Control {
|
||||
open var aspectRatio: AspectRatio = .ratio1x1 { didSet { setNeedsUpdate() } }
|
||||
|
||||
/// Sets the background color for the component.
|
||||
open var color: BackgroundColor = .white { didSet { setNeedsUpdate() } }
|
||||
open var color: BackgroundColor = .secondary { didSet { setNeedsUpdate() } }
|
||||
|
||||
/// Sets the background effect for the component.
|
||||
open var backgroundEffect: BackgroundEffect = .none { didSet { setNeedsUpdate() } }
|
||||
|
||||
/// Sets the inside padding for the component
|
||||
open var padding: Padding = .padding4X { didSet { setNeedsUpdate() } }
|
||||
@ -164,6 +185,7 @@ open class TileContainer: Control {
|
||||
private let cornerRadius = VDSFormControls.borderRadius * 2
|
||||
|
||||
private var backgroundColorConfiguration = BackgroundColorConfiguration()
|
||||
private var dropshadowConfiguration = DropshadowConfiguration()
|
||||
|
||||
private var borderColorConfiguration = SurfaceColorConfiguration().with {
|
||||
$0.lightColor = VDSColor.elementsLowcontrastOnlight
|
||||
@ -200,24 +222,20 @@ open class TileContainer: Control {
|
||||
addSubview(highlightView)
|
||||
|
||||
widthConstraint = layoutGuide.widthAnchor.constraint(equalToConstant: 0)
|
||||
widthConstraint?.priority = .defaultHigh
|
||||
|
||||
heightGreaterThanConstraint = layoutGuide.heightAnchor.constraint(greaterThanOrEqualToConstant: 44.0)
|
||||
heightGreaterThanConstraint?.isActive = false
|
||||
|
||||
heightConstraint = layoutGuide.heightAnchor.constraint(equalToConstant: 0)
|
||||
heightConstraint?.priority = .defaultHigh
|
||||
|
||||
backgroundImageView.pin(layoutGuide)
|
||||
backgroundImageView.isUserInteractionEnabled = false
|
||||
backgroundImageView.isHidden = true
|
||||
|
||||
containerView.backgroundColor = .clear
|
||||
|
||||
containerTopConstraint = containerView.pinTop(anchor: layoutGuide.topAnchor, constant: padding.value)
|
||||
containerBottomConstraint = containerView.pinBottom(anchor: layoutGuide.bottomAnchor, constant: padding.value)
|
||||
containerBottomConstraint = layoutGuide.pinBottom(anchor: containerView.bottomAnchor, constant: padding.value)
|
||||
containerLeadingConstraint = containerView.pinLeading(anchor: layoutGuide.leadingAnchor, constant: padding.value)
|
||||
containerTrailingConstraint = containerView.pinTrailing(anchor: layoutGuide.trailingAnchor, constant: padding.value)
|
||||
containerTrailingConstraint = layoutGuide.pinTrailing(anchor: containerView.trailingAnchor, constant: padding.value)
|
||||
|
||||
highlightView.pin(layoutGuide)
|
||||
highlightView.isHidden = true
|
||||
@ -227,7 +245,6 @@ open class TileContainer: Control {
|
||||
layer.cornerRadius = cornerRadius
|
||||
backgroundImageView.layer.cornerRadius = cornerRadius
|
||||
highlightView.layer.cornerRadius = cornerRadius
|
||||
|
||||
}
|
||||
|
||||
/// Resets to default settings.
|
||||
@ -267,8 +284,8 @@ open class TileContainer: Control {
|
||||
|
||||
containerTopConstraint?.constant = padding.value
|
||||
containerLeadingConstraint?.constant = padding.value
|
||||
containerBottomConstraint?.constant = -padding.value
|
||||
containerTrailingConstraint?.constant = -padding.value
|
||||
containerBottomConstraint?.constant = padding.value
|
||||
containerTrailingConstraint?.constant = padding.value
|
||||
|
||||
if let width, aspectRatio == .none && height == nil{
|
||||
widthConstraint?.constant = width
|
||||
@ -292,6 +309,12 @@ open class TileContainer: Control {
|
||||
widthConstraint?.isActive = false
|
||||
heightConstraint?.isActive = false
|
||||
}
|
||||
if showDropShadows, surface == .light {
|
||||
addDropShadow(dropshadowConfiguration)
|
||||
} else {
|
||||
removeDropShadows()
|
||||
}
|
||||
applyBackgroundEffects()
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
@ -309,6 +332,36 @@ open class TileContainer: Control {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Private Methods
|
||||
//--------------------------------------------------
|
||||
|
||||
private func applyBackgroundEffects() {
|
||||
let color = backgroundColorConfiguration.getColor(self)
|
||||
var alphaConfiguration: CGFloat = 1.0
|
||||
let imageFallbackColor = imageFallbackColorConfiguration.getColor(self)
|
||||
switch backgroundEffect {
|
||||
case .transparency:
|
||||
alphaConfiguration = 0.8
|
||||
removeGradientLayer()
|
||||
case .gradient(let firstColor, let secondColor):
|
||||
alphaConfiguration = 1.0
|
||||
addGradientLayer(with: UIColor(hexString: firstColor), secondColor: UIColor(hexString: secondColor))
|
||||
backgroundImageView.isHidden = true
|
||||
backgroundImageView.alpha = 1.0
|
||||
case .none:
|
||||
alphaConfiguration = 1.0
|
||||
removeGradientLayer()
|
||||
}
|
||||
if let backgroundImage {
|
||||
backgroundImageView.image = backgroundImage
|
||||
backgroundImageView.isHidden = false
|
||||
backgroundImageView.alpha = alphaConfiguration
|
||||
backgroundColor = imageFallbackColor.withAlphaComponent(alphaConfiguration)
|
||||
} else {
|
||||
backgroundImageView.isHidden = true
|
||||
backgroundImageView.alpha = 1.0
|
||||
backgroundColor = color.withAlphaComponent(alphaConfiguration)
|
||||
}
|
||||
}
|
||||
|
||||
private func ratioSize(for width: CGFloat) -> CGSize {
|
||||
var height: CGFloat = width
|
||||
|
||||
@ -342,22 +395,40 @@ open class TileContainer: Control {
|
||||
}
|
||||
|
||||
extension TileContainer {
|
||||
class BackgroundColorConfiguration: ObjectColorable {
|
||||
|
||||
struct DropshadowConfiguration: Dropshadowable {
|
||||
var shadowColorConfiguration: AnyColorable = SurfaceColorConfiguration().with {
|
||||
$0.lightColor = VDSColor.elementsPrimaryOnlight
|
||||
}.eraseToAnyColorable()
|
||||
var shadowOpacity: CGFloat = 0.01
|
||||
var shadowOffset: CGSize = .init(width: 0, height: 6)
|
||||
var shadowRadius: CGFloat = 3
|
||||
}
|
||||
|
||||
final class BackgroundColorConfiguration: ObjectColorable {
|
||||
|
||||
typealias ObjectType = TileContainer
|
||||
|
||||
let primaryColorConfig = SurfaceColorConfiguration(VDSColor.backgroundPrimaryLight, VDSColor.backgroundPrimaryDark)
|
||||
let secondaryColorConfig = SurfaceColorConfiguration(VDSColor.backgroundSecondaryLight, VDSColor.backgroundSecondaryDark)
|
||||
let grayColorConfig = SurfaceColorConfiguration(VDSColor.backgroundSecondaryLight, VDSColor.backgroundSecondaryDark)
|
||||
let whiteColorConfig = SurfaceColorConfiguration(VDSColor.paletteWhite, VDSColor.paletteWhite)
|
||||
let blackColorConfig = SurfaceColorConfiguration(VDSColor.paletteBlack, VDSColor.paletteBlack)
|
||||
|
||||
required init() { }
|
||||
|
||||
func getColor(_ object: TileContainer) -> UIColor {
|
||||
switch object.color {
|
||||
|
||||
case .primary:
|
||||
primaryColorConfig.getColor(object.surface)
|
||||
case .secondary:
|
||||
secondaryColorConfig.getColor(object.surface)
|
||||
case .white:
|
||||
return VDSColor.backgroundPrimaryLight
|
||||
whiteColorConfig.getColor(object.surface)
|
||||
case .black:
|
||||
return VDSColor.backgroundPrimaryDark
|
||||
case .gray:
|
||||
return VDSColor.backgroundSecondaryLight
|
||||
case .transparent:
|
||||
return UIColor.clear
|
||||
blackColorConfig.getColor(object.surface)
|
||||
case .custom(let hexCode):
|
||||
UIColor(hexString: hexCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
90
VDS/Components/TileContainer/TileContainerChangeLog.txt
Normal file
90
VDS/Components/TileContainer/TileContainerChangeLog.txt
Normal file
@ -0,0 +1,90 @@
|
||||
MM/DD/YYYY
|
||||
----------------
|
||||
|
||||
02/01/2022
|
||||
----------------
|
||||
- ACTION | Migrated Spec file from working file into VDS Brand 3.0 Core SPECs & Test App.
|
||||
|
||||
02/02/2022
|
||||
----------------
|
||||
- Elements | Added option for user to manually define a custom Padding valueElements.
|
||||
- Elements | Removed option for 40px Padding
|
||||
- Elements | Added background options of Hex code and Transparency.
|
||||
|
||||
02/07/2022
|
||||
----------------
|
||||
- Anatomy | Updated descriptions to simplify. (Removed “Tile” from many)
|
||||
|
||||
02/08/2022
|
||||
----------------
|
||||
- Elements | Background color section updated
|
||||
- Elements | Removed option for 20px Padding value
|
||||
- Configurations | Surface section added
|
||||
- Configurations | Multiple sections updated
|
||||
- Behaviors | Interaction section added
|
||||
- States | Multiple sections updated/moved
|
||||
- ACTION | Sent to Accessibility Team to review
|
||||
|
||||
02/14/2022
|
||||
----------------
|
||||
- ACTION | Received sign off from Accessibility
|
||||
- ACTION | Sent to Talia for design review
|
||||
|
||||
02/21/2022
|
||||
----------------
|
||||
- Elements | Background colors and tokens updated.
|
||||
- ACTION | Ready for dev review.
|
||||
|
||||
02/22/2022
|
||||
----------------
|
||||
- Elements | Background names updated to infer surface prop selection.
|
||||
- ACTION | Web dev handoff completed.
|
||||
|
||||
12/15/2022
|
||||
----------------
|
||||
- States | Android drop shadow specs added, along with screenshot to the right of specs.
|
||||
- States | Added "(web only)" to any instance of "keyboard focus".
|
||||
- States | Replaced focus border pixel and style & spacing values with tokens.
|
||||
- Elements | Updated border color values to use element tokens.
|
||||
- Configurations | Updated border and drop shadow section titles to “Show border” and “Show drop shadow.”
|
||||
|
||||
01/18/2023
|
||||
----------------
|
||||
- Anatomy | Updated item #2 to “Padding” from “Container Internal Padding”
|
||||
|
||||
05/11/2023
|
||||
----------------
|
||||
- Removed showdropshadow prop from Configurations (dropshadow will be on automatically now for Surface=Light)
|
||||
- Updated states frame to remove states featuring dropshadow suppression, clarified state names, and removed inaccurate dev notes.
|
||||
|
||||
06/15/2023
|
||||
----------------
|
||||
- Added showDropShadow prop back into Configurations.
|
||||
- Moved Padding to Configurations
|
||||
|
||||
11/09/2023
|
||||
----------------
|
||||
- Updated showBorder section to match API prop names/values.
|
||||
- Moved Padding to Configurations.
|
||||
|
||||
11/20/2023
|
||||
----------------
|
||||
- Added corner radius token in the Anatomy
|
||||
- Updated visuals to reflect new corner radius value - 12px
|
||||
- Updated focus border corner radius to 14px
|
||||
- View changes
|
||||
|
||||
11/27/2023
|
||||
----------------
|
||||
- Updated “border radius” to “corner radius” in Anatomy
|
||||
- Updated “focus border radius” to “focus corner radius” in States
|
||||
- View changes
|
||||
|
||||
12/14/2023
|
||||
----------------
|
||||
- Added backgroundColor configuration section, removed Background Colors element section
|
||||
- Added secondary, primary backgroundColor options
|
||||
- Simplified backgroundImage section to remove backgroundColor example
|
||||
- Added background property section, with examples
|
||||
- Deprecated the gray backgroundColor option
|
||||
- View changes
|
||||
@ -77,6 +77,7 @@ open class TitleLockup: View {
|
||||
/// Label used to render the title model.
|
||||
open var titleLabel = Label().with {
|
||||
$0.setContentCompressionResistancePriority(.required, for: .vertical)
|
||||
$0.accessibilityTraits.insert([.header])
|
||||
}
|
||||
|
||||
/// Model used in rendering the title label.
|
||||
|
||||
@ -24,4 +24,30 @@ extension UIView {
|
||||
public func setAccessibilityLabel(for views: [UIView]) {
|
||||
accessibilityLabel = combineAccessibilityLabel(for: views)
|
||||
}
|
||||
|
||||
/// Will tell if the view is actually visibile on screen, also it will check the hierarchy above this view.
|
||||
public var isVisibleOnScreen: Bool {
|
||||
// Ensure the view has a window, meaning it's part of the view hierarchy
|
||||
guard let window = self.window, !self.isHidden, self.alpha > 0 else {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if the view's frame intersects with the window's bounds
|
||||
let viewFrameInWindow = self.convert(self.bounds, to: window)
|
||||
var isIntersecting = viewFrameInWindow.intersects(window.bounds)
|
||||
|
||||
// Check parent views for visibility
|
||||
var currentView: UIView? = self
|
||||
while let view = currentView, isIntersecting {
|
||||
// If any parent has a constraint making it effectively invisible, set isIntersecting to false
|
||||
if view.bounds.size.width == 0 || view.bounds.size.height == 0 {
|
||||
isIntersecting = false
|
||||
break
|
||||
}
|
||||
currentView = view.superview
|
||||
}
|
||||
|
||||
return isIntersecting
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
64
VDS/Protocols/Dropshadowable.swift
Normal file
64
VDS/Protocols/Dropshadowable.swift
Normal file
@ -0,0 +1,64 @@
|
||||
//
|
||||
// Dropshadowable.swift
|
||||
// VDS
|
||||
//
|
||||
// Created by Bandaru, Krishna Kishore on 16/02/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
protocol Dropshadowable {
|
||||
|
||||
var shadowColorConfiguration: AnyColorable { get set }
|
||||
var shadowOpacity: CGFloat { get set }
|
||||
var shadowOffset: CGSize { get set }
|
||||
var shadowRadius: CGFloat { get set }
|
||||
}
|
||||
|
||||
extension ViewProtocol where Self: UIView {
|
||||
|
||||
func addDropShadow(_ config: Dropshadowable) {
|
||||
removeDropShadows()
|
||||
layer.backgroundColor = backgroundColor?.cgColor
|
||||
layer.masksToBounds = false
|
||||
let shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: layer.cornerRadius)
|
||||
let shadowLayer = CALayer()
|
||||
shadowLayer.shadowPath = shadowPath.cgPath
|
||||
shadowLayer.frame = bounds
|
||||
shadowLayer.position = center
|
||||
shadowLayer.backgroundColor = UIColor.clear.cgColor
|
||||
shadowLayer.cornerRadius = layer.cornerRadius
|
||||
shadowLayer.shadowColor = config.shadowColorConfiguration.getColor(self).cgColor
|
||||
shadowLayer.shadowOpacity = Float(config.shadowOpacity)
|
||||
shadowLayer.shadowOffset = .init(width: config.shadowOffset.width, height: config.shadowOffset.height)
|
||||
shadowLayer.shadowRadius = config.shadowRadius
|
||||
shadowLayer.name = "dropShadowLayer"
|
||||
shadowLayer.shouldRasterize = true
|
||||
shadowLayer.rasterizationScale = UIScreen.main.scale
|
||||
layer.insertSublayer(shadowLayer, at: 0)
|
||||
}
|
||||
|
||||
func removeDropShadows() {
|
||||
layer.sublayers?.removeAll { $0.name == "dropShadowLayer" }
|
||||
}
|
||||
|
||||
func addGradientLayer(with firstColor: UIColor, secondColor: UIColor) {
|
||||
removeGradientLayer()
|
||||
let gradientLayer = CAGradientLayer()
|
||||
gradientLayer.frame = bounds
|
||||
gradientLayer.startPoint = CGPoint(x: 0, y: 1)
|
||||
gradientLayer.endPoint = CGPoint(x: 1, y: 0)
|
||||
gradientLayer.position = center
|
||||
gradientLayer.shouldRasterize = true
|
||||
gradientLayer.rasterizationScale = UIScreen.main.scale
|
||||
gradientLayer.cornerRadius = layer.cornerRadius
|
||||
gradientLayer.colors = [firstColor.cgColor, secondColor.cgColor]
|
||||
gradientLayer.name = "gradientLayer"
|
||||
layer.insertSublayer(gradientLayer, at: 0)
|
||||
}
|
||||
|
||||
func removeGradientLayer() {
|
||||
layer.sublayers?.removeAll { $0.name == "gradientLayer" }
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,17 @@
|
||||
1.0.54
|
||||
----------------
|
||||
- CXTDT-518373 Accessibility Voiceover is reading “Still Loading” after waiting for a short time in all the screens.
|
||||
- ONEAPP-6358 - Notification - Finished Development
|
||||
- ONEAPP-6315- ButtonIcon - Finished Development
|
||||
|
||||
1.0.53
|
||||
----------------
|
||||
- ONEAPP-4683 - Updated to accesibilityValue for the tabs position.
|
||||
|
||||
1.0.52
|
||||
----------------
|
||||
- ONEAPP-6244 - TitleLockup TitleLabel marked as .heading for Accessibility.
|
||||
|
||||
1.0.51
|
||||
----------------
|
||||
- ONEAPP-6239 - Loader is still showing when inactive.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user