// // Tab.swift // VDS // // Created by Matt Bruce on 5/18/23. // import Foundation import VDSColorTokens import Combine extension Tabs { @objc(VDSTab) open class Tab: Control { //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- ///position of the tab open var index: Int = 0 ///label to write out the text open var label: Label = Label().with { $0.isUserInteractionEnabled = false $0.setContentCompressionResistancePriority(.required, for: .horizontal) $0.setContentHuggingPriority(.required, for: .horizontal) } ///orientation of the tabs open var orientation: Tabs.Orientation = .horizontal { didSet { setNeedsUpdate() } } ///Size for tab open var size: Tabs.Size = .medium { didSet { setNeedsUpdate() } } ///Text position left or center open var textPosition: TextPosition = .left { didSet { setNeedsUpdate() } } ///Sets the Position of the Selected/Hover Border Accent for All Tabs. open var indicatorPosition: Tabs.IndicatorPosition = .bottom { didSet { setNeedsUpdate() } } ///If provided, it will set fixed width for this Tab. open var width: CGFloat? { didSet { setNeedsUpdate() } } ///The text label of the tab. open var text: String = "Tab" { didSet { setNeedsUpdate() } } ///Minimum width for the tab open var minWidth: CGFloat = 44.0 { didSet { setNeedsUpdate() } } //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- private var textStyle: TextStyle { if size == .medium { return .boldBodyLarge } else { //specs show that the font size shouldn't change however boldTitleSmall does //change point size between iPad/iPhone. This is a "fix" so each device will //load the correct pointSize return UIDevice.isIPad ? .boldTitleSmall : .boldTitleMedium } } private var labelWidthConstraint: NSLayoutConstraint? private var labelLeadingConstraint: NSLayoutConstraint? private var labelTopConstraint: NSLayoutConstraint? private var labelBottomConstraint: NSLayoutConstraint? //-------------------------------------------------- // MARK: - Configuration //-------------------------------------------------- private var textColorConfiguration: SurfaceColorConfiguration { isSelected ? textColorSelectedConfiguration : textColorNonSelectedConfiguration } private var textColorNonSelectedConfiguration = SurfaceColorConfiguration(VDSColor.elementsSecondaryOnlight , VDSColor.elementsSecondaryOnlight) private var textColorSelectedConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark) private var indicatorColorConfiguration = SurfaceColorConfiguration(VDSColor.paletteRed, VDSColor.elementsPrimaryOndark) private var indicatorWidth: CGFloat = 4.0 private var indicatorSide: UIRectEdge { guard orientation == .horizontal else { return .left } return indicatorPosition.value } private var leadingSpace: CGFloat { guard orientation == .vertical else { return 0 } return VDSLayout.Spacing.space4X.value } private var otherSpace: CGFloat { if orientation == .horizontal { return size == .medium ? VDSLayout.Spacing.space3X.value : VDSLayout.Spacing.space4X.value } else { return VDSLayout.Spacing.space2X.value } } private let layoutGuide = UILayoutGuide() private func updateWidth() { labelWidthConstraint?.isActive = false guard let width else { return } labelWidthConstraint?.constant = width labelWidthConstraint?.isActive = true } //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- public override init(frame: CGRect) { super.init(frame: frame) } public required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } required public convenience init() { self.init(frame: .zero) } //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- open override func setup() { super.setup() canHighlight = false addLayoutGuide(layoutGuide) addSubview(label) accessibilityTraits = .button isAccessibilityElement = true //pin layoutguide layoutGuide.pinToSuperView() //pin trailing label.pinTrailing(layoutGuide.trailingAnchor) //setup constraints labelWidthConstraint = label.width(constant: 0).with { $0.isActive = false } labelTopConstraint = label.pinTop(anchor: layoutGuide.topAnchor) labelLeadingConstraint = label.pinLeading(anchor: layoutGuide.leadingAnchor) labelBottomConstraint = label.pinBottom(anchor: layoutGuide.bottomAnchor, priority: .defaultHigh) } /// Function used to make changes to the View based off a change events or from local properties. open override func updateView() { super.updateView() guard !text.isEmpty else { return } accessibilityIdentifier = "VDSTab:\(text)" //constaints updateWidth() labelLeadingConstraint?.constant = leadingSpace labelTopConstraint?.constant = otherSpace labelBottomConstraint?.constant = -otherSpace //label properties label.text = text label.surface = surface label.textStyle = textStyle label.textPosition = textPosition label.textColorConfiguration = textColorConfiguration.eraseToAnyColorable() setNeedsLayout() layoutIfNeeded() } open override func updateAccessibility() { super.updateAccessibility() accessibilityLabel = text } open override func layoutSubviews() { super.layoutSubviews() removeBorders() if isSelected { addBorder(side: indicatorSide, width: indicatorWidth, color: indicatorColorConfiguration.getColor(self)) } } } }