diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index dd2613d1..e0ea031a 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 180636C92C29B0DF00C92D86 /* InputStepperLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 180636C82C29B0DF00C92D86 /* InputStepperLog.txt */; }; 1808BEBC2BA41C3200129230 /* CarouselScrollbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1808BEBB2BA41C3200129230 /* CarouselScrollbar.swift */; }; 1832AC572BA0791D008AE476 /* BreadcrumbCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1832AC562BA0791D008AE476 /* BreadcrumbCellItem.swift */; }; + 183B16F72C80B32200BA6A10 /* FootnoteGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 183B16F62C80B32200BA6A10 /* FootnoteGroup.swift */; }; 184023452C61E7AD00A412C8 /* PriceLockup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 184023442C61E7AD00A412C8 /* PriceLockup.swift */; }; 184023472C61E7EC00A412C8 /* PriceLockupChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 184023462C61E7EC00A412C8 /* PriceLockupChangeLog.txt */; }; 1842B1DF2BECE28B0021AFCA /* CalendarDateViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1842B1DE2BECE28B0021AFCA /* CalendarDateViewCell.swift */; }; @@ -218,6 +219,7 @@ 1808BEBB2BA41C3200129230 /* CarouselScrollbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselScrollbar.swift; sourceTree = ""; }; 1808BEBF2BA456B700129230 /* CarouselScrollbarChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CarouselScrollbarChangeLog.txt; sourceTree = ""; }; 1832AC562BA0791D008AE476 /* BreadcrumbCellItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbCellItem.swift; sourceTree = ""; }; + 183B16F62C80B32200BA6A10 /* FootnoteGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FootnoteGroup.swift; sourceTree = ""; }; 184023442C61E7AD00A412C8 /* PriceLockup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PriceLockup.swift; sourceTree = ""; }; 184023462C61E7EC00A412C8 /* PriceLockupChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = PriceLockupChangeLog.txt; sourceTree = ""; }; 1842B1DE2BECE28B0021AFCA /* CalendarDateViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarDateViewCell.swift; sourceTree = ""; }; @@ -502,6 +504,7 @@ isa = PBXGroup; children = ( 18926F5A2C7616A500C55BF6 /* Footnote.swift */, + 183B16F62C80B32200BA6A10 /* FootnoteGroup.swift */, 18926F5C2C7616C600C55BF6 /* FootnoteChangeLog.txt */, ); path = Footnote; @@ -1291,6 +1294,7 @@ EA3361C328902D960071C351 /* Toggle.swift in Sources */, EAF7F0A0289AB7EC00B287F5 /* View.swift in Sources */, EAC58C232BF2824200BA39FA /* DatePicker.swift in Sources */, + 183B16F72C80B32200BA6A10 /* FootnoteGroup.swift in Sources */, EA89201328B568D8006B9984 /* RadioBoxItem.swift in Sources */, 71FC86E42B9841AC00700965 /* PaginationFlowLayout.swift in Sources */, EAC9258C2911C9DE00091998 /* InputField.swift in Sources */, diff --git a/VDS/Components/Footnote/Footnote.swift b/VDS/Components/Footnote/Footnote.swift index 0771333c..0d1575f0 100644 --- a/VDS/Components/Footnote/Footnote.swift +++ b/VDS/Components/Footnote/Footnote.swift @@ -8,7 +8,6 @@ import Foundation import UIKit import VDSCoreTokens -import Combine /// 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. @@ -120,7 +119,7 @@ open class Footnote: View { open var tooltipModel: Tooltip.TooltipModel? { didSet { setNeedsUpdate() } } - /// Any percentage or pixel value and cannot exceed container size.. + /// 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 } @@ -143,6 +142,9 @@ open class Footnote: View { } } + /// To set the widest symbol width from the symbol container in the group. + open var symbolWiderWidth: CGFloat = 0 { didSet { setNeedsUpdate() } } + //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- @@ -167,12 +169,22 @@ open class Footnote: View { $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 //-------------------------------------------------- @@ -183,7 +195,12 @@ open class Footnote: View { // add footnote item stackview. addSubview(itemStackView) - itemStackView.pinToSuperView() + 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) @@ -192,7 +209,6 @@ open class Footnote: View { symbolWidthConstraint = symbolLabel.widthAnchor.constraint(greaterThanOrEqualToConstant: 0) symbolWidthConstraint?.isActive = true - } open override func setDefaults() { @@ -221,8 +237,13 @@ open class Footnote: View { symbolLabel.textStyle = size.textStyle.regular symbolLabel.surface = surface - // Update symbolLabel width as per updated text size - symbolWidthConstraint = symbolLabel.widthAnchor.constraint(equalToConstant: symbolLabel.intrinsicContentSize.width) + //Set width to the symbol label + if symbolWiderWidth > 0 { + // Set the widest symbol width from the symbol container in the group. + symbolWidthConstraint = symbolLabel.widthAnchor.constraint(equalToConstant: symbolWiderWidth) + } else { + symbolWidthConstraint = symbolLabel.widthAnchor.constraint(equalToConstant: symbolLabel.intrinsicContentSize.width) + } symbolWidthConstraint?.isActive = true // Update textLabel @@ -237,5 +258,33 @@ open class Footnote: View { 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() + } } } diff --git a/VDS/Components/Footnote/FootnoteGroup.swift b/VDS/Components/Footnote/FootnoteGroup.swift new file mode 100644 index 00000000..755ed9aa --- /dev/null +++ b/VDS/Components/Footnote/FootnoteGroup.swift @@ -0,0 +1,180 @@ +// +// FootnoteGroup.swift +// VDS +// +// Created by Kanamarlapudi, Vasavi on 29/08/24. +// + +import Foundation +import UIKit +import VDSCoreTokens + +/// This must always be paired with one or more ``Footnote`` in a FootnoteGroup. +@objcMembers +@objc(VDSFootnoteGroup) +open class FootnoteGroup: 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 width of a fixed value or percentage of parent's width. + public enum Width { + case percentage(CGFloat) + case value(CGFloat) + } + + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + /// Array of ``Footnote`` for the Footnote items. + open var footnoteitems: [Footnote] = [] { 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 + + private lazy var stackView = UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.axis = .vertical + $0.alignment = .top + $0.distribution = .fill + $0.spacing = VDSLayout.space3X + $0.backgroundColor = .clear + } + + //-------------------------------------------------- + // MARK: - Configuration Properties + //-------------------------------------------------- + internal var maxWidth: CGFloat { frame.size.width } + internal var minWidth: CGFloat { containerSize.width } + internal var containerSize: CGSize { CGSize(width: 55, height: 44) } + + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + internal var widthConstraint: 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(stackView) + stackView.pinTop().pinBottom().pinLeading() + trailingEqualsConstraint = stackView.pinTrailing(anchor: trailingAnchor) + + // width constraints + trailingLessThanEqualsConstraint = stackView.pinTrailingLessThanOrEqualTo(anchor: trailingAnchor)?.deactivate() + widthConstraint = stackView.widthAnchor.constraint(equalToConstant: 0).deactivate() + } + + open override func setDefaults() { + super.setDefaults() + width = nil + footnoteitems = [] + } + + /// Resets to default settings. + open override func 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() + updateContainerWidth() + + // symbol containers are as wide as the widest symbol container in the group. + var symbolMaxWidth = 0.0 + if footnoteitems.count > 0 { + for index in 0...footnoteitems.count - 1 { + let footnote: Footnote = footnoteitems[index] + let separatorWidth = Label().with { $0.text = footnote.symbolType.text; $0.sizeToFit() }.intrinsicContentSize.width + symbolMaxWidth = separatorWidth > symbolMaxWidth ? separatorWidth : symbolMaxWidth + } + } + + stackView.subviews.forEach{$0.removeFromSuperview()} + // add symbol label, text label to stack. + if footnoteitems.count > 0 { + for index in 0...footnoteitems.count - 1 { + let footnote: Footnote = footnoteitems[index] + footnote.symbolWiderWidth = symbolMaxWidth + footnote.surface = surface + stackView.addArrangedSubview(footnote) + } + } + } + + /// 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 + } + + widthConstraint?.deactivate() + trailingLessThanEqualsConstraint?.deactivate() + trailingEqualsConstraint?.deactivate() + + if newWidth > minWidth && newWidth < maxWidth { + widthConstraint?.constant = newWidth + widthConstraint?.activate() + trailingLessThanEqualsConstraint?.activate() + } else { + trailingEqualsConstraint?.activate() + } + } +} diff --git a/VDS/VDS.docc/VDS.md b/VDS/VDS.docc/VDS.md index fd139f95..d1afd2b1 100755 --- a/VDS/VDS.docc/VDS.md +++ b/VDS/VDS.docc/VDS.md @@ -33,6 +33,7 @@ Using the system allows designers and developers to collaborate more easily and - ``CheckboxGroup`` - ``DropdownSelect`` - ``Footnote`` +- ``FootnoteGroup`` - ``Icon`` - ``InputStepper`` - ``InputField``