// SecureInput.swift // iOS UI Components - Form Components // // Secure password input with reveal toggle import SwiftUI import LilithDesignTokens /// Secure text input for passwords /// /// Password field with show/hide toggle. /// /// Example: /// ```swift /// @State private var password = "" /// /// SecureInput("Password", text: $password) /// ``` public struct SecureInput: View { // MARK: - Properties private let label: String @Binding private var text: String private let placeholder: String private let errorMessage: String? private let helperText: String? private let isDisabled: Bool private let onCommit: (() -> Void)? // MARK: - State @State private var isSecure = true @FocusState private var isFocused: Bool // MARK: - Initialization public init( _ label: String, text: Binding, placeholder: String = "", errorMessage: String? = nil, helperText: String? = nil, isDisabled: Bool = false, onCommit: (() -> Void)? = nil ) { self.label = label self._text = text self.placeholder = placeholder self.errorMessage = errorMessage self.helperText = helperText self.isDisabled = isDisabled self.onCommit = onCommit } // MARK: - Body public var body: some View { VStack(alignment: .leading, spacing: AppSpacing.xs) { // Label Text(label) .font(AppTypography.label(weight: .medium)) .foregroundColor(labelColor) // Input container HStack(spacing: AppSpacing.sm) { Image(systemName: "lock") .font(.system(size: AppTypography.FontSize.base)) .foregroundColor(iconColor) if isSecure { SecureField(placeholder, text: $text) .font(AppTypography.body()) .foregroundColor(AppColors.textPrimary) .disabled(isDisabled) .focused($isFocused) .onSubmit { onCommit?() } } else { TextField(placeholder, text: $text) .font(AppTypography.body()) .foregroundColor(AppColors.textPrimary) .disabled(isDisabled) .focused($isFocused) .onSubmit { onCommit?() } } // Show/hide toggle Button(action: { isSecure.toggle() }) { Image(systemName: isSecure ? "eye" : "eye.slash") .font(.system(size: AppTypography.FontSize.base)) .foregroundColor(AppColors.textSecondary) } } .padding(.horizontal, AppSpacing.md) .padding(.vertical, AppSpacing.sm) .background(backgroundColor) .overlay( RoundedRectangle(cornerRadius: AppRadius.input) .stroke(borderColor, lineWidth: isFocused ? 2 : 1) ) .clipShape(RoundedRectangle(cornerRadius: AppRadius.input)) // Helper or error text if let errorMessage = errorMessage { Text(errorMessage) .font(AppTypography.caption()) .foregroundColor(AppColors.Semantic.error) } else if let helperText = helperText { Text(helperText) .font(AppTypography.caption()) .foregroundColor(AppColors.textSecondary) } } .animation(AppAnimations.fast, value: isFocused) .animation(AppAnimations.fast, value: errorMessage) } // MARK: - Computed Properties private var labelColor: Color { if isDisabled { return AppColors.textTertiary } if errorMessage != nil { return AppColors.Semantic.error } if isFocused { return AppColors.primary } return AppColors.textSecondary } private var borderColor: Color { if isDisabled { return AppColors.Gray.gray700 } if errorMessage != nil { return AppColors.Semantic.error } if isFocused { return AppColors.primary } return AppColors.border } private var backgroundColor: Color { isDisabled ? AppColors.Gray.gray800 : AppColors.surface } private var iconColor: Color { if isDisabled { return AppColors.textTertiary } if isFocused { return AppColors.primary } return AppColors.textSecondary } } // MARK: - Preview Provider #if DEBUG struct SecureInput_Previews: PreviewProvider { static var previews: some View { VStack(spacing: AppSpacing.xl) { SecureInput("Password", text: .constant("")) SecureInput("Password", text: .constant("secret123")) SecureInput( "Password", text: .constant(""), helperText: "Minimum 8 characters" ) SecureInput( "Password", text: .constant("weak"), errorMessage: "Password is too weak" ) SecureInput( "Password", text: .constant("locked"), isDisabled: true ) } .padding() .background(AppColors.background) .previewDisplayName("Secure Input States") } } #endif