Skip to content

Instantly share code, notes, and snippets.

@hawkkiller
Created January 24, 2025 10:51
Show Gist options
  • Select an option

  • Save hawkkiller/2b0e759256b1022cd1c1c2f00d9dfcaf to your computer and use it in GitHub Desktop.

Select an option

Save hawkkiller/2b0e759256b1022cd1c1c2f00d9dfcaf to your computer and use it in GitHub Desktop.
Different buttons for design system
import 'package:ui/ui.dart';
sealed class UiButton extends StatelessWidget {
const UiButton({super.key});
const factory UiButton.primary({
required String label,
required VoidCallback? onPressed,
Widget? icon,
bool isEnabled,
bool isLoading,
IconAlignment iconAlignment,
Key? key,
}) = _UiButtonPrimary;
const factory UiButton.secondary({
required String label,
required VoidCallback? onPressed,
Widget? icon,
bool isEnabled,
bool isLoading,
IconAlignment iconAlignment,
Key? key,
}) = _UiButtonSecondary;
const factory UiButton.tertiary({
required String label,
required VoidCallback? onPressed,
bool isEnabled,
bool isLoading,
Widget? icon,
IconAlignment iconAlignment,
Key? key,
}) = _TertiaryUiButton;
const factory UiButton.icon({
required Widget icon,
required VoidCallback? onPressed,
bool isEnabled,
bool isLoading,
Key? key,
}) = _IconUiButton;
}
class _UiButtonPrimary extends UiButton {
const _UiButtonPrimary({
required this.label,
this.icon,
this.onPressed,
this.isEnabled = true,
this.isLoading = false,
this.iconAlignment = IconAlignment.start,
super.key,
});
final String label;
final IconAlignment iconAlignment;
final bool isEnabled;
final bool isLoading;
final Widget? icon;
final VoidCallback? onPressed;
/// Returns true if the button is enabled.
bool get _isEnabled => onPressed != null && !isLoading && isEnabled;
@override
Widget build(BuildContext context) => FilledButton.icon(
label: _ContentLoadingSwitch(
_ButtonContent(label: label, icon: icon, iconAlignment: iconAlignment),
isLoading,
),
onPressed: _isEnabled ? onPressed : null,
icon: icon,
iconAlignment: iconAlignment,
);
}
class _UiButtonSecondary extends UiButton {
const _UiButtonSecondary({
required this.label,
this.icon,
this.onPressed,
this.isEnabled = true,
this.isLoading = false,
this.iconAlignment = IconAlignment.start,
super.key,
});
final String label;
final IconAlignment iconAlignment;
final bool isEnabled;
final bool isLoading;
final Widget? icon;
final VoidCallback? onPressed;
/// Returns true if the button is enabled.
bool get _isEnabled => onPressed != null && !isLoading && isEnabled;
@override
Widget build(BuildContext context) => OutlinedButton.icon(
label: _ContentLoadingSwitch(
_ButtonContent(label: label, icon: icon, iconAlignment: iconAlignment),
isLoading,
),
onPressed: _isEnabled ? onPressed : null,
icon: icon,
iconAlignment: iconAlignment,
);
}
class _TertiaryUiButton extends UiButton {
const _TertiaryUiButton({
required this.label,
this.icon,
this.onPressed,
this.isEnabled = true,
this.isLoading = false,
this.iconAlignment = IconAlignment.start,
super.key,
});
final String label;
final IconAlignment iconAlignment;
final bool isEnabled;
final bool isLoading;
final Widget? icon;
final VoidCallback? onPressed;
/// Returns true if the button is enabled.
bool get _isEnabled => onPressed != null && !isLoading && isEnabled;
@override
Widget build(BuildContext context) => TextButton.icon(
label: _ContentLoadingSwitch(
_ButtonContent(label: label, icon: icon, iconAlignment: iconAlignment),
isLoading,
),
onPressed: _isEnabled ? onPressed : null,
);
}
class _IconUiButton extends UiButton {
const _IconUiButton({
required this.icon,
required this.onPressed,
this.isEnabled = true,
this.isLoading = false,
super.key,
});
final Widget icon;
final bool isEnabled;
final bool isLoading;
final VoidCallback? onPressed;
/// Returns true if the button is enabled.
bool get _isEnabled => onPressed != null && !isLoading && isEnabled;
@override
Widget build(BuildContext context) => IconButton(
icon: _ContentLoadingSwitch(icon, isLoading),
onPressed: _isEnabled ? onPressed : null,
);
}
class _ButtonContent extends StatelessWidget {
const _ButtonContent({
required this.label,
required this.iconAlignment,
this.icon,
});
final String label;
final Widget? icon;
final IconAlignment iconAlignment;
@override
Widget build(BuildContext context) => Row(
mainAxisSize: MainAxisSize.min,
spacing: 8,
children: iconAlignment == IconAlignment.start
? [if (icon != null) icon!, Text(label)]
: [Text(label), if (icon != null) const SizedBox(width: 8)],
);
}
class _ContentLoadingSwitch extends StatelessWidget {
const _ContentLoadingSwitch(this.content, this.isLoading);
final Widget content;
final bool isLoading;
@override
Widget build(BuildContext context) => Stack(
alignment: Alignment.center,
children: [
Visibility(
visible: !isLoading,
maintainState: true,
maintainSize: true,
maintainAnimation: true,
maintainInteractivity: true,
maintainSemantics: true,
child: content,
),
Visibility(
visible: isLoading,
child: SizedBox.square(
dimension: 24,
child: UiProgressIndicator(),
),
),
],
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment