vds_ios/VDS/Components/InputStepper/InputStepper.swift

231 lines
7.0 KiB
Swift

//
// 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.
@objc(VDSInputStepper)
open class InputStepper: EntryFieldBase {
//--------------------------------------------------
// 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
}
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
open var controlWidth: String? {
get { _controlWidth }
set {
setControlWidth(newValue)
setNeedsUpdate()
}
}
/// Default value of the input stepper, defaults to '0'.
open var defaultValue:Int = 0 { didSet { setNeedsUpdate() } }
/// Maximum value of the input stepper, defaults to '99'.
open var maxValue: Int? {
get { return _maxValue }
set {
if let newValue, newValue <= 99 && newValue > 0 {
_maxValue = newValue
} else {
_maxValue = 99
}
setNeedsUpdate()
}
}
/// Minimum value of the input stepper, defaults to '0'.
open var minValue: Int? {
get { return _minValue }
set {
if let newValue, newValue >= 0 && newValue < 99 {
_minValue = newValue
} else {
_minValue = 0
}
setNeedsUpdate()
}
}
/// The size of the input stepper. Defaults to 'large'.
open var size: Size {
get { return _size }
set {
_size = newValue
updateSize()
}
}
/// Accepts any text or character to appear next to input stepper value.
open var trailingText: String? { didSet { setNeedsUpdate() } }
//--------------------------------------------------
// 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
let 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
}
let 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
}
let textLabel = Label().with {
$0.setContentCompressionResistancePriority(.required, for: .vertical)
$0.textStyle = .boldBodyLarge
$0.backgroundColor = .clear
}
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
open override func initialSetup() {
super.initialSetup()
}
open override func setup() {
super.setup()
isAccessibilityElement = false
accessibilityLabel = "Input Stepper"
containerView.isEnabled = false
decrementButton.onClick = { _ in self.decrementButtonClick() }
incrementButton.onClick = { _ in self.incrementButtonClick() }
}
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
}
open override func updateView() {
super.updateView()
updateContainerView(flag: false)
textLabel.text = String(defaultValue) + " " + (trailingText ?? "")
decrementButton.surface = surface
incrementButton.surface = surface
textLabel.surface = surface
updateButtonStates()
statusIcon.isHidden = true
}
/// Resets to default settings.
open override func reset() {
super.reset()
textLabel.reset()
textLabel.textStyle = .boldBodyLarge
textLabel.text = ""
controlWidth = nil
minValue = nil
maxValue = nil
trailingText = nil
helperTextPlacement = .bottom
}
//--------------------------------------------------
// MARK: - Private Methods
//--------------------------------------------------
internal func decrementButtonClick() {
defaultValue = defaultValue - 1
updateButtonStates()
}
internal func incrementButtonClick() {
defaultValue = defaultValue + 1
updateButtonStates()
}
internal func updateButtonStates() {
if isReadOnly || !isEnabled {
decrementButton.isEnabled = false
incrementButton.isEnabled = false
} else {
decrementButton.isEnabled = defaultValue > _minValue ? true : false
incrementButton.isEnabled = defaultValue < _maxValue ? true : false
}
}
internal func updateSize() {
let value = size == .large ? 6.0 : VDSLayout.space1X
updateConstraintsToFieldStackView(value: value)
// 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
}
internal func setControlWidth(_ text: String?) {
if let text, text == "auto" {
// Set fixed width relative to default value, trailing text label
} else if let controlWidth = Int(text ?? "") {
// Use provided new width either pixel or percentage
width = CGFloat(controlWidth)
} else {
// Use EntryFieldBase width
}
}
}