Merge branch 'vasavk/inputStepper' into 'develop'
VDS Brand 3.0 Stepper Input for IOS See merge request BPHV_MIPS/vds_ios!275
This commit is contained in:
commit
19b07b782c
@ -7,6 +7,8 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
180636C72C29B0A400C92D86 /* InputStepper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 180636C62C29B0A400C92D86 /* InputStepper.swift */; };
|
||||
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 */; };
|
||||
1842B1DF2BECE28B0021AFCA /* CalendarDateViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1842B1DE2BECE28B0021AFCA /* CalendarDateViewCell.swift */; };
|
||||
@ -207,6 +209,8 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
180636C62C29B0A400C92D86 /* InputStepper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputStepper.swift; sourceTree = "<group>"; };
|
||||
180636C82C29B0DF00C92D86 /* InputStepperLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = InputStepperLog.txt; sourceTree = "<group>"; };
|
||||
1808BEBB2BA41C3200129230 /* CarouselScrollbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselScrollbar.swift; sourceTree = "<group>"; };
|
||||
1808BEBF2BA456B700129230 /* CarouselScrollbarChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CarouselScrollbarChangeLog.txt; sourceTree = "<group>"; };
|
||||
1832AC562BA0791D008AE476 /* BreadcrumbCellItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbCellItem.swift; sourceTree = "<group>"; };
|
||||
@ -449,6 +453,15 @@
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
180636C52C29B06200C92D86 /* InputStepper */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
180636C62C29B0A400C92D86 /* InputStepper.swift */,
|
||||
180636C82C29B0DF00C92D86 /* InputStepperLog.txt */,
|
||||
);
|
||||
path = InputStepper;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1808BEBA2BA41B1D00129230 /* CarouselScrollbar */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -681,6 +694,7 @@
|
||||
EAC58C1F2BF127F000BA39FA /* DatePicker */,
|
||||
186D13C92BBA8A3500986B53 /* DropdownSelect */,
|
||||
EA985BF3296C609E00F2FF2E /* Icon */,
|
||||
180636C52C29B06200C92D86 /* InputStepper */,
|
||||
EA3362412892EF700071C351 /* Label */,
|
||||
44604AD529CE195300E62B51 /* Line */,
|
||||
EAD0688C2A55F801002E3A2D /* Loader */,
|
||||
@ -1188,6 +1202,7 @@
|
||||
EA3362072891E14D0071C351 /* VerizonNHGeDS-Regular.otf in Resources */,
|
||||
EA3362062891E14D0071C351 /* VerizonNHGeTX-Regular.otf in Resources */,
|
||||
EA3362052891E14D0071C351 /* VerizonNHGeDS-Bold.otf in Resources */,
|
||||
180636C92C29B0DF00C92D86 /* InputStepperLog.txt in Resources */,
|
||||
EAA5EEB928ECD24B003B3210 /* Icons.xcassets in Resources */,
|
||||
EAA5EEE428F5B855003B3210 /* VerizonNHGDS-Light.otf in Resources */,
|
||||
);
|
||||
@ -1231,6 +1246,7 @@
|
||||
files = (
|
||||
445BA07829C07B3D0036A7C5 /* Notification.swift in Sources */,
|
||||
EA5E304C294CBDD00082B959 /* TileContainer.swift in Sources */,
|
||||
180636C72C29B0A400C92D86 /* InputStepper.swift in Sources */,
|
||||
EAF7F0A6289B0CE000B287F5 /* Resetable.swift in Sources */,
|
||||
EA985C2D296F03FE00F2FF2E /* TileletIconModels.swift in Sources */,
|
||||
EA89200428AECF4B006B9984 /* UITextField+Publisher.swift in Sources */,
|
||||
|
||||
@ -78,6 +78,7 @@ open class View: UIView, ViewProtocol, UserInfoable, Clickable {
|
||||
backgroundColor = .clear
|
||||
surface = .light
|
||||
isEnabled = true
|
||||
onClick = nil
|
||||
userInfo.removeAll()
|
||||
}
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ import Combine
|
||||
/// A dropdown select is an expandable menu of predefined options that allows a customer to make a single selection.
|
||||
@objcMembers
|
||||
@objc(VDSDatePicker)
|
||||
open class DatePicker: EntryFieldBase {
|
||||
open class DatePicker: EntryFieldBase<String> {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializers
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -13,7 +13,7 @@ import Combine
|
||||
/// A dropdown select is an expandable menu of predefined options that allows a customer to make a single selection.
|
||||
@objcMembers
|
||||
@objc(VDSDropdownSelect)
|
||||
open class DropdownSelect: EntryFieldBase {
|
||||
open class DropdownSelect: EntryFieldBase<String> {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializers
|
||||
//--------------------------------------------------
|
||||
|
||||
422
VDS/Components/InputStepper/InputStepper.swift
Normal file
422
VDS/Components/InputStepper/InputStepper.swift
Normal file
@ -0,0 +1,422 @@
|
||||
//
|
||||
// InputStepper.swift
|
||||
// VDS
|
||||
//
|
||||
// Created by Kanamarlapudi, Vasavi on 24/06/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import VDSCoreTokens
|
||||
import Combine
|
||||
|
||||
/// A stepper is a two-segment control that people use to increase or decrease an incremental value.'
|
||||
@objcMembers
|
||||
@objc(VDSInputStepper)
|
||||
open class InputStepper: EntryFieldBase<Int> {
|
||||
|
||||
//--------------------------------------------------
|
||||
// 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 size of Input Stepper.
|
||||
public enum Size: String, CaseIterable {
|
||||
case large, small
|
||||
|
||||
var minWidth: CGFloat {
|
||||
self == .large ? 121 : 90
|
||||
}
|
||||
|
||||
var minHeight: CGFloat {
|
||||
self == .large ? 44 : 32
|
||||
}
|
||||
|
||||
var space: CGFloat {
|
||||
self == .large ? VDSLayout.space3X : VDSLayout.space2X
|
||||
}
|
||||
var padding: CGFloat {
|
||||
self == .large ? 6.0 : VDSLayout.space1X
|
||||
}
|
||||
|
||||
var buttonContainerSize: Int {
|
||||
self == .large ? 32 : 24
|
||||
}
|
||||
|
||||
var textStyle: TextStyle {
|
||||
self == .large ? .boldBodyLarge : .boldBodySmall
|
||||
}
|
||||
}
|
||||
|
||||
/// Enum used to describe the width of a fixed value or percentage of the input stepper control.
|
||||
public enum ControlWidth {
|
||||
case percentage(CGFloat)
|
||||
case value(CGFloat)
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Public Properties
|
||||
//--------------------------------------------------
|
||||
/// If there is a width that is larger than this size's minimumWidth, the input stepper will resize to this width.
|
||||
open var controlWidth: ControlWidth? {
|
||||
get { _controlWidth }
|
||||
set {
|
||||
if let newValue {
|
||||
switch newValue {
|
||||
case .percentage(let percentage):
|
||||
if percentage <= 100.0 {
|
||||
_controlWidth = newValue
|
||||
}
|
||||
case .value(let value):
|
||||
if value > 0 && value > containerSize.width {
|
||||
_controlWidth = newValue
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_controlWidth = nil
|
||||
}
|
||||
setNeedsUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
/// Accepts percentage value to width of parent container.
|
||||
open var widthPercentage: CGFloat? {
|
||||
didSet {
|
||||
if let percentage = widthPercentage, percentage > 100 {
|
||||
widthPercentage = 100
|
||||
}
|
||||
setNeedsUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
private var _defaultValue: Int = 0
|
||||
open override var defaultValue: Int? {
|
||||
get { _defaultValue }
|
||||
set {
|
||||
if let newValue {
|
||||
_defaultValue = newValue > maxValue ? maxValue : newValue < minValue ? minValue : newValue
|
||||
} else {
|
||||
_defaultValue = 0
|
||||
}
|
||||
setNeedsUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
open override var value: Int? { return defaultValue }
|
||||
|
||||
/// Maximum value of the input stepper, defaults to '99'.
|
||||
lazy open var maxValue: Int = { _defaultMaxValue }() {
|
||||
didSet {
|
||||
if maxValue > _defaultMaxValue || maxValue < _defaultMinValue && maxValue > minValue {
|
||||
maxValue = _defaultMaxValue
|
||||
}
|
||||
setNeedsUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
/// Minimum value of the input stepper, defaults to '0'.
|
||||
lazy open var minValue: Int = { _defaultMinValue }() {
|
||||
didSet {
|
||||
if minValue < _defaultMinValue && minValue >= _defaultMaxValue && minValue < maxValue {
|
||||
minValue = _defaultMinValue
|
||||
}
|
||||
setNeedsUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
/// The size of the input stepper. Defaults to 'large'.
|
||||
open var size: Size = .large { didSet { setNeedsUpdate() } }
|
||||
|
||||
/// Accepts any text or character to appear next to input stepper value.
|
||||
open var trailingText: String? { didSet { setNeedsUpdate() } }
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Private Properties
|
||||
//--------------------------------------------------
|
||||
private var _controlWidth: ControlWidth? = nil
|
||||
private var _defaultMinValue: Int = 0
|
||||
private var _defaultMaxValue: Int = 99
|
||||
|
||||
/// This is the view that will be wrapped with the border for userInteraction.
|
||||
/// The only subview of this view is the stepperStackView.
|
||||
internal var stepperContainerView = View().with {
|
||||
$0.isAccessibilityElement = true
|
||||
$0.accessibilityLabel = "Input Stepper"
|
||||
}
|
||||
|
||||
internal var stepperStackView = UIStackView().with {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
$0.axis = .horizontal
|
||||
$0.distribution = .fill
|
||||
$0.alignment = .fill
|
||||
}
|
||||
|
||||
internal var decrementButton = ButtonIcon().with {
|
||||
$0.kind = .ghost
|
||||
$0.iconName = Icon.Name(name: "minus")
|
||||
$0.iconOffset = .init(x: -2, y: 0)
|
||||
$0.customContainerSize = 32
|
||||
$0.icon.customSize = 16
|
||||
$0.backgroundColor = .clear
|
||||
}
|
||||
|
||||
internal var incrementButton = ButtonIcon().with {
|
||||
$0.kind = .ghost
|
||||
$0.iconName = Icon.Name(name: "plus")
|
||||
$0.iconOffset = .init(x: 2, y: 0)
|
||||
$0.customContainerSize = 32
|
||||
$0.icon.customSize = 16
|
||||
$0.backgroundColor = .clear
|
||||
}
|
||||
|
||||
internal var textLabel = Label().with {
|
||||
$0.setContentCompressionResistancePriority(.required, for: .vertical)
|
||||
$0.textStyle = .boldBodyLarge
|
||||
$0.numberOfLines = 1
|
||||
$0.lineBreakMode = .byTruncatingTail
|
||||
$0.textAlignment = .center
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Constraints
|
||||
//--------------------------------------------------
|
||||
internal var stepperWidthConstraint: NSLayoutConstraint?
|
||||
internal var stepperHeightConstraint: NSLayoutConstraint?
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Configuration Properties
|
||||
//--------------------------------------------------
|
||||
internal override var containerSize: CGSize { CGSize(width: size.minWidth, height: size.minHeight) }
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Lifecycle
|
||||
//--------------------------------------------------
|
||||
|
||||
/// 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()
|
||||
// Set initial states
|
||||
defaultValue = 0
|
||||
containerView.isEnabled = false
|
||||
statusIcon.isHidden = true
|
||||
|
||||
//override the default settings since the containerView
|
||||
//fieldStackView relationShip needs to be updated
|
||||
//we are not applying spacing either in the edges since this
|
||||
//is the view that will take place of the containerView for the
|
||||
//design of the original "containerView". This will get refactored at
|
||||
//some point.
|
||||
fieldStackView.applyAlignment(.leading)
|
||||
|
||||
// Add listeners
|
||||
decrementButton.onClick = { _ in self.decrementButtonClick() }
|
||||
incrementButton.onClick = { _ in self.incrementButtonClick() }
|
||||
|
||||
// setting color config
|
||||
textLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable()
|
||||
}
|
||||
|
||||
open override func getFieldContainer() -> UIView {
|
||||
stepperStackView.addArrangedSubview(decrementButton)
|
||||
stepperStackView.addArrangedSubview(textLabel)
|
||||
stepperStackView.addArrangedSubview(incrementButton)
|
||||
|
||||
// Set space between decrement button, label, and increment button relative to input Stepper size.
|
||||
stepperStackView.setCustomSpacing(size.space, after: decrementButton)
|
||||
stepperStackView.setCustomSpacing(size.space, after: textLabel)
|
||||
|
||||
// stepperContainerView for controls in EntryFieldBase.controlContainerView
|
||||
stepperContainerView.addSubview(stepperStackView)
|
||||
|
||||
// Update Edge insets relative to input Stepper size.
|
||||
stepperStackView.pinToSuperView(.uniform(size.padding))
|
||||
|
||||
stepperWidthConstraint = stepperContainerView.width(constant: containerSize.width, priority: .required)
|
||||
stepperHeightConstraint = stepperContainerView.height(constant: containerSize.height, priority: .required)
|
||||
|
||||
return stepperContainerView
|
||||
}
|
||||
|
||||
/// Used to make changes to the View based off a change events or from local properties.
|
||||
open override func updateView() {
|
||||
super.updateView()
|
||||
statusIcon.isHidden = true
|
||||
|
||||
// Update label
|
||||
textLabel.isEnabled = isEnabled
|
||||
textLabel.surface = surface
|
||||
textLabel.text = "\(_defaultValue) " + (trailingText ?? "")
|
||||
textLabel.textStyle = size.textStyle
|
||||
|
||||
updateButtonStates()
|
||||
}
|
||||
|
||||
open override var accessibilityElements: [Any]? {
|
||||
get {
|
||||
var elements = [Any]()
|
||||
|
||||
if !isReadOnly || isEnabled {
|
||||
elements.append(contentsOf: [titleLabel, containerView, decrementButton, textLabel, incrementButton])
|
||||
} else {
|
||||
elements.append(contentsOf: [titleLabel, containerView, textLabel])
|
||||
}
|
||||
|
||||
if showError {
|
||||
if let errorText, !errorText.isEmpty {
|
||||
elements.append(errorLabel)
|
||||
}
|
||||
}
|
||||
if let helperText, !helperText.isEmpty {
|
||||
elements.append(helperLabel)
|
||||
}
|
||||
return elements
|
||||
}
|
||||
|
||||
set { super.accessibilityElements = newValue }
|
||||
}
|
||||
|
||||
/// Resets to default settings.
|
||||
open override func reset() {
|
||||
super.reset()
|
||||
textLabel.reset()
|
||||
controlWidth = nil
|
||||
widthPercentage = nil
|
||||
defaultValue = 0
|
||||
minValue = _defaultMinValue
|
||||
maxValue = _defaultMaxValue
|
||||
trailingText = nil
|
||||
size = .large
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Overrides
|
||||
//--------------------------------------------------
|
||||
override func updateContainerView() {
|
||||
//we are not calling super since we
|
||||
//are using the fieldStackView as the "containerView"
|
||||
//which will get the look/feel of the containerView.
|
||||
//this will get refactored in the future.
|
||||
fieldStackView.backgroundColor = containerBackgroundColor
|
||||
fieldStackView.layer.borderColor = borderColorConfiguration.getColor(self).cgColor
|
||||
fieldStackView.layer.borderWidth = VDSFormControls.borderWidth
|
||||
}
|
||||
|
||||
internal override func updateContainerWidth() {
|
||||
//we are not calling super here since
|
||||
//we are changing how the widths are getting calculated
|
||||
//now by including a percentage.
|
||||
|
||||
defer {
|
||||
fieldStackView.layer.cornerRadius = containerSize.height / 2
|
||||
}
|
||||
|
||||
stepperWidthConstraint?.deactivate()
|
||||
widthConstraint?.deactivate()
|
||||
trailingLessThanEqualsConstraint?.deactivate()
|
||||
trailingEqualsConstraint?.deactivate()
|
||||
|
||||
var widthConstraintConstant: CGFloat?
|
||||
|
||||
if let widthPercentage, let superWidth = horizontalPinnedWidth() {
|
||||
// test value vs minimum width and take the greater value
|
||||
widthConstraintConstant = max(superWidth * (widthPercentage / 100), minWidth)
|
||||
} else if let width, width >= minWidth, width <= maxWidth {
|
||||
widthConstraintConstant = width
|
||||
} else if let parentWidth = width, parentWidth >= maxWidth {
|
||||
widthConstraintConstant = maxWidth
|
||||
} else if let parentWidth = width, parentWidth <= minWidth {
|
||||
widthConstraintConstant = minWidth
|
||||
}
|
||||
|
||||
if let widthConstraintConstant {
|
||||
widthConstraint?.constant = widthConstraintConstant
|
||||
widthConstraint?.activate()
|
||||
trailingLessThanEqualsConstraint?.activate()
|
||||
} else {
|
||||
trailingEqualsConstraint?.activate()
|
||||
}
|
||||
|
||||
// Update Edge insets if size changes applied.
|
||||
stepperStackView.applyAlignment(.fill, edges: .uniform(size.padding))
|
||||
|
||||
// Update height if size changes applied.
|
||||
stepperHeightConstraint?.constant = containerSize.height
|
||||
|
||||
//update the stepper's widthConstraint if
|
||||
//controlWidth was set
|
||||
guard let controlWidth else {
|
||||
return
|
||||
}
|
||||
|
||||
// Set the inputStepper's controlWidth based on percentage received relative to its parentView's frame.
|
||||
let containerWidth: CGFloat = widthConstraintConstant ?? containerView.frame.size.width
|
||||
var stepperWidthConstant: CGFloat?
|
||||
var stepperWidth: CGFloat
|
||||
|
||||
switch controlWidth {
|
||||
case .percentage(let percentage):
|
||||
stepperWidth = max(containerWidth * ((percentage) / 100), minWidth)
|
||||
|
||||
case .value(let value):
|
||||
stepperWidth = value
|
||||
}
|
||||
|
||||
//get the value of the stepperWidthConstant
|
||||
if stepperWidth >= containerSize.width && stepperWidth <= containerWidth {
|
||||
stepperWidthConstant = stepperWidth
|
||||
} else if stepperWidth >= containerWidth {
|
||||
stepperWidthConstant = containerWidth
|
||||
}
|
||||
|
||||
if let stepperWidthConstant {
|
||||
stepperWidthConstraint?.constant = stepperWidthConstant
|
||||
stepperWidthConstraint?.activate()
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Private Methods
|
||||
//--------------------------------------------------
|
||||
internal func decrementButtonClick() {
|
||||
if _defaultValue > minValue {
|
||||
defaultValue = _defaultValue - 1
|
||||
sendActions(for: .valueChanged)
|
||||
}
|
||||
}
|
||||
|
||||
internal func incrementButtonClick() {
|
||||
if _defaultValue < maxValue {
|
||||
defaultValue = _defaultValue + 1
|
||||
sendActions(for: .valueChanged)
|
||||
}
|
||||
}
|
||||
|
||||
internal func updateButtonStates() {
|
||||
decrementButton.customContainerSize = size.buttonContainerSize
|
||||
incrementButton.customContainerSize = size.buttonContainerSize
|
||||
decrementButton.surface = surface
|
||||
incrementButton.surface = surface
|
||||
|
||||
if isReadOnly || !isEnabled {
|
||||
decrementButton.isEnabled = false
|
||||
incrementButton.isEnabled = false
|
||||
} else {
|
||||
decrementButton.isEnabled = (defaultValue ?? _defaultMaxValue ) > minValue ? true : false
|
||||
incrementButton.isEnabled = (defaultValue ?? _defaultMinValue) < maxValue ? true : false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
44
VDS/Components/InputStepper/InputStepperLog.txt
Normal file
44
VDS/Components/InputStepper/InputStepperLog.txt
Normal file
@ -0,0 +1,44 @@
|
||||
|
||||
MM/DD/YYYY
|
||||
----------------
|
||||
|
||||
02/2024
|
||||
----------------
|
||||
- New component
|
||||
|
||||
02/15/2024
|
||||
----------------
|
||||
- Added Border align: Inside to Anatomy
|
||||
- Removed leadingText property values from States.
|
||||
- Added Read-only to States.
|
||||
- Created a section for Minimum width in Layout and spacing and updated the minWidth to 145px.
|
||||
- Added trailingText spacing to Layout and spacing.
|
||||
- Reduced space between Button Icons and text to 12px in Layout and spacing.
|
||||
- Added top/bottom padding values to Layout and spacing.
|
||||
|
||||
03/01/2024
|
||||
----------------
|
||||
- Removed Leading Text from “Content and other properties” section.
|
||||
|
||||
03/20/2024
|
||||
----------------
|
||||
- Updated Anatomy artwork and items
|
||||
- Added width and controlWidth to Configurations
|
||||
- Updated Width under Layout and spacing to show layout examples
|
||||
|
||||
04/12/2024
|
||||
----------------
|
||||
- Added a new configuration property (size) that includes large and small
|
||||
- Reduced details from Anatomy page and added them to Configurations/Size
|
||||
- Added Hit area, Small Input Stepper spacing properties to Layout and spacing
|
||||
- Updated the Behavior page to display disabled button icon when value is at max and min
|
||||
|
||||
04/29/2024
|
||||
----------------
|
||||
- Updated the Behavior page to display disabled button icon when value is at max and min
|
||||
|
||||
05/10/2024
|
||||
----------------
|
||||
- Added helperTextPlacement property to Configurations
|
||||
- Added Layout examples for right Helper Text placement in Layout and Spacing
|
||||
- Added overflow examples in Overflow section of Layout and Spacing
|
||||
@ -11,10 +11,7 @@ import VDSCoreTokens
|
||||
import Combine
|
||||
|
||||
/// Base Class used to build out a Input controls.
|
||||
@objcMembers
|
||||
@objc(VDSEntryField)
|
||||
open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
|
||||
|
||||
open class EntryFieldBase<ValueType>: Control, Changeable, FormFieldInternalValidatable {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializers
|
||||
//--------------------------------------------------
|
||||
@ -229,11 +226,11 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
|
||||
open var inputId: String? { didSet { setNeedsUpdate() } }
|
||||
|
||||
/// The text of this textField.
|
||||
open var value: String? {
|
||||
open var value: ValueType? {
|
||||
get { fatalError("must be read from subclass")}
|
||||
}
|
||||
|
||||
open var defaultValue: AnyHashable? { didSet { setNeedsUpdate() } }
|
||||
open var defaultValue: ValueType? { didSet { setNeedsUpdate() } }
|
||||
|
||||
open var isRequired: Bool = false { didSet { setNeedsUpdate() } }
|
||||
|
||||
@ -245,7 +242,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
|
||||
}
|
||||
}
|
||||
|
||||
open var rules = [AnyRule<String>]()
|
||||
open var rules = [AnyRule<ValueType>]()
|
||||
|
||||
open var accessibilityHintText: String = "Double tap to open"
|
||||
|
||||
@ -377,8 +374,8 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
|
||||
}
|
||||
|
||||
containerView.bridge_accessibilityValueBlock = { [weak self] in
|
||||
guard let self else { return "" }
|
||||
return value
|
||||
guard let self, let value else { return "" }
|
||||
return "\(value)"
|
||||
}
|
||||
|
||||
statusIcon.bridge_accessibilityLabelBlock = { [weak self] in
|
||||
@ -531,8 +528,8 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
|
||||
//--------------------------------------------------
|
||||
internal func updateRules() {
|
||||
rules.removeAll()
|
||||
if isRequired && useRequiredRule {
|
||||
let rule = RequiredRule()
|
||||
if isRequired && useRequiredRule && ValueType.self == String.self {
|
||||
let rule = RequiredRule<ValueType>()
|
||||
if let errorText, !errorText.isEmpty {
|
||||
rule.errorMessage = errorText
|
||||
} else if let labelText{
|
||||
@ -550,7 +547,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
|
||||
containerView.layer.borderWidth = VDSFormControls.borderWidth
|
||||
containerView.layer.cornerRadius = VDSFormControls.borderRadius
|
||||
}
|
||||
|
||||
|
||||
internal func updateContainerWidth() {
|
||||
widthConstraint?.deactivate()
|
||||
trailingLessThanEqualsConstraint?.deactivate()
|
||||
|
||||
@ -15,7 +15,7 @@ import Combine
|
||||
/// dates and security codes in their correct formats.
|
||||
@objcMembers
|
||||
@objc(VDSInputField)
|
||||
open class InputField: EntryFieldBase {
|
||||
open class InputField: EntryFieldBase<String> {
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializers
|
||||
|
||||
@ -7,12 +7,14 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class RequiredRule: Rule {
|
||||
class RequiredRule<ValueType>: Rule {
|
||||
var maxLength: Int?
|
||||
var errorMessage: String = "This field is required."
|
||||
|
||||
func isValid(value: String?) -> Bool {
|
||||
guard let value, !value.isEmpty, value.count > 0 else { return false }
|
||||
func isValid(value: ValueType?) -> Bool {
|
||||
guard let value,
|
||||
!"\(value)".isEmpty,
|
||||
"\(value)".count > 0 else { return false }
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@ import Combine
|
||||
/// Use a text area when you want customers to enter text that’s longer than a single line.
|
||||
@objcMembers
|
||||
@objc(VDSTextArea)
|
||||
open class TextArea: EntryFieldBase {
|
||||
open class TextArea: EntryFieldBase<String> {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializers
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
import Foundation
|
||||
|
||||
/// Protocol used for a FormField object.
|
||||
public protocol FormFieldable {
|
||||
public protocol FormFieldable<ValueType> {
|
||||
associatedtype ValueType = AnyHashable
|
||||
|
||||
/// Unique Id for the Form Field object within a Form.
|
||||
@ -19,7 +19,7 @@ public protocol FormFieldable {
|
||||
}
|
||||
|
||||
/// Protocol for FormFieldable that require internal validation.
|
||||
public protocol FormFieldInternalValidatable: FormFieldable, Errorable {
|
||||
public protocol FormFieldInternalValidatable<ValueType>: FormFieldable, Errorable {
|
||||
/// Rules that drive the validator
|
||||
var rules: [AnyRule<ValueType>] { get set }
|
||||
|
||||
|
||||
@ -33,6 +33,7 @@ Using the system allows designers and developers to collaborate more easily and
|
||||
- ``CheckboxGroup``
|
||||
- ``DropdownSelect``
|
||||
- ``Icon``
|
||||
- ``InputStepper``
|
||||
- ``InputField``
|
||||
- ``Label``
|
||||
- ``Line``
|
||||
|
||||
Loading…
Reference in New Issue
Block a user