mirror of
https://github.com/bitwarden/ios.git
synced 2025-12-11 04:34:55 -06:00
[PM-26064] Move BitwardenTextField to BitwardenKit (#2010)
This commit is contained in:
parent
c9cb4f2588
commit
fbbb2a2811
@ -1,263 +0,0 @@
|
||||
import BitwardenResources
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - BitwardenTextField
|
||||
|
||||
/// The standard text field used within this application. The text field can be
|
||||
/// configured to act as a password field with visibility toggling, and supports
|
||||
/// displaying additional content on the trailing edge of the text field.
|
||||
///
|
||||
struct BitwardenTextField<TrailingContent: View>: View {
|
||||
// MARK: Private Properties
|
||||
|
||||
/// A flag indicating if this field is currently focused.
|
||||
private var isFocused: Bool { isTextFieldFocused || isSecureFieldFocused }
|
||||
|
||||
/// A flag indicating if the secure field is currently focused.
|
||||
@FocusState private var isSecureFieldFocused
|
||||
|
||||
/// A flag indicating if the text field is currently focused.
|
||||
@FocusState private var isTextFieldFocused
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
/// The accessibility identifier for the text field.
|
||||
let accessibilityIdentifier: String?
|
||||
|
||||
/// Whether the password can be viewed (only applies if a password exists in the field).
|
||||
let canViewPassword: Bool
|
||||
|
||||
/// The footer text displayed below the text field.
|
||||
let footer: String?
|
||||
|
||||
/// Whether a password in this text field is visible.
|
||||
let isPasswordVisible: Binding<Bool>?
|
||||
|
||||
/// The accessibility identifier for the button to toggle password visibility.
|
||||
let passwordVisibilityAccessibilityId: String?
|
||||
|
||||
/// The placeholder that is displayed in the textfield.
|
||||
let placeholder: String
|
||||
|
||||
/// The text entered into the text field.
|
||||
@Binding var text: String
|
||||
|
||||
/// The title of the text field.
|
||||
let title: String?
|
||||
|
||||
/// Optional content view that is displayed on the trailing edge of the menu value.
|
||||
let trailingContent: TrailingContent?
|
||||
|
||||
// MARK: View
|
||||
|
||||
var body: some View {
|
||||
let isTrailingContentShown = isPasswordVisible != nil || trailingContent != nil
|
||||
if isTrailingContentShown {
|
||||
BitwardenField(title: title, footer: footer) {
|
||||
textField
|
||||
} accessoryContent: {
|
||||
if let isPasswordVisible, canViewPassword {
|
||||
AccessoryButton(
|
||||
asset: isPasswordVisible.wrappedValue
|
||||
? SharedAsset.Icons.eyeSlash24
|
||||
: SharedAsset.Icons.eye24,
|
||||
accessibilityLabel: isPasswordVisible.wrappedValue
|
||||
? Localizations.passwordIsVisibleTapToHide
|
||||
: Localizations.passwordIsNotVisibleTapToShow
|
||||
) {
|
||||
isPasswordVisible.wrappedValue.toggle()
|
||||
}
|
||||
.accessibilityIdentifier(passwordVisibilityAccessibilityId ?? "TextVisibilityToggle")
|
||||
|
||||
if let trailingContent {
|
||||
trailingContent
|
||||
}
|
||||
} else if let trailingContent {
|
||||
trailingContent
|
||||
}
|
||||
}
|
||||
} else {
|
||||
BitwardenField(title: title, footer: footer) {
|
||||
textField
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Private views
|
||||
|
||||
/// The text field.
|
||||
private var textField: some View {
|
||||
HStack(spacing: 8) {
|
||||
ZStack {
|
||||
let isPassword = isPasswordVisible != nil
|
||||
let isPasswordVisible = isPasswordVisible?.wrappedValue ?? false
|
||||
|
||||
TextField(placeholder, text: $text)
|
||||
.focused($isTextFieldFocused)
|
||||
.styleGuide(isPassword ? .bodyMonospaced : .body, includeLineSpacing: false)
|
||||
.hidden(!isPasswordVisible && isPassword)
|
||||
.id(title)
|
||||
if isPassword, !isPasswordVisible {
|
||||
SecureField(placeholder, text: $text)
|
||||
.focused($isSecureFieldFocused)
|
||||
.styleGuide(.bodyMonospaced, includeLineSpacing: false)
|
||||
.id(title)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, minHeight: 28)
|
||||
.accessibilityIdentifier(accessibilityIdentifier ?? "BitwardenTextField")
|
||||
|
||||
Button {
|
||||
text = ""
|
||||
} label: {
|
||||
SharedAsset.Icons.circleX16.swiftUIImage
|
||||
.foregroundColor(Asset.Colors.primaryBitwarden.swiftUIColor)
|
||||
.frame(width: 14, height: 14)
|
||||
}
|
||||
.padding(.vertical, 5)
|
||||
.hidden(text.isEmpty || !isFocused)
|
||||
}
|
||||
.tint(Asset.Colors.primaryBitwarden.swiftUIColor)
|
||||
}
|
||||
|
||||
// MARK: Initialization
|
||||
|
||||
/// Initializes a new `BitwardenTextField`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - title: The title of the text field.
|
||||
/// - footer: The footer text displayed below the text field.
|
||||
/// - text: The text entered into the text field.
|
||||
/// - accessibilityIdentifier: The accessibility identifier for the text field.
|
||||
/// - passwordVisibilityAccessibilityId: The accessibility ID for the button to toggle password visibility.
|
||||
/// - canViewPassword: Whether the password can be viewed.
|
||||
/// - isPasswordVisible: Whether the password is visible.
|
||||
/// - placeholder: An optional placeholder to display in the text field.
|
||||
///
|
||||
init(
|
||||
title: String? = nil,
|
||||
text: Binding<String>,
|
||||
footer: String? = nil,
|
||||
accessibilityIdentifier: String? = nil,
|
||||
passwordVisibilityAccessibilityId: String? = nil,
|
||||
canViewPassword: Bool = true,
|
||||
isPasswordVisible: Binding<Bool>? = nil,
|
||||
placeholder: String? = nil,
|
||||
@ViewBuilder trailingContent: () -> TrailingContent
|
||||
) {
|
||||
self.accessibilityIdentifier = accessibilityIdentifier
|
||||
self.isPasswordVisible = isPasswordVisible
|
||||
self.footer = footer
|
||||
self.canViewPassword = canViewPassword
|
||||
self.passwordVisibilityAccessibilityId = passwordVisibilityAccessibilityId
|
||||
self.placeholder = placeholder ?? ""
|
||||
_text = text
|
||||
self.title = title
|
||||
self.trailingContent = trailingContent()
|
||||
}
|
||||
}
|
||||
|
||||
extension BitwardenTextField where TrailingContent == EmptyView {
|
||||
/// Initializes a new `BitwardenTextField`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - title: The title of the text field.
|
||||
/// - footer: The footer text displayed below the text field.
|
||||
/// - text: The text entered into the text field.
|
||||
/// - accessibilityIdentifier: The accessibility identifier for the text field.
|
||||
/// - passwordVisibilityAccessibilityId: The accessibility ID for the button to toggle password visibility.
|
||||
/// - canViewPassword: Whether the password can be viewed.
|
||||
/// - isPasswordVisible: Whether the password is visible.
|
||||
/// - placeholder: An optional placeholder to display in the text field.
|
||||
///
|
||||
init(
|
||||
title: String? = nil,
|
||||
text: Binding<String>,
|
||||
footer: String? = nil,
|
||||
accessibilityIdentifier: String? = nil,
|
||||
passwordVisibilityAccessibilityId: String? = nil,
|
||||
canViewPassword: Bool = true,
|
||||
isPasswordVisible: Binding<Bool>? = nil,
|
||||
placeholder: String? = nil
|
||||
) {
|
||||
self.accessibilityIdentifier = accessibilityIdentifier
|
||||
self.canViewPassword = canViewPassword
|
||||
self.footer = footer
|
||||
self.isPasswordVisible = isPasswordVisible
|
||||
self.passwordVisibilityAccessibilityId = passwordVisibilityAccessibilityId
|
||||
self.placeholder = placeholder ?? ""
|
||||
_text = text
|
||||
self.title = title
|
||||
trailingContent = nil
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Previews
|
||||
|
||||
#if DEBUG
|
||||
#Preview("No buttons") {
|
||||
VStack {
|
||||
BitwardenTextField(
|
||||
title: "Title",
|
||||
text: .constant("Text field text")
|
||||
)
|
||||
.textContentType(.emailAddress)
|
||||
.padding()
|
||||
}
|
||||
.background(Color(.systemGroupedBackground))
|
||||
}
|
||||
|
||||
#Preview("Password button") {
|
||||
VStack {
|
||||
BitwardenTextField(
|
||||
title: "Title",
|
||||
text: .constant("Text field text"),
|
||||
isPasswordVisible: .constant(false)
|
||||
)
|
||||
.textContentType(.password)
|
||||
.padding()
|
||||
}
|
||||
.background(Color(.systemGroupedBackground))
|
||||
}
|
||||
|
||||
#Preview("Password revealed") {
|
||||
VStack {
|
||||
BitwardenTextField(
|
||||
title: "Title",
|
||||
text: .constant("Password"),
|
||||
isPasswordVisible: .constant(true)
|
||||
)
|
||||
.textContentType(.password)
|
||||
.padding()
|
||||
}
|
||||
.background(Color(.systemGroupedBackground))
|
||||
}
|
||||
|
||||
#Preview("Additional buttons") {
|
||||
VStack {
|
||||
BitwardenTextField(
|
||||
title: "Title",
|
||||
text: .constant("Text field text")
|
||||
) {
|
||||
AccessoryButton(asset: SharedAsset.Icons.cog16, accessibilityLabel: "") {}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.background(Color(.systemGroupedBackground))
|
||||
}
|
||||
|
||||
#Preview("Footer text") {
|
||||
VStack {
|
||||
BitwardenTextField(
|
||||
title: "Title",
|
||||
text: .constant("Text field text"),
|
||||
footer: "Text field footer",
|
||||
isPasswordVisible: .constant(false)
|
||||
) {
|
||||
AccessoryButton(asset: SharedAsset.Icons.cog16, accessibilityLabel: "") {}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.background(Color(.systemGroupedBackground))
|
||||
}
|
||||
#endif
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - FormTextField
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
import SwiftUI
|
||||
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
import SwiftUI
|
||||
|
||||
/// A view that displays a button for use as an accessory to a field.
|
||||
///
|
||||
struct AccessoryButton: View {
|
||||
public struct AccessoryButton: View {
|
||||
// MARK: Types
|
||||
|
||||
/// A type that wraps a synchrounous or asynchrounous block that is executed by this button.
|
||||
@ -31,7 +30,7 @@ struct AccessoryButton: View {
|
||||
/// The image to display in the button.
|
||||
var asset: SharedImageAsset
|
||||
|
||||
var body: some View {
|
||||
public var body: some View {
|
||||
switch action {
|
||||
case let .async(action):
|
||||
AsyncButton(action: action) {
|
||||
@ -64,7 +63,7 @@ struct AccessoryButton: View {
|
||||
/// - accessibilityLabel: The accessibility label of the button.
|
||||
/// - action: The action to perform when the user triggers the button.
|
||||
///
|
||||
init(asset: SharedImageAsset,
|
||||
public init(asset: SharedImageAsset,
|
||||
accessibilityLabel: String,
|
||||
accessibilityIdentifier: String = "",
|
||||
action: @escaping () -> Void) {
|
||||
@ -83,7 +82,7 @@ struct AccessoryButton: View {
|
||||
/// - accessibilityIdentifier: The accessibility identifier of the button.
|
||||
/// - action: The action to perform when the user triggers the button.
|
||||
///
|
||||
init(asset: SharedImageAsset,
|
||||
public init(asset: SharedImageAsset,
|
||||
accessibilityLabel: String,
|
||||
accessibilityIdentifier: String = "",
|
||||
action: @escaping () async -> Void) {
|
||||
@ -8,7 +8,7 @@ import SwiftUI
|
||||
/// contains a value. At that point, the text label will float up above the input field. This is
|
||||
/// primarily meant to wrap a text field or view.
|
||||
///
|
||||
struct BitwardenFloatingTextLabel<Content: View, TrailingContent: View>: View {
|
||||
public struct BitwardenFloatingTextLabel<Content: View, TrailingContent: View>: View {
|
||||
// MARK: Properties
|
||||
|
||||
/// The primary content containing the text input field for the label.
|
||||
@ -31,7 +31,7 @@ struct BitwardenFloatingTextLabel<Content: View, TrailingContent: View>: View {
|
||||
|
||||
// MARK: View
|
||||
|
||||
var body: some View {
|
||||
public var body: some View {
|
||||
HStack(spacing: 8) {
|
||||
ZStack(alignment: showPlaceholder ? .leading : .topLeading) {
|
||||
// The placeholder and title text which is vertically centered in the view when the
|
||||
@ -77,7 +77,7 @@ struct BitwardenFloatingTextLabel<Content: View, TrailingContent: View>: View {
|
||||
/// - trailingContent: Optional trailing content to display on the trailing edge of the label
|
||||
/// and text input field.
|
||||
///
|
||||
init(
|
||||
public init(
|
||||
title: String?,
|
||||
isTextFieldDisabled: Bool = false,
|
||||
showPlaceholder: Bool,
|
||||
@ -100,7 +100,7 @@ struct BitwardenFloatingTextLabel<Content: View, TrailingContent: View>: View {
|
||||
/// the field.
|
||||
/// - content: The primary content containing the text input field for the label.
|
||||
///
|
||||
init(
|
||||
public init(
|
||||
title: String?,
|
||||
isTextFieldDisabled: Bool = false,
|
||||
showPlaceholder: Bool,
|
||||
@ -11,7 +11,7 @@ import SwiftUIIntrospect
|
||||
/// displaying additional content on the trailing edge of the text field.
|
||||
///
|
||||
@MainActor
|
||||
struct BitwardenTextField<FooterContent: View, TrailingContent: View>: View {
|
||||
public struct BitwardenTextField<FooterContent: View, TrailingContent: View>: View {
|
||||
// MARK: Private Properties
|
||||
|
||||
/// A value indicating whether the textfield is currently enabled or disabled.
|
||||
@ -68,7 +68,7 @@ struct BitwardenTextField<FooterContent: View, TrailingContent: View>: View {
|
||||
|
||||
// MARK: View
|
||||
|
||||
var body: some View {
|
||||
public var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
contentView
|
||||
|
||||
@ -215,7 +215,7 @@ struct BitwardenTextField<FooterContent: View, TrailingContent: View>: View {
|
||||
/// - isTextFieldDisabled: Whether the text field is disabled.
|
||||
/// - trailingContent: Optional content view that is displayed on the trailing edge of the field.
|
||||
///
|
||||
init(
|
||||
public init(
|
||||
title: String? = nil,
|
||||
text: Binding<String>,
|
||||
footer: String? = nil,
|
||||
@ -254,7 +254,7 @@ struct BitwardenTextField<FooterContent: View, TrailingContent: View>: View {
|
||||
/// - trailingContent: Optional content view that is displayed on the trailing edge of the field.
|
||||
/// - footerContent: The (optional) footer content to display underneath the field.
|
||||
///
|
||||
init(
|
||||
public init(
|
||||
title: String? = nil,
|
||||
text: Binding<String>,
|
||||
accessibilityIdentifier: String? = nil,
|
||||
@ -280,7 +280,7 @@ struct BitwardenTextField<FooterContent: View, TrailingContent: View>: View {
|
||||
}
|
||||
}
|
||||
|
||||
extension BitwardenTextField where TrailingContent == EmptyView {
|
||||
public extension BitwardenTextField where TrailingContent == EmptyView {
|
||||
/// Initializes a new `BitwardenTextField`.
|
||||
///
|
||||
/// - Parameters:
|
||||
@ -321,7 +321,7 @@ extension BitwardenTextField where TrailingContent == EmptyView {
|
||||
}
|
||||
}
|
||||
|
||||
extension BitwardenTextField where FooterContent == EmptyView, TrailingContent == EmptyView {
|
||||
public extension BitwardenTextField where FooterContent == EmptyView, TrailingContent == EmptyView {
|
||||
/// Initializes a new `BitwardenTextField`.
|
||||
///
|
||||
/// - Parameters:
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
import SwiftUI
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
import SwiftUI
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
import SwiftUI
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
import SwiftUI
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
import SwiftUI
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
import SwiftUI
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - FormTextField
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
import SwiftUI
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
import BitwardenSdk
|
||||
import SwiftUI
|
||||
|
||||
@ -20,7 +20,7 @@ struct BitwardenTextFieldType: BaseViewType {
|
||||
static var typePrefix: String = "BitwardenTextField"
|
||||
|
||||
static var namespacedPrefixes: [String] = [
|
||||
"AuthenticatorShared.BitwardenTextField",
|
||||
"BitwardenKit.BitwardenTextField",
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@ -56,7 +56,7 @@ struct BitwardenTextFieldType: BaseViewType {
|
||||
static var typePrefix: String = "BitwardenTextField"
|
||||
|
||||
static var namespacedPrefixes: [String] = [
|
||||
"BitwardenShared.BitwardenTextField",
|
||||
"BitwardenKit.BitwardenTextField",
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@ -125,6 +125,7 @@ targets:
|
||||
- "**/GoogleService-Info.*.plist"
|
||||
buildPhase: none
|
||||
dependencies:
|
||||
- package: SwiftUIIntrospect
|
||||
- target: BitwardenResources
|
||||
- target: Networking
|
||||
BitwardenKitMocks:
|
||||
|
||||
@ -14,10 +14,6 @@ options:
|
||||
settings:
|
||||
MARKETING_VERSION: 2024.6.0 # Bump this for a new version update.
|
||||
CURRENT_PROJECT_VERSION: 1
|
||||
packages:
|
||||
SwiftUIIntrospect:
|
||||
url: https://github.com/siteline/SwiftUI-Introspect
|
||||
exactVersion: 1.3.0
|
||||
projectReferences:
|
||||
BitwardenKit:
|
||||
path: BitwardenKit.xcodeproj
|
||||
@ -350,7 +346,6 @@ targets:
|
||||
buildPhase: none
|
||||
dependencies:
|
||||
- package: BitwardenSdk
|
||||
- package: SwiftUIIntrospect
|
||||
- target: BitwardenKit/AuthenticatorBridgeKit
|
||||
- target: BitwardenKit/BitwardenKit
|
||||
- target: BitwardenKit/BitwardenResources
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user