From d2258d27448c5803898159426799bb4ef82cec8d Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 23 Aug 2022 16:54:59 -0500 Subject: [PATCH] added radiobox model/group Signed-off-by: Matt Bruce --- VDS.xcodeproj/project.pbxproj | 12 + VDS/Components/RadioBox/RadioBox.swift | 393 ++++++++++++++++++ VDS/Components/RadioBox/RadioBoxGroup.swift | 113 +++++ .../RadioBox/RadioBoxGroupModel.swift | 28 ++ VDS/Components/RadioBox/RadioBoxModel.swift | 110 +++++ 5 files changed, 656 insertions(+) create mode 100644 VDS/Components/RadioBox/RadioBox.swift create mode 100644 VDS/Components/RadioBox/RadioBoxGroup.swift create mode 100644 VDS/Components/RadioBox/RadioBoxGroupModel.swift create mode 100644 VDS/Components/RadioBox/RadioBoxModel.swift diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 752dd62e..ec7a13cd 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -44,6 +44,9 @@ EA89200D28B530FD006B9984 /* RadioBoxModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89200C28B530FD006B9984 /* RadioBoxModel.swift */; }; EA89200F28B53921006B9984 /* Selectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89200E28B53921006B9984 /* Selectable.swift */; }; EA89201128B567B5006B9984 /* SelectorGroupHandlerable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89201028B567B5006B9984 /* SelectorGroupHandlerable.swift */; }; + EA89201328B568D8006B9984 /* RadioBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89201228B568D8006B9984 /* RadioBox.swift */; }; + EA89201528B56CF4006B9984 /* RadioBoxGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89201428B56CF4006B9984 /* RadioBoxGroup.swift */; }; + EA89201728B56CFF006B9984 /* RadioBoxGroupModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89201628B56CFF006B9984 /* RadioBoxGroupModel.swift */; }; EAB1D29A28A5611D00DAE764 /* SelectorGroupModelable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D29928A5611D00DAE764 /* SelectorGroupModelable.swift */; }; EAB1D29C28A5618900DAE764 /* RadioButtonGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D29B28A5618900DAE764 /* RadioButtonGroup.swift */; }; EAB1D29E28A5619500DAE764 /* RadioButtonGroupModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D29D28A5619500DAE764 /* RadioButtonGroupModel.swift */; }; @@ -131,6 +134,9 @@ EA89200C28B530FD006B9984 /* RadioBoxModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioBoxModel.swift; sourceTree = ""; }; EA89200E28B53921006B9984 /* Selectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Selectable.swift; sourceTree = ""; }; EA89201028B567B5006B9984 /* SelectorGroupHandlerable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectorGroupHandlerable.swift; sourceTree = ""; }; + EA89201228B568D8006B9984 /* RadioBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioBox.swift; sourceTree = ""; }; + EA89201428B56CF4006B9984 /* RadioBoxGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioBoxGroup.swift; sourceTree = ""; }; + EA89201628B56CFF006B9984 /* RadioBoxGroupModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioBoxGroupModel.swift; sourceTree = ""; }; EAB1D29928A5611D00DAE764 /* SelectorGroupModelable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectorGroupModelable.swift; sourceTree = ""; }; EAB1D29B28A5618900DAE764 /* RadioButtonGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButtonGroup.swift; sourceTree = ""; }; EAB1D29D28A5619500DAE764 /* RadioButtonGroupModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButtonGroupModel.swift; sourceTree = ""; }; @@ -367,7 +373,10 @@ EA89200B28B530F0006B9984 /* RadioBox */ = { isa = PBXGroup; children = ( + EA89201228B568D8006B9984 /* RadioBox.swift */, EA89200C28B530FD006B9984 /* RadioBoxModel.swift */, + EA89201428B56CF4006B9984 /* RadioBoxGroup.swift */, + EA89201628B56CFF006B9984 /* RadioBoxGroupModel.swift */, ); path = RadioBox; sourceTree = ""; @@ -559,6 +568,7 @@ EAF7F0A0289AB7EC00B287F5 /* View.swift in Sources */, EAF7F11828A1475A00B287F5 /* RadioButtonModel.swift in Sources */, EA89200D28B530FD006B9984 /* RadioBoxModel.swift in Sources */, + EA89201328B568D8006B9984 /* RadioBox.swift in Sources */, EA3362402892EF6C0071C351 /* Label.swift in Sources */, EAF7F0B3289B1ADC00B287F5 /* LabelAttributeAction.swift in Sources */, EA33622E2891EA3C0071C351 /* DispatchQueue+Once.swift in Sources */, @@ -569,6 +579,7 @@ EAB1D2A128A598FE00DAE764 /* UsesAutoLayout.swift in Sources */, EAB1D2CD28ABE76100DAE764 /* Withable.swift in Sources */, EAF7F0952899861000B287F5 /* Checkbox.swift in Sources */, + EA89201728B56CFF006B9984 /* RadioBoxGroupModel.swift in Sources */, EA3361C9289054C50071C351 /* Surfaceable.swift in Sources */, EA3361A2288B1E840071C351 /* ToggleModel.swift in Sources */, EA3362432892EFF20071C351 /* LabelModel.swift in Sources */, @@ -589,6 +600,7 @@ EA3361AF288B26310071C351 /* FormFieldable.swift in Sources */, EAB1D29A28A5611D00DAE764 /* SelectorGroupModelable.swift in Sources */, EAF7F0BB289D80ED00B287F5 /* Modelable.swift in Sources */, + EA89201528B56CF4006B9984 /* RadioBoxGroup.swift in Sources */, EAF7F09E289AAEC000B287F5 /* Constants.swift in Sources */, EA3361B3288B265D0071C351 /* Changable.swift in Sources */, EA89200228AECF2A006B9984 /* UIButton+Publisher.swift in Sources */, diff --git a/VDS/Components/RadioBox/RadioBox.swift b/VDS/Components/RadioBox/RadioBox.swift new file mode 100644 index 00000000..733cded4 --- /dev/null +++ b/VDS/Components/RadioBox/RadioBox.swift @@ -0,0 +1,393 @@ +// +// RadioBox.swift +// VDS +// +// Created by Matt Bruce on 8/23/22. +// + +import Foundation +import UIKit +import VDSColorTokens +import VDSFormControlsTokens +import Combine + +public class RadioBox: RadioBoxBase{} + +open class RadioBoxBase: Control, Changable { + + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + private var mainStackView: UIStackView = { + return UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.alignment = .top + $0.axis = .vertical + } + }() + + private var selectorStackView: UIStackView = { + return UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.alignment = .top + $0.axis = .horizontal + } + }() + + private var selectorLeftLabelStackView: UIStackView = { + return UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.axis = .vertical + } + }() + + private var textLabel = Label() + + private var subTextLabel = Label() + + private var subTextRightLabel = Label() + + private var errorLabel = Label() + + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + public var selectorView: UIView = { + return UIView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + } + }() + + public var onChange: Blocks.ActionBlock? + + @Proxy(\.model.id) + open var id: UUID + + //can't bind to @Proxy + open override var isSelected: Bool { + get { model.selected } + set { + if model.selected != newValue { + model.selected = newValue + } + } + } + + @Proxy(\.model.text) + open var text: String + + @Proxy(\.model.subText) + open var subText: String? + + @Proxy(\.model.subTextRight) + open var subTextRight: String? + + @Proxy(\.model.hasError) + open var hasError: Bool + + @Proxy(\.model.errorText) + open var errorText: String? + + @Proxy(\.model.strikethrough) + open var strikethrough: Bool + + @Proxy(\.model.inputId) + open var inputId: String? + + @Proxy(\.model.value) + open var value: AnyHashable? + + @Proxy(\.model.dataAnalyticsTrack) + open var dataAnalyticsTrack: String? + + @Proxy(\.model.dataClickStream) + open var dataClickStream: String? + + @Proxy(\.model.dataTrack) + open var dataTrack: String? + + @Proxy(\.model.accessibilityHintEnabled) + open var accessibilityHintEnabled: String? + + @Proxy(\.model.accessibilityHintDisabled) + open var accessibilityHintDisabled: String? + + @Proxy(\.model.accessibilityValueEnabled) + open var accessibilityValueEnabled: String? + + @Proxy(\.model.accessibilityValueDisabled) + open var accessibilityValueDisabled: String? + + @Proxy(\.model.accessibilityLabelEnabled) + open var accessibilityLabelEnabled: String? + + @Proxy(\.model.accessibilityLabelDisabled) + open var accessibilityLabelDisabled: String? + + //functions + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + open override func setup() { + super.setup() + addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(Self.tap))) + + isAccessibilityElement = true + accessibilityTraits = .button + addSubview(selectorView) + + selectorView.addSubview(mainStackView) + + //2 vertical rows + mainStackView.addArrangedSubview(selectorStackView) + mainStackView.addArrangedSubview(errorLabel) + + selectorStackView.addArrangedSubview(selectorLeftLabelStackView) + selectorStackView.addArrangedSubview(subTextRightLabel) + selectorLeftLabelStackView.addArrangedSubview(textLabel) + selectorLeftLabelStackView.addArrangedSubview(subTextLabel) + + selectorStackView.spacing = 12 + selectorLeftLabelStackView.spacing = 4 + selectorLeftLabelStackView.isHidden = false + + updateSelector(model) + + selectorView.topAnchor.constraint(equalTo: topAnchor).isActive = true + selectorView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true + selectorView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true + selectorView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + + mainStackView.topAnchor.constraint(equalTo: selectorView.topAnchor, constant: 16).isActive = true + mainStackView.leadingAnchor.constraint(equalTo: selectorView.leadingAnchor, constant: 16).isActive = true + mainStackView.trailingAnchor.constraint(equalTo: selectorView.trailingAnchor, constant: -16).isActive = true + mainStackView.bottomAnchor.constraint(equalTo: selectorView.bottomAnchor, constant: -16).isActive = true + + } + + func updateLabels(_ viewModel: ModelType) { + + //add the stackview to hold the 2 labels + //text label + textLabel.set(with: viewModel.textModel) + + //subText label + if let subTextModel = viewModel.subTextModel { + subTextLabel.set(with: subTextModel) + subTextLabel.isHidden = false + } else { + subTextLabel.isHidden = true + } + + //subTextRight label + if let subTextRightModel = viewModel.subTextRightModel { + subTextRightLabel.set(with: subTextRightModel) + subTextRightLabel.isHidden = false + } else { + subTextRightLabel.isHidden = true + } + + //either add/remove the error from the main stack + if let errorModel = model.errorModel, model.shouldShowError { + errorLabel.set(with: errorModel) + mainStackView.spacing = 8 + errorLabel.isHidden = false + } else { + mainStackView.spacing = 0 + errorLabel.isHidden = true + } + + } + + public override func reset() { + super.reset() + updateSelector(model) + setAccessibilityLabel() + onChange = nil + } + + //-------------------------------------------------- + // MARK: - Actions + //-------------------------------------------------- + open override func sendActions(for controlEvents: UIControl.Event) { + super.sendActions(for: controlEvents) + if controlEvents.contains(.touchUpInside) { + toggle() + } + } + + @objc func tap() { + sendActions(for: .touchUpInside) + + } + + /// This will radioBox the state of the Selector and execute the actionBlock if provided. + open func toggle() { + //removed error + if hasError && isSelected == false { + hasError.toggle() + } + isSelected.toggle() + sendActions(for: .valueChanged) + onChange?() + } + + override open func accessibilityActivate() -> Bool { + // Hold state in case User wanted isAnimated to remain off. + guard isUserInteractionEnabled else { return false } + sendActions(for: .touchUpInside) + return true + } + + //-------------------------------------------------- + // MARK: - State + //-------------------------------------------------- + /// Follow the SwiftUI View paradigm + /// - Parameter viewModel: state + open override func shouldUpdateView(viewModel: ModelType) -> Bool { + let update = viewModel.selected != model.selected + || viewModel.text != model.text + || viewModel.subText != model.subText + || viewModel.subTextRight != model.subTextRight + || viewModel.hasError != model.hasError + || viewModel.surface != model.surface + || viewModel.disabled != model.disabled + return update + } + + open override func updateView(viewModel: ModelType) { + let enabled = !viewModel.disabled + + updateLabels(viewModel) + updateSelector(viewModel) + setAccessibilityHint(enabled) + setAccessibilityValue(viewModel.selected) + setAccessibilityLabel(viewModel.selected) + isUserInteractionEnabled = !viewModel.disabled + setNeedsLayout() + layoutIfNeeded() + } + + + //-------------------------------------------------- + // MARK: - Configuration Properties + //-------------------------------------------------- + private var strikeThroughLineThickness: CGFloat = 1.0 + private var selectorCornerRadius: CGFloat = 4.0 + private var selectorBorderWidthSelected: CGFloat = 2.0 + private var selectorBorderWidth: CGFloat = 1.0 + + private var radioBoxBackgroundColorConfiguration: RadioBoxErrorColorConfiguration = { + return RadioBoxErrorColorConfiguration().with { + $0.forFalse.enabled.lightColor = VDSFormControlsColor.backgroundOnlight + $0.forFalse.enabled.darkColor = VDSFormControlsColor.backgroundOndark + $0.forFalse.disabled.lightColor = VDSFormControlsColor.backgroundOnlight + $0.forFalse.disabled.darkColor = VDSFormControlsColor.backgroundOndark + + $0.forTrue.enabled.lightColor = VDSColor.elementsPrimaryOnlight + $0.forTrue.enabled.darkColor = VDSColor.elementsPrimaryOndark + $0.forTrue.disabled.lightColor = VDSColor.interactiveDisabledOnlight + $0.forTrue.disabled.darkColor = VDSColor.interactiveDisabledOndark + //error doesn't care enabled/disable + $0.error.forTrue.lightColor = VDSColor.elementsPrimaryOnlight + $0.error.forTrue.darkColor = VDSColor.elementsPrimaryOndark + $0.error.forFalse.lightColor = VDSColor.feedbackErrorBackgroundOnlight + $0.error.forFalse.darkColor = VDSColor.feedbackErrorBackgroundOndark + } + }() + + private var radioBoxBorderColorConfiguration: RadioBoxErrorColorConfiguration = { + return RadioBoxErrorColorConfiguration().with { + $0.forTrue.enabled.lightColor = VDSColor.elementsPrimaryOnlight + $0.forTrue.enabled.darkColor = VDSColor.elementsPrimaryOndark + $0.forFalse.enabled.lightColor = VDSFormControlsColor.borderOnlight + $0.forFalse.enabled.darkColor = VDSFormControlsColor.borderOndark + $0.forTrue.disabled.lightColor = VDSColor.interactiveDisabledOnlight + $0.forTrue.disabled.darkColor = VDSColor.interactiveDisabledOndark + $0.forFalse.disabled.lightColor = VDSColor.interactiveDisabledOnlight + $0.forFalse.disabled.darkColor = VDSColor.interactiveDisabledOndark + //error doesn't care enabled/disable + $0.error.forTrue.lightColor = VDSColor.elementsPrimaryOnlight + $0.error.forTrue.darkColor = VDSColor.elementsPrimaryOndark + $0.error.forFalse.lightColor = VDSColor.feedbackErrorOnlight + $0.error.forFalse.darkColor = VDSColor.feedbackErrorOndark + } + }() + + private var radioBoxStrikethroughColorConfiguration: BinarySurfaceColorConfiguration = { + return BinarySurfaceColorConfiguration().with { + $0.forTrue.lightColor = VDSColor.elementsPrimaryOnlight + $0.forTrue.darkColor = VDSColor.elementsPrimaryOndark + $0.forFalse.lightColor = VDSColor.elementsSecondaryOnlight + $0.forFalse.darkColor = VDSColor.elementsSecondaryOndark + } + }() + + //-------------------------------------------------- + // MARK: - RadioBox View Updates + //-------------------------------------------------- + /// Manages the appearance of the radioBox. + + private var shapeLayer: CAShapeLayer? + + open func updateSelector(_ viewModel: ModelType) { + //get the colors + let backgroundColor = radioBoxBackgroundColorConfiguration.getColor(viewModel) + let borderColor = radioBoxBorderColorConfiguration.getColor(viewModel) + let strikethroughColor = radioBoxStrikethroughColorConfiguration.getColor(viewModel) + let borderWidth = viewModel.selected ? selectorBorderWidthSelected : selectorBorderWidth + + if let shapeLayer = shapeLayer, let sublayers = layer.sublayers, sublayers.contains(shapeLayer) { + shapeLayer.removeFromSuperlayer() + self.shapeLayer = nil + } + + selectorView.backgroundColor = backgroundColor + selectorView.layer.borderColor = borderColor.cgColor + selectorView.layer.cornerRadius = selectorCornerRadius + selectorView.layer.borderWidth = borderWidth + + if shapeLayer == nil && viewModel.strikethrough { + let bounds = selectorView.bounds + let length = max(bounds.size.height, bounds.size.width) + guard length > 0.0, shapeLayer == nil else { return } + + let border = CAShapeLayer() + border.name = "strikethrough" + border.fillColor = nil + border.opacity = 1.0 + border.lineWidth = strikeThroughLineThickness + border.strokeColor = strikethroughColor.cgColor + + let linePath = UIBezierPath() + + let offsetPercent: CGFloat = 0.0 + linePath.move(to: CGPoint(x: selectorCornerRadius, y: bounds.height * (1 - offsetPercent))) + linePath.addLine(to: CGPoint(x: bounds.width - selectorCornerRadius, y: bounds.height * offsetPercent)) + border.path = linePath.cgPath + shapeLayer = border + layer.addSublayer(border) + } + + } + + //-------------------------------------------------- + // MARK: - Color Class Configurations + //-------------------------------------------------- + private class RadioBoxErrorColorConfiguration: BinaryDisabledSurfaceColorConfiguration { + public let error = BinarySurfaceColorConfiguration() + override func getColor(_ viewModel: ModelType) -> UIColor { + //only show error is enabled and showError == true + let showErrorColor = !viewModel.disabled && viewModel.hasError + + if showErrorColor { + return error.getColor(viewModel) + } else { + return super.getColor(viewModel) + } + } + } + +} + diff --git a/VDS/Components/RadioBox/RadioBoxGroup.swift b/VDS/Components/RadioBox/RadioBoxGroup.swift new file mode 100644 index 00000000..494a8366 --- /dev/null +++ b/VDS/Components/RadioBox/RadioBoxGroup.swift @@ -0,0 +1,113 @@ +// +// RadioBoxGroup.swift +// VDS +// +// Created by Matt Bruce on 8/23/22. +// + +import Foundation +import UIKit + +public class RadioBoxGroup: Control, SelectorGroupHandlerable, Changable { + public typealias ModelHandlerType = RadioBox + + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + public var selectorViews: [ModelHandlerType] = [] + + public var hasError: Bool { + get { model.hasError } + set { + let selectors = model.selectors.compactMap { existing in + return existing.copyWith { + $0.hasError = newValue + } + } + model.hasError = newValue + model.selectors = selectors + } + } + + public var onChange: Blocks.ActionBlock? + + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + private var mainStackView: UIStackView = { + return UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.alignment = .top + $0.axis = .vertical + $0.distribution = .fill + $0.spacing = 10 + } + }() + + //-------------------------------------------------- + // MARK: - Overrides + //-------------------------------------------------- + override public var disabled: Bool { + didSet { + updateSelectors() + } + } + + override public var surface: Surface { + didSet { + updateSelectors() + } + } + + open override func setup() { + super.setup() + + isAccessibilityElement = true + accessibilityTraits = .button + addSubview(mainStackView) + + mainStackView.topAnchor.constraint(equalTo: topAnchor).isActive = true + mainStackView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true + mainStackView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true + mainStackView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + } + + open override func shouldUpdateView(viewModel: ModelType) -> Bool { + let update = viewModel.selectors.count != model.selectors.count + || viewModel.hasError != model.hasError + || viewModel.surface != model.surface + || viewModel.disabled != model.disabled + return update + } + + open override func updateView(viewModel: ModelType) { + func findSelectorView(id: UUID) -> ModelHandlerType? { + return selectorViews.first(where: { existingSelectorView in + return existingSelectorView.model.id == id + }) + } + + for selectorModel in viewModel.selectors { + //see if view is there for the model + if let foundSelectorView = findSelectorView(id: selectorModel.id) { + foundSelectorView.set(with: selectorModel) + } else { + + //create view + let newSelectorView = ModelHandlerType(with: selectorModel) + + //add model update to the subscribers + newSelectorView.handlerPublisher() + .sink { [weak self] model in + if let cached = self?.getCachedSelector(viewModel: model), newSelectorView.shouldUpdateView(viewModel: cached) { + self?.replace(viewModel: model) + } + } + .store(in: &subscribers) + + self.selectorViews.append(newSelectorView) + mainStackView.addArrangedSubview(newSelectorView) + } + } + } +} diff --git a/VDS/Components/RadioBox/RadioBoxGroupModel.swift b/VDS/Components/RadioBox/RadioBoxGroupModel.swift new file mode 100644 index 00000000..c6f9d441 --- /dev/null +++ b/VDS/Components/RadioBox/RadioBoxGroupModel.swift @@ -0,0 +1,28 @@ +// +// RadioBoxGroupModel.swift +// VDS +// +// Created by Matt Bruce on 8/23/22. +// + +import Foundation + +public protocol RadioBoxGroupModel: SelectorGroupModelable where SelectorModelType: RadioBoxModel { + +} + +public struct DefaultRadioBoxGroupModel: RadioBoxGroupModel { + public typealias SelectorModelType = DefaultRadioBoxModel + public var id: UUID = UUID() + public var inputId: String? + public var value: AnyHashable? + public var surface: Surface = .light + public var disabled: Bool = false + public var selectors: [SelectorModelType] + public var hasError: Bool = false + public var errorText: String? + public init() { selectors = [] } + public init(selectors: [SelectorModelType]){ + self.selectors = selectors + } +} diff --git a/VDS/Components/RadioBox/RadioBoxModel.swift b/VDS/Components/RadioBox/RadioBoxModel.swift new file mode 100644 index 00000000..d8f634f5 --- /dev/null +++ b/VDS/Components/RadioBox/RadioBoxModel.swift @@ -0,0 +1,110 @@ +// +// RadioBoxModel.swift +// VDS +// +// Created by Matt Bruce on 8/23/22. +// + +import Foundation +import UIKit + +public protocol RadioBoxModel: Modelable, FormFieldable, Errorable, DataTrackable, Accessable, Selectable, BinaryColorable { + var text: String { get set } + var textAttributes: [LabelAttributeModel]? { get set } + var subText: String? { get set } + var subTextAttributes: [LabelAttributeModel]? { get set } + var subTextRight: String? { get set } + var subTextRightAttributes: [LabelAttributeModel]? { get set } + var strikethrough: Bool { get set } +} + +extension RadioBoxModel { + public var userTrueColor: Bool { return selected } + + public var shouldShowError: Bool { + guard hasError && !disabled && errorText?.isEmpty == false else { return false } + return true + } + + public var textModel: DefaultLabelModel { + var model = DefaultLabelModel() + model.textPosition = .left + model.typograpicalStyle = .BoldBodyLarge + model.text = text + model.surface = surface + model.disabled = disabled + model.attributes = textAttributes + return model + } + + public var subTextModel: DefaultLabelModel? { + guard let subText else { return nil } + var model = DefaultLabelModel() + model.textPosition = .left + model.typograpicalStyle = .BodyLarge + model.text = subText + model.surface = surface + model.disabled = disabled + model.attributes = subTextAttributes + return model + } + + public var subTextRightModel: DefaultLabelModel? { + guard let subTextRight else { return nil } + var model = DefaultLabelModel() + model.textPosition = .right + model.typograpicalStyle = .BodyLarge + model.text = subTextRight + model.surface = surface + model.disabled = disabled + model.attributes = subTextRightAttributes + return model + } + + public var errorModel: DefaultLabelModel? { + guard let errorText, hasError else { return nil } + var model = DefaultLabelModel() + model.textPosition = .left + model.typograpicalStyle = .BodyMedium + model.text = errorText + model.surface = surface + model.disabled = disabled + return model + } +} + +public struct DefaultRadioBoxModel: RadioBoxModel { + + public var id: UUID = UUID() + public var selected: Bool = false + + public var text: String = "Default Text" + public var textAttributes: [LabelAttributeModel]? + public var subText: String? + public var subTextAttributes: [LabelAttributeModel]? + public var subTextRight: String? + public var subTextRightAttributes: [LabelAttributeModel]? + public var selectedAccentColor: UIColor? + public var strikethrough: Bool = false + + public var hasError: Bool = false + public var errorText: String? + + public var inputId: String? + public var value: AnyHashable? + + public var surface: Surface = .light + public var disabled: Bool = false + + public var dataAnalyticsTrack: String? + public var dataClickStream: String? + public var dataTrack: String? + public var accessibilityHintEnabled: String? + public var accessibilityHintDisabled: String? + public var accessibilityValueEnabled: String? + public var accessibilityValueDisabled: String? + public var accessibilityLabelEnabled: String? + public var accessibilityLabelDisabled: String? + + public init() {} +}