From 0210e0d1280f35a2a31844bdf07881440b5653f6 Mon Sep 17 00:00:00 2001 From: Vasavi Kanamarlapudi Date: Wed, 24 Jul 2024 20:40:16 +0530 Subject: [PATCH] Digital ACT-191 ONEAPP-9311 story: control width can be set to auto (default) or value (pixel) , updating stepper view when size changes. --- .../DropdownSelect/DropdownSelect.swift | 2 +- .../InputStepper/InputStepper.swift | 184 ++++++++++++------ .../TextFields/EntryFieldBase.swift | 42 +++- .../TextFields/InputField/InputField.swift | 2 +- 4 files changed, 161 insertions(+), 69 deletions(-) diff --git a/VDS/Components/DropdownSelect/DropdownSelect.swift b/VDS/Components/DropdownSelect/DropdownSelect.swift index 7fc52808..59b48ac9 100644 --- a/VDS/Components/DropdownSelect/DropdownSelect.swift +++ b/VDS/Components/DropdownSelect/DropdownSelect.swift @@ -299,7 +299,7 @@ extension DropdownSelect: UIPickerViewDelegate, UIPickerViewDataSource { dropdownField.resignFirstResponder() } optionsPicker.isHidden = !optionsPicker.isHidden - updateContainerView() + updateContainerView(flag: true) updateErrorLabel() } diff --git a/VDS/Components/InputStepper/InputStepper.swift b/VDS/Components/InputStepper/InputStepper.swift index d630d1d1..57cdf756 100644 --- a/VDS/Components/InputStepper/InputStepper.swift +++ b/VDS/Components/InputStepper/InputStepper.swift @@ -40,17 +40,17 @@ open class InputStepper: EntryFieldBase { //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- - open var controlWidth: String? { - get { _controlWidth } - set { - setControlWidth(newValue) - setNeedsUpdate() - } - } - + /// Accepts a string or number value to control the width of input stepper. + /// auto(default) - The control's width is determined by the combined width of the value, trailing text and padding + /// Value - The control's width can be set to a fixed pixel or percentage value. + open var controlWidth: String? = "auto" { didSet { setNeedsUpdate() } } + /// Default value of the input stepper, defaults to '0'. open var defaultValue:Int = 0 { didSet { setNeedsUpdate() } } + /// Allows an id to be passed to input stepper. + open var id: Int? { didSet { setNeedsUpdate() } } + /// Maximum value of the input stepper, defaults to '99'. open var maxValue: Int? { get { return _maxValue } @@ -63,7 +63,7 @@ open class InputStepper: EntryFieldBase { setNeedsUpdate() } } - + /// Minimum value of the input stepper, defaults to '0'. open var minValue: Int? { get { return _minValue } @@ -78,11 +78,10 @@ open class InputStepper: EntryFieldBase { } /// The size of the input stepper. Defaults to 'large'. - open var size: Size { - get { return _size } - set { - _size = newValue - updateSize() + open var size: Size = .large { + didSet { + updateStepperContainerViewSize() + setNeedsUpdate() } } @@ -92,15 +91,23 @@ open class InputStepper: EntryFieldBase { //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- - internal var _controlWidth = "auto" internal var _maxValue: Int = 99 internal var _minValue: Int = 0 - internal var _size: Size = .large - private var largeMinWidth = 121 - private var smallMinWidth = 90 + /// 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 + } - let decrementButton = ButtonIcon().with { + 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) @@ -109,7 +116,7 @@ open class InputStepper: EntryFieldBase { $0.backgroundColor = .clear } - let incrementButton = ButtonIcon().with { + internal var incrementButton = ButtonIcon().with { $0.kind = .ghost $0.iconName = Icon.Name(name: "plus") $0.iconOffset = .init(x: 2, y: 0) @@ -118,17 +125,31 @@ open class InputStepper: EntryFieldBase { $0.backgroundColor = .clear } - let textLabel = Label().with { + internal var textLabel = Label().with { $0.setContentCompressionResistancePriority(.required, for: .vertical) $0.textStyle = .boldBodyLarge - $0.backgroundColor = .clear + $0.numberOfLines = 1 + $0.lineBreakMode = .byTruncatingTail } - + //-------------------------------------------------- - // MARK: - Configuration + // MARK: - Constraints //-------------------------------------------------- - private var labelColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight , VDSColor.elementsPrimaryOndark) - private var labelDisabledColorConfiguration = SurfaceColorConfiguration(VDSColor.interactiveDisabledOnlight , VDSColor.interactiveDisabledOndark) + internal var stepperWidthConstraint: NSLayoutConstraint? + internal var stepperHeightConstraint: NSLayoutConstraint? + + //-------------------------------------------------- + // MARK: - Configuration Properties + //-------------------------------------------------- + internal override var containerSize: CGSize { CGSize(width: size == .large ? largeMinWidth : smallMinWidth, height: size == .large ? largeMinHeight : smallMinHeight) } + + internal var largeMinWidth = 121 + internal var smallMinWidth = 90 + internal var largeMinHeight = 44 + internal var smallMinHeight = 32 + + internal let labelColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight , VDSColor.elementsPrimaryOndark) + internal let labelDisabledColorConfiguration = SurfaceColorConfiguration(VDSColor.interactiveDisabledOnlight , VDSColor.interactiveDisabledOndark) //-------------------------------------------------- // MARK: - Lifecycle @@ -137,38 +158,68 @@ open class InputStepper: EntryFieldBase { super.initialSetup() } + /// 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() + + // accessibility isAccessibilityElement = false accessibilityLabel = "Input Stepper" + + // Set initial states containerView.isEnabled = false + statusIcon.isHidden = true + + // Add listeners decrementButton.onClick = { _ in self.decrementButtonClick() } incrementButton.onClick = { _ in self.incrementButtonClick() } + + // setting color config + textLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable() } open override func getFieldContainer() -> UIView { - // stackview for controls in EntryFieldBase.controlContainerView - let controlStackView = UIStackView().with { - $0.translatesAutoresizingMaskIntoConstraints = false - $0.axis = .horizontal - $0.spacing = VDSLayout.space3X - $0.backgroundColor = .clear - } - controlStackView.addArrangedSubview(decrementButton) - controlStackView.addArrangedSubview(textLabel) - controlStackView.addArrangedSubview(incrementButton) - return controlStackView + stepperStackView.addArrangedSubview(decrementButton) + stepperStackView.addArrangedSubview(textLabel) + stepperStackView.addArrangedSubview(incrementButton) + + // Set space between decrement button, label, and increment button relative to input Stepper size. + let space = size == .large ? VDSLayout.space3X : VDSLayout.space2X + stepperStackView.setCustomSpacing(space, after: decrementButton) + stepperStackView.setCustomSpacing(space, after: textLabel) + + // Update Edge insets relative to input Stepper size. + stepperStackView.pinToSuperView(.uniform(size == .large ? 6.0 : VDSLayout.space1X)) + + // stepperContainerView for controls in EntryFieldBase.controlContainerView + stepperContainerView.addSubview(stepperStackView) + + stepperWidthConstraint = stepperContainerView.widthAnchor.constraint(equalToConstant: containerSize.width) + stepperWidthConstraint?.deactivate() + stepperHeightConstraint = stepperContainerView.heightAnchor.constraint(equalToConstant: containerSize.height) + stepperHeightConstraint?.deactivate() + + return stepperContainerView } + /// Used to make changes to the View based off a change events or from local properties. open override func updateView() { super.updateView() - updateContainerView(flag: false) - textLabel.text = String(defaultValue) + " " + (trailingText ?? "") - decrementButton.surface = surface - incrementButton.surface = surface - textLabel.surface = surface statusIcon.isHidden = true + updateConstraintsToFieldStackView(flag: false) + + // Update label text, style, color, ande surface. + textLabel.text = String(defaultValue) + " " + (trailingText ?? "") + textLabel.textStyle = size == .large ? .boldBodyLarge : .boldBodySmall + textLabel.textColorConfiguration = !isEnabled ? labelDisabledColorConfiguration.eraseToAnyColorable() : labelColorConfiguration.eraseToAnyColorable() + textLabel.surface = surface + + // Update increment and decrement button. updateButtonStates() + + // Update stepper container border and corner radius. + setControlWidth(controlWidth) + updateContainerView(flag: false) } /// Resets to default settings. @@ -204,7 +255,11 @@ open class InputStepper: EntryFieldBase { } internal func updateButtonStates() { - textLabel.textColorConfiguration = !isEnabled ? labelDisabledColorConfiguration.eraseToAnyColorable() : labelColorConfiguration.eraseToAnyColorable() + decrementButton.customContainerSize = size == .large ? 32 : 24 + incrementButton.customContainerSize = size == .large ? 32 : 24 + decrementButton.surface = surface + incrementButton.surface = surface + if isReadOnly || !isEnabled { decrementButton.isEnabled = false incrementButton.isEnabled = false @@ -214,27 +269,42 @@ open class InputStepper: EntryFieldBase { } } - internal func updateSize() { - let value = size == .large ? 6.0 : VDSLayout.space1X - updateConstraintsToFieldStackView(value: value) + // Update edge insets and height when size changes. + internal func updateStepperContainerViewSize() { + updateButtonStates() - // textLabel.textStyle = size == .large ? .boldBodyLarge : .boldBodySmall - // textLabel.heightAnchor.constraint(equalToConstant: size == .large ? 44 : 32).activate() - - // decrementButton.customContainerSize = size == .large ? 32 : 24 - // incrementButton.customContainerSize = size == .large ? 32 : 24 + // Update Edge insets if size changes applied. + stepperStackView.removeFromSuperview() + stepperContainerView.addSubview(stepperStackView) + stepperStackView.pinToSuperView(.uniform(size == .large ? 6.0 : VDSLayout.space1X)) + // Update height if size changes applied. + stepperHeightConstraint?.deactivate() + stepperHeightConstraint = stepperContainerView.heightAnchor.constraint(equalToConstant: containerSize.height) + stepperHeightConstraint?.activate() } + // Set control width to input stepper. internal func setControlWidth(_ text: String?) { if let text, text == "auto" { - // Set fixed width relative to default value, trailing text label + stepperWidthConstraint?.deactivate() } else if let controlWidth = Int(text ?? "") { - // Use provided new width either pixel or percentage - width = CGFloat(controlWidth) - } else { - // Use EntryFieldBase width - } + // Set controlWidth provided which is either pixel or percentage + let width = width ?? CGFloat(containerView.frame.size.width) + updateStepperContainerWidth(controlWidth: CGFloat(controlWidth), width: width) + } } + // Handling the controlwidth without going beyond the width of the parent container. + internal func updateStepperContainerWidth(controlWidth: CGFloat, width: CGFloat) { + if controlWidth >= containerSize.width && controlWidth <= width { + stepperWidthConstraint?.deactivate() + stepperWidthConstraint?.constant = controlWidth + stepperWidthConstraint?.activate() + } else if controlWidth >= width { + stepperWidthConstraint?.deactivate() + stepperWidthConstraint?.constant = width + stepperWidthConstraint?.activate() + } + } } diff --git a/VDS/Components/TextFields/EntryFieldBase.swift b/VDS/Components/TextFields/EntryFieldBase.swift index 2ef89480..bf75d747 100644 --- a/VDS/Components/TextFields/EntryFieldBase.swift +++ b/VDS/Components/TextFields/EntryFieldBase.swift @@ -227,7 +227,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { get { fatalError("must be read from subclass")} } - open var defaultValue: AnyHashable? { didSet { setNeedsUpdate() } } +// open var defaultValue: AnyHashable? { didSet { setNeedsUpdate() } } open var isRequired: Bool = false { didSet { setNeedsUpdate() } } @@ -346,7 +346,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { /// Updates the UI open override func updateView() { super.updateView() - updateContainerView() + updateContainerView(flag: true) updateContainerWidth() updateTitleLabel() updateErrorLabel() @@ -372,7 +372,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { transparentBackground = false width = nil inputId = nil - defaultValue = nil +// defaultValue = nil isRequired = false isReadOnly = false onChange = nil @@ -401,7 +401,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { open func getFieldContainer() -> UIView { fatalError("Subclass must return the view that contains the field/view the user will interact with.") } - + /// Container for the area in which helper or error text presents. open func getBottomContainer() -> UIView { return bottomContainerStackView @@ -520,13 +520,35 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { } } - internal func updateContainerView() { - containerView.backgroundColor = containerBackgroundColor - containerView.layer.borderColor = borderColorConfiguration.getColor(self).cgColor - containerView.layer.borderWidth = VDSFormControls.borderWidth - containerView.layer.cornerRadius = VDSFormControls.borderRadius + internal func updateContainerView(flag: Bool) { + if flag { + containerView.backgroundColor = containerBackgroundColor + containerView.layer.borderColor = borderColorConfiguration.getColor(self).cgColor + containerView.layer.borderWidth = VDSFormControls.borderWidth + containerView.layer.cornerRadius = VDSFormControls.borderRadius + } else { + containerView.backgroundColor = .clear + containerView.layer.borderColor = nil + containerView.layer.borderWidth = 0 + containerView.layer.cornerRadius = 0 + fieldStackView.backgroundColor = containerBackgroundColor + fieldStackView.layer.borderColor = borderColorConfiguration.getColor(self).cgColor + fieldStackView.layer.borderWidth = VDSFormControls.borderWidth + fieldStackView.layer.cornerRadius = containerView.frame.size.height / 2 + } } - + + /// Update constraints to containerStackView which has horizontal stack in which user interacts. + internal func updateConstraintsToFieldStackView(flag: Bool) { + fieldStackView.removeFromSuperview() + containerView.addSubview(fieldStackView) + if flag { + fieldStackView.pinToSuperView(.uniform(VDSLayout.space3X)) + } else { + fieldStackView.pinTop().pinLeading().pinBottom().pinTrailingLessThanOrEqualTo() + } + } + internal func updateContainerWidth() { widthConstraint?.deactivate() trailingLessThanEqualsConstraint?.deactivate() diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index c2f779d4..39074cf1 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -287,7 +287,7 @@ open class InputField: EntryFieldBase { extension InputField: UITextFieldDelegate { public func textFieldDidBeginEditing(_ textField: UITextField) { fieldType.handler().textFieldDidBeginEditing(self, textField: textField) - updateContainerView() + updateContainerView(flag: true) updateErrorLabel() }