Fix password visibility toggle position in LoginView
Add SecureIconTextField component that includes the eye toggle button inside the text field (matching RegisterView's OrganicSecureField). Update LoginView to use SecureIconTextField instead of IconTextField with an external button, ensuring consistent UI across auth screens. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -97,28 +97,17 @@ struct LoginView: View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
FieldLabel(text: L10n.Auth.loginPasswordLabel)
|
||||
|
||||
HStack(spacing: 12) {
|
||||
IconTextField(
|
||||
icon: "lock.fill",
|
||||
placeholder: L10n.Auth.enterPassword,
|
||||
text: $viewModel.password,
|
||||
isSecure: !isPasswordVisible,
|
||||
textContentType: .password,
|
||||
onSubmit: { viewModel.login() }
|
||||
)
|
||||
.onChange(of: viewModel.password) { _, _ in
|
||||
viewModel.clearError()
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityIdentifiers.Authentication.passwordField)
|
||||
|
||||
Button(action: {
|
||||
isPasswordVisible.toggle()
|
||||
}) {
|
||||
Image(systemName: isPasswordVisible ? "eye.slash.fill" : "eye.fill")
|
||||
.font(.system(size: 16, weight: .medium))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityIdentifiers.Authentication.passwordVisibilityToggle)
|
||||
SecureIconTextField(
|
||||
icon: "lock.fill",
|
||||
placeholder: L10n.Auth.enterPassword,
|
||||
text: $viewModel.password,
|
||||
isVisible: $isPasswordVisible,
|
||||
textContentType: .password,
|
||||
onSubmit: { viewModel.login() },
|
||||
accessibilityId: AccessibilityIdentifiers.Authentication.passwordField
|
||||
)
|
||||
.onChange(of: viewModel.password) { _, _ in
|
||||
viewModel.clearError()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -288,6 +288,62 @@ struct IconTextField: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Secure TextField with Icon and Visibility Toggle
|
||||
|
||||
struct SecureIconTextField: View {
|
||||
let icon: String
|
||||
let placeholder: String
|
||||
@Binding var text: String
|
||||
@Binding var isVisible: Bool
|
||||
var textContentType: UITextContentType? = nil
|
||||
var onSubmit: (() -> Void)? = nil
|
||||
var accessibilityId: String? = nil
|
||||
|
||||
@FocusState private var isFocused: Bool
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 12) {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(Color.appPrimary.opacity(0.1))
|
||||
.frame(width: 32, height: 32)
|
||||
Image(systemName: icon)
|
||||
.font(.system(size: 14, weight: .medium))
|
||||
.foregroundColor(Color.appPrimary)
|
||||
}
|
||||
|
||||
Group {
|
||||
if isVisible {
|
||||
TextField(placeholder, text: $text)
|
||||
.accessibilityIdentifier(accessibilityId ?? "")
|
||||
} else {
|
||||
SecureField(placeholder, text: $text)
|
||||
.accessibilityIdentifier(accessibilityId ?? "")
|
||||
}
|
||||
}
|
||||
.font(.system(size: 16, weight: .medium))
|
||||
.textContentType(textContentType)
|
||||
.focused($isFocused)
|
||||
.submitLabel(.go)
|
||||
.onSubmit { onSubmit?() }
|
||||
|
||||
Button(action: { isVisible.toggle() }) {
|
||||
Image(systemName: isVisible ? "eye.slash.fill" : "eye.fill")
|
||||
.font(.system(size: 16, weight: .medium))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
}
|
||||
.padding(16)
|
||||
.background(Color.appBackgroundPrimary.opacity(0.5))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 16, style: .continuous)
|
||||
.stroke(isFocused ? Color.appPrimary : Color.appTextSecondary.opacity(0.15), lineWidth: 1.5)
|
||||
)
|
||||
.animation(.easeInOut(duration: 0.2), value: isFocused)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Field Label
|
||||
|
||||
struct FieldLabel: View {
|
||||
|
||||
Reference in New Issue
Block a user