feat(01-03): create reusable auth components

- AuthButton widget with loading states and variants (primary, secondary, outline)
- AuthForm widget with email/password fields and real-time validation
- Password visibility toggle functionality
- Responsive design with proper accessibility labels
- Form validation for email format and password strength

Components ready for use in login/signup pages.
This commit is contained in:
Dani B
2026-01-28 10:05:05 -05:00
parent 752443d27b
commit 122b45e04d
2 changed files with 422 additions and 0 deletions

View File

@@ -0,0 +1,120 @@
import 'package:flutter/material.dart';
/// Custom authentication button with loading states and variants
class AuthButton extends StatelessWidget {
final String text;
final VoidCallback? onPressed;
final bool isLoading;
final AuthButtonVariant variant;
final bool fullWidth;
final Widget? icon;
const AuthButton({
super.key,
required this.text,
this.onPressed,
this.isLoading = false,
this.variant = AuthButtonVariant.primary,
this.fullWidth = true,
this.icon,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colors = _getColors(theme);
return SizedBox(
width: fullWidth ? double.infinity : null,
height: 48,
child: ElevatedButton(
onPressed: isLoading ? null : onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: colors.background,
foregroundColor: colors.foreground,
disabledBackgroundColor: colors.disabledBackground,
disabledForegroundColor: colors.disabledForeground,
elevation: variant == AuthButtonVariant.primary ? 2 : 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: variant == AuthButtonVariant.outline
? BorderSide(color: colors.background)
: BorderSide.none,
),
),
child: isLoading
? SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(colors.foreground),
),
)
: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (icon != null) ...[
icon!,
const SizedBox(width: 8),
],
Text(
text,
style: theme.textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.w600,
color: colors.foreground,
),
),
],
),
),
);
}
_ButtonColors _getColors(ThemeData theme) {
switch (variant) {
case AuthButtonVariant.primary:
return _ButtonColors(
background: theme.colorScheme.primary,
foreground: theme.colorScheme.onPrimary,
disabledBackground: theme.colorScheme.primary.withOpacity(0.12),
disabledForeground: theme.colorScheme.onPrimary.withOpacity(0.38),
);
case AuthButtonVariant.secondary:
return _ButtonColors(
background: theme.colorScheme.secondary,
foreground: theme.colorScheme.onSecondary,
disabledBackground: theme.colorScheme.secondary.withOpacity(0.12),
disabledForeground: theme.colorScheme.onSecondary.withOpacity(0.38),
);
case AuthButtonVariant.outline:
return _ButtonColors(
background: Colors.transparent,
foreground: theme.colorScheme.primary,
disabledBackground: Colors.transparent,
disabledForeground: theme.colorScheme.primary.withOpacity(0.38),
);
}
}
}
enum AuthButtonVariant {
primary,
secondary,
outline,
}
class _ButtonColors {
final Color background;
final Color foreground;
final Color disabledBackground;
final Color disabledForeground;
const _ButtonColors({
required this.background,
required this.foreground,
required this.disabledBackground,
required this.disabledForeground,
});
}