diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index f48fbe28..416c3bdb 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -47,6 +47,8 @@ EA3C3B4C2894823E000CA526 /* AnyProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3C3B4B2894823E000CA526 /* AnyProxy.swift */; }; EA4DB18528CA967F00103EE3 /* SelectorGroupHandlerBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA4DB18428CA967F00103EE3 /* SelectorGroupHandlerBase.swift */; }; EA4DB2FD28D3D0CA00103EE3 /* AnyEquatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA4DB2FC28D3D0CA00103EE3 /* AnyEquatable.swift */; }; + EA4DB30028DCBC9900103EE3 /* BadgeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA4DB2FF28DCBC9900103EE3 /* BadgeModel.swift */; }; + EA4DB30228DCBCA500103EE3 /* Badge.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA4DB30128DCBCA500103EE3 /* Badge.swift */; }; EA84F6B128B94A2500D67ABC /* CodableColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA84F6B028B94A2500D67ABC /* CodableColor.swift */; }; EA89200228AECF2A006B9984 /* UIButton+Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89200128AECF2A006B9984 /* UIButton+Publisher.swift */; }; EA89200428AECF4B006B9984 /* UITextField+Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89200328AECF4B006B9984 /* UITextField+Publisher.swift */; }; @@ -147,6 +149,8 @@ EA3C3B4B2894823E000CA526 /* AnyProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyProxy.swift; sourceTree = ""; }; EA4DB18428CA967F00103EE3 /* SelectorGroupHandlerBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectorGroupHandlerBase.swift; sourceTree = ""; }; EA4DB2FC28D3D0CA00103EE3 /* AnyEquatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyEquatable.swift; sourceTree = ""; }; + EA4DB2FF28DCBC9900103EE3 /* BadgeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeModel.swift; sourceTree = ""; }; + EA4DB30128DCBCA500103EE3 /* Badge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Badge.swift; sourceTree = ""; }; EA84F6B028B94A2500D67ABC /* CodableColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodableColor.swift; sourceTree = ""; }; EA89200128AECF2A006B9984 /* UIButton+Publisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIButton+Publisher.swift"; sourceTree = ""; }; EA89200328AECF4B006B9984 /* UITextField+Publisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextField+Publisher.swift"; sourceTree = ""; }; @@ -299,6 +303,7 @@ isa = PBXGroup; children = ( 5FC35BE128D513EB004EBEAC /* Button */, + EA4DB2FE28DCBC1900103EE3 /* Badge */, EAF7F092289985E200B287F5 /* Checkbox */, EA3362412892EF700071C351 /* Label */, EA89200B28B530F0006B9984 /* RadioBox */, @@ -415,6 +420,15 @@ path = Label; sourceTree = ""; }; + EA4DB2FE28DCBC1900103EE3 /* Badge */ = { + isa = PBXGroup; + children = ( + EA4DB2FF28DCBC9900103EE3 /* BadgeModel.swift */, + EA4DB30128DCBCA500103EE3 /* Badge.swift */, + ); + path = Badge; + sourceTree = ""; + }; EA89200B28B530F0006B9984 /* RadioBox */ = { isa = PBXGroup; children = ( @@ -631,6 +645,7 @@ EA3361C9289054C50071C351 /* Surfaceable.swift in Sources */, EA3361A2288B1E840071C351 /* ToggleModel.swift in Sources */, EA1F265D28B944F00033E859 /* CollectionView.swift in Sources */, + EA4DB30228DCBCA500103EE3 /* Badge.swift in Sources */, EA3362432892EFF20071C351 /* LabelModel.swift in Sources */, EA33624728931B050071C351 /* Initable.swift in Sources */, EAF7F0A4289B017C00B287F5 /* LabelAttributeModel.swift in Sources */, @@ -652,6 +667,7 @@ EAB1D29A28A5611D00DAE764 /* SelectorGroupModelable.swift in Sources */, EAF7F0BB289D80ED00B287F5 /* Modelable.swift in Sources */, EA89201528B56CF4006B9984 /* RadioBoxGroup.swift in Sources */, + EA4DB30028DCBC9900103EE3 /* BadgeModel.swift in Sources */, EAF7F09E289AAEC000B287F5 /* Constants.swift in Sources */, EA1F266528B945070033E859 /* RadioSwatch.swift in Sources */, EA4DB18528CA967F00103EE3 /* SelectorGroupHandlerBase.swift in Sources */, diff --git a/VDS/Classes/ColorConfiguration.swift b/VDS/Classes/ColorConfiguration.swift index 65d84f41..8633185e 100644 --- a/VDS/Classes/ColorConfiguration.swift +++ b/VDS/Classes/ColorConfiguration.swift @@ -112,7 +112,9 @@ open class BinarySurfaceColorConfiguration() /// /// //True diff --git a/VDS/Components/Badge/Badge.swift b/VDS/Components/Badge/Badge.swift new file mode 100644 index 00000000..1f3fe2b2 --- /dev/null +++ b/VDS/Components/Badge/Badge.swift @@ -0,0 +1,185 @@ +// +// Badge.swift +// VDS +// +// Created by Matt Bruce on 9/22/22. +// + +import Foundation +import UIKit +import VDSColorTokens +import VDSFormControlsTokens +import Combine + +public class Badge: BadgeBase{} + +open class BadgeBase: View { + + private var label = Label() + + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + @Proxy(\.model.fillColor) + open var fillColor: BadgeFillColor + + @Proxy(\.model.text) + open var text: String + + @Proxy(\.model.maxWidth) + open var maxWidth: CGFloat? + + @Proxy(\.model.numberOfLines) + open var numberOfLines: Int + + @Proxy(\.model.accessibilityHintEnabled) + open var accessibilityHintEnabled: String? + + @Proxy(\.model.accessibilityHintDisabled) + open var accessibilityHintDisabled: String? + + @Proxy(\.model.accessibilityValueEnabled) + open var accessibilityValueEnabled: String? + + @Proxy(\.model.accessibilityValueDisabled) + open var accessibilityValueDisabled: String? + + @Proxy(\.model.accessibilityLabelEnabled) + open var accessibilityLabelEnabled: String? + + @Proxy(\.model.accessibilityLabelDisabled) + open var accessibilityLabelDisabled: String? + + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + private var maxWidthConstraint: NSLayoutConstraint? + private var minWidthConstraint: NSLayoutConstraint? + + //functions + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + open override func setup() { + super.setup() + + isAccessibilityElement = true + accessibilityTraits = .staticText + addSubview(label) + + layer.cornerRadius = 2 + label.adjustsFontSizeToFitWidth = false + label.lineBreakMode = .byTruncatingTail + + label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 4).isActive = true + label.topAnchor.constraint(equalTo: topAnchor, constant: 2).isActive = true + trailingAnchor.constraint(greaterThanOrEqualTo: label.trailingAnchor, constant: 4).isActive = true + bottomAnchor.constraint(greaterThanOrEqualTo: label.bottomAnchor, constant: 2).isActive = true + + maxWidthConstraint = label.widthAnchor.constraint(lessThanOrEqualToConstant: 100) + minWidthConstraint = label.widthAnchor.constraint(greaterThanOrEqualToConstant: 23) + minWidthConstraint?.isActive = true + } + + public override func reset() { + super.reset() + setAccessibilityLabel() + } + + //-------------------------------------------------- + // MARK: - Configuration + //-------------------------------------------------- + public func backgroundColor(for fillColor: BadgeFillColor) -> UIColor { + var config: SurfaceColorConfiguration + switch model.fillColor { + case .red: + config = SurfaceColorConfiguration().with { + $0.lightColor = VDSColor.backgroundBrandhighlight + $0.darkColor = VDSColor.backgroundBrandhighlight + } + case .yellow: + config = SurfaceColorConfiguration().with { + $0.lightColor = VDSColor.paletteYellow62 + $0.darkColor = VDSColor.paletteYellow62 + } + case .green: + config = SurfaceColorConfiguration().with { + $0.lightColor = VDSColor.paletteGreen26 + $0.darkColor = VDSColor.paletteGreen34 + } + case .orange: + config = SurfaceColorConfiguration().with { + $0.lightColor = VDSColor.paletteOrange39 + $0.darkColor = VDSColor.paletteOrange46 + } + case .blue: + config = SurfaceColorConfiguration().with { + $0.lightColor = VDSColor.paletteBlue35 + $0.darkColor = VDSColor.paletteBlue45 + } + case .black: + config = SurfaceColorConfiguration().with { + $0.lightColor = VDSColor.paletteBlack + $0.darkColor = VDSColor.paletteBlack + } + case .white: + config = SurfaceColorConfiguration().with { + $0.lightColor = VDSColor.paletteWhite + $0.darkColor = VDSColor.paletteWhite + } + } + + return config.getColor(model) + } + + public func textColorConfiguration(for fillColor: BadgeFillColor) -> DisabledSurfaceColorConfiguration { + + switch fillColor { + + case .red, .black: + return DisabledSurfaceColorConfiguration().with { + $0.disabled.lightColor = VDSColor.elementsPrimaryOndark + $0.disabled.darkColor = VDSColor.elementsPrimaryOndark + $0.enabled.lightColor = VDSColor.elementsPrimaryOndark + $0.enabled.darkColor = VDSColor.elementsPrimaryOndark + } + case .yellow, .white: + return DisabledSurfaceColorConfiguration().with { + $0.disabled.lightColor = VDSColor.elementsPrimaryOnlight + $0.disabled.darkColor = VDSColor.elementsPrimaryOnlight + $0.enabled.lightColor = VDSColor.elementsPrimaryOnlight + $0.enabled.darkColor = VDSColor.elementsPrimaryOnlight + } + case .orange, .green, .blue: + return DisabledSurfaceColorConfiguration().with { + $0.disabled.lightColor = VDSColor.elementsPrimaryOndark + $0.disabled.darkColor = VDSColor.elementsPrimaryOnlight + $0.enabled.lightColor = VDSColor.elementsPrimaryOndark + $0.enabled.darkColor = VDSColor.elementsPrimaryOnlight + } + } + } + + + //-------------------------------------------------- + // MARK: - State + //-------------------------------------------------- + open override func updateView(viewModel: ModelType) { + backgroundColor = backgroundColor(for: viewModel.fillColor) + + label.textColorConfiguration = textColorConfiguration(for: viewModel.fillColor) + label.numberOfLines = viewModel.numberOfLines + + if let maxWidth = viewModel.maxWidth, let minWidth = minWidthConstraint?.constant, maxWidth > minWidth { + maxWidthConstraint?.constant = maxWidth + maxWidthConstraint?.isActive = true + } else { + maxWidthConstraint?.isActive = false + } + label.set(with: viewModel.label) + setAccessibilityLabel(!viewModel.disabled) + } + +} + diff --git a/VDS/Components/Badge/BadgeModel.swift b/VDS/Components/Badge/BadgeModel.swift new file mode 100644 index 00000000..93876157 --- /dev/null +++ b/VDS/Components/Badge/BadgeModel.swift @@ -0,0 +1,53 @@ +// +// BadgeModel.swift +// VDS +// +// Created by Matt Bruce on 9/22/22. +// + +import Foundation +import UIKit +import VDSColorTokens + +public enum BadgeFillColor: String, Codable, CaseIterable { + case red, yellow, green, orange, blue, black, white +} + +public protocol BadgeModel: Modelable, Accessable { + var fillColor: BadgeFillColor { get set } + var text: String { get set } + var maxWidth: CGFloat? { get set } + var numberOfLines: Int { get set } +} + +extension BadgeModel { + public var label: DefaultLabelModel { + var model = DefaultLabelModel() + model.textPosition = .left + model.typograpicalStyle = .BoldBodySmall + model.text = text + model.surface = surface + model.disabled = disabled + return model + } +} + +public struct DefaultBadgeModel: BadgeModel { + public var id = UUID() + public var fillColor: BadgeFillColor = .red + public var text: String = "" + public var maxWidth: CGFloat? + public var numberOfLines: Int = 1 + + public var surface: Surface = .light + public var disabled: Bool = false + + public var accessibilityHintEnabled: String? + public var accessibilityHintDisabled: String? + public var accessibilityValueEnabled: String? + public var accessibilityValueDisabled: String? + public var accessibilityLabelEnabled: String? + public var accessibilityLabelDisabled: String? + + public init() { } +} diff --git a/VDS/Components/Label/Label.swift b/VDS/Components/Label/Label.swift index 0e6c037d..93eb5754 100644 --- a/VDS/Components/Label/Label.swift +++ b/VDS/Components/Label/Label.swift @@ -67,14 +67,12 @@ open class LabelBase: UILabel, ModelHandlerable, ViewProt //-------------------------------------------------- // MARK: - Configuration Properties //-------------------------------------------------- - private var textColorConfiguration: DisabledSurfaceColorConfiguration = { - return DisabledSurfaceColorConfiguration().with { - $0.disabled.lightColor = VDSColor.elementsSecondaryOnlight - $0.disabled.darkColor = VDSColor.elementsSecondaryOndark - $0.enabled.lightColor = VDSColor.elementsPrimaryOnlight - $0.enabled.darkColor = VDSColor.elementsPrimaryOndark - } - } () + public var textColorConfiguration = DisabledSurfaceColorConfiguration().with { + $0.disabled.lightColor = VDSColor.elementsSecondaryOnlight + $0.disabled.darkColor = VDSColor.elementsSecondaryOndark + $0.enabled.lightColor = VDSColor.elementsPrimaryOnlight + $0.enabled.darkColor = VDSColor.elementsPrimaryOndark + } //-------------------------------------------------- // MARK: - Initializers @@ -100,7 +98,7 @@ open class LabelBase: UILabel, ModelHandlerable, ViewProt super.init(coder: coder) initialSetup() } - + //-------------------------------------------------- // MARK: - Public Functions //-------------------------------------------------- @@ -127,14 +125,14 @@ open class LabelBase: UILabel, ModelHandlerable, ViewProt accessibilityTraits = .staticText numberOfLines = 0 } - + //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- open func updateView(viewModel: ModelType) { textAlignment = viewModel.textPosition.textAlignment textColor = textColorConfiguration.getColor(viewModel) - + if let vdsFont = viewModel.font { font = vdsFont } else { @@ -200,6 +198,7 @@ open class LabelBase: UILabel, ModelHandlerable, ViewProt $0.maximumLineHeight = lineHeight $0.minimumLineHeight = lineHeight $0.alignment = viewModel.textPosition.textAlignment + $0.lineBreakMode = lineBreakMode } attributedString.addAttribute(.baselineOffset, value: baselineOffset, range: entireRange) attributedString.addAttribute( .paragraphStyle, value: paragraph, range: entireRange) @@ -207,6 +206,7 @@ open class LabelBase: UILabel, ModelHandlerable, ViewProt } else if viewModel.textPosition != .left { let paragraph = NSMutableParagraphStyle().with { $0.alignment = viewModel.textPosition.textAlignment + $0.lineBreakMode = lineBreakMode } attributedString.addAttribute( .paragraphStyle, value: paragraph, range: entireRange) }