// // Button.swift // VDS // // Created by Jarrod Courtney on 9/16/22. // import Foundation import UIKit import VDSColorTokens import VDSFormControlsTokens import Combine public enum ButtonSize: String, Codable, CaseIterable { case large case small } @objc(VDSButton) open class Button: UIButton, Handlerable, ViewProtocol, Resettable, Useable { //-------------------------------------------------- // MARK: - Combine Properties //-------------------------------------------------- public var subject = PassthroughSubject() public var subscribers = Set() //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- private var minWidthConstraint: NSLayoutConstraint? private var widthConstraint: NSLayoutConstraint? private var heightConstraint: NSLayoutConstraint? //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- 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() } } //-------------------------------------------------- // MARK: - Configuration Properties //-------------------------------------------------- private var buttonBackgroundColorConfiguration = UseableColorConfiguration().with { $0.primary.enabled.lightColor = VDSColor.backgroundPrimaryDark $0.primary.enabled.darkColor = VDSColor.backgroundPrimaryLight $0.primary.disabled.lightColor = VDSColor.interactiveDisabledOnlight $0.primary.disabled.darkColor = VDSColor.interactiveDisabledOndark $0.secondary.enabled.lightColor = UIColor.clear $0.secondary.enabled.darkColor = UIColor.clear $0.secondary.disabled.lightColor = UIColor.clear $0.secondary.disabled.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.secondary.enabled.lightColor = VDSColor.elementsPrimaryOnlight $0.secondary.enabled.darkColor = VDSColor.elementsPrimaryOndark $0.secondary.disabled.lightColor = VDSColor.interactiveDisabledOnlight $0.secondary.disabled.darkColor = VDSColor.interactiveDisabledOndark } private var buttonTitleColorConfiguration = UseableColorConfiguration().with { $0.primary.enabled.lightColor = VDSColor.elementsPrimaryOndark $0.primary.enabled.darkColor = VDSColor.elementsPrimaryOnlight $0.primary.disabled.lightColor = VDSColor.elementsPrimaryOndark $0.primary.disabled.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 } //-------------------------------------------------- // 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 func initialSetup() { backgroundColor = .clear translatesAutoresizingMaskIntoConstraints = false accessibilityCustomActions = [] accessibilityTraits = .staticText setup() } open func setup() { translatesAutoresizingMaskIntoConstraints = false titleLabel?.adjustsFontSizeToFitWidth = false titleLabel?.lineBreakMode = .byTruncatingTail //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?.isActive = true } open func reset() { surface = .light disabled = false use = .primary width = nil size = .large accessibilityCustomActions = [] accessibilityTraits = .staticText } open override func layoutSubviews() { super.layoutSubviews() updateView() } //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- open func 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 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 if let width, width > minWidth { widthConstraint?.constant = width widthConstraint?.isActive = true minWidthConstraint?.isActive = false } else { widthConstraint?.isActive = false minWidthConstraint?.isActive = true } } //-------------------------------------------------- // MARK: - PRIVATE //-------------------------------------------------- private class UseableColorConfiguration: ObjectColorable { typealias ObjectType = Disabling & Surfaceable & Useable public var primary = DisabledSurfaceColorConfiguration() public var secondary = DisabledSurfaceColorConfiguration() required public init(){} public func getColor(_ object: ObjectType) -> UIColor { return object.use == .primary ? primary.getColor(object) : secondary.getColor(object) } } } extension ButtonSize { public var height: CGFloat { switch self { case .large: return 44 case .small: return 32 } } public var minimumWidth: CGFloat { switch self { case .large: return 76 case .small: return 60 } } public var edgeInsets: UIEdgeInsets { var verticalPadding = 0.0 var horizontalPadding = 0.0 switch self { case .large: verticalPadding = 12 horizontalPadding = 24 break case .small: verticalPadding = 8 horizontalPadding = 16 break } return UIEdgeInsets(top: verticalPadding, left: horizontalPadding, bottom: verticalPadding, right: horizontalPadding) } } extension Use { public var color: UIColor { return self == .primary ? VDSColor.backgroundPrimaryDark : .clear } }