diff --git a/WooCommerce/Classes/ViewRelated/Authentication/AccountCreationFormFieldView.swift b/WooCommerce/Classes/ViewRelated/Authentication/AccountCreationFormFieldView.swift index dad68c9bd49..b35a6be738d 100644 --- a/WooCommerce/Classes/ViewRelated/Authentication/AccountCreationFormFieldView.swift +++ b/WooCommerce/Classes/ViewRelated/Authentication/AccountCreationFormFieldView.swift @@ -22,8 +22,16 @@ struct AccountCreationFormFieldViewModel { struct AccountCreationFormFieldView: View { private let viewModel: AccountCreationFormFieldViewModel + /// Whether the text field is *shown* as secure. + /// When the field is secure, there is a button to show/hide the text field input. + @State private var showsSecureInput: Bool = true + + // Tracks the scale of the view due to accessibility changes. + @ScaledMetric private var scale: CGFloat = 1.0 + init(viewModel: AccountCreationFormFieldViewModel) { self.viewModel = viewModel + self.showsSecureInput = viewModel.isSecure } var body: some View { @@ -31,9 +39,39 @@ struct AccountCreationFormFieldView: View { Text(viewModel.header) .subheadlineStyle() if viewModel.isSecure { - SecureField(viewModel.placeholder, text: viewModel.text) - .textFieldStyle(RoundedBorderTextFieldStyle(focused: viewModel.isFocused)) + ZStack(alignment: .trailing) { + // Text field based on the `isTextFieldSecure` state. + Group { + if showsSecureInput { + SecureField(viewModel.placeholder, text: viewModel.text) + } else { + TextField(viewModel.placeholder, text: viewModel.text) + } + } + .font(.body) + .textFieldStyle(RoundedBorderTextFieldStyle( + focused: viewModel.isFocused, + // Custom insets to leave trailing space for the reveal button. + insets: .init(top: RoundedBorderTextFieldStyle.Defaults.insets.top, + leading: RoundedBorderTextFieldStyle.Defaults.insets.leading, + bottom: RoundedBorderTextFieldStyle.Defaults.insets.bottom, + trailing: Layout.secureFieldRevealButtonHorizontalPadding * 2 + Layout.secureFieldRevealButtonDimension * scale), + height: 44 * scale + )) .keyboardType(viewModel.keyboardType) + + // Button to show/hide the text field content. + Button(action: { + showsSecureInput.toggle() + }) { + Image(systemName: showsSecureInput ? "eye.slash" : "eye") + .accentColor(Color(.textSubtle)) + .frame(width: Layout.secureFieldRevealButtonDimension * scale, + height: Layout.secureFieldRevealButtonDimension * scale) + .padding(.leading, Layout.secureFieldRevealButtonHorizontalPadding) + .padding(.trailing, Layout.secureFieldRevealButtonHorizontalPadding) + } + } } else { TextField(viewModel.placeholder, text: viewModel.text) .textFieldStyle(RoundedBorderTextFieldStyle(focused: viewModel.isFocused)) @@ -50,6 +88,8 @@ struct AccountCreationFormFieldView: View { private extension AccountCreationFormFieldView { enum Layout { static let verticalSpacing: CGFloat = 8 + static let secureFieldRevealButtonHorizontalPadding: CGFloat = 16 + static let secureFieldRevealButtonDimension: CGFloat = 18 } } @@ -62,12 +102,24 @@ struct AccountCreationFormField_Previews: PreviewProvider { isSecure: false, errorMessage: nil, isFocused: true)) - AccountCreationFormFieldView(viewModel: .init(header: "Choose a password", - placeholder: "Password", - keyboardType: .default, - text: .constant("w"), - isSecure: true, - errorMessage: "Too simple", - isFocused: false)) + VStack { + AccountCreationFormFieldView(viewModel: .init(header: "Choose a password", + placeholder: "Password", + keyboardType: .default, + text: .constant("wwwwwwwwwwwwwwwwwwwwwwww"), + isSecure: true, + errorMessage: "Too simple", + isFocused: false)) + .environment(\.sizeCategory, .medium) + + AccountCreationFormFieldView(viewModel: .init(header: "Choose a password", + placeholder: "Password", + keyboardType: .default, + text: .constant("wwwwwwwwwwwwwwwwwwwwwwww"), + isSecure: true, + errorMessage: "Too simple", + isFocused: false)) + .environment(\.sizeCategory, .extraExtraExtraLarge) + } } } diff --git a/WooCommerce/Classes/ViewRelated/ReusableViews/SwiftUI Components/TextFieldStyles.swift b/WooCommerce/Classes/ViewRelated/ReusableViews/SwiftUI Components/TextFieldStyles.swift index a6dd8a0895c..7ff2f8a6f4e 100644 --- a/WooCommerce/Classes/ViewRelated/ReusableViews/SwiftUI Components/TextFieldStyles.swift +++ b/WooCommerce/Classes/ViewRelated/ReusableViews/SwiftUI Components/TextFieldStyles.swift @@ -5,23 +5,37 @@ struct RoundedBorderTextFieldStyle: TextFieldStyle { private let focused: Bool private let focusedBorderColor: Color private let unfocusedBorderColor: Color + private let insets: EdgeInsets + private let height: CGFloat? + /// - Parameters: + /// - focused: Whether the field is focused or not. + /// - focusedBorderColor: The border color when the field is focused. + /// - unfocusedBorderColor: The border color when the field is not focused. + /// - insets: The insets between the background border and the text input. + /// - height: An optional fixed height for the field. init(focused: Bool, focusedBorderColor: Color = Defaults.focusedBorderColor, - unfocusedBorderColor: Color = Defaults.unfocusedBorderColor) { + unfocusedBorderColor: Color = Defaults.unfocusedBorderColor, + insets: EdgeInsets = Defaults.insets, + height: CGFloat? = nil) { self.focused = focused self.focusedBorderColor = focusedBorderColor self.unfocusedBorderColor = unfocusedBorderColor + self.insets = insets + self.height = height } func _body(configuration: TextField) -> some View { configuration - .padding(10) + .padding(insets) .background( RoundedRectangle(cornerRadius: 8, style: .continuous) .stroke(focused ? focusedBorderColor: unfocusedBorderColor, lineWidth: focused ? 2: 1) + .frame(height: height) ) + .frame(height: height) } } @@ -29,6 +43,7 @@ extension RoundedBorderTextFieldStyle { enum Defaults { static let focusedBorderColor: Color = .init(uiColor: .brand) static let unfocusedBorderColor: Color = .gray + static let insets = EdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10) } } @@ -41,8 +56,26 @@ struct TextFieldStyles_Previews: PreviewProvider { .textFieldStyle(RoundedBorderTextFieldStyle(focused: false)) TextField("placeholder", text: .constant("focused with a different color")) .textFieldStyle(RoundedBorderTextFieldStyle(focused: true, focusedBorderColor: .orange)) + .environment(\.sizeCategory, .extraExtraExtraLarge) TextField("placeholder", text: .constant("unfocused with a different color")) .textFieldStyle(RoundedBorderTextFieldStyle(focused: false, unfocusedBorderColor: .cyan)) + TextField("placeholder", text: .constant("custom insets")) + .textFieldStyle(RoundedBorderTextFieldStyle(focused: false, insets: .init(top: 20, leading: 0, bottom: 10, trailing: 50))) + .frame(width: 150) + HStack { + TextField("placeholder", text: .constant("text field")) + .textFieldStyle(RoundedBorderTextFieldStyle(focused: true)) + SecureField("placeholder", text: .constant("secure")) + .textFieldStyle(RoundedBorderTextFieldStyle(focused: true)) + } + .environment(\.sizeCategory, .extraExtraExtraLarge) + HStack { + TextField("placeholder", text: .constant("text field")) + .textFieldStyle(RoundedBorderTextFieldStyle(focused: true, height: 100)) + SecureField("placeholder", text: .constant("secure")) + .textFieldStyle(RoundedBorderTextFieldStyle(focused: true)) + } + .environment(\.sizeCategory, .extraExtraExtraLarge) } .preferredColorScheme(.dark) }