vds_ios/VDS/Components/Footnote/FootnoteItem.swift
Matt Bruce 9f6c115f50 made internal since only group uses this
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-09-18 15:00:27 -05:00

269 lines
9.3 KiB
Swift

//
// Footnote.swift
// VDS
//
// Created by Kanamarlapudi, Vasavi on 21/08/24.
//
import Foundation
import UIKit
import VDSCoreTokens
/// A footnote is text that provides supporting details, legal copy and links to related content.
/// It exists at the bottom or "foot" of a page or section.
@objcMembers
@objc(VDSFootnoteItem)
open class FootnoteItem: View {
//--------------------------------------------------
// 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: - enums
//--------------------------------------------------
/// Enum used to describe the kind of component.
public enum Kind: String, DefaultValuing, CaseIterable {
case primary, secondary
/// The default kind is 'primary'.
public static var defaultValue : Self { .secondary }
/// Color configuation to Symbol and Text relative to kind.
public var colorConfiguration: SurfaceColorConfiguration {
switch self {
case .primary:
return SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark)
case .secondary:
return SurfaceColorConfiguration(VDSColor.elementsSecondaryOnlight, VDSColor.elementsSecondaryOndark)
}
}
}
/// Enum that represents the size availble for component.
public enum Size: String, DefaultValuing, CaseIterable {
case micro
case small
case large
public static var defaultValue: Self { .micro }
/// TextStyle relative to Size.
public var textStyle: TextStyle.StandardStyle {
switch self {
case .micro:
return .micro
case .small:
return .bodySmall
case .large:
return .bodyLarge
}
}
}
/// Enum used to describe the width of a fixed value or percentage of parent's width.
public enum Width {
case percentage(CGFloat)
case value(CGFloat)
}
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
/// Color to the component. The default kind is Secondary.
open var kind: Kind = .defaultValue { didSet { setNeedsUpdate() } }
/// Size of the component. The default size is Micro.
open var size: Size = .defaultValue { didSet { setNeedsUpdate() } }
/// If hideSymbol true, the component will show text without symbol.
open var hideSymbol: Bool = false { didSet { setNeedsUpdate() } }
/// symbol type will be shown for the footnote item. The default symbolType is 'asterisk'.
open var symbolType: String = "*" { didSet { setNeedsUpdate() } }
/// Text of the footnote item.
open var text: String? { didSet { setNeedsUpdate() } }
open var tooltipModel: Tooltip.TooltipModel? { didSet { setNeedsUpdate() } }
/// Any percentage or pixel value and cannot exceed container size.
/// If there is a width that is larger than container size, the footnote will resize to container's width.
open var width: Width? {
get { _width }
set {
if let newValue {
switch newValue {
case .percentage(let percentage):
if percentage <= 100.0 {
_width = newValue
}
case .value(let value):
if value > 0 {
_width = newValue
}
}
} else {
_width = nil
}
setNeedsUpdate()
}
}
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
private var _width: Width? = nil
/// To set the widest symbol width from the symbol container in the group.
internal var symbolWidth: CGFloat? { didSet { setNeedsUpdate() } }
private lazy var itemStackView = UIStackView().with {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.axis = .horizontal
$0.alignment = .leading
$0.distribution = .fill
$0.spacing = VDSLayout.space1X
$0.backgroundColor = .clear
}
internal var symbolLabel = Label().with {
$0.isAccessibilityElement = true
$0.numberOfLines = 1
$0.sizeToFit()
}
internal var textLabel = Label().with {
$0.isAccessibilityElement = true
$0.lineBreakMode = .byWordWrapping
}
//--------------------------------------------------
// MARK: - Configuration Properties
//--------------------------------------------------
internal var maxWidth: CGFloat { frame.size.width }
internal var minWidth: CGFloat { containerSize.width }
internal var containerSize: CGSize { CGSize(width: 45, height: 44) }
//--------------------------------------------------
// MARK: - Constraints
//--------------------------------------------------
internal var symbolWidthConstraint: NSLayoutConstraint?
internal var itemWidthConstraint: NSLayoutConstraint?
internal var trailingEqualsConstraint: NSLayoutConstraint?
internal var trailingLessThanEqualsConstraint: NSLayoutConstraint?
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
/// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations.
open override func setup() {
super.setup()
// add footnote item stackview.
addSubview(itemStackView)
itemStackView.pinTop().pinBottom().pinLeading()
trailingEqualsConstraint = itemStackView.pinTrailing(anchor: trailingAnchor)
// width constraints
itemWidthConstraint = itemStackView.widthAnchor.constraint(equalToConstant: 0).deactivate()
trailingLessThanEqualsConstraint = itemStackView.pinTrailingLessThanOrEqualTo(anchor: trailingAnchor)?.deactivate()
// add symbol label, text label to stack.
itemStackView.addArrangedSubview(symbolLabel)
itemStackView.addArrangedSubview(textLabel)
itemStackView.setCustomSpacing(VDSLayout.space1X, after: symbolLabel)
symbolWidthConstraint = symbolLabel.widthAnchor.constraint(greaterThanOrEqualToConstant: 0)
symbolWidthConstraint?.isActive = true
}
open override func setDefaults() {
super.setDefaults()
hideSymbol = false
text = nil
tooltipModel = nil
width = nil
}
/// Resets to default settings.
open override func reset() {
symbolLabel.reset()
textLabel.reset()
super.reset()
}
/// Used to make changes to the View based off a change events or from local properties.
open override func updateView() {
super.updateView()
// Update symbolLabel
symbolLabel.text = symbolType
symbolLabel.isHidden = hideSymbol
symbolLabel.textColor = kind.colorConfiguration.getColor(self)
symbolLabel.textStyle = size.textStyle.regular
symbolLabel.surface = surface
//Set width to the symbol label
if let symbolWidth, symbolWidth > 0 {
// Set the widest symbol width from the symbol container in the group.
symbolWidthConstraint?.constant = symbolWidth
} else {
symbolWidthConstraint?.constant = symbolLabel.intrinsicContentSize.width
}
// Update textLabel
textLabel.text = text
textLabel.textColor = kind.colorConfiguration.getColor(self)
textLabel.textStyle = size.textStyle.regular
textLabel.surface = surface
// Set the textLabel attributes
if let tooltipModel {
var attributes: [any LabelAttributeModel] = []
attributes.append(TooltipLabelAttribute(surface: surface, model: tooltipModel, presenter: self))
textLabel.attributes = attributes
}
updateContainerWidth()
}
/// Update container width after updating content.
internal func updateContainerWidth() {
var newWidth = 0.0
switch width {
case .percentage(let percentage):
newWidth = max(maxWidth * ((percentage) / 100), minWidth)
case .value(let value):
newWidth = value > maxWidth ? maxWidth : value
case nil:
newWidth = maxWidth
}
itemWidthConstraint?.deactivate()
trailingLessThanEqualsConstraint?.deactivate()
trailingEqualsConstraint?.deactivate()
if newWidth > minWidth && newWidth < maxWidth {
itemWidthConstraint?.constant = newWidth
itemWidthConstraint?.activate()
trailingLessThanEqualsConstraint?.activate()
} else {
trailingEqualsConstraint?.activate()
}
}
}