diff --git a/VDS/Components/Buttons/Button/Button.swift b/VDS/Components/Buttons/Button/Button.swift index 89a9440b..fb43f30f 100644 --- a/VDS/Components/Buttons/Button/Button.swift +++ b/VDS/Components/Buttons/Button/Button.swift @@ -11,26 +11,14 @@ import VDSColorTokens import VDSFormControlsTokens import Combine -public protocol Buttonable: UIControl, Surfaceable, Disabling { - var availableSizes: [ButtonSize] { get } - var text: String? { get set } - var intrinsicContentSize: CGSize { get } -} - public enum ButtonSize: String, Codable, CaseIterable { case large case small } @objc(VDSButton) -open class Button: UIButton, Buttonable, Handlerable, ViewProtocol, Resettable, Useable { +open class Button: BaseButton, Useable { - //-------------------------------------------------- - // MARK: - Combine Properties - //-------------------------------------------------- - public var subject = PassthroughSubject() - public var subscribers = Set() - //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- @@ -42,31 +30,21 @@ open class Button: UIButton, Buttonable, Handlerable, ViewProtocol, Resettable, //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - public var availableSizes: [ButtonSize] { [.large, .small] } + open override var availableSizes: [ButtonSize] { [.large, .small] } - open var text: String? { didSet { didChange() } } - open var use: Use = .primary { didSet { didChange() }} open var size: ButtonSize = .large { didSet { didChange() }} open var width: CGFloat? { didSet { didChange() }} - - open var surface: Surface = .light { didSet { didChange() }} - - open var disabled: Bool = false { didSet { isEnabled = !disabled } } - open override var isEnabled: Bool { - get { !disabled } - set { - if disabled != !newValue { - disabled = !newValue - } - isUserInteractionEnabled = isEnabled - didChange() - } + open override var textColor: UIColor { + buttonTitleColorConfiguration.getColor(self) + } + + open override var typograpicalStyle: TypographicalStyle { + size == .large ? TypographicalStyle.BoldBodyLarge : TypographicalStyle.BoldBodySmall } - //-------------------------------------------------- // MARK: - Configuration Properties @@ -76,23 +54,35 @@ open class Button: UIButton, Buttonable, Handlerable, ViewProtocol, Resettable, $0.primary.enabled.darkColor = VDSColor.backgroundPrimaryLight $0.primary.disabled.lightColor = VDSColor.interactiveDisabledOnlight $0.primary.disabled.darkColor = VDSColor.interactiveDisabledOndark + + $0.primaryHighlighted.lightColor = VDSColor.interactiveActiveOnlight + $0.primaryHighlighted.darkColor = VDSColor.interactiveActiveOndark $0.secondary.enabled.lightColor = UIColor.clear $0.secondary.enabled.darkColor = UIColor.clear $0.secondary.disabled.lightColor = UIColor.clear $0.secondary.disabled.darkColor = UIColor.clear + + $0.secondaryHighlighted.lightColor = UIColor.clear + $0.secondaryHighlighted.darkColor = UIColor.clear } - + private var buttonBorderColorConfiguration = UseableColorConfiguration().with { $0.primary.enabled.lightColor = VDSColor.elementsPrimaryOndark $0.primary.enabled.darkColor = VDSColor.elementsPrimaryOnlight $0.primary.disabled.lightColor = VDSColor.interactiveDisabledOnlight $0.primary.disabled.darkColor = VDSColor.interactiveDisabledOndark - + + $0.primaryHighlighted.lightColor = VDSColor.elementsPrimaryOndark + $0.primaryHighlighted.darkColor = VDSColor.elementsPrimaryOnlight + $0.secondary.enabled.lightColor = VDSColor.elementsPrimaryOnlight $0.secondary.enabled.darkColor = VDSColor.elementsPrimaryOndark $0.secondary.disabled.lightColor = VDSColor.interactiveDisabledOnlight $0.secondary.disabled.darkColor = VDSColor.interactiveDisabledOndark + + $0.secondaryHighlighted.lightColor = VDSColor.interactiveActiveOnlight + $0.secondaryHighlighted.darkColor = VDSColor.interactiveActiveOndark } private var buttonTitleColorConfiguration = UseableColorConfiguration().with { @@ -100,11 +90,17 @@ open class Button: UIButton, Buttonable, Handlerable, ViewProtocol, Resettable, $0.primary.enabled.darkColor = VDSColor.elementsPrimaryOnlight $0.primary.disabled.lightColor = VDSColor.elementsPrimaryOndark $0.primary.disabled.darkColor = VDSColor.elementsPrimaryOnlight - + + $0.primaryHighlighted.lightColor = VDSColor.elementsPrimaryOndark + $0.primaryHighlighted.darkColor = VDSColor.elementsPrimaryOnlight + $0.secondary.enabled.lightColor = VDSColor.elementsPrimaryOnlight $0.secondary.enabled.darkColor = VDSColor.elementsPrimaryOndark $0.secondary.disabled.lightColor = VDSColor.interactiveDisabledOnlight $0.secondary.disabled.darkColor = VDSColor.interactiveDisabledOndark + + $0.secondaryHighlighted.lightColor = VDSColor.interactiveActiveOnlight + $0.secondaryHighlighted.darkColor = VDSColor.interactiveActiveOndark } //-------------------------------------------------- @@ -112,91 +108,57 @@ open class Button: UIButton, Buttonable, Handlerable, ViewProtocol, Resettable, //-------------------------------------------------- required public init() { super.init(frame: .zero) - initialSetup() } public override init(frame: CGRect) { super.init(frame: .zero) - initialSetup() } public required init?(coder: NSCoder) { super.init(coder: coder) - initialSetup() } //-------------------------------------------------- // MARK: - Public Functions //-------------------------------------------------- - open func initialSetup() { - if !initialSetupPerformed { - backgroundColor = .clear - translatesAutoresizingMaskIntoConstraints = false - accessibilityCustomActions = [] - accessibilityTraits = .staticText - setup() - setupDidChangeEvent() - updateView() - } - } - open func setup() { - translatesAutoresizingMaskIntoConstraints = false - titleLabel?.adjustsFontSizeToFitWidth = false - titleLabel?.lineBreakMode = .byTruncatingTail - + open override func setup() { + super.setup() //only 1 of the 2 widths can be on at the same time widthConstraint = widthAnchor.constraint(equalToConstant: 0) minWidthConstraint = widthAnchor.constraint(greaterThanOrEqualToConstant: size.minimumWidth) //height - heightConstraint = heightAnchor.constraint(equalToConstant: size.height) + heightConstraint = heightAnchor.constraint(equalToConstant: 0) heightConstraint?.isActive = true } - open func reset() { - surface = .light - disabled = false + open override func reset() { + super.reset() use = .primary width = nil size = .large - accessibilityCustomActions = [] - accessibilityTraits = .staticText } //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- - override open var intrinsicContentSize: CGSize { - let intrinsicContentSize = super.intrinsicContentSize - - let adjustedWidth = intrinsicContentSize.width + titleEdgeInsets.left + titleEdgeInsets.right - let adjustedHeight = intrinsicContentSize.height + titleEdgeInsets.top + titleEdgeInsets.bottom - - return CGSize(width: adjustedWidth, height: adjustedHeight) - } - - open func updateView() { - + open override func updateView() { + super.updateView() let bgColor = buttonBackgroundColorConfiguration.getColor(self) let borderColor = buttonBorderColorConfiguration.getColor(self) - let titleColor = buttonTitleColorConfiguration.getColor(self) let borderWidth = use == .secondary ? 1.0 : 0.0 let buttonHeight = size.height - let cornerRadius = buttonHeight / 2 + let cornerRadius = size.cornerRadius let minWidth = size.minimumWidth - let font = size == .large ? TypographicalStyle.BoldBodyLarge.font : TypographicalStyle.BoldBodySmall.font let edgeInsets = size.edgeInsets - - setTitle(text ?? "No Text", for: .normal) - titleLabel?.font = font + backgroundColor = bgColor - setTitleColor(titleColor, for: .normal) layer.borderColor = borderColor.cgColor layer.cornerRadius = cornerRadius layer.borderWidth = borderWidth contentEdgeInsets = edgeInsets - + minWidthConstraint?.constant = minWidth heightConstraint?.constant = buttonHeight @@ -215,17 +177,23 @@ open class Button: UIButton, Buttonable, Handlerable, ViewProtocol, Resettable, //-------------------------------------------------- private class UseableColorConfiguration: ObjectColorable { - typealias ObjectType = Disabling & Surfaceable & Useable + typealias ObjectType = Buttonable & Useable public var primary = DisabledSurfaceColorConfiguration() public var secondary = DisabledSurfaceColorConfiguration() + + public var primaryHighlighted = SurfaceColorConfiguration() + public var secondaryHighlighted = SurfaceColorConfiguration() required public init(){} public func getColor(_ object: ObjectType) -> UIColor { - return object.use == .primary ? primary.getColor(object) : secondary.getColor(object) + if object.isHighlighted { + return object.use == .primary ? primaryHighlighted.getColor(object) : secondaryHighlighted.getColor(object) + } else { + return object.use == .primary ? primary.getColor(object) : secondary.getColor(object) + } } - } - + } } extension ButtonSize { @@ -239,6 +207,10 @@ extension ButtonSize { } } + public var cornerRadius: CGFloat { + height / 2 + } + public var minimumWidth: CGFloat { switch self { case .large: @@ -267,7 +239,6 @@ extension ButtonSize { } extension Use { - public var color: UIColor { return self == .primary ? VDSColor.backgroundPrimaryDark : .clear } diff --git a/VDS/Components/Buttons/TextLink/TextLink.swift b/VDS/Components/Buttons/TextLink/TextLink.swift index f3e8a6f3..fbe3ab0f 100644 --- a/VDS/Components/Buttons/TextLink/TextLink.swift +++ b/VDS/Components/Buttons/TextLink/TextLink.swift @@ -12,23 +12,40 @@ import VDSFormControlsTokens import Combine @objc(VDSTextLink) -open class TextLink: Control, Buttonable { +open class TextLink: BaseButton { //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- private var heightConstraint: NSLayoutConstraint? - private var label = Label() - + private var lineHeightConstraint: NSLayoutConstraint? + //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - open var text: String? { didSet { didChange() } } - open var size: ButtonSize = .large { didSet { didChange() }} - public var availableSizes: [ButtonSize] { [.large, .small] } + open override var availableSizes: [ButtonSize] { [.large, .small] } + open override var typograpicalStyle: TypographicalStyle { + size == .large ? TypographicalStyle.BodyLarge : TypographicalStyle.BodySmall + } + + open override var textColor: UIColor { + textColorConfiguration.getColor(self) + } + + private var textColorConfiguration = HighlightDisabledSurfaceColorConfiguration().with { + $0.disabled.lightColor = VDSColor.elementsSecondaryOnlight + $0.disabled.darkColor = VDSColor.elementsSecondaryOndark + $0.enabled.lightColor = VDSColor.elementsPrimaryOnlight + $0.enabled.darkColor = VDSColor.elementsPrimaryOndark + + $0.highlighted.lightColor = VDSColor.interactiveActiveOnlight + $0.highlighted.darkColor = VDSColor.interactiveActiveOndark + + } + private var height: CGFloat { switch size { case .large: @@ -43,49 +60,42 @@ open class TextLink: Control, Buttonable { //-------------------------------------------------- required public init() { super.init(frame: .zero) - initialSetup() } public override init(frame: CGRect) { super.init(frame: .zero) - initialSetup() } public required init?(coder: NSCoder) { super.init(coder: coder) - initialSetup() + } + + private var line = UIView().with { + $0.translatesAutoresizingMaskIntoConstraints = false } //-------------------------------------------------- // MARK: - Public Functions //-------------------------------------------------- - open override func initialSetup() { - super.initialSetup() - } - open override func setup() { super.setup() - addSubview(label) - - //add tapGesture to self - publisher(for: UITapGestureRecognizer()).sink { [weak self] _ in - self?.sendActions(for: .touchUpInside) - }.store(in: &subscribers) - - //pin stackview to edges - label.pinToSuperView() - label.numberOfLines = 1 + if let titleLabel { + addSubview(line) + line.pinLeading(titleLabel.leadingAnchor) + line.pinTrailing(titleLabel.trailingAnchor) + line.pinTop(titleLabel.bottomAnchor, 1) + lineHeightConstraint = line.heightAnchor.constraint(equalToConstant: 1.0) + lineHeightConstraint?.isActive = true + } + heightConstraint = heightAnchor.constraint(equalToConstant: height) heightConstraint?.isActive = true } open override func reset() { super.reset() - label.reset() - size = .large - text = nil - + size = .large accessibilityCustomActions = [] accessibilityTraits = .staticText } @@ -93,17 +103,32 @@ open class TextLink: Control, Buttonable { //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- - override open var intrinsicContentSize: CGSize { - return CGSize(width: label.intrinsicContentSize.width, height: height) - } - open override func updateView() { - label.surface = surface - label.disabled = disabled - label.typograpicalStyle = size == .large ? TypographicalStyle.BodyLarge : TypographicalStyle.BodySmall - label.text = text ?? "" - label.attributes = [UnderlineLabelAttribute(location: 0, length: label.text!.count)] + //need to set the properties so the super class + //can render out the label correctly heightConstraint?.constant = height + lineHeightConstraint?.constant = isHighlighted ? 2.0 : 1.0 + line.backgroundColor = textColor + + //always call last so the label is rendered + super.updateView() } } + +class HighlightDisabledSurfaceColorConfiguration: ObjectColorable { + typealias ObjectType = Buttonable + public var highlighted = SurfaceColorConfiguration() + public var disabled = SurfaceColorConfiguration() + public var enabled = SurfaceColorConfiguration() + + required public init(){} + + public func getColor(_ object: any ObjectType) -> UIColor { + if object.isHighlighted { + return highlighted.getColor(object) + } else { + return object.disabled ? disabled.getColor(object) : enabled.getColor(object) + } + } +} diff --git a/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift b/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift index 577ff332..e4733a1b 100644 --- a/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift +++ b/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift @@ -16,103 +16,103 @@ public enum TextLinkCaretPosition: String, CaseIterable { } @objc(VDSTextLinkCaret) -open class TextLinkCaret: Control, Buttonable { +open class TextLinkCaret: BaseButton { //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- private var heightConstraint: NSLayoutConstraint? - private var label = Label().with { - $0.typograpicalStyle = TypographicalStyle.BoldBodyLarge + open override var typograpicalStyle: TypographicalStyle { + TypographicalStyle.BoldBodyLarge } - + private var caretView = CaretView().with { $0.size = CaretView.CaretSize.small(.vertical) $0.lineWidth = 2 } + private var imageAttribute: ImageLabelAttribute? + + open override var attributes: [any LabelAttributeModel]? { + guard let imageAttribute else { return nil } + return [imageAttribute] + } //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - public var availableSizes: [ButtonSize] { [.large] } - - open var text: String? { didSet { didChange() } } + public override var availableSizes: [ButtonSize] { [.large] } open var iconPosition: TextLinkCaretPosition = .right { didSet { didChange() } } private var height: CGFloat { 44 } + + private var _text: String? + + open override var text: String? { + get{ _text } + set { + var updatedText = newValue ?? "" + updatedText = iconPosition == .right ? "\(updatedText) " : " \(updatedText)" + _text = updatedText + didChange() + } + } + + open override var textColor: UIColor { + textColorConfiguration.getColor(self) + } + private var textColorConfiguration = HighlightDisabledSurfaceColorConfiguration().with { + $0.disabled.lightColor = VDSColor.elementsSecondaryOnlight + $0.disabled.darkColor = VDSColor.elementsSecondaryOndark + $0.enabled.lightColor = VDSColor.elementsPrimaryOnlight + $0.enabled.darkColor = VDSColor.elementsPrimaryOndark + + $0.highlighted.lightColor = VDSColor.interactiveActiveOnlight + $0.highlighted.darkColor = VDSColor.interactiveActiveOndark + } + //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- required public init() { super.init(frame: .zero) - initialSetup() } public override init(frame: CGRect) { super.init(frame: .zero) - initialSetup() } public required init?(coder: NSCoder) { super.init(coder: coder) - initialSetup() } //-------------------------------------------------- // MARK: - Public Functions //-------------------------------------------------- - open override func initialSetup() { - super.initialSetup() - } - open override func setup() { super.setup() - //add tapGesture to self - publisher(for: UITapGestureRecognizer()).sink { [weak self] _ in - self?.sendActions(for: .touchUpInside) - }.store(in: &subscribers) //constraints heightAnchor.constraint(greaterThanOrEqualToConstant: height).isActive = true let size = caretView.size!.dimensions() caretView.frame = .init(x: 0, y: 0, width: size.width, height: size.height) - addSubview(label) - label.pinToSuperView() - - label.numberOfLines = 1 - } - //-------------------------------------------------- - // MARK: - Constraints - //-------------------------------------------------- - private var caretLeadingConstraint: NSLayoutConstraint? - private var caretTrailingConstraint: NSLayoutConstraint? - private var labelConstraint: NSLayoutConstraint? - open override func reset() { super.reset() - label.reset() - - label.typograpicalStyle = TypographicalStyle.BoldBodyLarge - text = nil iconPosition = .right - - accessibilityCustomActions = [] - accessibilityTraits = .staticText } //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- override open var intrinsicContentSize: CGSize { - var itemWidth = label.intrinsicContentSize.width + var itemWidth = super.intrinsicContentSize.width if let caretWidth = caretView.size?.dimensions().width { itemWidth += caretWidth } @@ -121,22 +121,19 @@ open class TextLinkCaret: Control, Buttonable { open override func updateView() { - let updatedText = text ?? "" - + var updatedText = text ?? "" caretView.surface = surface caretView.disabled = disabled caretView.direction = iconPosition == .right ? CaretView.Direction.right : CaretView.Direction.left let image = caretView.getImage() - let location = iconPosition == .right ? updatedText.count + 1 : 0 - let textColor = label.textColorConfiguration.getColor(self) - let imageAttribute = ImageLabelAttribute(location: location, + let location = iconPosition == .right ? updatedText.count : 0 + + imageAttribute = ImageLabelAttribute(location: location, image: image, tintColor: textColor) - label.surface = surface - label.disabled = disabled - label.text = iconPosition == .right ? "\(updatedText) " : " \(updatedText)" - label.attributes = [imageAttribute] + + super.updateView() } }