// // Badge.swift // VDS // // Created by Matt Bruce on 9/22/22. // import Foundation import UIKit import VDSColorTokens import VDSFormControlsTokens import Combine /// Badges are visual labels used to convey status or highlight supplemental information. @objc(VDSBadge) open class Badge: View { //-------------------------------------------------- // MARK: - Enums //-------------------------------------------------- public enum FillColor: String, CaseIterable { case red, yellow, green, orange, blue, black, white } //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- /// Label used to render text open var label = Label().with { $0.lineBreakMode = .byTruncatingTail $0.setContentCompressionResistancePriority(.required, for: .vertical) $0.setContentHuggingPriority(.defaultHigh, for: .vertical) $0.setContentCompressionResistancePriority(.required, for: .horizontal) $0.setContentHuggingPriority(.defaultHigh, for: .horizontal) $0.textPosition = .left $0.textStyle = .boldBodySmall } /// This will render the badges fill color based on the available options. /// When used in conjunction with the surface prop, this fill color will change its tint automatically based on a light or dark surface. open var fillColor: FillColor = .red { didSet { setNeedsUpdate() }} /// The text that will be shown in the label. open var text: String = "" { didSet { setNeedsUpdate() }} /// When applied, this property takes a px value that will restrict the width at that point. open var maxWidth: CGFloat? { didSet { setNeedsUpdate() }} /// This will restrict the badge height to a specific number of lines. If the text overflows the allowable space, ellipsis will show. open var numberOfLines: Int = 1 { didSet { setNeedsUpdate() }} //-------------------------------------------------- // MARK: - Constraints //-------------------------------------------------- private var labelWidthConstraint: NSLayoutConstraint? private var widthConstraint: NSLayoutConstraint { // Determine which constraint to activate based on the maxWidth and minWidth properties if let maxWidth = maxWidth, maxWidth > minWidth { // Apply maximum width constraint if set and greater than minWidth return label.widthAnchor.constraint(lessThanOrEqualToConstant: maxWidth) } else { // Apply minimum width constraint return label.widthAnchor.constraint(greaterThanOrEqualToConstant: minWidth) } } //-------------------------------------------------- // MARK: - Configuration //-------------------------------------------------- private var minWidth: CGFloat = 23.0 private var labelInset: UIEdgeInsets = .init(top: 2, left: VDSLayout.Spacing.space1X.value, bottom: 2, right: VDSLayout.Spacing.space1X.value) /// ColorConfiguration that is mapped to the 'fillColor' for the surface. private var backgroundColorConfiguration: AnyColorable = { let config = KeyedColorConfiguration(keyPath: \.fillColor) config.setSurfaceColors(VDSColor.backgroundBrandhighlight, VDSColor.backgroundBrandhighlight, forKey: .red) config.setSurfaceColors(VDSColor.paletteYellow53, VDSColor.paletteYellow53, forKey: .yellow) config.setSurfaceColors(VDSColor.paletteGreen26, VDSColor.paletteGreen36, forKey: .green) config.setSurfaceColors(VDSColor.paletteOrange41, VDSColor.paletteOrange58, forKey: .orange) config.setSurfaceColors(VDSColor.paletteBlue38, VDSColor.paletteBlue46, forKey: .blue) config.setSurfaceColors(VDSColor.backgroundPrimaryDark, VDSColor.backgroundPrimaryDark, forKey: .black) config.setSurfaceColors(VDSColor.backgroundPrimaryLight, VDSColor.backgroundPrimaryLight, forKey: .white) return config.eraseToAnyColorable() }() /// ColorConfiguration for the Text. private var textColorConfiguration = ViewColorConfiguration() /// Updates the textColorConfiguration based on the fillColor. public func updateTextColorConfig() { textColorConfiguration.reset() switch fillColor { case .red, .black: textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOndark, VDSColor.elementsPrimaryOndark, forDisabled: false) textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOndark, VDSColor.elementsPrimaryOndark, forDisabled: true) case .yellow, .white: textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOnlight, forDisabled: false) textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOnlight, forDisabled: true) case .orange, .green, .blue: textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOndark, VDSColor.elementsPrimaryOnlight, forDisabled: false) textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOndark, VDSColor.elementsPrimaryOnlight, forDisabled: true) } } //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- open override func setup() { super.setup() accessibilityElements = [label] layer.cornerRadius = 2 addSubview(label) label.pinToSuperView(labelInset) } /// Resets to default settings. open override func reset() { super.reset() shouldUpdateView = false label.reset() label.lineBreakMode = .byTruncatingTail label.textPosition = .left label.textStyle = .boldBodySmall fillColor = .red text = "" maxWidth = nil numberOfLines = 1 shouldUpdateView = true setNeedsUpdate() } /// Function used to make changes to the View based off a change events or from local properties. open override func updateView() { super.updateView() updateTextColorConfig() backgroundColor = backgroundColorConfiguration.getColor(self) labelWidthConstraint?.isActive = false labelWidthConstraint = widthConstraint labelWidthConstraint?.isActive = true label.textColorConfiguration = textColorConfiguration.eraseToAnyColorable() label.numberOfLines = numberOfLines label.text = text label.surface = surface label.disabled = disabled } }