// // Tab.swift // VDS // // Created by Matt Bruce on 5/18/23. // import Foundation import VDSColorTokens import Combine extension Tabs { @objc(VDSTab) open class Tab: View { //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- ///position of the tab open var index: Int = 0 ///label to write out the text open var label: Label = Label() ///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() } } ///An optional callback that is called when this Tab is clicked. Passes parameters (tabIndex). open var onClick: ((Int) -> Void)? { didSet { setNeedsUpdate() } } ///If provided, it will set fixed width for this Tab. open var width: CGFloat? { didSet { setNeedsUpdate() } } ///If provided, it will set this Tab to the Active Tab on render. open var selected: Bool = false { 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 labelWidthConstraint: NSLayoutConstraint? private var labelLeadingConstraint: NSLayoutConstraint? private var labelTopConstraint: NSLayoutConstraint? private var labelBottomConstraint: NSLayoutConstraint? //-------------------------------------------------- // MARK: - Configuration //-------------------------------------------------- private var textColorConfiguration: SurfaceColorConfiguration { selected ? 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 var widthConstraint: NSLayoutConstraint { if let width, orientation == .vertical { return label.widthAnchor.constraint(equalToConstant: width) } else { return label.widthAnchor.constraint(greaterThanOrEqualToConstant: minWidth) } } //-------------------------------------------------- // 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() addSubview(label) accessibilityTraits = .button label.translatesAutoresizingMaskIntoConstraints = false label.pinTrailing() labelTopConstraint = label.topAnchor.constraint(equalTo: topAnchor) labelTopConstraint?.isActive = true labelBottomConstraint = label.bottomAnchor.constraint(equalTo: bottomAnchor) labelBottomConstraint?.isActive = true labelLeadingConstraint = label.leadingAnchor.constraint(equalTo: leadingAnchor) labelLeadingConstraint?.isActive = true let layoutGuide = UILayoutGuide() addLayoutGuide(layoutGuide) labelWidthConstraint = layoutGuide.widthAnchor.constraint(greaterThanOrEqualToConstant: minWidth) labelWidthConstraint?.isActive = true //activate the constraints NSLayoutConstraint.activate([layoutGuide.topAnchor.constraint(equalTo: topAnchor), layoutGuide.bottomAnchor.constraint(equalTo: bottomAnchor), layoutGuide.leadingAnchor.constraint(equalTo: leadingAnchor), layoutGuide.trailingAnchor.constraint(equalTo: trailingAnchor)]) publisher(for: UITapGestureRecognizer()) .sink { [weak self] _ in guard let self else { return } self.onClick?(self.index) }.store(in: &subscribers) } open override func updateView() { //label properties label.text = text label.textPosition = textPosition label.textStyle = size.textStyle label.textColor = textColorConfiguration.getColor(self) //constaints labelWidthConstraint?.isActive = false labelWidthConstraint = widthConstraint labelWidthConstraint?.isActive = true labelLeadingConstraint?.constant = leadingSpace labelTopConstraint?.constant = otherSpace labelBottomConstraint?.constant = -otherSpace setNeedsLayout() } open override func layoutSubviews() { super.layoutSubviews() removeBorders() if selected { addBorder(side: indicatorSide, width: indicatorWidth, color: indicatorColorConfiguration.getColor(self)) } } } }