import 'package:flutter/material.dart'; /// Custom button widget with loading state and consistent styling /// /// This widget provides a reusable button component with: /// - Loading indicator support /// - Disabled state /// - Customizable colors, icons, and text /// - Consistent padding and styling /// /// Usage: /// ```dart /// CustomButton( /// text: 'Login', /// onPressed: _handleLogin, /// isLoading: _isLoading, /// ) /// /// CustomButton.outlined( /// text: 'Cancel', /// onPressed: _handleCancel, /// ) /// /// CustomButton.text( /// text: 'Skip', /// onPressed: _handleSkip, /// ) /// ``` class CustomButton extends StatelessWidget { /// Button text final String text; /// Callback when button is pressed final VoidCallback? onPressed; /// Whether the button is in loading state final bool isLoading; /// Optional icon to display before text final IconData? icon; /// Button style variant final ButtonStyle? style; /// Whether this is an outlined button final bool isOutlined; /// Whether this is a text button final bool isTextButton; /// Minimum button width (null for full width) final double? minWidth; /// Minimum button height final double? minHeight; /// Background color (only for elevated buttons) final Color? backgroundColor; /// Foreground/text color final Color? foregroundColor; /// Border color (only for outlined buttons) final Color? borderColor; /// Font size final double? fontSize; /// Font weight final FontWeight? fontWeight; const CustomButton({ super.key, required this.text, required this.onPressed, this.isLoading = false, this.icon, this.style, this.minWidth, this.minHeight, this.backgroundColor, this.foregroundColor, this.fontSize, this.fontWeight, }) : isOutlined = false, isTextButton = false, borderColor = null; /// Create an outlined button variant const CustomButton.outlined({ super.key, required this.text, required this.onPressed, this.isLoading = false, this.icon, this.style, this.minWidth, this.minHeight, this.foregroundColor, this.borderColor, this.fontSize, this.fontWeight, }) : isOutlined = true, isTextButton = false, backgroundColor = null; /// Create a text button variant const CustomButton.text({ super.key, required this.text, required this.onPressed, this.isLoading = false, this.icon, this.style, this.minWidth, this.minHeight, this.foregroundColor, this.fontSize, this.fontWeight, }) : isOutlined = false, isTextButton = true, backgroundColor = null, borderColor = null; @override Widget build(BuildContext context) { final theme = Theme.of(context); final colorScheme = theme.colorScheme; // Determine if button should be disabled final bool isDisabled = onPressed == null || isLoading; // Build button content Widget content; if (isLoading) { content = SizedBox( height: 20, width: 20, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation( isTextButton ? foregroundColor ?? colorScheme.primary : foregroundColor ?? colorScheme.onPrimary, ), ), ); } else if (icon != null) { content = Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(icon, size: 20), const SizedBox(width: 8), Flexible( child: Text( text, overflow: TextOverflow.ellipsis, ), ), ], ); } else { content = Text(text); } // Build button style final ButtonStyle buttonStyle = style ?? (isTextButton ? _buildTextButtonStyle(context) : isOutlined ? _buildOutlinedButtonStyle(context) : _buildElevatedButtonStyle(context)); // Build appropriate button widget if (isTextButton) { return TextButton( onPressed: isDisabled ? null : onPressed, style: buttonStyle, child: content, ); } else if (isOutlined) { return OutlinedButton( onPressed: isDisabled ? null : onPressed, style: buttonStyle, child: content, ); } else { return ElevatedButton( onPressed: isDisabled ? null : onPressed, style: buttonStyle, child: content, ); } } /// Build elevated button style ButtonStyle _buildElevatedButtonStyle(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return ElevatedButton.styleFrom( backgroundColor: backgroundColor ?? colorScheme.primary, foregroundColor: foregroundColor ?? colorScheme.onPrimary, minimumSize: Size( minWidth ?? double.infinity, minHeight ?? 48, ), textStyle: TextStyle( fontSize: fontSize ?? 16, fontWeight: fontWeight ?? FontWeight.w600, ), elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), padding: const EdgeInsets.symmetric( horizontal: 24, vertical: 12, ), ); } /// Build outlined button style ButtonStyle _buildOutlinedButtonStyle(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return OutlinedButton.styleFrom( foregroundColor: foregroundColor ?? colorScheme.primary, side: BorderSide( color: borderColor ?? colorScheme.primary, width: 1.5, ), minimumSize: Size( minWidth ?? double.infinity, minHeight ?? 48, ), textStyle: TextStyle( fontSize: fontSize ?? 16, fontWeight: fontWeight ?? FontWeight.w600, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), padding: const EdgeInsets.symmetric( horizontal: 24, vertical: 12, ), ); } /// Build text button style ButtonStyle _buildTextButtonStyle(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return TextButton.styleFrom( foregroundColor: foregroundColor ?? colorScheme.primary, minimumSize: Size( minWidth ?? 0, minHeight ?? 48, ), textStyle: TextStyle( fontSize: fontSize ?? 16, fontWeight: fontWeight ?? FontWeight.w600, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), ); } } /// Icon button with loading state class CustomIconButton extends StatelessWidget { /// Icon to display final IconData icon; /// Callback when button is pressed final VoidCallback? onPressed; /// Whether the button is in loading state final bool isLoading; /// Icon size final double? iconSize; /// Icon color final Color? color; /// Background color final Color? backgroundColor; /// Tooltip text final String? tooltip; const CustomIconButton({ super.key, required this.icon, required this.onPressed, this.isLoading = false, this.iconSize, this.color, this.backgroundColor, this.tooltip, }); @override Widget build(BuildContext context) { final theme = Theme.of(context); final bool isDisabled = onPressed == null || isLoading; Widget button = IconButton( icon: isLoading ? SizedBox( height: iconSize ?? 24, width: iconSize ?? 24, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation( color ?? theme.colorScheme.primary, ), ), ) : Icon( icon, size: iconSize ?? 24, color: color, ), onPressed: isDisabled ? null : onPressed, style: backgroundColor != null ? IconButton.styleFrom( backgroundColor: backgroundColor, ) : null, ); if (tooltip != null) { return Tooltip( message: tooltip!, child: button, ); } return button; } }