Skip to content

Commit c85406c

Browse files
authored
Merge pull request #8006 from woocommerce/feat/7891-show-hide-password-input
Account creation: design polishes - add a button to show/hide text field input for a secure field
2 parents e99b66b + a09e7e3 commit c85406c

File tree

2 files changed

+96
-11
lines changed

2 files changed

+96
-11
lines changed

WooCommerce/Classes/ViewRelated/Authentication/AccountCreationFormFieldView.swift

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,56 @@ struct AccountCreationFormFieldViewModel {
2222
struct AccountCreationFormFieldView: View {
2323
private let viewModel: AccountCreationFormFieldViewModel
2424

25+
/// Whether the text field is *shown* as secure.
26+
/// When the field is secure, there is a button to show/hide the text field input.
27+
@State private var showsSecureInput: Bool = true
28+
29+
// Tracks the scale of the view due to accessibility changes.
30+
@ScaledMetric private var scale: CGFloat = 1.0
31+
2532
init(viewModel: AccountCreationFormFieldViewModel) {
2633
self.viewModel = viewModel
34+
self.showsSecureInput = viewModel.isSecure
2735
}
2836

2937
var body: some View {
3038
VStack(alignment: .leading, spacing: Layout.verticalSpacing) {
3139
Text(viewModel.header)
3240
.subheadlineStyle()
3341
if viewModel.isSecure {
34-
SecureField(viewModel.placeholder, text: viewModel.text)
35-
.textFieldStyle(RoundedBorderTextFieldStyle(focused: viewModel.isFocused))
42+
ZStack(alignment: .trailing) {
43+
// Text field based on the `isTextFieldSecure` state.
44+
Group {
45+
if showsSecureInput {
46+
SecureField(viewModel.placeholder, text: viewModel.text)
47+
} else {
48+
TextField(viewModel.placeholder, text: viewModel.text)
49+
}
50+
}
51+
.font(.body)
52+
.textFieldStyle(RoundedBorderTextFieldStyle(
53+
focused: viewModel.isFocused,
54+
// Custom insets to leave trailing space for the reveal button.
55+
insets: .init(top: RoundedBorderTextFieldStyle.Defaults.insets.top,
56+
leading: RoundedBorderTextFieldStyle.Defaults.insets.leading,
57+
bottom: RoundedBorderTextFieldStyle.Defaults.insets.bottom,
58+
trailing: Layout.secureFieldRevealButtonHorizontalPadding * 2 + Layout.secureFieldRevealButtonDimension * scale),
59+
height: 44 * scale
60+
))
3661
.keyboardType(viewModel.keyboardType)
62+
63+
// Button to show/hide the text field content.
64+
Button(action: {
65+
showsSecureInput.toggle()
66+
}) {
67+
Image(systemName: showsSecureInput ? "eye.slash" : "eye")
68+
.accentColor(Color(.textSubtle))
69+
.frame(width: Layout.secureFieldRevealButtonDimension * scale,
70+
height: Layout.secureFieldRevealButtonDimension * scale)
71+
.padding(.leading, Layout.secureFieldRevealButtonHorizontalPadding)
72+
.padding(.trailing, Layout.secureFieldRevealButtonHorizontalPadding)
73+
}
74+
}
3775
} else {
3876
TextField(viewModel.placeholder, text: viewModel.text)
3977
.textFieldStyle(RoundedBorderTextFieldStyle(focused: viewModel.isFocused))
@@ -50,6 +88,8 @@ struct AccountCreationFormFieldView: View {
5088
private extension AccountCreationFormFieldView {
5189
enum Layout {
5290
static let verticalSpacing: CGFloat = 8
91+
static let secureFieldRevealButtonHorizontalPadding: CGFloat = 16
92+
static let secureFieldRevealButtonDimension: CGFloat = 18
5393
}
5494
}
5595

@@ -62,12 +102,24 @@ struct AccountCreationFormField_Previews: PreviewProvider {
62102
isSecure: false,
63103
errorMessage: nil,
64104
isFocused: true))
65-
AccountCreationFormFieldView(viewModel: .init(header: "Choose a password",
66-
placeholder: "Password",
67-
keyboardType: .default,
68-
text: .constant("w"),
69-
isSecure: true,
70-
errorMessage: "Too simple",
71-
isFocused: false))
105+
VStack {
106+
AccountCreationFormFieldView(viewModel: .init(header: "Choose a password",
107+
placeholder: "Password",
108+
keyboardType: .default,
109+
text: .constant("wwwwwwwwwwwwwwwwwwwwwwww"),
110+
isSecure: true,
111+
errorMessage: "Too simple",
112+
isFocused: false))
113+
.environment(\.sizeCategory, .medium)
114+
115+
AccountCreationFormFieldView(viewModel: .init(header: "Choose a password",
116+
placeholder: "Password",
117+
keyboardType: .default,
118+
text: .constant("wwwwwwwwwwwwwwwwwwwwwwww"),
119+
isSecure: true,
120+
errorMessage: "Too simple",
121+
isFocused: false))
122+
.environment(\.sizeCategory, .extraExtraExtraLarge)
123+
}
72124
}
73125
}

WooCommerce/Classes/ViewRelated/ReusableViews/SwiftUI Components/TextFieldStyles.swift

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,45 @@ struct RoundedBorderTextFieldStyle: TextFieldStyle {
55
private let focused: Bool
66
private let focusedBorderColor: Color
77
private let unfocusedBorderColor: Color
8+
private let insets: EdgeInsets
9+
private let height: CGFloat?
810

11+
/// - Parameters:
12+
/// - focused: Whether the field is focused or not.
13+
/// - focusedBorderColor: The border color when the field is focused.
14+
/// - unfocusedBorderColor: The border color when the field is not focused.
15+
/// - insets: The insets between the background border and the text input.
16+
/// - height: An optional fixed height for the field.
917
init(focused: Bool,
1018
focusedBorderColor: Color = Defaults.focusedBorderColor,
11-
unfocusedBorderColor: Color = Defaults.unfocusedBorderColor) {
19+
unfocusedBorderColor: Color = Defaults.unfocusedBorderColor,
20+
insets: EdgeInsets = Defaults.insets,
21+
height: CGFloat? = nil) {
1222
self.focused = focused
1323
self.focusedBorderColor = focusedBorderColor
1424
self.unfocusedBorderColor = unfocusedBorderColor
25+
self.insets = insets
26+
self.height = height
1527
}
1628

1729
func _body(configuration: TextField<Self._Label>) -> some View {
1830
configuration
19-
.padding(10)
31+
.padding(insets)
2032
.background(
2133
RoundedRectangle(cornerRadius: 8, style: .continuous)
2234
.stroke(focused ? focusedBorderColor: unfocusedBorderColor,
2335
lineWidth: focused ? 2: 1)
36+
.frame(height: height)
2437
)
38+
.frame(height: height)
2539
}
2640
}
2741

2842
extension RoundedBorderTextFieldStyle {
2943
enum Defaults {
3044
static let focusedBorderColor: Color = .init(uiColor: .brand)
3145
static let unfocusedBorderColor: Color = .gray
46+
static let insets = EdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
3247
}
3348
}
3449

@@ -41,8 +56,26 @@ struct TextFieldStyles_Previews: PreviewProvider {
4156
.textFieldStyle(RoundedBorderTextFieldStyle(focused: false))
4257
TextField("placeholder", text: .constant("focused with a different color"))
4358
.textFieldStyle(RoundedBorderTextFieldStyle(focused: true, focusedBorderColor: .orange))
59+
.environment(\.sizeCategory, .extraExtraExtraLarge)
4460
TextField("placeholder", text: .constant("unfocused with a different color"))
4561
.textFieldStyle(RoundedBorderTextFieldStyle(focused: false, unfocusedBorderColor: .cyan))
62+
TextField("placeholder", text: .constant("custom insets"))
63+
.textFieldStyle(RoundedBorderTextFieldStyle(focused: false, insets: .init(top: 20, leading: 0, bottom: 10, trailing: 50)))
64+
.frame(width: 150)
65+
HStack {
66+
TextField("placeholder", text: .constant("text field"))
67+
.textFieldStyle(RoundedBorderTextFieldStyle(focused: true))
68+
SecureField("placeholder", text: .constant("secure"))
69+
.textFieldStyle(RoundedBorderTextFieldStyle(focused: true))
70+
}
71+
.environment(\.sizeCategory, .extraExtraExtraLarge)
72+
HStack {
73+
TextField("placeholder", text: .constant("text field"))
74+
.textFieldStyle(RoundedBorderTextFieldStyle(focused: true, height: 100))
75+
SecureField("placeholder", text: .constant("secure"))
76+
.textFieldStyle(RoundedBorderTextFieldStyle(focused: true))
77+
}
78+
.environment(\.sizeCategory, .extraExtraExtraLarge)
4679
}
4780
.preferredColorScheme(.dark)
4881
}

0 commit comments

Comments
 (0)