diff --git a/VDS/Components/Tabs/Tab.swift b/VDS/Components/Tabs/Tab.swift index 2190202c..bd755252 100644 --- a/VDS/Components/Tabs/Tab.swift +++ b/VDS/Components/Tabs/Tab.swift @@ -9,132 +9,164 @@ import Foundation import VDSColorTokens import Combine -public class TabItem: View { +extension Tabs { - //-------------------------------------------------- - // MARK: - Public Properties - //-------------------------------------------------- - public var label: Label = Label() - public var orientation: Tabs.Orientation = .horizontal { didSet { setNeedsUpdate() } } - public var size: Tabs.Size = .medium { didSet { setNeedsUpdate() } } - public var indicatorPosition: Tabs.IndicatorPosition = .bottom { didSet { setNeedsUpdate() } } - public var onClick: (() -> Void)? { didSet { setNeedsUpdate() } } - public var width: CGFloat? { didSet { setNeedsUpdate() } } - public var selected: Bool = false { didSet { setNeedsUpdate() } } - public var text: String? { 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 - - //-------------------------------------------------- - // 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 - //-------------------------------------------------- - override public func setup() { - super.setup() - addSubview(label) - backgroundColor = .clear - label.backgroundColor = .clear + open class TabItem: View { + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + ///position of the tab + open var index: Int = 0 - label.translatesAutoresizingMaskIntoConstraints = false - label.pinTrailing() + ///label to write out the text + open var label: Label = Label() - labelTopConstraint = label.topAnchor.constraint(equalTo: topAnchor, constant: 0) - labelTopConstraint?.isActive = true + ///orientation of the tabs + open var orientation: Tabs.Orientation = .horizontal { didSet { setNeedsUpdate() } } - labelBottomConstraint = label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0) - labelBottomConstraint?.isActive = true + ///Size for tab + open var size: Tabs.Size = .medium { didSet { setNeedsUpdate() } } - labelLeadingConstraint = label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0) - labelLeadingConstraint?.isActive = true + ///Text position left or center + open var textPosition: TextPosition = .left { didSet { setNeedsUpdate() } } - labelWidthConstraint = label.widthAnchor.constraint(greaterThanOrEqualToConstant: 44.0) - labelWidthConstraint?.isActive = true + ///Sets the Position of the Selected/Hover Border Accent for All Tabs. + open var indicatorPosition: Tabs.IndicatorPosition = .bottom { didSet { setNeedsUpdate() } } - publisher(for: UITapGestureRecognizer()) - .sink { [weak self] _ in - self?.onClick?() - }.store(in: &subscribers) + ///An optional callback that is called when this Tab is clicked. Passes parameters (tabIndex). + open var onClick: ((Int) -> Void)? { didSet { setNeedsUpdate() } } - } - - public override func updateView() { - if orientation == .horizontal { - label.textPosition = .center - } else { - label.textPosition = .left + ///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? { 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 + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + public override init(frame: CGRect) { + super.init(frame: frame) } - label.text = text - label.textStyle = size.textStyle - label.textColor = textColorConfiguration.getColor(self) - labelWidthConstraint?.isActive = false - if let width { - labelWidthConstraint = label.widthAnchor.constraint(equalToConstant: width) - } else { - labelWidthConstraint = label.widthAnchor.constraint(greaterThanOrEqualToConstant: 44.0) + public required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") } - labelWidthConstraint?.isActive = true - - var leadingSpace: CGFloat - if orientation == .horizontal { - leadingSpace = 0 - } else { - leadingSpace = size == .medium ? VDSLayout.Spacing.space4X.value : VDSLayout.Spacing.space6X.value - } - labelLeadingConstraint?.constant = leadingSpace - - var otherSpace: CGFloat - if orientation == .horizontal { - otherSpace = size == .medium ? VDSLayout.Spacing.space3X.value : VDSLayout.Spacing.space4X.value - } else { - otherSpace = VDSLayout.Spacing.space2X.value - } - labelTopConstraint?.constant = otherSpace - labelBottomConstraint?.constant = -otherSpace - - setNeedsLayout() - } - - public override func layoutSubviews() { - super.layoutSubviews() - removeBorders() - - if selected { - var indicator: UIRectEdge = .left - if orientation == .horizontal { - indicator = indicatorPosition.value + required public convenience init() { + self.init(frame: .zero) + } + + //-------------------------------------------------- + // MARK: - Overrides + //-------------------------------------------------- + open override func setup() { + super.setup() + addSubview(label) + backgroundColor = .clear + label.backgroundColor = .clear + + 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: 44.0) + 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.text = text + label.textPosition = textPosition + label.textStyle = size.textStyle + label.textColor = textColorConfiguration.getColor(self) + labelWidthConstraint?.isActive = false + if let width { + labelWidthConstraint = label.widthAnchor.constraint(equalToConstant: width) + } else { + labelWidthConstraint = label.widthAnchor.constraint(greaterThanOrEqualToConstant: 44.0) + } + labelWidthConstraint?.isActive = true + + var leadingSpace: CGFloat + if orientation == .horizontal { + leadingSpace = 0 + } else { + leadingSpace = size == .medium ? VDSLayout.Spacing.space4X.value : VDSLayout.Spacing.space6X.value + } + labelLeadingConstraint?.constant = leadingSpace + + var otherSpace: CGFloat + if orientation == .horizontal { + otherSpace = size == .medium ? VDSLayout.Spacing.space3X.value : VDSLayout.Spacing.space4X.value + } else { + otherSpace = VDSLayout.Spacing.space2X.value + } + labelTopConstraint?.constant = otherSpace + labelBottomConstraint?.constant = -otherSpace + + setNeedsLayout() + } + + open override func layoutSubviews() { + super.layoutSubviews() + + removeBorders() + + if selected { + var indicator: UIRectEdge = .left + if orientation == .horizontal { + indicator = indicatorPosition.value + } + addBorder(side: indicator, width: indicatorWidth, color: indicatorColorConfiguration.getColor(self), offset: 1) } - addBorder(side: indicator, width: indicatorWidth, color: indicatorColorConfiguration.getColor(self)) } } } diff --git a/VDS/Components/Tabs/TabModel.swift b/VDS/Components/Tabs/TabModel.swift index 8922edfa..02f5bd43 100644 --- a/VDS/Components/Tabs/TabModel.swift +++ b/VDS/Components/Tabs/TabModel.swift @@ -7,14 +7,22 @@ import Foundation -public struct TabModel { - public var text: String - public var onClick: (() -> Void)? - public var width: CGFloat? - - public init(text: String, onClick: (() -> Void)? = nil, width: CGFloat? = nil) { - self.text = text - self.onClick = onClick - self.width = width +extension Tabs { + public struct TabModel { + + ///Text that goes in the Tab + public var text: String + + ///Click event when you click on a tab + public var onClick: ((Int) -> Void)? + + ///Width of the tab + public var width: CGFloat? + + public init(text: String, onClick: ((Int) -> Void)? = nil, width: CGFloat? = nil) { + self.text = text + self.onClick = onClick + self.width = width + } } }