// // 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, CaseIterable { case large case small } @objc(VDSButton) open class Button: ButtonBase, Useable { //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- private var initialSetupPerformed = false //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- open override var availableSizes: [ButtonSize] { [.large, .small] } open var use: Use = .primary { didSet { setNeedsUpdate() }} open var size: ButtonSize = .large { didSet { setNeedsUpdate() }} private var _width: CGFloat? = nil open var width: CGFloat? { get { _width } set { if let newValue, newValue > size.minimumWidth { _width = newValue } else { _width = nil } setNeedsUpdate() } } open override var textColor: UIColor { textColorConfiguration.getColor(self) } /// TextStyle used on the titleLabel. open override var textStyle: TextStyle { size == .large ? TextStyle.boldBodyLarge : TextStyle.boldBodySmall } //-------------------------------------------------- // MARK: - Configuration Properties //-------------------------------------------------- // Background Color Config private var primaryBackgroundColorConfiguration = ControlColorConfiguration().with { $0.setSurfaceColors(VDSColor.backgroundPrimaryDark, VDSColor.backgroundPrimaryLight, forState: .normal) $0.setSurfaceColors(VDSColor.interactiveActiveOnlight, VDSColor.interactiveActiveOndark, forState: .highlighted) $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled) } private var secondaryBackgroundColorConfiguration = ControlColorConfiguration() private var backgroundColorConfiguration: ControlColorConfiguration{ use == .primary ? primaryBackgroundColorConfiguration : secondaryBackgroundColorConfiguration } // Border Color Config private var primaryBorderColorConfiguration = ControlColorConfiguration().with { $0.setSurfaceColors(VDSColor.elementsPrimaryOndark, VDSColor.elementsPrimaryOnlight, forState: .normal) $0.setSurfaceColors(VDSColor.elementsPrimaryOndark, VDSColor.elementsPrimaryOnlight, forState: .highlighted) $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled) } private var secondaryBorderColorConfiguration = ControlColorConfiguration().with { $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .normal) $0.setSurfaceColors(VDSColor.interactiveActiveOnlight, VDSColor.interactiveActiveOndark, forState: .highlighted) $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled) } private var borderColorConfiguration: ControlColorConfiguration { use == .primary ? primaryBorderColorConfiguration : secondaryBorderColorConfiguration } // Text Color Config private var primaryTextColorConfiguration = ControlColorConfiguration().with { $0.setSurfaceColors(VDSColor.elementsPrimaryOndark, VDSColor.elementsPrimaryOnlight, forState: .normal) $0.setSurfaceColors(VDSColor.elementsPrimaryOndark, VDSColor.elementsPrimaryOnlight, forState: .highlighted) $0.setSurfaceColors(VDSColor.elementsPrimaryOndark, VDSColor.elementsPrimaryOnlight, forState: .disabled) } private var secondaryTextColorConfiguration = ControlColorConfiguration().with { $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .normal) $0.setSurfaceColors(VDSColor.interactiveActiveOnlight, VDSColor.interactiveActiveOndark, forState: .highlighted) $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled) } private var textColorConfiguration: ControlColorConfiguration { use == .primary ? primaryTextColorConfiguration : secondaryTextColorConfiguration } //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- required public init() { super.init(frame: .zero) } public override init(frame: CGRect) { super.init(frame: .zero) } public required init?(coder: NSCoder) { super.init(coder: coder) } //-------------------------------------------------- // MARK: - Public Methods //-------------------------------------------------- open override func setup() { super.setup() isAccessibilityElement = true accessibilityTraits = .button } /// Resets to default settings. open override func reset() { super.reset() shouldUpdateView = false use = .primary width = nil size = .large shouldUpdateView = true setNeedsUpdate() } //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- /// The natural size for the receiving view, considering only properties of the view itself. open override var intrinsicContentSize: CGSize { guard let width, width > 0 else { var superSize = super.intrinsicContentSize superSize.height = size.height return superSize } return CGSize(width: width > size.minimumWidth ? width : size.minimumWidth, height: size.height) } /// Used to make changes to the View based off a change events or from local properties. open override func updateView() { super.updateView() let bgColor = backgroundColorConfiguration.getColor(self) let borderColor = borderColorConfiguration.getColor(self) let borderWidth = use == .secondary ? VDSFormControls.widthBorder : 0.0 let cornerRadius = size.cornerRadius let edgeInsets = size.edgeInsets backgroundColor = bgColor layer.borderColor = borderColor.cgColor layer.cornerRadius = cornerRadius layer.borderWidth = borderWidth contentEdgeInsets = edgeInsets invalidateIntrinsicContentSize() } } internal extension ButtonSize { var height: CGFloat { switch self { case .large: return 44 case .small: return 32 } } var cornerRadius: CGFloat { height / 2 } var minimumWidth: CGFloat { switch self { case .large: return 76 case .small: return 60 } } 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 } }