now subclassing EntryFieldBase
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
This commit is contained in:
parent
8311de5409
commit
4f0f611fb6
@ -13,8 +13,7 @@ import Combine
|
||||
|
||||
/// A dropdown select is an expandable menu of predefined options that allows a customer to make a single selection.
|
||||
@objc(VDSDropdownSelect)
|
||||
open class DropdownSelect: Control {
|
||||
|
||||
open class DropdownSelect: EntryFieldBase {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializers
|
||||
//--------------------------------------------------
|
||||
@ -33,86 +32,27 @@ open class DropdownSelect: Control {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Public Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
/// Boolean value that determines if component should show the error state/error message.
|
||||
open var showError: Bool = false { didSet { setNeedsUpdate() }}
|
||||
|
||||
/// Message displayed when there is an error.
|
||||
open var errorText: String? { didSet { setNeedsUpdate() }}
|
||||
|
||||
/// If provided, will be used as text for the helper label.
|
||||
open var helperText: String? { didSet { setNeedsUpdate() }}
|
||||
|
||||
/// If true, the label will be displayed inside the dropdown containerView. Otherwise, the label will be above the dropdown containerView like a normal text input.
|
||||
open var showInlineLabel: Bool = false { didSet { setNeedsUpdate() }}
|
||||
|
||||
/// If provided, will be used as context for the label on the input field.
|
||||
open var labelText: String? { didSet { setNeedsUpdate() }}
|
||||
|
||||
/// Not allowed the user interaction to select/change input if it is true.
|
||||
open var readOnly: Bool = false { didSet { setNeedsUpdate() }}
|
||||
|
||||
/// Used to show optional indicator for the label.
|
||||
open var required: Bool = false { didSet { setNeedsUpdate() }}
|
||||
|
||||
|
||||
/// Allows unique ID to be passed to the element.
|
||||
open var selectId: Int? { didSet { setNeedsUpdate() }}
|
||||
|
||||
/// Config object for tooltip option, is optional.
|
||||
open var tooltipModel: Tooltip.TooltipModel? { didSet { setNeedsUpdate() } }
|
||||
|
||||
/// If provided, will render with trnasparent background.
|
||||
open var transparentBackground: Bool = false { didSet { setNeedsUpdate() }}
|
||||
|
||||
/// Used to set width for the Dropdown Select.
|
||||
open var width: CGFloat? { didSet { setNeedsUpdate() } }
|
||||
|
||||
/// Array of options to show
|
||||
open var options: [DropdownOptionModel] = [] { didSet { setNeedsUpdate() }}
|
||||
|
||||
/// A callback when the selected option changes. Passes parameters (option).
|
||||
open var onDropdownItemSelect: ((DropdownOptionModel) -> Void)?
|
||||
|
||||
open override var state: UIControl.State {
|
||||
get {
|
||||
var state = super.state
|
||||
if showError {
|
||||
state.insert(.error)
|
||||
}
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Private Properties
|
||||
//--------------------------------------------------
|
||||
internal var minWidthDefault = 66.0
|
||||
internal var minWidthInlineLabel = 102.0
|
||||
|
||||
var stackView: UIStackView = UIStackView().with {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
$0.axis = .vertical
|
||||
}
|
||||
|
||||
var containerView: UIView = UIView().with {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
}
|
||||
|
||||
var containerStackView: UIStackView = UIStackView().with {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
$0.axis = .horizontal
|
||||
$0.spacing = VDSFormControls.spaceInset
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Public Properties
|
||||
//--------------------------------------------------
|
||||
open var titleLabel = Label().with {
|
||||
$0.setContentCompressionResistancePriority(.required, for: .vertical)
|
||||
$0.textAlignment = .left
|
||||
$0.textStyle = .bodySmall
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
open var inlineDisplayLabel = Label().with {
|
||||
$0.textAlignment = .left
|
||||
$0.textStyle = .boldBodyLarge
|
||||
@ -126,24 +66,7 @@ open class DropdownSelect: Control {
|
||||
$0.textStyle = .bodyLarge
|
||||
$0.lineBreakMode = .byCharWrapping
|
||||
}
|
||||
|
||||
open var icon: Icon = Icon().with {
|
||||
$0.size = .medium
|
||||
$0.name = Icon.Name(name: "down-caret")
|
||||
}
|
||||
|
||||
open var errorLabel = Label().with {
|
||||
$0.setContentCompressionResistancePriority(.required, for: .vertical)
|
||||
$0.textAlignment = .left
|
||||
$0.textStyle = .bodySmall
|
||||
}
|
||||
|
||||
open var helperLabel = Label().with {
|
||||
$0.setContentCompressionResistancePriority(.required, for: .vertical)
|
||||
$0.textAlignment = .left
|
||||
$0.textStyle = .bodySmall
|
||||
}
|
||||
|
||||
|
||||
open var dropdownField = UITextField().with {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
$0.tintColor = UIColor.clear
|
||||
@ -160,42 +83,14 @@ open class DropdownSelect: Control {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Configuration Properties
|
||||
//--------------------------------------------------
|
||||
internal var containerSize: CGSize { CGSize(width: showInlineLabel ? minWidthInlineLabel : width ?? minWidthDefault, height: 44) }
|
||||
|
||||
internal let primaryColorConfig = ViewColorConfiguration().with {
|
||||
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forDisabled: true)
|
||||
$0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forDisabled: false)
|
||||
}
|
||||
|
||||
internal let secondaryColorConfig = ViewColorConfiguration().with {
|
||||
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forDisabled: true)
|
||||
$0.setSurfaceColors(VDSColor.elementsSecondaryOnlight, VDSColor.elementsSecondaryOndark, forDisabled: false)
|
||||
}
|
||||
|
||||
internal let iconColorConfig = ControlColorConfiguration().with {
|
||||
internal override var containerSize: CGSize { CGSize(width: showInlineLabel ? minWidthInlineLabel : width ?? minWidthDefault, height: 44) }
|
||||
|
||||
internal let iconColorConfiguration = ControlColorConfiguration().with {
|
||||
$0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .normal)
|
||||
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled)
|
||||
$0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .error)
|
||||
}
|
||||
|
||||
internal var backgroundColorConfiguration = ControlColorConfiguration().with {
|
||||
$0.setSurfaceColors(VDSFormControlsColor.backgroundOnlight, VDSFormControlsColor.backgroundOndark, forState: .normal)
|
||||
$0.setSurfaceColors(VDSFormControlsColor.backgroundOnlight, VDSFormControlsColor.backgroundOndark, forState: .disabled)
|
||||
$0.setSurfaceColors(VDSColor.feedbackErrorBackgroundOnlight, VDSColor.feedbackErrorBackgroundOndark, forState: .error)
|
||||
}
|
||||
|
||||
internal var borderColorConfiguration = ControlColorConfiguration().with {
|
||||
$0.setSurfaceColors(VDSFormControlsColor.borderOnlight, VDSFormControlsColor.borderOnlight, forState: .normal)
|
||||
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled)
|
||||
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: [.disabled,.error])
|
||||
$0.setSurfaceColors(VDSColor.feedbackErrorOnlight, VDSColor.feedbackErrorOndark, forState: .error)
|
||||
$0.setSurfaceColors(VDSFormControlsColor.borderHoverOnlight, VDSFormControlsColor.borderHoverOndark, forState: .focused)
|
||||
}
|
||||
|
||||
internal var readOnlyBorderColorConfiguration = ControlColorConfiguration().with {
|
||||
$0.setSurfaceColors(VDSFormControlsColor.borderReadonlyOnlight, VDSFormControlsColor.borderReadonlyOndark, forState: .normal)
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Overrides
|
||||
//--------------------------------------------------
|
||||
@ -203,107 +98,78 @@ open class DropdownSelect: Control {
|
||||
/// 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()
|
||||
isAccessibilityElement = true
|
||||
|
||||
accessibilityLabel = "Dropdown Select"
|
||||
|
||||
// stackview
|
||||
addSubview(stackView)
|
||||
stackView.pinToSuperView()
|
||||
stackView.heightAnchor.constraint(greaterThanOrEqualToConstant: containerSize.height).isActive = true
|
||||
|
||||
// containerView stack
|
||||
containerView.addSubview(containerStackView)
|
||||
let spacing = VDSFormControls.spaceInset
|
||||
containerStackView.pinToSuperView(.init(top: spacing, left: spacing, bottom: spacing, right: spacing))
|
||||
// stackview for controls in EntryFieldBase.controlContainerView
|
||||
let controlStackView = UIStackView().with {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
$0.axis = .horizontal
|
||||
$0.spacing = VDSFormControls.spaceInset
|
||||
}
|
||||
controlContainerView.addSubview(controlStackView)
|
||||
controlStackView.pinToSuperView()
|
||||
|
||||
containerStackView.addArrangedSubview(dropdownField)
|
||||
containerStackView.addArrangedSubview(inlineDisplayLabel)
|
||||
containerStackView.addArrangedSubview(selectedOptionLabel)
|
||||
containerStackView.addArrangedSubview(icon)
|
||||
controlStackView.addArrangedSubview(dropdownField)
|
||||
controlStackView.addArrangedSubview(inlineDisplayLabel)
|
||||
controlStackView.addArrangedSubview(selectedOptionLabel)
|
||||
|
||||
containerStackView.setCustomSpacing(0, after: dropdownField)
|
||||
containerStackView.setCustomSpacing(VDSLayout.Spacing.space1X.value, after: inlineDisplayLabel)
|
||||
containerStackView.setCustomSpacing(VDSLayout.Spacing.space3X.value, after: selectedOptionLabel)
|
||||
controlStackView.setCustomSpacing(0, after: dropdownField)
|
||||
controlStackView.setCustomSpacing(VDSLayout.Spacing.space1X.value, after: inlineDisplayLabel)
|
||||
controlStackView.setCustomSpacing(VDSLayout.Spacing.space3X.value, after: selectedOptionLabel)
|
||||
dropdownField.width(0)
|
||||
inlineWidthConstraint = inlineDisplayLabel.widthAnchor.constraint(greaterThanOrEqualToConstant: 0)
|
||||
inlineWidthConstraint?.isActive = true
|
||||
|
||||
// component stackview subviews
|
||||
stackView.addArrangedSubview(titleLabel)
|
||||
stackView.addArrangedSubview(containerView)
|
||||
stackView.addArrangedSubview(errorLabel)
|
||||
stackView.addArrangedSubview(helperLabel)
|
||||
|
||||
stackView.setCustomSpacing(4, after: titleLabel)
|
||||
stackView.setCustomSpacing(8, after: containerView)
|
||||
stackView.setCustomSpacing(8, after: errorLabel)
|
||||
|
||||
// setting color config
|
||||
titleLabel.textColorConfiguration = primaryColorConfig.eraseToAnyColorable()
|
||||
errorLabel.textColorConfiguration = primaryColorConfig.eraseToAnyColorable()
|
||||
helperLabel.textColorConfiguration = secondaryColorConfig.eraseToAnyColorable()
|
||||
inlineDisplayLabel.textColorConfiguration = primaryColorConfig.eraseToAnyColorable()
|
||||
selectedOptionLabel.textColorConfiguration = primaryColorConfig.eraseToAnyColorable()
|
||||
icon.color = iconColorConfig.getColor(self)
|
||||
inlineDisplayLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable()
|
||||
selectedOptionLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable()
|
||||
|
||||
// Options PickerView
|
||||
optionsPicker.delegate = self
|
||||
optionsPicker.dataSource = self
|
||||
optionsPicker.isHidden = true
|
||||
dropdownField.inputView = optionsPicker
|
||||
dropdownField.inputAccessoryView = toolBarForPicker()
|
||||
containerStackView.publisher(for: UITapGestureRecognizer()).sink { [weak self] _ in
|
||||
self?.launchPicker()
|
||||
}.store(in: &subscribers)
|
||||
dropdownField.inputAccessoryView = {
|
||||
let inputToolbar = UIToolbar().with {
|
||||
$0.barStyle = .default
|
||||
$0.isTranslucent = true
|
||||
$0.items=[
|
||||
UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: self, action: nil),
|
||||
UIBarButtonItem(title: "Done", style: UIBarButtonItem.Style.done, target: self, action: #selector(pickerDoneClicked))
|
||||
]
|
||||
}
|
||||
inputToolbar.sizeToFit()
|
||||
return inputToolbar
|
||||
}()
|
||||
|
||||
// tap gesture
|
||||
containerStackView
|
||||
.publisher(for: UITapGestureRecognizer())
|
||||
.sink { [weak self] _ in
|
||||
self?.launchPicker()
|
||||
}
|
||||
.store(in: &subscribers)
|
||||
}
|
||||
|
||||
/// Used to make changes to the View based off a change events or from local properties.
|
||||
open override func updateView() {
|
||||
super.updateView()
|
||||
|
||||
containerView.backgroundColor = backgroundColorConfiguration.getColor(self)
|
||||
containerView.layer.borderWidth = VDSFormControls.widthBorder
|
||||
containerView.layer.cornerRadius = VDSFormControls.borderradius
|
||||
containerView.layer.borderColor = readOnly ? readOnlyBorderColorConfiguration.getColor(self).cgColor : borderColorConfiguration.getColor(self).cgColor
|
||||
dropdownField.isUserInteractionEnabled = readOnly ? false : true
|
||||
stackView.backgroundColor = transparentBackground ? .clear : surface.color
|
||||
|
||||
updateTitleLabel()
|
||||
updateInlineLabel()
|
||||
updateErrorLabel()
|
||||
updateHelperLabel()
|
||||
if readOnly {
|
||||
icon.name = nil
|
||||
}
|
||||
|
||||
dropdownField.isUserInteractionEnabled = readOnly ? false : true
|
||||
selectedOptionLabel.surface = surface
|
||||
selectedOptionLabel.isEnabled = isEnabled
|
||||
backgroundColor = surface.color
|
||||
}
|
||||
|
||||
/// Resets to default settings.
|
||||
open override func reset() {
|
||||
super.reset()
|
||||
|
||||
titleLabel.reset()
|
||||
inlineDisplayLabel.reset()
|
||||
selectedOptionLabel.reset()
|
||||
errorLabel.reset()
|
||||
helperLabel.reset()
|
||||
|
||||
titleLabel.textStyle = .bodySmall
|
||||
inlineDisplayLabel.textStyle = .boldBodyLarge
|
||||
selectedOptionLabel.textStyle = .bodyLarge
|
||||
errorLabel.textStyle = .bodySmall
|
||||
helperLabel.textStyle = .bodySmall
|
||||
tooltipModel = nil
|
||||
labelText = nil
|
||||
errorText = nil
|
||||
showError = false
|
||||
isEnabled = false
|
||||
readOnly = false
|
||||
showInlineLabel = false
|
||||
helperText = nil
|
||||
transparentBackground = false
|
||||
required = false
|
||||
options = []
|
||||
selectId = 0
|
||||
}
|
||||
@ -312,7 +178,7 @@ open class DropdownSelect: Control {
|
||||
// MARK: - Public Methods
|
||||
//--------------------------------------------------
|
||||
|
||||
open func updateTitleLabel() {
|
||||
open override func updateTitleLabel() {
|
||||
|
||||
//update the local vars for the label since we no long have a model
|
||||
var attributes: [any LabelAttributeModel] = []
|
||||
@ -323,7 +189,7 @@ open class DropdownSelect: Control {
|
||||
if let oldText = updatedLabelText, !required, !oldText.hasSuffix("Optional") {
|
||||
let optionColorAttr = ColorLabelAttribute(location: oldText.count + 2,
|
||||
length: 8,
|
||||
color: secondaryColorConfig.getColor(self))
|
||||
color: secondaryColorConfiguration.getColor(self))
|
||||
|
||||
updatedLabelText = showInlineLabel ? "Optional" : "\(oldText) Optional"
|
||||
attributes.append(optionColorAttr)
|
||||
@ -357,70 +223,37 @@ open class DropdownSelect: Control {
|
||||
inlineWidthConstraint?.isActive = true
|
||||
|
||||
if let selectId, selectId < options.count {
|
||||
updateSelectedOptionLabel(text: options[selectId].text)
|
||||
updateSelectedOptionLabel(option: options[selectId])
|
||||
}
|
||||
}
|
||||
|
||||
open func updateSelectedOptionLabel(text: String? = nil) {
|
||||
selectedOptionLabel.text = text ?? ""
|
||||
open func updateSelectedOptionLabel(option: DropdownOptionModel? = nil) {
|
||||
selectedOptionLabel.text = option?.text ?? ""
|
||||
value = option?.value
|
||||
}
|
||||
|
||||
open func updateErrorLabel() {
|
||||
if showError, let errorText {
|
||||
errorLabel.text = errorText
|
||||
errorLabel.surface = surface
|
||||
errorLabel.isEnabled = isEnabled
|
||||
errorLabel.isHidden = false
|
||||
icon.name = .error
|
||||
icon.size = .medium
|
||||
icon.surface = surface
|
||||
} else {
|
||||
open override func updateErrorLabel() {
|
||||
super.updateErrorLabel()
|
||||
if !showError && !hasInternalError {
|
||||
icon.name = .downCaret
|
||||
icon.surface = surface
|
||||
errorLabel.isHidden = true
|
||||
}
|
||||
icon.color = iconColorConfig.getColor(self)
|
||||
}
|
||||
|
||||
open func updateHelperLabel() {
|
||||
if let helperText {
|
||||
helperLabel.text = helperText
|
||||
helperLabel.surface = surface
|
||||
helperLabel.isEnabled = isEnabled
|
||||
helperLabel.isHidden = false
|
||||
} else {
|
||||
helperLabel.isHidden = true
|
||||
}
|
||||
icon.surface = surface
|
||||
icon.isHidden = readOnly ? true : false
|
||||
icon.color = iconColorConfiguration.getColor(self)
|
||||
}
|
||||
|
||||
@objc open func pickerDoneClicked() {
|
||||
optionsPicker.isHidden = true
|
||||
dropdownField.resignFirstResponder()
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Private Methods
|
||||
//--------------------------------------------------
|
||||
|
||||
private func toolBarForPicker() -> UIToolbar {
|
||||
|
||||
let inputToolbar: UIToolbar = UIToolbar()
|
||||
inputToolbar.barStyle = .default
|
||||
inputToolbar.isTranslucent = true
|
||||
|
||||
// add a done button to the toolbar
|
||||
inputToolbar.items=[
|
||||
UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: self, action: nil),
|
||||
UIBarButtonItem(title: "Done", style: UIBarButtonItem.Style.done, target: self, action: #selector(pickerDoneClicked))
|
||||
]
|
||||
inputToolbar.sizeToFit()
|
||||
return inputToolbar
|
||||
}
|
||||
}
|
||||
|
||||
extension DropdownSelect: UIPickerViewDelegate, UIPickerViewDataSource, UITextFieldDelegate {
|
||||
//--------------------------------------------------
|
||||
// MARK: - UIPickerView Delegate & Datasource
|
||||
//--------------------------------------------------
|
||||
extension DropdownSelect: UIPickerViewDelegate, UIPickerViewDataSource {
|
||||
|
||||
func launchPicker() {
|
||||
internal func launchPicker() {
|
||||
if optionsPicker.isHidden {
|
||||
dropdownField.becomeFirstResponder()
|
||||
} else {
|
||||
@ -429,9 +262,6 @@ extension DropdownSelect: UIPickerViewDelegate, UIPickerViewDataSource, UITextFi
|
||||
optionsPicker.isHidden = !optionsPicker.isHidden
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - UIPickerView Delegate & Datasource
|
||||
//--------------------------------------------------
|
||||
public func numberOfComponents(in pickerView: UIPickerView) -> Int {
|
||||
return 1
|
||||
}
|
||||
@ -448,7 +278,7 @@ extension DropdownSelect: UIPickerViewDelegate, UIPickerViewDataSource, UITextFi
|
||||
public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
|
||||
guard options.count > row else { return }
|
||||
selectId = row
|
||||
updateSelectedOptionLabel(text: options[row].text)
|
||||
updateSelectedOptionLabel(option: options[row])
|
||||
self.onDropdownItemSelect?(options[row])
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user