diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 1d13c142..ee4cc638 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -62,6 +62,7 @@ EA985BF7296C665E00F2FF2E /* IconName.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA985BF6296C665E00F2FF2E /* IconName.swift */; }; EA985BF9296C710100F2FF2E /* IconColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA985BF8296C710100F2FF2E /* IconColor.swift */; }; EA985C1D296CD13600F2FF2E /* BundleManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA985C1C296CD13600F2FF2E /* BundleManager.swift */; }; + EA985C23296E033A00F2FF2E /* TextArea.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA985C22296E033A00F2FF2E /* TextArea.swift */; }; EAA5EEB528ECBFB4003B3210 /* ImageLabelAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA5EEB428ECBFB4003B3210 /* ImageLabelAttribute.swift */; }; EAA5EEB728ECC03A003B3210 /* ToolTipLabelAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA5EEB628ECC03A003B3210 /* ToolTipLabelAttribute.swift */; }; EAA5EEB928ECD24B003B3210 /* Icons.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EAA5EEB828ECD24B003B3210 /* Icons.xcassets */; }; @@ -173,6 +174,7 @@ EA985BF6296C665E00F2FF2E /* IconName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconName.swift; sourceTree = ""; }; EA985BF8296C710100F2FF2E /* IconColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconColor.swift; sourceTree = ""; }; EA985C1C296CD13600F2FF2E /* BundleManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleManager.swift; sourceTree = ""; }; + EA985C22296E033A00F2FF2E /* TextArea.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextArea.swift; sourceTree = ""; }; EAA5EEB428ECBFB4003B3210 /* ImageLabelAttribute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageLabelAttribute.swift; sourceTree = ""; }; EAA5EEB628ECC03A003B3210 /* ToolTipLabelAttribute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolTipLabelAttribute.swift; sourceTree = ""; }; EAA5EEB828ECD24B003B3210 /* Icons.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Icons.xcassets; sourceTree = ""; }; @@ -519,6 +521,14 @@ path = Icon; sourceTree = ""; }; + EA985C21296E032000F2FF2E /* TextArea */ = { + isa = PBXGroup; + children = ( + EA985C22296E033A00F2FF2E /* TextArea.swift */, + ); + path = TextArea; + sourceTree = ""; + }; EAB1D2D028ABEF3100DAE764 /* Typography */ = { isa = PBXGroup; children = ( @@ -559,6 +569,7 @@ isa = PBXGroup; children = ( EAC925862911C9DE00091998 /* InputField */, + EA985C21296E032000F2FF2E /* TextArea */, EAC925892911C9DE00091998 /* EntryField */, ); path = TextFields; @@ -739,6 +750,7 @@ EA89201328B568D8006B9984 /* RadioBox.swift in Sources */, EAC9258C2911C9DE00091998 /* InputField.swift in Sources */, EA3362402892EF6C0071C351 /* Label.swift in Sources */, + EA985C23296E033A00F2FF2E /* TextArea.swift in Sources */, EAF7F0B3289B1ADC00B287F5 /* ActionLabelAttribute.swift in Sources */, EAC925832911B35400091998 /* TextLinkCaret.swift in Sources */, EA33622E2891EA3C0071C351 /* DispatchQueue+Once.swift in Sources */, diff --git a/VDS/Components/TextFields/TextArea/TextArea.swift b/VDS/Components/TextFields/TextArea/TextArea.swift new file mode 100644 index 00000000..349fc33f --- /dev/null +++ b/VDS/Components/TextFields/TextArea/TextArea.swift @@ -0,0 +1,142 @@ +// +// TextArea.swift +// VDS +// +// Created by Matt Bruce on 1/10/23. +// + +import Foundation +import Foundation +import UIKit +import VDSColorTokens +import VDSFormControlsTokens +import Combine + +@objc(VDSTextArea) +public class TextArea: EntryField { + //-------------------------------------------------- + // 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: - Private Properties + //-------------------------------------------------- + internal var inputFieldStackView: UIStackView = { + return UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.axis = .horizontal + $0.distribution = .fill + $0.spacing = 12 + } + }() + + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + override var containerSize: CGSize { CGSize(width: 45, height: 88) } + + private var textView = UITextView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.font = TypographicalStyle.BodyLarge.font + $0.sizeToFit() + $0.isScrollEnabled = false + } + + public var textViewTextColorConfiguration: AnyColorable = ViewColorConfiguration().with { + $0.setSurfaceColors(VDSColor.elementsSecondaryOnlight, VDSColor.elementsSecondaryOndark, forDisabled: true) + $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forDisabled: false) + }.eraseToAnyColorable() + + internal var minWidthConstraint: NSLayoutConstraint? + internal var textViewHeightConstraint: NSLayoutConstraint? + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + open override func setup() { + super.setup() + + minWidthConstraint = containerView.widthAnchor.constraint(greaterThanOrEqualToConstant: 0) + minWidthConstraint?.isActive = true + + controlContainerView.addSubview(textView) + textView.pinToSuperView() + textViewHeightConstraint = textView.heightAnchor.constraint(greaterThanOrEqualToConstant: 64) + textViewHeightConstraint?.isActive = true + backgroundColorConfiguration.setSurfaceColors(VDSColor.feedbackSuccessBackgroundOnlight, VDSColor.feedbackSuccessBackgroundOndark, forState: .success) + borderColorConfiguration.setSurfaceColors(VDSColor.feedbackSuccessOnlight, VDSColor.feedbackSuccessOndark, forState: .success) + + textView.delegate = self + } + + public override func reset() { + super.reset() + textView.text = "" + } + + open override func getContainer() -> UIView { + inputFieldStackView.addArrangedSubview(containerView) + return inputFieldStackView + } + + //-------------------------------------------------- + // MARK: - State + //-------------------------------------------------- + open override func updateView() { + super.updateView() + + textView.isEditable = isEnabled + textView.textColor = textViewTextColorConfiguration.getColor(self) + + //set the width constraints + if let width { + widthConstraint?.constant = width + widthConstraint?.isActive = true + minWidthConstraint?.isActive = false + } else { + minWidthConstraint?.constant = containerSize.width + widthConstraint?.isActive = false + minWidthConstraint?.isActive = true + } + } +} + +extension TextArea: UITextViewDelegate { + //-------------------------------------------------- + // MARK: - UITextViewDelegate + //-------------------------------------------------- + public func textViewDidChange(_ textView: UITextView) { + + //dynamic textView Height sizing based on Figma + //if you want it to work "as-is" delete this code + //since it will autogrow with the current settings + if let textViewHeightConstraint, textView.isEditable { + let height = textView.frame.size.height + let constraintHeight = textViewHeightConstraint.constant + if height > constraintHeight { + if height > 64 && height < 152 { + textViewHeightConstraint.constant = 152 + } else if height > 152 { + textViewHeightConstraint.constant = 328 + } else { + textViewHeightConstraint.constant = 64 + } + } + } + + //setting the value and firing control event + value = textView.text + sendActions(for: .valueChanged) + + } +}