gcx/frontend/mobile/lib/shared/widgets/genex_button.dart

162 lines
4.5 KiB
Dart

import 'package:flutter/material.dart';
import '../../app/theme/app_colors.dart';
import '../../app/theme/app_typography.dart';
import '../../app/theme/app_spacing.dart';
/// Genex 按钮组件
///
/// 统一的按钮样式,支持 primary/secondary/outline/text 4种变体
class GenexButton extends StatelessWidget {
final String label;
final VoidCallback? onPressed;
final GenexButtonVariant variant;
final GenexButtonSize size;
final IconData? icon;
final bool isLoading;
final bool fullWidth;
const GenexButton({
super.key,
required this.label,
this.onPressed,
this.variant = GenexButtonVariant.primary,
this.size = GenexButtonSize.large,
this.icon,
this.isLoading = false,
this.fullWidth = true,
});
@override
Widget build(BuildContext context) {
final child = Row(
mainAxisSize: fullWidth ? MainAxisSize.max : MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (isLoading) ...[
SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation(_foregroundColor),
),
),
const SizedBox(width: 8),
],
if (icon != null && !isLoading) ...[
Icon(icon, size: 20),
const SizedBox(width: 6),
],
Text(label),
],
);
final effectiveOnPressed = isLoading ? null : onPressed;
Widget button;
switch (variant) {
case GenexButtonVariant.primary:
button = ElevatedButton(
onPressed: effectiveOnPressed,
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primary,
foregroundColor: Colors.white,
disabledBackgroundColor: AppColors.primary.withValues(alpha: 0.4),
disabledForegroundColor: Colors.white70,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: AppSpacing.borderRadiusMd,
),
textStyle: _textStyle,
),
child: child,
);
case GenexButtonVariant.secondary:
button = ElevatedButton(
onPressed: effectiveOnPressed,
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primarySurface,
foregroundColor: AppColors.primary,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: AppSpacing.borderRadiusMd,
),
textStyle: _textStyle,
),
child: child,
);
case GenexButtonVariant.outline:
button = OutlinedButton(
onPressed: effectiveOnPressed,
style: OutlinedButton.styleFrom(
foregroundColor: AppColors.primary,
side: const BorderSide(color: AppColors.primary, width: 1.5),
shape: RoundedRectangleBorder(
borderRadius: AppSpacing.borderRadiusMd,
),
textStyle: _textStyle,
),
child: child,
);
case GenexButtonVariant.text:
button = TextButton(
onPressed: effectiveOnPressed,
style: TextButton.styleFrom(
foregroundColor: AppColors.primary,
textStyle: _textStyle,
),
child: child,
);
}
// Use LayoutBuilder to safely handle fullWidth in unbounded contexts (e.g. Row)
if (fullWidth) {
return LayoutBuilder(
builder: (context, constraints) => SizedBox(
width: constraints.hasBoundedWidth ? double.infinity : null,
height: _height,
child: button,
),
);
}
return SizedBox(height: _height, child: button);
}
double get _height {
switch (size) {
case GenexButtonSize.large:
return AppSpacing.buttonHeight;
case GenexButtonSize.medium:
return AppSpacing.buttonHeightSm;
case GenexButtonSize.small:
return 32;
}
}
TextStyle get _textStyle {
switch (size) {
case GenexButtonSize.large:
return AppTypography.labelLarge;
case GenexButtonSize.medium:
return AppTypography.labelMedium;
case GenexButtonSize.small:
return AppTypography.labelSmall;
}
}
Color get _foregroundColor {
switch (variant) {
case GenexButtonVariant.primary:
return Colors.white;
default:
return AppColors.primary;
}
}
}
enum GenexButtonVariant { primary, secondary, outline, text }
enum GenexButtonSize { large, medium, small }