diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 2f702291..3188ba91 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -406,8 +406,8 @@ EA33619D288B1E330071C351 /* Components */ = { isa = PBXGroup; children = ( - EAD062AE2A3B87210015965D /* BadgeIndicator */, EA4DB2FE28DCBC1900103EE3 /* Badge */, + EAD062AE2A3B87210015965D /* BadgeIndicator */, EA0FC2BE2912D18200DF80B4 /* Buttons */, EAF7F092289985E200B287F5 /* Checkbox */, EA985BF3296C609E00F2FF2E /* Icon */, @@ -1131,7 +1131,7 @@ BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 26; + CURRENT_PROJECT_VERSION = 27; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1168,7 +1168,7 @@ BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 26; + CURRENT_PROJECT_VERSION = 27; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; diff --git a/VDS/Classes/ColorConfiguration.swift b/VDS/Classes/ColorConfiguration.swift index 3a4b468e..a1474610 100644 --- a/VDS/Classes/ColorConfiguration.swift +++ b/VDS/Classes/ColorConfiguration.swift @@ -23,7 +23,7 @@ public typealias ObjectColorable = Colorable & Initable & ObjectWithable /// let textColor = config.getColor(model) //returns .black /// -/// You can pass in a Surfaceable object and it will give you the color responding. +/// You can pass in a Surfaceable object and this will return the corresponding Color based on the object's surface property. open class SurfaceColorConfiguration: ObjectColorable { public typealias ObjectType = Surfaceable public var lightColor: UIColor = .clear diff --git a/VDS/Classes/Control.swift b/VDS/Classes/Control.swift index 50a10326..2823bb80 100644 --- a/VDS/Classes/Control.swift +++ b/VDS/Classes/Control.swift @@ -10,6 +10,7 @@ import UIKit import Combine @objc(VDSControl) +/// Base Class use to build Controls. open class Control: UIControl, Handlerable, ViewProtocol, Resettable, UserInfoable, Clickable { //-------------------------------------------------- diff --git a/VDS/Classes/View.swift b/VDS/Classes/View.swift index 56dae7bd..08bb2b34 100644 --- a/VDS/Classes/View.swift +++ b/VDS/Classes/View.swift @@ -11,6 +11,7 @@ import Combine @objc(VDSView) +/// Base Class used to build Views. open class View: UIView, Handlerable, ViewProtocol, Resettable, UserInfoable { //-------------------------------------------------- @@ -29,10 +30,10 @@ open class View: UIView, Handlerable, ViewProtocol, Resettable, UserInfoable { /// Dictionary for keeping information for this Control use only Primitives open var userInfo = [String: Primitive]() - /// Current Surface used within this Control and used to passdown to child views + /// Current Surface and this is used to pass down to child objects that implement Surfacable open var surface: Surface = .light { didSet { setNeedsUpdate() } } - /// Control is disabled or not + /// Whether this object is disabled or not open var disabled: Bool = false { didSet { setNeedsUpdate(); isUserInteractionEnabled = !disabled } } //-------------------------------------------------- diff --git a/VDS/Components/Badge/Badge.swift b/VDS/Components/Badge/Badge.swift index 85760edd..da8ff792 100644 --- a/VDS/Components/Badge/Badge.swift +++ b/VDS/Components/Badge/Badge.swift @@ -24,6 +24,8 @@ open class Badge: View { //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- + + /// Label used to render text open var label = Label().with { $0.setContentCompressionResistancePriority(.required, for: .vertical) $0.adjustsFontSizeToFitWidth = false @@ -32,12 +34,17 @@ open class Badge: View { $0.textStyle = .boldBodySmall } + /// This will render the badges fill color based on the available options. + /// When used in conjunction with the surface prop, this fill color will change its tint automatically based on a light or dark surface. open var fillColor: FillColor = .red { didSet { setNeedsUpdate() }} + /// The text that will be shown in the label. open var text: String = "" { didSet { setNeedsUpdate() }} - + + /// When applied, this property takes a px value that will restrict the width at that point open var maxWidth: CGFloat? { didSet { setNeedsUpdate() }} + /// This will restrict the badge height to a specific number of lines. If the text overflows the allowable space, ellipsis will show. open var numberOfLines: Int = 1 { didSet { setNeedsUpdate() }} //-------------------------------------------------- @@ -86,6 +93,8 @@ open class Badge: View { //-------------------------------------------------- // MARK: - Configuration //-------------------------------------------------- + + /// ColorConfiguration that is mapped to the 'fillColor' for the surface private var backgroundColorConfiguration: AnyColorable = { let config = KeyedColorConfiguration(keyPath: \.fillColor) config.setSurfaceColors(VDSColor.backgroundBrandhighlight, VDSColor.backgroundBrandhighlight, forKey: .red) @@ -98,8 +107,10 @@ open class Badge: View { return config.eraseToAnyColorable() }() + /// ColorConfiguration for the Text private var textColorConfiguration = ViewColorConfiguration() + /// Updates the textColorConfiguration based on the fillColor public func updateTextColorConfig() { textColorConfiguration.reset() diff --git a/VDS/Components/BadgeIndicator/BadgeIndicator.swift b/VDS/Components/BadgeIndicator/BadgeIndicator.swift index ad64cf7b..748025e2 100644 --- a/VDS/Components/BadgeIndicator/BadgeIndicator.swift +++ b/VDS/Components/BadgeIndicator/BadgeIndicator.swift @@ -12,27 +12,32 @@ import VDSFormControlsTokens import Combine import VDSTypographyTokens -/// Badges are visual labels used to convey status or highlight supplemental information. +/// Badge Indicators are visual labels . @objc(VDSBadgeIndicator) open class BadgeIndicator: View { //-------------------------------------------------- // MARK: - Enums //-------------------------------------------------- + + /// Enum type for fill color public enum FillColor: String, CaseIterable { case red, yellow, green, orange, blue, gray, grayLowContrast, black, white } + /// Enum type for kind of BadgeIndicator public enum Kind: String, CaseIterable { case simple, numbered } - public enum MaxDigits: String, CaseIterable { + /// Enum type for maximum number of digits + public enum MaximumDigits: String, CaseIterable { case one case two case three case four case five case six + case none public var value: Int { switch self { @@ -48,17 +53,21 @@ open class BadgeIndicator: View { return 5 case .six: return 6 + case .none: + return 0 } } } + /// Enum type describing size of Badge Indicator public enum Size: String, CaseIterable { case xxlarge = "2XLarge" case xlarge = "XLarge" case large = "Large" case medium = "Medium" case small = "Small" - + + /// Dynamic TextStyle for the size public var textStyle: TextStyle { let style = TextStyle.bodySmall var pointSize: CGFloat = VDSTypography.fontSizeBody12 @@ -89,6 +98,7 @@ open class BadgeIndicator: View { letterSpacing: letterSpacing) } + //EdgeInsets for the label public var edgeInset: UIEdgeInsets { var horizontalPadding: CGFloat = VDSLayout.Spacing.space1X.value let verticalPadding: CGFloat = 0 @@ -109,6 +119,8 @@ open class BadgeIndicator: View { //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- + + /// Label used for the numeric kind open var label = Label().with { $0.setContentCompressionResistancePriority(.required, for: .vertical) $0.adjustsFontSizeToFitWidth = false @@ -117,6 +129,7 @@ open class BadgeIndicator: View { $0.numberOfLines = 1 } + /// BorderColor for Surface.light open var borderColorLight: UIColor? { didSet { if let borderColorLight { @@ -128,6 +141,7 @@ open class BadgeIndicator: View { } } + /// BorderColor for Surface.dark open var borderColorDark: UIColor? { didSet { if let borderColorDark { @@ -139,46 +153,97 @@ open class BadgeIndicator: View { } } + /// This will render the badges fill color based on the available options. + /// When used in conjunction with the surface prop, this fill color will change its tint automatically based on a light or dark surface. open var fillColor: FillColor = .red { didSet { setNeedsUpdate() }} - + + /// Badge Number that will be shown if you are using Kind.numbered open var number: Int? { didSet { setNeedsUpdate() }} + /// Type of Badge Indicator, simple is a dot, whereas numbered shows a number. open var kind: Kind = .simple { didSet { setNeedsUpdate() }} + /// Character that is always at the begging. Accepts any character and if unaffected by maximumDigits open var leadingCharacter: String? { didSet { setNeedsUpdate() }} + /// Determines the size of the Badge Indicator as well as the textStyle and padding used. open var size: Size = .xxlarge { didSet { setNeedsUpdate() }} + /// Pixel size of the dot when the kind is set to simple open var dotSize: CGFloat? { didSet { setNeedsUpdate() }} + /// Sets the padding at the top/bottom of the label open var verticalPadding: CGFloat? { didSet { setNeedsUpdate() }} + /// Sets the padding at the left/right of the label open var horitonalPadding: CGFloat? { didSet { setNeedsUpdate() }} + /// Hides the dot when you are in Kind.simple mode. open var hideDot: Bool = false { didSet { setNeedsUpdate() }} + /// Will not show the border open var hideBorder: Bool = false { didSet { setNeedsUpdate() }} - open var maxDigits: MaxDigits = .two { didSet { setNeedsUpdate() }} - + /// When in Kind.numbered this is the amount of digits that will show up when the user adds a number. + open var maximumDigits: MaximumDigits = .two { didSet { setNeedsUpdate() }} + + /// The Container's width open var width: CGFloat? { didSet { setNeedsUpdate() }} + /// The Container's height open var height: CGFloat? { didSet { setNeedsUpdate() }} //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- - private let borderWidth: CGFloat = 1.0 - + private let badgeView = View() + private var badgeSize: CGFloat { max(minSize, size.textStyle.font.lineHeight) } + private var labelEdgeInset: UIEdgeInsets { + var newInset = size.edgeInset + if let verticalPadding, let horitonalPadding { + newInset = .init(top: verticalPadding, left: horitonalPadding, bottom: verticalPadding, right: horitonalPadding) + } else if let verticalPadding { + newInset = .init(top: verticalPadding, left: newInset.left, bottom: verticalPadding, right: newInset.right) + } else if let horitonalPadding { + newInset = .init(top: newInset.top, left: horitonalPadding, bottom: newInset.bottom, right: horitonalPadding) + } + + return newInset + } + //-------------------------------------------------- // MARK: - Constraints //-------------------------------------------------- - private let badgeView = View() - private var badgeSize: CGFloat { max(16.0, size.textStyle.font.lineHeight) } private var widthConstraint: NSLayoutConstraint? private var heightConstraint: NSLayoutConstraint? private var labelContraints = NSLayoutConstraint.Container() + + //-------------------------------------------------- + // MARK: - Configuration + //-------------------------------------------------- + private let borderWidth: CGFloat = 1.0 + private let minSize: CGFloat = 16.0 + private let minDotSize: CGFloat = 4.0 + private let dotRatio: CGFloat = 0.24 + + private var borderColorConfiguration = SurfaceColorConfiguration(VDSColor.paletteWhite, VDSColor.paletteBlack) + private var backgroundColorConfiguration: AnyColorable = { + let config = KeyedColorConfiguration(keyPath: \.fillColor) + config.setSurfaceColors(VDSColor.backgroundBrandhighlight, VDSColor.backgroundBrandhighlight, forKey: .red) + config.setSurfaceColors(VDSColor.paletteYellow62, VDSColor.paletteYellow62, forKey: .yellow) + config.setSurfaceColors(VDSColor.paletteGreen26, VDSColor.paletteGreen36, forKey: .green) + config.setSurfaceColors(VDSColor.paletteOrange41, VDSColor.paletteOrange58, forKey: .orange) + config.setSurfaceColors(VDSColor.paletteBlue38, VDSColor.paletteBlue46, forKey: .blue) + config.setSurfaceColors(VDSColor.paletteGray44, VDSColor.paletteGray65, forKey: .gray) + config.setSurfaceColors(VDSColor.paletteGray85, VDSColor.paletteGray20, forKey: .grayLowContrast) + config.setSurfaceColors(VDSColor.backgroundPrimaryDark, VDSColor.backgroundPrimaryDark, forKey: .black) + config.setSurfaceColors(VDSColor.backgroundPrimaryLight, VDSColor.backgroundPrimaryLight, forKey: .white) + return config.eraseToAnyColorable() + }() + + private var textColorConfiguration = ViewColorConfiguration() + //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- @@ -224,41 +289,7 @@ open class BadgeIndicator: View { shouldUpdateView = true setNeedsUpdate() } - - private var labelEdgeInset: UIEdgeInsets { - var newInset = size.edgeInset - if let verticalPadding, let horitonalPadding { - newInset = .init(top: verticalPadding, left: horitonalPadding, bottom: verticalPadding, right: horitonalPadding) - } else if let verticalPadding { - newInset = .init(top: verticalPadding, left: newInset.left, bottom: verticalPadding, right: newInset.right) - } else if let horitonalPadding { - newInset = .init(top: newInset.top, left: horitonalPadding, bottom: newInset.bottom, right: horitonalPadding) - } - - return newInset - } - - //-------------------------------------------------- - // MARK: - Configuration - //-------------------------------------------------- - private var borderColorConfiguration = SurfaceColorConfiguration(VDSColor.paletteWhite, VDSColor.paletteBlack) - - private var backgroundColorConfiguration: AnyColorable = { - let config = KeyedColorConfiguration(keyPath: \.fillColor) - config.setSurfaceColors(VDSColor.backgroundBrandhighlight, VDSColor.backgroundBrandhighlight, forKey: .red) - config.setSurfaceColors(VDSColor.paletteYellow62, VDSColor.paletteYellow62, forKey: .yellow) - config.setSurfaceColors(VDSColor.paletteGreen26, VDSColor.paletteGreen36, forKey: .green) - config.setSurfaceColors(VDSColor.paletteOrange41, VDSColor.paletteOrange58, forKey: .orange) - config.setSurfaceColors(VDSColor.paletteBlue38, VDSColor.paletteBlue46, forKey: .blue) - config.setSurfaceColors(VDSColor.paletteGray44, VDSColor.paletteGray65, forKey: .gray) - config.setSurfaceColors(VDSColor.paletteGray85, VDSColor.paletteGray20, forKey: .grayLowContrast) - config.setSurfaceColors(VDSColor.backgroundPrimaryDark, VDSColor.backgroundPrimaryDark, forKey: .black) - config.setSurfaceColors(VDSColor.backgroundPrimaryLight, VDSColor.backgroundPrimaryLight, forKey: .white) - return config.eraseToAnyColorable() - }() - - private var textColorConfiguration = ViewColorConfiguration() - + public func updateTextColorConfig() { textColorConfiguration.reset() @@ -323,19 +354,13 @@ open class BadgeIndicator: View { let badgeCount = number ?? 0 var text: String = "" if kind == .numbered && badgeCount >= 0 { - let maxBadgetCount = limitDigits(number: badgeCount, maxDigits: maxDigits.value) + let maxBadgetCount = maximumDigits == .none ? badgeCount : limitDigits(number: badgeCount, maxDigits: maximumDigits.value) let formatter = NumberFormatter() formatter.numberStyle = .decimal text = formatter.string(from: .init(integerLiteral: maxBadgetCount))! - if maxDigits.value < "\(badgeCount)".count { - let formatter = NumberFormatter() - formatter.numberStyle = .decimal - text = "\(text)+" - } + if let leadingCharacter { text = "\(leadingCharacter)\(text)" - } else { - text = "\(text)" } } return text @@ -368,7 +393,8 @@ open class BadgeIndicator: View { let frame = badgeView.frame //default calculation if a dotSize isn't given - var dot: CGFloat = frame.height * 0.1875 + let dotSizeRatio = frame.height * dotRatio + var dot: CGFloat = dotSizeRatio < minDotSize ? minDotSize : dotSizeRatio if let dotSize, dotSize < frame.width, dotSize < frame.height { dot = dotSize } diff --git a/VDS/Components/Loader/LoaderLaunchable.swift b/VDS/Components/Loader/LoaderLaunchable.swift index 7793b000..7befd931 100644 --- a/VDS/Components/Loader/LoaderLaunchable.swift +++ b/VDS/Components/Loader/LoaderLaunchable.swift @@ -8,12 +8,18 @@ import Foundation import UIKit +/// Protocol used for any object to be able to launch a loader public protocol LoaderLaunchable { func presentLoader(surface: Surface, size: Int?) func dismissLoader() } extension LoaderLaunchable { + + /// Default implementation for presenting + /// - Parameters: + /// - surface: Surface for the Loader + /// - size: Size for the loader, nil is default. public func presentLoader(surface: Surface, size: Int? = nil) { if let presenting = UIApplication.topViewController() { let viewController = LoaderViewController(nibName: nil, bundle: nil).with { @@ -28,6 +34,7 @@ extension LoaderLaunchable { } } + /// Dismisses the Loader public func dismissLoader() { if let presenting = UIApplication.topViewController() as? LoaderViewController { presenting.dismiss(animated: true) diff --git a/VDS/Components/Loader/LoaderViewController.swift b/VDS/Components/Loader/LoaderViewController.swift index 260dc5a7..7e18c0c1 100644 --- a/VDS/Components/Loader/LoaderViewController.swift +++ b/VDS/Components/Loader/LoaderViewController.swift @@ -9,23 +9,41 @@ import Foundation import UIKit import VDSColorTokens -public class LoaderViewController: UIViewController, Surfaceable { +/// ViewController to show the Loader, this will be presented using the LoaderLaunchable Protocl. +open class LoaderViewController: UIViewController, Surfaceable { + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- private var loader = Loader() private var backgroundColorConfiguration = SurfaceColorConfiguration(VDSColor.backgroundPrimaryLight, VDSColor.backgroundPrimaryDark) - - public var surface: Surface = .light - public var size: Int? - public override func viewDidLoad() { + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + /// Current Surface used within this Control and used to passdown to child views + open var surface: Surface = .light { didSet { updateView() }} + + /// Size of the Height and Width of the Loader view. + open var size: Int? { didSet { updateView() }} + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + open override func viewDidLoad() { super.viewDidLoad() - setup() + view.addSubview(loader) + NSLayoutConstraint.activate([ + loader.centerXAnchor.constraint(equalTo: view.centerXAnchor), + loader.centerYAnchor.constraint(equalTo: view.centerYAnchor) + ]) } - public override func viewWillAppear(_ animated: Bool) { + open override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) updateView() } - + + /// Update this view based off of property chang open func updateView() { view.backgroundColor = backgroundColorConfiguration.getColor(self).withAlphaComponent(0.8) if let size { @@ -33,12 +51,4 @@ public class LoaderViewController: UIViewController, Surfaceable { } loader.surface = surface } - - private func setup() { - view.addSubview(loader) - NSLayoutConstraint.activate([ - loader.centerXAnchor.constraint(equalTo: view.centerXAnchor), - loader.centerYAnchor.constraint(equalTo: view.centerYAnchor) - ]) - } } diff --git a/VDS/Components/Tabs/Tab.swift b/VDS/Components/Tabs/Tab.swift index eabc4da3..1b7cda8d 100644 --- a/VDS/Components/Tabs/Tab.swift +++ b/VDS/Components/Tabs/Tab.swift @@ -44,7 +44,7 @@ extension Tabs { open var selected: Bool = false { didSet { setNeedsUpdate() } } ///The text label of the tab. - open var text: String = "Tab" { didSet { setNeedsUpdate() } } + open var text: String = "" { didSet { setNeedsUpdate() } } ///Minimum width for the tab open var minWidth: CGFloat = 44.0 { didSet { setNeedsUpdate() } } @@ -52,6 +52,17 @@ extension Tabs { //-------------------------------------------------- // 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? @@ -148,10 +159,12 @@ extension Tabs { } open override func updateView() { + guard !text.isEmpty else { return } + //label properties label.text = text + label.textStyle = textStyle label.textPosition = textPosition - label.textStyle = size.textStyle label.textColor = textColorConfiguration.getColor(self) //constaints diff --git a/VDS/Components/Tabs/Tabs.swift b/VDS/Components/Tabs/Tabs.swift index 9ad1e19c..6728700b 100644 --- a/VDS/Components/Tabs/Tabs.swift +++ b/VDS/Components/Tabs/Tabs.swift @@ -47,17 +47,6 @@ open class Tabs: View { public enum Size: String, CaseIterable { case medium case large - - public var textStyle: TextStyle { - if self == .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 - } - } } //-------------------------------------------------- @@ -220,6 +209,7 @@ open class Tabs: View { // Create new tab items from the models for model in models { let tabItem = Tab() + tabItem.size = size tabItem.text = model.text tabItem.onClick = model.onClick tabItem.width = model.width @@ -258,10 +248,10 @@ open class Tabs: View { // Update tab appearance based on properties for (index, tabItem) in tabViews.enumerated() { + tabItem.size = size tabItem.selected = selectedIndex == index tabItem.index = index tabItem.minWidth = minWidth - tabItem.size = size tabItem.textPosition = textPosition tabItem.orientation = orientation tabItem.surface = surface diff --git a/VDS/Components/Toggle/Toggle.swift b/VDS/Components/Toggle/Toggle.swift index 42f3c4a2..ac20ef31 100644 --- a/VDS/Components/Toggle/Toggle.swift +++ b/VDS/Components/Toggle/Toggle.swift @@ -119,6 +119,10 @@ open class Toggle: Control, Changeable { open var label = Label().with { $0.setContentCompressionResistancePriority(.required, for: .vertical) + $0.textColorConfiguration = ViewColorConfiguration().with { + $0.setSurfaceColors(VDSColor.elementsSecondaryOnlight, VDSColor.elementsSecondaryOndark, forDisabled: true) + $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forDisabled: false) + }.eraseToAnyColorable() } open var isOn: Bool { diff --git a/VDS/Components/Tooltip/TooltipAlertViewController.swift b/VDS/Components/Tooltip/TooltipAlertViewController.swift index b645c424..23d84c48 100644 --- a/VDS/Components/Tooltip/TooltipAlertViewController.swift +++ b/VDS/Components/Tooltip/TooltipAlertViewController.swift @@ -10,7 +10,7 @@ import UIKit import Combine import VDSColorTokens -open class TooltipAlertViewController: UIViewController, Surfaceable, UIScrollViewDelegate { +open class TooltipAlertViewController: UIViewController, Surfaceable { /// Set of Subscribers for any Publishers for this Control public var subscribers = Set() @@ -26,69 +26,22 @@ open class TooltipAlertViewController: UIViewController, Surfaceable, UIScrollVi } } - private var scrollView = UIScrollView().with { - $0.translatesAutoresizingMaskIntoConstraints = false - $0.backgroundColor = .clear - } + private let tooltipDialog = TooltipDialog() - private let modalView = View().with { - $0.layer.cornerRadius = 8 - } - - public var contentView: UIView? = nil - - private let contentStackView = UIStackView().with { - $0.translatesAutoresizingMaskIntoConstraints = false - $0.axis = .vertical - $0.distribution = .fillProportionally - $0.spacing = 0 - } - - private var line = Line().with { instance in - instance.lineViewColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsLowcontrastOnlight, VDSColor.elementsLowcontrastOndark).eraseToAnyColorable() - } - //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- open var surface: Surface = .light { didSet { updateView() }} open var titleText: String? { didSet { updateView() }} - open var titleLabel = Label().with { label in - label.textStyle = .boldTitleMedium - } - open var contentText: String? { didSet { updateView() }} - open var contentLabel = Label().with { label in - label.textStyle = .bodyLarge - } - + open var contentView: UIView? { didSet { updateView() }} open var closeButtonText: String = "Close" { didSet { updateView() }} - - open lazy var closeButton: UIButton = { - let button = UIButton(type: .system) - button.backgroundColor = .clear - button.setTitle("Close", for: .normal) - button.titleLabel?.font = TextStyle.bodyLarge.font - button.translatesAutoresizingMaskIntoConstraints = false - return button - }() //-------------------------------------------------- // MARK: - Configuration //-------------------------------------------------- - private let containerViewBackgroundColorConfiguration = SurfaceColorConfiguration().with { instance in - instance.lightColor = .white - instance.darkColor = .black - } - private let backgroundColorConfiguration = SurfaceColorConfiguration(VDSColor.backgroundPrimaryDark, VDSColor.backgroundPrimaryLight) - - private let closeButtonTextColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark) - private let containerViewInset = VDSLayout.Spacing.space4X.value - private var containerBottomConstraint: NSLayoutConstraint? - private var containerHeightConstraint: NSLayoutConstraint? - private var contentStackViewBottomConstraint: NSLayoutConstraint? //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- @@ -114,57 +67,145 @@ open class TooltipAlertViewController: UIViewController, Surfaceable, UIScrollVi }.store(in: &subscribers) //clicking button - onClickSubscriber = closeButton.publisher(for: .touchUpInside) + onClickSubscriber = tooltipDialog.closeButton.publisher(for: .touchUpInside) .sink {[weak self] button in guard let self else { return } self.dismiss(animated: true, completion: nil) } - contentStackView.addArrangedSubview(titleLabel) - contentStackView.addArrangedSubview(contentLabel) - scrollView.addSubview(contentStackView) - modalView.addSubview(scrollView) - modalView.addSubview(line) - modalView.addSubview(closeButton) - view.addSubview(modalView) + view.addSubview(tooltipDialog) // Activate constraints NSLayoutConstraint.activate([ // Constraints for the floating modal view - modalView.centerXAnchor.constraint(equalTo: view.centerXAnchor), - modalView.centerYAnchor.constraint(equalTo: view.centerYAnchor), - modalView.widthAnchor.constraint(equalToConstant: 296), - modalView.heightAnchor.constraint(greaterThanOrEqualToConstant: 96), - modalView.heightAnchor.constraint(lessThanOrEqualToConstant: 312), - - // Constraints for the scroll view - scrollView.topAnchor.constraint(equalTo: modalView.topAnchor, constant: VDSLayout.Spacing.space4X.value), - scrollView.leadingAnchor.constraint(equalTo: modalView.leadingAnchor), - scrollView.trailingAnchor.constraint(equalTo: modalView.trailingAnchor), - scrollView.bottomAnchor.constraint(equalTo: line.topAnchor), - - line.leadingAnchor.constraint(equalTo: modalView.leadingAnchor), - line.trailingAnchor.constraint(equalTo: modalView.trailingAnchor), - - closeButton.topAnchor.constraint(equalTo: line.bottomAnchor), - closeButton.leadingAnchor.constraint(equalTo: modalView.leadingAnchor), - closeButton.trailingAnchor.constraint(equalTo: modalView.trailingAnchor), - closeButton.bottomAnchor.constraint(equalTo: modalView.bottomAnchor), - closeButton.heightAnchor.constraint(equalToConstant: 44.0), - - contentStackView.topAnchor.constraint(equalTo: scrollView.topAnchor), - contentStackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: containerViewInset), - contentStackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: -containerViewInset), - contentStackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: -containerViewInset), - contentStackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor, constant: -(containerViewInset * 2)), - contentStackView.heightAnchor.constraint(greaterThanOrEqualTo: scrollView.heightAnchor, constant: -(containerViewInset * 2)) - + tooltipDialog.centerXAnchor.constraint(equalTo: view.centerXAnchor), + tooltipDialog.centerYAnchor.constraint(equalTo: view.centerYAnchor), + tooltipDialog.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor), + tooltipDialog.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor), + tooltipDialog.topAnchor.constraint(greaterThanOrEqualTo: view.topAnchor), + tooltipDialog.bottomAnchor.constraint(lessThanOrEqualTo: view.bottomAnchor) ]) } open func updateView() { view.backgroundColor = backgroundColorConfiguration.getColor(self).withAlphaComponent(0.3) - modalView.backgroundColor = containerViewBackgroundColorConfiguration.getColor(self) + tooltipDialog.surface = surface + tooltipDialog.titleText = titleText + tooltipDialog.contentText = contentText + tooltipDialog.contentView = contentView + } +} + +open class TooltipDialog: View, UIScrollViewDelegate { + + private var scrollView = UIScrollView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.backgroundColor = .clear + } + + private let contentStackView = UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.axis = .vertical + $0.distribution = .fillProportionally + $0.spacing = 0 + } + + private var line = Line().with { instance in + instance.lineViewColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsLowcontrastOnlight, VDSColor.elementsLowcontrastOndark).eraseToAnyColorable() + } + + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + open var titleText: String? { didSet { setNeedsUpdate() }} + open var titleLabel = Label().with { label in + label.textStyle = .boldTitleMedium + } + + open var contentText: String? { didSet { setNeedsUpdate() }} + open var contentLabel = Label().with { label in + label.textStyle = .bodyLarge + } + + open var contentView: UIView? = nil + + open var closeButtonText: String = "Close" { didSet { setNeedsUpdate() }} + + open lazy var closeButton: UIButton = { + let button = UIButton(type: .system) + button.backgroundColor = .clear + button.setTitle("Close", for: .normal) + button.titleLabel?.font = TextStyle.bodyLarge.font + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + //-------------------------------------------------- + // MARK: - Configuration + //-------------------------------------------------- + private var closeButtonHeight: CGFloat = 44.0 + private var fullWidth: CGFloat = 296 + private var minHeight: CGFloat = 96.0 + private var maxHeight: CGFloat = 312.0 + private let containerViewInset = VDSLayout.Spacing.space4X.value + + private let backgroundColorConfiguration = SurfaceColorConfiguration(VDSColor.backgroundPrimaryLight, VDSColor.backgroundPrimaryDark) + private let closeButtonTextColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark) + + private var contentStackViewBottomConstraint: NSLayoutConstraint? + private var heightConstraint: NSLayoutConstraint? + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + open override func setup() { + super.setup() + + layer.cornerRadius = 8 + + contentStackView.addArrangedSubview(titleLabel) + contentStackView.addArrangedSubview(contentLabel) + scrollView.addSubview(contentStackView) + addSubview(scrollView) + addSubview(line) + addSubview(closeButton) + + // Activate constraints + NSLayoutConstraint.activate([ + widthAnchor.constraint(equalToConstant: fullWidth), + + // Constraints for the scroll view + scrollView.topAnchor.constraint(equalTo: topAnchor, constant: VDSLayout.Spacing.space4X.value), + scrollView.leadingAnchor.constraint(equalTo: leadingAnchor), + scrollView.trailingAnchor.constraint(equalTo: trailingAnchor), + scrollView.bottomAnchor.constraint(equalTo: line.topAnchor), + + line.leadingAnchor.constraint(equalTo: leadingAnchor), + line.trailingAnchor.constraint(equalTo: trailingAnchor), + + closeButton.topAnchor.constraint(equalTo: line.bottomAnchor), + closeButton.leadingAnchor.constraint(equalTo: leadingAnchor), + closeButton.trailingAnchor.constraint(equalTo: trailingAnchor), + closeButton.bottomAnchor.constraint(equalTo: bottomAnchor), + closeButton.heightAnchor.constraint(equalToConstant: closeButtonHeight), + + contentStackView.topAnchor.constraint(equalTo: scrollView.topAnchor), + contentStackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: containerViewInset), + contentStackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: -containerViewInset), + contentStackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor, constant: -(containerViewInset * 2)), + + ]) + contentStackViewBottomConstraint = contentStackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor) + contentStackViewBottomConstraint?.activate() + + heightConstraint = heightAnchor.constraint(equalToConstant: minHeight) + heightConstraint?.activate() + } + + open override func updateView() { + super.updateView() + + backgroundColor = backgroundColorConfiguration.getColor(self) scrollView.indicatorStyle = surface == .light ? .black : .white titleLabel.removeFromSuperview() @@ -217,7 +258,31 @@ open class TooltipAlertViewController: UIViewController, Surfaceable, UIScrollVi closeButton.setTitleColor(closeButtonTextColor, for: .highlighted) closeButton.setTitle(closeButtonText, for: .normal) + contentStackView.setNeedsLayout() + contentStackView.layoutIfNeeded() + + scrollView.setNeedsLayout() scrollView.layoutIfNeeded() - + + //dealing with height + //we can't really use the minMax height and set constraints for + //greaterThan or lessThan on the heightAnchor due to scrollView/stackView intrinsic size + //therefore we can do a little math and manually set the height based off all of the content + var contentHeight = closeButtonHeight + scrollView.contentSize.height + (containerViewInset * 2) + + //reset the bottomConstraint + contentStackViewBottomConstraint?.constant = 0 + + if contentHeight < minHeight { + contentHeight = minHeight + + } else if contentHeight > maxHeight { + contentHeight = maxHeight + //since we are now scrolling, add padding to the bottom of the + //stackView between the bottom of the scrollView + contentStackViewBottomConstraint?.constant = -containerViewInset + } + + heightConstraint?.constant = contentHeight } } diff --git a/VDS/Protocols/Colorable.swift b/VDS/Protocols/Colorable.swift index 3c43bf1a..db70123b 100644 --- a/VDS/Protocols/Colorable.swift +++ b/VDS/Protocols/Colorable.swift @@ -8,12 +8,17 @@ import Foundation import UIKit +/// Protocol used to be implemented on a object that can return a color for a Generic ObjectType. public protocol Colorable { associatedtype ObjectType + /// Method used to get the current color needed for the Generic ObjectType passed into the method. func getColor(_ object: ObjectType) -> UIColor } extension Colorable { + /// Helper method used where you don't know the ObjectType to return the color. + /// - Parameter object: Any object, but this objectType will be checked against the registered Generic ObjectType for the Colorable + /// - Returns: UIColor for the rule of this Colorable implementation for the object passed into the function fileprivate func getColor(_ object: Any) -> UIColor { guard let model = object as? ObjectType else { assertionFailure("Invalid ObjectType, Expecting type \(ObjectType.self), received \(object) ") @@ -22,21 +27,12 @@ extension Colorable { return getColor(model) } + /// TypeErasure for the Colorable to AnyColorable + /// - Returns: AnyColorable public func eraseToAnyColorable() -> AnyColorable { AnyColorable(self) } } -public struct GenericColorable: Colorable, Withable { - private var wrapped: any Colorable - - public init(colorable: C) where C.ObjectType == ObjectType { - wrapped = colorable - } - - public func getColor(_ object: ObjectType) -> UIColor { - wrapped.getColor(object) - } -} - +/// TypeErased Struct of a Colorable Type. This type can be used anywhere as long as the ObjectType being passed into this method matches the wrapped Colorable ObjectType. public struct AnyColorable: Colorable, Withable { private let wrapped: any Colorable diff --git a/VDS/Protocols/Disabling.swift b/VDS/Protocols/Disabling.swift index c0be6d22..51a5bb50 100644 --- a/VDS/Protocols/Disabling.swift +++ b/VDS/Protocols/Disabling.swift @@ -7,6 +7,8 @@ import Foundation +/// Any object that can be disabled, which may change the appearance public protocol Disabling { + /// Whether this object is disabled or not var disabled: Bool { get set } } diff --git a/VDS/Protocols/Surfaceable.swift b/VDS/Protocols/Surfaceable.swift index d7889f04..b3a2dc6e 100644 --- a/VDS/Protocols/Surfaceable.swift +++ b/VDS/Protocols/Surfaceable.swift @@ -9,6 +9,7 @@ import Foundation import UIKit import VDSColorTokens +/// The background tint that the component will be placed on. This will automatically adjust other elements as needed and takes "light" or "dark" public enum Surface: String, Equatable { case light, dark public var color: UIColor { @@ -17,5 +18,6 @@ public enum Surface: String, Equatable { } public protocol Surfaceable { + /// Current Surface and this is used to pass down to child objects that implement Surfacable var surface: Surface { get set } } diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index 20b54f1c..4f5bea0b 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -1,6 +1,7 @@ 1.0.27 ======= - Added Loader View +- CXTDT-426628 - Toggle - Incorrect disabled text color 1.0.26 ======= diff --git a/VDS/VDS.docc/VDS.md b/VDS/VDS.docc/VDS.md index 0cf1e32e..fade2c59 100755 --- a/VDS/VDS.docc/VDS.md +++ b/VDS/VDS.docc/VDS.md @@ -8,19 +8,50 @@ Using the system allows designers and developers to collaborate more easily and ## Topics -### Components +### BaseComponents +- ``ButtonBase`` +- ``Control`` +- ``View`` +- ``SelfSizingCollectionView`` +- ``SelectorBase`` +- ``SelectorItemBase`` +- ``SelectorGroupHandlerBase`` +- ``SelectorGroupSelectedHandlerBase`` +### Components - ``Badge`` +- ``BadgeIndicator`` - ``Button`` -- ``TextLink`` -- ``TextLinkCaret`` -- ``CheckboxGroup`` +- ``ButtonIcon`` +- ``ButtonGroup`` - ``Checkbox`` +- ``CheckboxItem`` +- ``CheckboxGroup`` +- ``Icon`` - ``Label`` +- ``Line`` +- ``Loader`` +- ``RadioBoxItem`` - ``RadioBoxGroup`` -- ``RadioBox`` -- ``RadioButtonGroup`` - ``RadioButton`` +- ``RadioButtonItem`` +- ``RadioButtonGroup`` - ``RadioSwatchGroup`` - ``RadioSwatch`` +- ``Tabs`` +- ``TextLink`` +- ``TextLinkCaret`` +- ``TileContainer`` +- ``Tilelet`` +- ``TitleLockup`` - ``Toggle`` +- ``Tooltip`` + +### ColorConfiguration +- ``Colorable`` +- ``AnyColorable`` +- ``SurfaceColorConfiguration`` +- ``KeyColorConfiguration`` +- ``KeyedColorConfiguration`` +- ``ControlColorConfiguration`` +- ``ViewColorConfiguration``