[PM-26064] Consolidate form fields (#2032)

This commit is contained in:
Katherine Bertelsen 2025-10-10 15:03:04 -05:00 committed by GitHub
parent 07176cd3c0
commit af42bd42c7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 152 additions and 638 deletions

View File

@ -1,125 +0,0 @@
import BitwardenKit
import SwiftUI
// MARK: - FormMenuField
/// The data necessary for displaying a `FormMenuFieldView`.
///
struct FormMenuField<State, T: Menuable>: Equatable, Identifiable {
// MARK: Properties
/// The accessibility identifier to apply to the field.
let accessibilityIdentifier: String?
/// The footer text displayed below the menu field.
let footer: String?
/// A key path for updating the backing value for the menu field.
let keyPath: WritableKeyPath<State, T>
/// The options displayed in the menu.
let options: [T]
/// The current selection.
let selection: T
/// The title of the field.
let title: String
// MARK: Identifiable
var id: String {
"FormMenuField-\(title)"
}
// MARK: Initialization
/// Initialize a `FormMenuField`.
///
/// - Parameters:
/// - accessibilityIdentifier: The accessibility identifier given to the menu field.
/// - footer: The footer text displayed below the menu field.
/// - keyPath: A key path for updating the backing value for the menu field.
/// - options: The options displayed in the menu.
/// - selection: The current selection.
/// - title: The title of the field.
init(
accessibilityIdentifier: String?,
footer: String? = nil,
keyPath: WritableKeyPath<State, T>,
options: [T],
selection: T,
title: String,
) {
self.accessibilityIdentifier = accessibilityIdentifier
self.footer = footer
self.keyPath = keyPath
self.options = options
self.selection = selection
self.title = title
}
}
// MARK: - FormMenuFieldView
/// A view that displays a menu field for display in a form.
///
struct FormMenuFieldView<State, T: Menuable, TrailingContent: View>: View {
// MARK: Properties
/// A closure containing the action to take when the menu selection is changed.
let action: (T) -> Void
/// The data for displaying the field.
let field: FormMenuField<State, T>
/// Optional content view that is displayed to the right of the menu value.
let trailingContent: TrailingContent
// MARK: View
var body: some View {
BitwardenMenuField(
title: field.title,
footer: field.footer,
accessibilityIdentifier: field.accessibilityIdentifier,
options: field.options,
selection: Binding(get: { field.selection }, set: action),
trailingContent: { trailingContent },
)
}
// MARK: Initialization
/// Initialize a `FormMenuFieldView`.
///
/// - Parameters:
/// - field: The data for displaying the field.
/// - action: A closure containing the action to take when the menu selection is changed.
///
init(
field: FormMenuField<State, T>,
action: @escaping (T) -> Void,
) where TrailingContent == EmptyView {
self.action = action
self.field = field
trailingContent = EmptyView()
}
/// Initialize a `FormMenuFieldView`.
///
/// - Parameters:
/// - field: The data for displaying the field.
/// - action: A closure containing the action to take when the menu selection is changed.
/// - trailingContent: Optional content view that is displayed to the right of the menu value.
///
init(
field: FormMenuField<State, T>,
action: @escaping (T) -> Void,
trailingContent: @escaping () -> TrailingContent,
) {
self.action = action
self.field = field
self.trailingContent = trailingContent()
}
}

View File

@ -1,108 +0,0 @@
import SwiftUI
// MARK: - SliderField
/// The data necessary for displaying a `SliderFieldView`.
///
struct SliderField<State>: Equatable, Identifiable {
// MARK: Properties
/// A key path for updating the backing value for the slider field.
let keyPath: WritableKeyPath<State, Double>
/// The range of allowable values for the slider.
let range: ClosedRange<Double>
/// The accessibility id for the slider. The `title` will be used as the accessibility id
/// if this is `nil`.
let sliderAccessibilityId: String?
/// The accessibility id for the slider value. The `id` will be used as the accessibility id
/// if this is `nil`.
let sliderValueAccessibilityId: String?
/// The distance between each valid value.
let step: Double
/// The title of the field.
let title: String
/// The current slider value.
let value: Double
// MARK: Identifiable
var id: String {
"SliderField-\(title)"
}
}
// MARK: - SliderFieldView
/// A view that displays a slider for display in a form.
///
struct SliderFieldView<State>: View {
// MARK: Properties
/// The data for displaying the field.
let field: SliderField<State>
/// A closure containing the action to take when the slider begins or ends editing.
let onEditingChanged: (Bool) -> Void
/// A closure containing the action to take when a new value is selected.
let onValueChanged: (Double) -> Void
var body: some View {
VStack(spacing: 8) {
HStack {
Text(field.title)
.styleGuide(.body)
.foregroundColor(Asset.Colors.textPrimary.swiftUIColor)
Spacer()
Text(String(Int(field.value)))
.styleGuide(.body, monoSpacedDigit: true)
.foregroundColor(Asset.Colors.textSecondary.swiftUIColor)
.accessibilityIdentifier(field.sliderValueAccessibilityId ?? field.id)
}
.accessibilityHidden(true)
Divider()
Slider(
value: Binding(get: { field.value }, set: onValueChanged),
in: field.range,
step: field.step,
onEditingChanged: onEditingChanged,
)
.tint(Asset.Colors.primaryBitwarden.swiftUIColor)
.accessibilityLabel(field.title)
.accessibilityIdentifier(field.sliderAccessibilityId ?? field.title)
}
.padding(.horizontal, 16)
.padding(.vertical, 8)
.background(Asset.Colors.backgroundPrimary.swiftUIColor)
.cornerRadius(10)
}
// MARK: Initialization
/// Initialize a `SliderFieldView`.
///
/// - Parameters:
/// - field: The data for displaying the field.
/// - onEditingChanged: A closure containing the action to take when the slider begins or ends editing.
/// - onValueChanged: A closure containing the action to take when a new value is selected.
///
init(
field: SliderField<State>,
onEditingChanged: @escaping (Bool) -> Void = { _ in },
onValueChanged: @escaping (Double) -> Void,
) {
self.field = field
self.onEditingChanged = onEditingChanged
self.onValueChanged = onValueChanged
}
}

View File

@ -1,82 +0,0 @@
import SwiftUI
// MARK: - StepperField
/// The data necessary for displaying a `StepperFieldView`.
///
struct StepperField<State>: Equatable, Identifiable {
// MARK: Properties
/// The accessibility id for the stepper. The `id` will be used as the accessibility id
/// if this is `nil`.
let accessibilityId: String?
/// A key path for updating the backing value for the stepper field.
let keyPath: WritableKeyPath<State, Int>
/// The range of allowable values for the stepper.
let range: ClosedRange<Int>
/// The title of the field.
let title: String
/// The current stepper value.
let value: Int
// MARK: Identifiable
var id: String {
"StepperField-\(title)"
}
}
// MARK: - StepperFieldView
/// A view that displays a stepper for display in a form.
///
struct StepperFieldView<State>: View {
// MARK: Properties
/// A closure containing the action to take when a new value is selected.
let action: (Int) -> Void
/// The data for displaying the field.
let field: StepperField<State>
var body: some View {
VStack(spacing: 16) {
Stepper(
value: Binding(get: { field.value }, set: action),
in: field.range,
) {
HStack {
Text(field.title)
.styleGuide(.body)
.foregroundColor(Asset.Colors.textPrimary.swiftUIColor)
Spacer()
Text(String(field.value))
.styleGuide(.body, monoSpacedDigit: true)
.foregroundColor(Asset.Colors.textSecondary.swiftUIColor)
}
.padding(.trailing, 4)
}
.padding(.top, 4)
.accessibilityIdentifier(field.accessibilityId ?? field.id)
}
}
// MARK: Initialization
/// Initialize a `StepperFieldView`.
///
/// - Parameters:
/// - field: The data for displaying the field.
/// - action: A closure containing the action to take when a new value is selected.
///
init(field: StepperField<State>, action: @escaping (Int) -> Void) {
self.action = action
self.field = field
}
}

View File

@ -1,79 +0,0 @@
import SwiftUI
// MARK: - ToggleField
/// The data necessary for displaying a `ToggleFieldView`.
///
struct ToggleField<State>: Equatable, Identifiable {
// MARK: Properties
/// The accessibility id for the toggle. The `id` will be used as the accessibility id
/// if this is `nil`.
let accessibilityId: String?
/// The accessibility label for the toggle. The title will be used as the accessibility label
/// if this is `nil`.
let accessibilityLabel: String?
/// Whether the toggle is disabled.
let isDisabled: Bool
/// The current toggle value.
let isOn: Bool
/// A key path for updating the backing value for the toggle field.
let keyPath: WritableKeyPath<State, Bool>
/// The title of the field.
let title: String
// MARK: Identifiable
var id: String {
"ToggleField-\(title)"
}
}
// MARK: - ToggleFieldView
/// A view that displays a toggle for display in a form.
///
struct ToggleFieldView<State>: View {
// MARK: Properties
/// A closure containing the action to take when the toggle is toggled.
let action: (Bool) -> Void
/// The data for displaying the field.
let field: ToggleField<State>
var body: some View {
VStack(spacing: 0) {
Toggle(
field.title,
isOn: Binding(get: { field.isOn }, set: action),
)
.accessibilityIdentifier(field.accessibilityId ?? field.id)
.accessibilityLabel(field.accessibilityLabel ?? field.title)
.disabled(field.isDisabled)
.toggleStyle(.bitwarden)
.padding(.bottom, 16)
.padding(.top, 4)
Divider()
}
}
// MARK: Initialization
/// Initialize a `ToggleFieldView`.
///
/// - Parameters:
/// - field: The data for displaying the field.
/// - action: A closure containing the action to take when the toggle is toggled.
///
init(field: ToggleField<State>, action: @escaping (Bool) -> Void) {
self.action = action
self.field = field
}
}

View File

@ -2,7 +2,7 @@ import Foundation
// MARK: - Int
extension Int {
public extension Int {
// MARK: Properties
/// Returns the number of digits within the value.

View File

@ -1,8 +1,7 @@
import BitwardenKit
import Foundation
import XCTest
@testable import BitwardenShared
class IntTests: BitwardenTestCase {
// MARK: Tests

View File

@ -1,9 +1,8 @@
// swiftlint:disable:this file_name
import BitwardenKit
import SnapshotTesting
import XCTest
@testable import BitwardenShared
// MARK: - BitwardenSliderTests
class BitwardenSliderTests: BitwardenTestCase {

View File

@ -3,7 +3,7 @@ import SwiftUI
/// A custom slider view that allows for custom styling and accessibility.
///
struct BitwardenSlider: View {
public struct BitwardenSlider: View {
// MARK: Private Properties
/// The size of the thumb view.
@ -31,7 +31,7 @@ struct BitwardenSlider: View {
/// The color of the filled portion of the slider track.
var filledTrackColor: Color = SharedAsset.Colors.sliderFilled.swiftUIColor
var body: some View {
public var body: some View {
GeometryReader { geometry in
let thumbPosition = thumbPosition(in: geometry.size)
ZStack {
@ -103,7 +103,7 @@ struct BitwardenSlider: View {
/// - trackColor: The color of the slider track.
/// - filledTrackColor: The color of the filled portion of the slider track.
///
init(
public init(
value: Binding<Double>,
in range: ClosedRange<Double>,
step: Double,

View File

@ -1,4 +1,3 @@
import BitwardenKit
import BitwardenResources
import SwiftUI
@ -6,7 +5,7 @@ import SwiftUI
/// A custom stepper component which performs increment and decrement actions.
///
struct BitwardenStepper<Label: View, Footer: View>: View {
public struct BitwardenStepper<Label: View, Footer: View>: View {
// MARK: Properties
/// Whether a text field can be used to type in the value as an alternative to using the
@ -66,7 +65,7 @@ struct BitwardenStepper<Label: View, Footer: View>: View {
// MARK: View
var body: some View {
public var body: some View {
VStack(spacing: 0) {
contentView()
@ -87,7 +86,7 @@ struct BitwardenStepper<Label: View, Footer: View>: View {
/// - label: The label to display for the stepper.
/// - footer: A footer to display below the stepper.
///
init(
public init(
value: Binding<Int>,
in range: ClosedRange<Int>,
allowTextFieldInput: Bool = false,
@ -113,7 +112,7 @@ struct BitwardenStepper<Label: View, Footer: View>: View {
/// - textFieldAccessibilityIdentifier: An accessibility identifier for the text field.
/// - label: The label to display for the stepper.
///
init(
public init(
value: Binding<Int>,
in range: ClosedRange<Int>,
allowTextFieldInput: Bool = false,

View File

@ -1,34 +1,33 @@
import BitwardenKit
import SwiftUI
// MARK: - FormMenuField
/// The data necessary for displaying a `FormMenuFieldView`.
///
struct FormMenuField<State, T: Menuable>: Equatable, Identifiable {
public struct FormMenuField<State, T: Menuable>: Equatable, Identifiable {
// MARK: Properties
/// The accessibility identifier to apply to the field.
let accessibilityIdentifier: String?
/// The footer text displayed below the menu field.
let footer: String?
public let footer: String?
/// A key path for updating the backing value for the menu field.
let keyPath: WritableKeyPath<State, T>
/// The options displayed in the menu.
let options: [T]
public let options: [T]
/// The current selection.
let selection: T
public let selection: T
/// The title of the field.
let title: String
public let title: String
// MARK: Identifiable
var id: String {
public var id: String {
"FormMenuField-\(title)"
}
@ -43,7 +42,7 @@ struct FormMenuField<State, T: Menuable>: Equatable, Identifiable {
/// - options: The options displayed in the menu.
/// - selection: The current selection.
/// - title: The title of the field.
init(
public init(
accessibilityIdentifier: String?,
footer: String? = nil,
keyPath: WritableKeyPath<State, T>,
@ -64,7 +63,7 @@ struct FormMenuField<State, T: Menuable>: Equatable, Identifiable {
/// A view that displays a menu field for display in a form.
///
struct FormMenuFieldView<State, T: Menuable, TitleAccessory: View, TrailingContent: View>: View {
public struct FormMenuFieldView<State, T: Menuable, TitleAccessory: View, TrailingContent: View>: View {
// MARK: Properties
/// A closure containing the action to take when the menu selection is changed.
@ -81,7 +80,7 @@ struct FormMenuFieldView<State, T: Menuable, TitleAccessory: View, TrailingConte
// MARK: View
var body: some View {
public var body: some View {
if let trailingContent, let titleAccessoryContent {
BitwardenMenuField(
title: field.title,
@ -129,7 +128,7 @@ struct FormMenuFieldView<State, T: Menuable, TitleAccessory: View, TrailingConte
/// - field: The data for displaying the field.
/// - action: A closure containing the action to take when the menu selection is changed.
///
init(
public init(
field: FormMenuField<State, T>,
action: @escaping (T) -> Void,
) where TrailingContent == EmptyView, TitleAccessory == EmptyView {
@ -146,7 +145,7 @@ struct FormMenuFieldView<State, T: Menuable, TitleAccessory: View, TrailingConte
/// - action: A closure containing the action to take when the menu selection is changed.
/// - trailingContent: Optional content view that is displayed to the right of the menu value.
///
init(
public init(
field: FormMenuField<State, T>,
action: @escaping (T) -> Void,
trailingContent: @escaping () -> TrailingContent,
@ -164,7 +163,7 @@ struct FormMenuFieldView<State, T: Menuable, TitleAccessory: View, TrailingConte
/// - action: A closure containing the action to take when the menu selection is changed.
/// - titleAccessoryContent: Optional title accessory view that is displayed to the right of the title.
///
init(
public init(
field: FormMenuField<State, T>,
action: @escaping (T) -> Void,
titleAccessoryContent: @escaping () -> TitleAccessory,
@ -183,7 +182,7 @@ struct FormMenuFieldView<State, T: Menuable, TitleAccessory: View, TrailingConte
/// - titleAccessoryContent: Optional title accessory view that is displayed to the right of the title.
/// - trailingContent: Optional content view that is displayed to the right of the menu value.
///
init(
public init(
field: FormMenuField<State, T>,
action: @escaping (T) -> Void,
titleAccessoryContent: () -> TitleAccessory,

View File

@ -1,16 +1,15 @@
import BitwardenKit
import SwiftUI
// MARK: - FormTextField
/// The data necessary for displaying a `FormTextFieldView`.
///
struct FormTextField<State>: Equatable, Identifiable {
public struct FormTextField<State>: Equatable, Identifiable {
// MARK: Types
/// An enum describing the behavior for when the input should be automatically capitalized.
///
enum Autocapitalization {
public enum Autocapitalization {
/// Input is never capitalized.
case never
@ -49,13 +48,13 @@ struct FormTextField<State>: Equatable, Identifiable {
let isPasswordVisible: Bool?
/// A key path for updating whether a password displayed in the text field is visible.
let isPasswordVisibleKeyPath: WritableKeyPath<State, Bool>?
public let isPasswordVisibleKeyPath: WritableKeyPath<State, Bool>?
/// The type of keyboard to display.
let keyboardType: UIKeyboardType
/// A key path for updating the backing value for the text field.
let keyPath: WritableKeyPath<State, String>
public let keyPath: WritableKeyPath<State, String>
/// The accessibility id for the button to toggle password visibility.
let passwordVisibilityAccessibilityId: String?
@ -64,14 +63,14 @@ struct FormTextField<State>: Equatable, Identifiable {
let textContentType: UITextContentType?
/// The title of the field.
let title: String
public let title: String
/// The current text value.
let value: String
public let value: String
// MARK: Identifiable
var id: String {
public var id: String {
"FormTextField-\(title)"
}
@ -94,7 +93,7 @@ struct FormTextField<State>: Equatable, Identifiable {
/// - textContentType: The expected type of content input in the text field. Defaults to `nil`.
/// - title: The title of the field.
/// - value: The current text value.
init(
public init(
accessibilityId: String? = nil,
autocapitalization: Autocapitalization = .sentences,
isAutocorrectDisabled: Bool = false,
@ -125,7 +124,7 @@ struct FormTextField<State>: Equatable, Identifiable {
/// A view that displays a text field for display in a form.
///
struct FormTextFieldView<State>: View {
public struct FormTextFieldView<State>: View {
// MARK: Properties
/// A closure containing the action to take when the text is changed.
@ -138,7 +137,7 @@ struct FormTextFieldView<State>: View {
/// in the text field is changed.
let isPasswordVisibleChangedAction: ((Bool) -> Void)?
var body: some View {
public var body: some View {
BitwardenTextField(
title: field.title,
text: Binding(get: { field.value }, set: action),
@ -164,7 +163,7 @@ struct FormTextFieldView<State>: View {
/// - isPasswordVisibleChangedAction: A closure containing the action to take when the value
/// for whether a password is displayed in the text field is changed.
///
init(
public init(
field: FormTextField<State>,
action: @escaping (String) -> Void,
isPasswordVisibleChangedAction: ((Bool) -> Void)? = nil,

View File

@ -5,14 +5,14 @@ import SwiftUI
/// The data necessary for displaying a `SliderFieldView`.
///
struct SliderField<State>: Equatable, Identifiable {
public struct SliderField<State>: Equatable, Identifiable {
// MARK: Properties
/// A key path for updating the backing value for the slider field.
let keyPath: WritableKeyPath<State, Double>
public let keyPath: WritableKeyPath<State, Double>
/// The range of allowable values for the slider.
let range: ClosedRange<Double>
public let range: ClosedRange<Double>
/// The accessibility id for the slider. The `title` will be used as the accessibility id
/// if this is `nil`.
@ -23,26 +23,58 @@ struct SliderField<State>: Equatable, Identifiable {
let sliderValueAccessibilityId: String?
/// The distance between each valid value.
let step: Double
public let step: Double
/// The title of the field.
let title: String
public let title: String
/// The current slider value.
let value: Double
public let value: Double
// MARK: Identifiable
var id: String {
public var id: String {
"SliderField-\(title)"
}
// MARK: Initializer
/// Public version of synthesized initializer
///
/// - Parameters:
/// - keyPath: A key path for updating the backing value for the slider field.
/// - range: The range of allowable values for the slider.
/// - sliderAccessibilityId: The accessibility ID for the slider.
/// The `title` will be used as the accessibility ID if this is `nil`.
/// - sliderValueAccessibilityId: The accessibility ID for the slider value.
/// The `id` will be used as the accessibility ID if this is `nil`.
/// - step: The distance between each valid value.
/// - title: The title of the field.
/// - value: The current slider value.
public init(
keyPath: WritableKeyPath<State, Double>,
range: ClosedRange<Double>,
sliderAccessibilityId: String?,
sliderValueAccessibilityId: String?,
step: Double,
title: String,
value: Double,
) {
self.keyPath = keyPath
self.range = range
self.sliderAccessibilityId = sliderAccessibilityId
self.sliderValueAccessibilityId = sliderValueAccessibilityId
self.step = step
self.title = title
self.value = value
}
}
// MARK: - SliderFieldView
/// A view that displays a slider for display in a form.
///
struct SliderFieldView<State>: View {
public struct SliderFieldView<State>: View {
// MARK: Properties
/// The data for displaying the field.
@ -57,7 +89,7 @@ struct SliderFieldView<State>: View {
/// The width of the three digit text "000" based on the current font.
@SwiftUI.State private var minTextWidth: CGFloat = 14
var body: some View {
public var body: some View {
HStack(alignment: .center, spacing: 16) {
Text(field.title)
.styleGuide(.body)
@ -110,7 +142,7 @@ struct SliderFieldView<State>: View {
/// - onEditingChanged: A closure containing the action to take when the slider begins or ends editing.
/// - onValueChanged: A closure containing the action to take when a new value is selected.
///
init(
public init(
field: SliderField<State>,
onEditingChanged: @escaping (Bool) -> Void = { _ in },
onValueChanged: @escaping (Double) -> Void,

View File

@ -5,7 +5,7 @@ import SwiftUI
/// The data necessary for displaying a `StepperFieldView`.
///
struct StepperField<State>: Equatable, Identifiable {
public struct StepperField<State>: Equatable, Identifiable {
// MARK: Properties
/// The accessibility id for the stepper. The `id` will be used as the accessibility id
@ -13,29 +13,54 @@ struct StepperField<State>: Equatable, Identifiable {
let accessibilityId: String?
/// A key path for updating the backing value for the stepper field.
let keyPath: WritableKeyPath<State, Int>
public let keyPath: WritableKeyPath<State, Int>
/// The range of allowable values for the stepper.
let range: ClosedRange<Int>
public let range: ClosedRange<Int>
/// The title of the field.
let title: String
public let title: String
/// The current stepper value.
let value: Int
public let value: Int
// MARK: Identifiable
var id: String {
public var id: String {
"StepperField-\(title)"
}
// MARK: Initializers
/// Public version of synthesized initializer.
///
/// - Parameters:
/// - accessibilityId: The accessibility ID for the stepper.
/// The `id` will be used as the accessibility ID if this is `nil`.
/// - keyPath: A key path for updating the backing value for the stepper field.
/// - range: The range of allowable values for the stepper.
/// - title: The title of the field.
/// - value: The current stepper value.
public init(
accessibilityId: String?,
keyPath: WritableKeyPath<State, Int>,
range: ClosedRange<Int>,
title: String,
value: Int,
) {
self.accessibilityId = accessibilityId
self.keyPath = keyPath
self.range = range
self.title = title
self.value = value
}
}
// MARK: - StepperFieldView
/// A view that displays a stepper for display in a form.
///
struct StepperFieldView<State>: View {
public struct StepperFieldView<State>: View {
// MARK: Properties
/// A closure containing the action to take when a new value is selected.
@ -44,7 +69,7 @@ struct StepperFieldView<State>: View {
/// The data for displaying the field.
let field: StepperField<State>
var body: some View {
public var body: some View {
BitwardenStepper(
value: Binding(get: { field.value }, set: action),
in: field.range,
@ -64,7 +89,7 @@ struct StepperFieldView<State>: View {
/// - field: The data for displaying the field.
/// - action: A closure containing the action to take when a new value is selected.
///
init(field: StepperField<State>, action: @escaping (Int) -> Void) {
public init(field: StepperField<State>, action: @escaping (Int) -> Void) {
self.action = action
self.field = field
}

View File

@ -4,7 +4,7 @@ import SwiftUI
/// The data necessary for displaying a `ToggleFieldView`.
///
struct ToggleField<State>: Equatable, Identifiable {
public struct ToggleField<State>: Equatable, Identifiable {
// MARK: Properties
/// The accessibility id for the toggle. The `id` will be used as the accessibility id
@ -19,26 +19,55 @@ struct ToggleField<State>: Equatable, Identifiable {
let isDisabled: Bool
/// The current toggle value.
let isOn: Bool
public let isOn: Bool
/// A key path for updating the backing value for the toggle field.
let keyPath: WritableKeyPath<State, Bool>
public let keyPath: WritableKeyPath<State, Bool>
/// The title of the field.
let title: String
public let title: String
// MARK: Identifiable
var id: String {
public var id: String {
"ToggleField-\(title)"
}
// MARK: Initializer
/// Public version of synthesized initializer.
///
/// - Parameters:
/// - accessibilityId: The accessibility ID for the toggle.
/// The `id` will be used as the accessibility ID if this is `nil`.
/// - accessibilityLabel: The accessibility label for the toggle.
/// The title will be used as the accessibility label if this is `nil`.
/// - isDisabled: Whether the toggle is disabled.
/// - isOn: The current toggle value.
/// - keyPath: A key path for updating the backing value for the toggle field.
/// - title: The title of the field.
public init(
accessibilityId: String?,
accessibilityLabel: String?,
isDisabled: Bool,
isOn: Bool,
keyPath: WritableKeyPath<State, Bool>,
title: String,
) {
self.accessibilityId = accessibilityId
self.accessibilityLabel = accessibilityLabel
self.isDisabled = isDisabled
self.isOn = isOn
self.keyPath = keyPath
self.title = title
}
}
// MARK: - ToggleFieldView
/// A view that displays a toggle for display in a form.
///
struct ToggleFieldView<State>: View {
public struct ToggleFieldView<State>: View {
// MARK: Properties
/// A closure containing the action to take when the toggle is toggled.
@ -47,7 +76,7 @@ struct ToggleFieldView<State>: View {
/// The data for displaying the field.
let field: ToggleField<State>
var body: some View {
public var body: some View {
Toggle(
field.title,
isOn: Binding(get: { field.isOn }, set: action),
@ -67,7 +96,7 @@ struct ToggleFieldView<State>: View {
/// - field: The data for displaying the field.
/// - action: A closure containing the action to take when the toggle is toggled.
///
init(field: ToggleField<State>, action: @escaping (Bool) -> Void) {
public init(field: ToggleField<State>, action: @escaping (Bool) -> Void) {
self.action = action
self.field = field
}

View File

@ -1,176 +0,0 @@
import BitwardenKit
import SwiftUI
// MARK: - FormTextField
/// The data necessary for displaying a `FormTextFieldView`.
///
struct FormTextField<State>: Equatable, Identifiable {
// MARK: Types
/// An enum describing the behavior for when the input should be automatically capitalized.
///
enum Autocapitalization {
/// Input is never capitalized.
case never
/// The first letter of a sentence should be capitalized.
case sentences
/// The first letter of every word should be capitalized.
case words
/// Returns the `TextInputAutocapitalization` behavior.
var textInputAutocapitalization: TextInputAutocapitalization {
switch self {
case .never:
.never
case .sentences:
.sentences
case .words:
.words
}
}
}
// MARK: Properties
/// The accessibility id for the text field. The `title` will be used as the accessibility id
/// if this is `nil`.
let accessibilityId: String?
/// The behavior for when the input should be automatically capitalized.
let autocapitalization: Autocapitalization
/// Whether autocorrect is disabled in the text field.
let isAutocorrectDisabled: Bool
/// Whether a password displayed in the text field is visible.
let isPasswordVisible: Bool?
/// A key path for updating whether a password displayed in the text field is visible.
let isPasswordVisibleKeyPath: WritableKeyPath<State, Bool>?
/// The type of keyboard to display.
let keyboardType: UIKeyboardType
/// A key path for updating the backing value for the text field.
let keyPath: WritableKeyPath<State, String>
/// The accessibility id for the button to toggle password visibility.
let passwordVisibilityAccessibilityId: String?
/// The expected type of content input in the text field.
let textContentType: UITextContentType?
/// The title of the field.
let title: String
/// The current text value.
let value: String
// MARK: Identifiable
var id: String {
"FormTextField-\(title)"
}
// MARK: Initialization
/// Initialize a `FormTextField`.
///
/// - Parameters:
/// - accessibilityId: The accessibility id for the text field.
/// - autocapitalization: The behavior for when the input should be automatically capitalized.
/// Defaults to `.sentences`.
/// - isAutocorrectDisabled: Whether autocorrect is disabled in the text field. Defaults to
/// `false`.
/// - isPasswordVisible: Whether a password displayed in the text field is visible
/// - isPasswordVisibleKeyPath: A key path for updating whether a password displayed in the
/// text field is visible.
/// - keyboardType: The type of keyboard to display.
/// - keyPath: A key path for updating the backing value for the text field.
/// - passwordVisibilityAccessibilityId: The accessibility id for the password visibility button.
/// - textContentType: The expected type of content input in the text field. Defaults to `nil`.
/// - title: The title of the field.
/// - value: The current text value.
init(
accessibilityId: String? = nil,
autocapitalization: Autocapitalization = .sentences,
isAutocorrectDisabled: Bool = false,
isPasswordVisible: Bool? = nil,
isPasswordVisibleKeyPath: WritableKeyPath<State, Bool>? = nil,
keyboardType: UIKeyboardType = .default,
keyPath: WritableKeyPath<State, String>,
passwordVisibilityAccessibilityId: String? = nil,
textContentType: UITextContentType? = nil,
title: String,
value: String,
) {
self.accessibilityId = accessibilityId
self.autocapitalization = autocapitalization
self.isAutocorrectDisabled = isAutocorrectDisabled
self.isPasswordVisible = isPasswordVisible
self.isPasswordVisibleKeyPath = isPasswordVisibleKeyPath
self.keyboardType = keyboardType
self.keyPath = keyPath
self.passwordVisibilityAccessibilityId = passwordVisibilityAccessibilityId
self.textContentType = textContentType
self.title = title
self.value = value
}
}
// MARK: - FormTextFieldView
/// A view that displays a text field for display in a form.
///
struct FormTextFieldView<State>: View {
// MARK: Properties
/// A closure containing the action to take when the text is changed.
let action: (String) -> Void
/// The data for displaying the field.
let field: FormTextField<State>
/// A closure containing the action to take when the value for whether a password is displayed
/// in the text field is changed.
let isPasswordVisibleChangedAction: ((Bool) -> Void)?
var body: some View {
BitwardenTextField(
title: field.title,
text: Binding(get: { field.value }, set: action),
accessibilityIdentifier: field.accessibilityId ?? field.title,
passwordVisibilityAccessibilityId: field.passwordVisibilityAccessibilityId,
isPasswordVisible: field.isPasswordVisible.map { isPasswordVisible in
Binding(get: { isPasswordVisible }, set: isPasswordVisibleChangedAction ?? { _ in })
},
)
.autocorrectionDisabled(field.isAutocorrectDisabled)
.keyboardType(field.keyboardType)
.textContentType(field.textContentType)
.textInputAutocapitalization(field.autocapitalization.textInputAutocapitalization)
}
// MARK: Initialization
/// Initialize a `FormTextFieldView`.
///
/// - Parameters:
/// - field: The data for displaying the field.
/// - action: A closure containing the action to take when the text is changed.
/// - isPasswordVisibleChangedAction: A closure containing the action to take when the value
/// for whether a password is displayed in the text field is changed.
///
init(
field: FormTextField<State>,
action: @escaping (String) -> Void,
isPasswordVisibleChangedAction: ((Bool) -> Void)? = nil,
) {
self.action = action
self.field = field
self.isPasswordVisibleChangedAction = isPasswordVisibleChangedAction
}
}

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenResources
import UIKit

View File

@ -1,3 +1,4 @@
import BitwardenKit
import BitwardenSdk
import InlineSnapshotTesting
import XCTest

View File

@ -1,4 +1,5 @@
// swiftlint:disable:this file_name
import BitwardenKit
import BitwardenResources
import SwiftUI
import ViewInspector

View File

@ -34,7 +34,7 @@ struct BitwardenSliderType: BaseViewType {
static var typePrefix: String = "BitwardenSlider"
static var namespacedPrefixes: [String] = [
"BitwardenShared.BitwardenSlider",
"BitwardenKit.BitwardenSlider",
]
}
@ -45,7 +45,7 @@ struct BitwardenStepperType: BaseViewType {
static var typePrefix: String = "BitwardenStepper"
static var namespacedPrefixes: [String] = [
"BitwardenShared.BitwardenStepper",
"BitwardenKit.BitwardenStepper",
]
}