Grain flutter app
1import 'package:flutter/material.dart';
2
3enum AppButtonVariant { primary, secondary, text }
4
5enum AppButtonSize { normal, small }
6
7class AppButton extends StatelessWidget {
8 final String label;
9 final VoidCallback? onPressed;
10 final bool loading;
11 final bool disabled;
12 final AppButtonVariant variant;
13 final AppButtonSize size;
14 final IconData? icon;
15 final double height;
16 final double borderRadius;
17 final double fontSize;
18 final EdgeInsetsGeometry? padding;
19
20 const AppButton({
21 super.key,
22 required this.label,
23 this.onPressed,
24 this.loading = false,
25 this.disabled = false,
26 this.variant = AppButtonVariant.primary,
27 this.size = AppButtonSize.normal,
28 this.icon,
29 this.height = 44,
30 this.borderRadius = 6,
31 this.fontSize = 16,
32 this.padding,
33 });
34
35 @override
36 Widget build(BuildContext context) {
37 final theme = Theme.of(context);
38 final Color primaryColor = theme.colorScheme.primary;
39 final Color secondaryColor = theme.colorScheme.surfaceContainerHighest;
40 final Color secondaryText = theme.colorScheme.onSurface;
41 final Color primaryText = theme.colorScheme.onPrimary;
42 final bool isPrimary = variant == AppButtonVariant.primary;
43 final bool isText = variant == AppButtonVariant.text;
44
45 final double resolvedHeight = size == AppButtonSize.small ? 32 : height;
46 final double resolvedFontSize = size == AppButtonSize.small ? 14 : fontSize;
47 final double resolvedBorderRadius = size == AppButtonSize.small ? 5 : borderRadius;
48 final EdgeInsetsGeometry resolvedPadding =
49 padding ??
50 (size == AppButtonSize.small
51 ? const EdgeInsets.symmetric(horizontal: 14, vertical: 0)
52 : const EdgeInsets.symmetric(horizontal: 16));
53
54 if (isText) {
55 return SizedBox(
56 height: resolvedHeight,
57 child: TextButton(
58 onPressed: (loading || disabled) ? null : onPressed,
59 style: TextButton.styleFrom(
60 padding: resolvedPadding,
61 foregroundColor: disabled ? secondaryText.withOpacity(0.5) : secondaryText,
62 textStyle: theme.textTheme.labelLarge?.copyWith(
63 fontWeight: FontWeight.w600,
64 fontSize: resolvedFontSize,
65 ),
66 ),
67 child: Text(
68 label,
69 style: theme.textTheme.labelLarge?.copyWith(
70 color: disabled ? primaryColor.withOpacity(0.5) : primaryColor,
71 fontWeight: FontWeight.w600,
72 fontSize: resolvedFontSize,
73 ),
74 ),
75 ),
76 );
77 }
78
79 return SizedBox(
80 height: resolvedHeight,
81 child: ElevatedButton(
82 onPressed: (loading || disabled) ? null : onPressed,
83 style: ElevatedButton.styleFrom(
84 backgroundColor: isPrimary
85 ? (disabled ? primaryColor.withOpacity(0.5) : primaryColor)
86 : (disabled ? secondaryColor.withOpacity(0.5) : secondaryColor),
87 foregroundColor: isPrimary ? primaryText : secondaryText,
88 elevation: 0,
89 shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(resolvedBorderRadius)),
90 padding: resolvedPadding,
91 textStyle: theme.textTheme.labelLarge?.copyWith(
92 fontWeight: FontWeight.w600,
93 fontSize: resolvedFontSize,
94 ),
95 ),
96 child: loading
97 ? SizedBox(
98 width: 22,
99 height: 22,
100 child: CircularProgressIndicator(
101 color: isPrimary ? primaryColor : secondaryColor,
102 strokeWidth: 2,
103 ),
104 )
105 : Row(
106 mainAxisSize: MainAxisSize.min,
107 mainAxisAlignment: MainAxisAlignment.center,
108 children: [
109 if (icon != null) ...[
110 Icon(icon, size: 20, color: isPrimary ? primaryText : secondaryText),
111 const SizedBox(width: 8),
112 ],
113 Text(
114 label,
115 style: theme.textTheme.labelLarge?.copyWith(
116 color: isPrimary ? primaryText : secondaryText,
117 fontWeight: FontWeight.w700,
118 fontSize: resolvedFontSize,
119 ),
120 ),
121 ],
122 ),
123 ),
124 );
125 }
126}