diff --git a/docs/pages/docs/alert.mdx b/docs/pages/docs/alert.mdx
new file mode 100644
index 000000000..f40846f5a
--- /dev/null
+++ b/docs/pages/docs/alert.mdx
@@ -0,0 +1,64 @@
+import { Tabs } from 'nextra/components';
+import { Widget } from "../../components/widget";
+
+# Alert
+Displays a callout for user attention.
+
+
+
+
+
+
+ ```dart
+ FAlert(
+ title: const Text('Heads Up!'),
+ subtitle: const Text('You can add components to your app using the cli.'),
+ );
+ ```
+
+
+
+## Usage
+
+### `FAlert(...)`
+
+```dart
+FAlert(
+ icon: FAlertIcon(icon: FAssets.icons.badgeAlert),
+ title: const Text('Heads Up!'),
+ subtitle: const Text('You can add components to your app using the cli.'),
+);
+```
+
+## Examples
+
+### Primary
+
+
+
+
+
+ ```dart
+ FAlert(
+ title: const Text('Heads Up!'),
+ subtitle: const Text('You can add components to your app using the cli.'),
+ );
+ ```
+
+
+
+### Destructive
+
+
+
+
+
+ ```dart
+ FAlert(
+ title: const Text('Heads Up!'),
+ subtitle: const Text('You can add components to your app using the cli.'),
+ style: FAlertStyle.destructive,
+ );
+ ```
+
+
diff --git a/forui/CHANGELOG.md b/forui/CHANGELOG.md
index 14edc0f29..c54ae0504 100644
--- a/forui/CHANGELOG.md
+++ b/forui/CHANGELOG.md
@@ -1,6 +1,7 @@
## Next
### Additions
+* Add `FAlert`
* Add `FCalendar`
* Add `FBottomNavigationBar`
diff --git a/forui/example/lib/example.dart b/forui/example/lib/example.dart
index 25f410fea..788445b5c 100644
--- a/forui/example/lib/example.dart
+++ b/forui/example/lib/example.dart
@@ -23,6 +23,10 @@ class _ExampleState extends State {
const SizedBox(height: 100),
FProgress(value: 0.9),
const SizedBox(height: 10),
+ FAlert(
+ title: const Text('Heads Up! Forui is coming to flutter!'),
+ subtitle: const Text('You can add components dfijsoi djfosfj to your app using the cli.'),
+ )
],
);
}
diff --git a/forui/lib/src/theme/theme_data.dart b/forui/lib/src/theme/theme_data.dart
index 18f131ffe..7528de02f 100644
--- a/forui/lib/src/theme/theme_data.dart
+++ b/forui/lib/src/theme/theme_data.dart
@@ -25,6 +25,9 @@ final class FThemeData with Diagnosticable {
/// The style. It is used to configure the miscellaneous properties, such as border radii, of Forui widgets.
final FStyle style;
+ /// The alert styles.
+ final FAlertStyles alertStyles;
+
/// The badge styles.
final FBadgeStyles badgeStyles;
@@ -80,6 +83,7 @@ final class FThemeData with Diagnosticable {
required this.buttonStyles,
required this.calendarStyle,
required this.cardStyle,
+ required this.alertStyles,
required this.checkboxStyle,
required this.dialogStyle,
required this.headerStyle,
@@ -105,6 +109,7 @@ final class FThemeData with Diagnosticable {
colorScheme: colorScheme,
typography: typography,
style: style,
+ alertStyles: FAlertStyles.inherit(colorScheme: colorScheme, typography: typography, style: style),
badgeStyles: FBadgeStyles.inherit(colorScheme: colorScheme, typography: typography, style: style),
bottomNavigationBarStyle: FBottomNavigationBarStyle.inherit(colorScheme: colorScheme, typography: typography),
buttonStyles: FButtonStyles.inherit(colorScheme: colorScheme, typography: typography, style: style),
@@ -143,6 +148,7 @@ final class FThemeData with Diagnosticable {
FColorScheme? colorScheme,
FTypography? typography,
FStyle? style,
+ FAlertStyles? alertStyles,
FBadgeStyles? badgeStyles,
FBottomNavigationBarStyle? bottomNavigationBarStyle,
FButtonStyles? buttonStyles,
@@ -162,6 +168,7 @@ final class FThemeData with Diagnosticable {
colorScheme: colorScheme ?? this.colorScheme,
typography: typography ?? this.typography,
style: style ?? this.style,
+ alertStyles: alertStyles ?? this.alertStyles,
badgeStyles: badgeStyles ?? this.badgeStyles,
bottomNavigationBarStyle: bottomNavigationBarStyle ?? this.bottomNavigationBarStyle,
buttonStyles: buttonStyles ?? this.buttonStyles,
@@ -185,6 +192,7 @@ final class FThemeData with Diagnosticable {
..add(DiagnosticsProperty('colorScheme', colorScheme, level: DiagnosticLevel.debug))
..add(DiagnosticsProperty('typography', typography, level: DiagnosticLevel.debug))
..add(DiagnosticsProperty('style', style, level: DiagnosticLevel.debug))
+ ..add(DiagnosticsProperty('alertStyles', alertStyles, level: DiagnosticLevel.debug))
..add(DiagnosticsProperty('badgeStyles', badgeStyles, level: DiagnosticLevel.debug))
..add(DiagnosticsProperty('bottomNavigationBarStyle', bottomNavigationBarStyle, level: DiagnosticLevel.debug))
..add(DiagnosticsProperty('buttonStyles', buttonStyles, level: DiagnosticLevel.debug))
@@ -209,6 +217,7 @@ final class FThemeData with Diagnosticable {
colorScheme == other.colorScheme &&
typography == other.typography &&
style == other.style &&
+ alertStyles == other.alertStyles &&
badgeStyles == other.badgeStyles &&
bottomNavigationBarStyle == other.bottomNavigationBarStyle &&
buttonStyles == other.buttonStyles &&
@@ -229,6 +238,7 @@ final class FThemeData with Diagnosticable {
colorScheme.hashCode ^
typography.hashCode ^
style.hashCode ^
+ alertStyles.hashCode ^
badgeStyles.hashCode ^
bottomNavigationBarStyle.hashCode ^
buttonStyles.hashCode ^
diff --git a/forui/lib/src/widgets/alert/alert.dart b/forui/lib/src/widgets/alert/alert.dart
new file mode 100644
index 000000000..8ea8cb3f1
--- /dev/null
+++ b/forui/lib/src/widgets/alert/alert.dart
@@ -0,0 +1,205 @@
+import 'package:flutter/foundation.dart';
+import 'package:flutter/widgets.dart';
+
+import 'package:meta/meta.dart';
+
+import 'package:forui/forui.dart';
+
+part 'alert_styles.dart';
+
+part 'alert_icon.dart';
+
+/// An alert.
+///
+/// Displays a callout for user attention.
+///
+/// See:
+/// * https://forui.dev/docs/alert for working examples.
+/// * [FAlertStyle] for customizing an alert's appearance.
+class FAlert extends StatelessWidget {
+ @useResult
+ static FAlertCustomStyle _of(BuildContext context) {
+ final theme = context.dependOnInheritedWidgetOfExactType<_InheritedData>();
+ return theme?.style ?? context.theme.alertStyles.primary;
+ }
+
+ /// The icon. Defaults to [FAssets.icons.circleAlert].
+ final Widget icon;
+
+ /// The title.
+ final Widget title;
+
+ /// The subtitle.
+ final Widget? subtitle;
+
+ /// The style. Defaults to [FAlertStyle.primary].
+ ///
+ /// Although typically one of the pre-defined styles in [FAlertStyle], it can also be a [FAlertCustomStyle].
+ final FAlertStyle style;
+
+ /// Creates a [FAlert] with a tile, subtitle, and icon.
+ ///
+ /// The alert's layout is as follows:
+ /// ```
+ /// |---------------------------|
+ /// | [icon] [title] |
+ /// | [subtitle] |
+ /// |---------------------------|
+ /// ```
+ FAlert({
+ required this.title,
+ Widget? icon,
+ this.subtitle,
+ this.style = FAlertStyle.primary,
+ super.key,
+ }) : icon = icon ?? FAlertIcon(icon: FAssets.icons.circleAlert);
+
+ @override
+ Widget build(BuildContext context) {
+ final style = switch (this.style) {
+ final FAlertCustomStyle style => style,
+ Variant.primary => context.theme.alertStyles.primary,
+ Variant.destructive => context.theme.alertStyles.destructive,
+ };
+
+ return DecoratedBox(
+ decoration: style.decoration,
+ child: Padding(
+ padding: style.padding,
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Row(
+ children: [
+ _InheritedData(style: style, child: icon),
+ Flexible(
+ child: Padding(
+ padding: const EdgeInsets.only(left: 8),
+ child: DefaultTextStyle.merge(
+ style: style.titleTextStyle,
+ child: title,
+ ),
+ ),
+ ),
+ ],
+ ),
+ if (subtitle != null)
+ Row(
+ children: [
+ SizedBox(width: style.icon.height),
+ Flexible(
+ child: Padding(
+ padding: const EdgeInsets.only(top: 3, left: 8),
+ child: DefaultTextStyle.merge(
+ style: style.subtitleTextStyle,
+ child: subtitle!,
+ ),
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ @override
+ void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+ super.debugFillProperties(properties);
+ properties.add(DiagnosticsProperty('style', style));
+ }
+}
+
+/// A [FAlert]'s style.
+///
+/// A style can be either one of the pre-defined styles in [FAlertStyle] or a [FAlertCustomStyle]. The pre-defined
+/// styles are a convenient shorthand for the various [FAlertCustomStyle]s in the current context's [FAlertStyles].
+sealed class FAlertStyle {
+ /// The alert's primary style.
+ ///
+ /// Shorthand for the current context's [FAlertStyle.primary] style.
+ static const FAlertStyle primary = Variant.primary;
+
+ /// The alert's destructive style.
+ ///
+ /// Shorthand for the current context's [FAlertStyle.destructive] style.
+ static const FAlertStyle destructive = Variant.destructive;
+}
+
+@internal
+enum Variant implements FAlertStyle {
+ primary,
+ destructive,
+}
+
+/// A custom [FAlert] style.
+final class FAlertCustomStyle extends FAlertStyle with Diagnosticable {
+ /// The decoration.
+ final BoxDecoration decoration;
+
+ /// The padding. Defaults to `EdgeInsets.fromLTRB(16, 12, 16, 16)`.
+ final EdgeInsets padding;
+
+ /// The icon's style.
+ final FAlertIconStyle icon;
+
+ /// The title's [TextStyle].
+ final TextStyle titleTextStyle;
+
+ /// The subtitle's [TextStyle].
+ final TextStyle subtitleTextStyle;
+
+ /// Creates a [FAlertCustomStyle].
+ FAlertCustomStyle({
+ required this.decoration,
+ required this.icon,
+ required this.titleTextStyle,
+ required this.subtitleTextStyle,
+ this.padding = const EdgeInsets.all(16),
+ });
+
+ @override
+ void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+ super.debugFillProperties(properties);
+ properties
+ ..add(DiagnosticsProperty('decoration', decoration))
+ ..add(DiagnosticsProperty('padding', padding))
+ ..add(DiagnosticsProperty('icon', icon))
+ ..add(DiagnosticsProperty('titleTextStyle', titleTextStyle))
+ ..add(DiagnosticsProperty('subtitleTextStyle', subtitleTextStyle));
+ }
+
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ other is FAlertCustomStyle &&
+ runtimeType == other.runtimeType &&
+ decoration == other.decoration &&
+ padding == other.padding &&
+ icon == other.icon &&
+ titleTextStyle == other.titleTextStyle &&
+ subtitleTextStyle == other.subtitleTextStyle;
+
+ @override
+ int get hashCode =>
+ decoration.hashCode ^ padding.hashCode ^ icon.hashCode ^ titleTextStyle.hashCode ^ subtitleTextStyle.hashCode;
+}
+
+class _InheritedData extends InheritedWidget {
+ final FAlertCustomStyle style;
+
+ const _InheritedData({
+ required this.style,
+ required super.child,
+ });
+
+ @override
+ bool updateShouldNotify(covariant _InheritedData old) => style != old.style;
+
+ @override
+ void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+ super.debugFillProperties(properties);
+ properties.add(DiagnosticsProperty('style', style));
+ }
+}
diff --git a/forui/lib/src/widgets/alert/alert_icon.dart b/forui/lib/src/widgets/alert/alert_icon.dart
new file mode 100644
index 000000000..7e05287d7
--- /dev/null
+++ b/forui/lib/src/widgets/alert/alert_icon.dart
@@ -0,0 +1,90 @@
+part of 'alert.dart';
+
+/// A [FAlert]'s icon.
+class FAlertIcon extends StatelessWidget {
+ /// The icon.
+ final SvgAsset icon;
+
+ /// Creates a [FAlertIcon] from the given SVG [icon].
+ const FAlertIcon({required this.icon, super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ final FAlertCustomStyle(:icon) = FAlert._of(context);
+
+ return this.icon(
+ height: icon.height,
+ colorFilter: ColorFilter.mode(icon.color, BlendMode.srcIn),
+ );
+ }
+
+ @override
+ void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+ super.debugFillProperties(properties);
+ properties.add(DiagnosticsProperty('icon', icon));
+ }
+}
+
+/// [FAlertIcon]'s style.
+final class FAlertIconStyle with Diagnosticable {
+ /// The icon's color.
+ final Color color;
+
+ /// The icon's height. Defaults to 20.
+ final double height;
+
+ /// Creates a [FButtonIconStyle].
+ ///
+ /// ## Contract:
+ /// Throws [AssertionError] if:
+ /// * `height` <= 0.0
+ /// * `height` is Nan
+ FAlertIconStyle({
+ required this.color,
+ this.height = 20,
+ }) : assert(0 < height, 'The height is $height, but it should be in the range "0 < height".');
+
+ /// Returns a copy of this [FAlertIconStyle] with the given properties replaced.
+ ///
+ /// ```dart
+ /// final style = FAlertIconStyle(
+ /// height: 20,
+ /// color: Colors.red,
+ /// );
+ ///
+ /// final copy = style.copyWith(
+ /// color: Colors.blue,
+ /// );
+ ///
+ /// print(copy.color); // Colors.blue
+ /// print(copy.height); // 20
+ /// ```
+ @useResult
+ FAlertIconStyle copyWith({
+ Color? color,
+ double? height,
+ }) =>
+ FAlertIconStyle(
+ color: color ?? this.color,
+ height: height ?? this.height,
+ );
+
+ @override
+ void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+ super.debugFillProperties(properties);
+ properties
+ ..add(ColorProperty('color', color))
+ ..add(DoubleProperty('height', height, defaultValue: 20));
+ }
+
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ other is FButtonIconStyle &&
+ runtimeType == other.runtimeType &&
+ color == other.enabledColor &&
+ height == other.height;
+
+ @override
+ int get hashCode => color.hashCode ^ height.hashCode;
+}
diff --git a/forui/lib/src/widgets/alert/alert_styles.dart b/forui/lib/src/widgets/alert/alert_styles.dart
new file mode 100644
index 000000000..8891add7d
--- /dev/null
+++ b/forui/lib/src/widgets/alert/alert_styles.dart
@@ -0,0 +1,92 @@
+part of 'alert.dart';
+
+/// [FAlertCustomStyle]'s style.
+class FAlertStyles with Diagnosticable {
+ /// The primary alert style.
+ final FAlertCustomStyle primary;
+
+ /// The destructive alert style.
+ final FAlertCustomStyle destructive;
+
+ /// Creates a [FAlertStyles].
+ const FAlertStyles({
+ required this.primary,
+ required this.destructive,
+ });
+
+ /// Creates a [FAlertStyles] that inherits its properties from the provided [colorScheme], [typography], and
+ /// [style].
+ FAlertStyles.inherit({required FColorScheme colorScheme, required FTypography typography, required FStyle style})
+ : primary = FAlertCustomStyle(
+ padding: const EdgeInsets.fromLTRB(16, 12, 16, 12),
+ titleTextStyle: typography.base.copyWith(
+ fontWeight: FontWeight.w500,
+ color: colorScheme.foreground,
+ height: 1.2,
+ ),
+ subtitleTextStyle: typography.sm.copyWith(color: colorScheme.foreground),
+ decoration: BoxDecoration(
+ border: Border.all(color: colorScheme.border),
+ borderRadius: style.borderRadius,
+ color: colorScheme.background,
+ ),
+ icon: FAlertIconStyle(color: colorScheme.foreground),
+ ),
+ destructive = FAlertCustomStyle(
+ padding: const EdgeInsets.fromLTRB(16, 12, 16, 12),
+ titleTextStyle: typography.base.copyWith(
+ fontWeight: FontWeight.w500,
+ color: colorScheme.destructive,
+ height: 1.2,
+ ),
+ subtitleTextStyle: typography.sm.copyWith(color: colorScheme.destructive),
+ decoration: BoxDecoration(
+ border: Border.all(color: colorScheme.destructive),
+ borderRadius: style.borderRadius,
+ color: colorScheme.background,
+ ),
+ icon: FAlertIconStyle(color: colorScheme.destructive),
+ );
+
+ @override
+ void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+ super.debugFillProperties(properties);
+ properties
+ ..add(DiagnosticsProperty('primary', primary))
+ ..add(DiagnosticsProperty('destructive', destructive));
+ }
+
+ /// Returns a copy of this [FAlertStyles] with the given properties replaced.
+ ///
+ /// ```dart
+ /// final style = FAlertStyles(
+ /// primary: ...,
+ /// destructive: ...,
+ /// );
+ ///
+ /// final copy = style.copyWith(destructive: ...);
+ ///
+ /// print(style.primary == copy.primary); // true
+ /// print(style.destructive == copy.destructive); // false
+ /// ```
+ @useResult
+ FAlertStyles copyWith({
+ FAlertCustomStyle? primary,
+ FAlertCustomStyle? destructive,
+ }) =>
+ FAlertStyles(
+ primary: primary ?? this.primary,
+ destructive: destructive ?? this.destructive,
+ );
+
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ other is FAlertStyles &&
+ runtimeType == other.runtimeType &&
+ primary == other.primary &&
+ destructive == other.destructive;
+
+ @override
+ int get hashCode => primary.hashCode ^ destructive.hashCode;
+}
diff --git a/forui/lib/src/widgets/card/card.dart b/forui/lib/src/widgets/card/card.dart
index 554d3b1f1..2c5dd572b 100644
--- a/forui/lib/src/widgets/card/card.dart
+++ b/forui/lib/src/widgets/card/card.dart
@@ -95,7 +95,11 @@ final class FCardStyle with Diagnosticable {
/// print(style.content == copy.content); // false
/// ```
@useResult
- FCardStyle copyWith({BoxDecoration? decoration, FCardContentStyle? content}) => FCardStyle(
+ FCardStyle copyWith({
+ BoxDecoration? decoration,
+ FCardContentStyle? content,
+ }) =>
+ FCardStyle(
decoration: decoration ?? this.decoration,
content: content ?? this.content,
);
@@ -104,8 +108,8 @@ final class FCardStyle with Diagnosticable {
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
- ..add(DiagnosticsProperty('content', content))
- ..add(DiagnosticsProperty('decoration', decoration));
+ ..add(DiagnosticsProperty('decoration', decoration))
+ ..add(DiagnosticsProperty('content', content));
}
@override
diff --git a/forui/lib/widgets.dart b/forui/lib/widgets.dart
index be92536da..19861440e 100644
--- a/forui/lib/widgets.dart
+++ b/forui/lib/widgets.dart
@@ -18,6 +18,7 @@ library forui.widgets;
import 'package:forui/forui.dart';
+export 'src/widgets/alert/alert.dart' hide Variant;
export 'src/widgets/badge/badge.dart' hide Variant;
export 'src/widgets/bottom_navigation_bar/bottom_navigation_bar.dart';
export 'src/widgets/button/button.dart' hide Variant;
diff --git a/forui/test/golden/alert/zinc-dark-Variant.destructive-with-default-icon.png b/forui/test/golden/alert/zinc-dark-Variant.destructive-with-default-icon.png
new file mode 100644
index 000000000..4641ac1a4
Binary files /dev/null and b/forui/test/golden/alert/zinc-dark-Variant.destructive-with-default-icon.png differ
diff --git a/forui/test/golden/alert/zinc-dark-Variant.destructive-without-user-icon.png b/forui/test/golden/alert/zinc-dark-Variant.destructive-without-user-icon.png
new file mode 100644
index 000000000..7b0eba8e0
Binary files /dev/null and b/forui/test/golden/alert/zinc-dark-Variant.destructive-without-user-icon.png differ
diff --git a/forui/test/golden/alert/zinc-dark-Variant.primary-with-default-icon.png b/forui/test/golden/alert/zinc-dark-Variant.primary-with-default-icon.png
new file mode 100644
index 000000000..4152520c7
Binary files /dev/null and b/forui/test/golden/alert/zinc-dark-Variant.primary-with-default-icon.png differ
diff --git a/forui/test/golden/alert/zinc-dark-Variant.primary-without-user-icon.png b/forui/test/golden/alert/zinc-dark-Variant.primary-without-user-icon.png
new file mode 100644
index 000000000..f25596a77
Binary files /dev/null and b/forui/test/golden/alert/zinc-dark-Variant.primary-without-user-icon.png differ
diff --git a/forui/test/golden/alert/zinc-light-Variant.destructive-with-default-icon.png b/forui/test/golden/alert/zinc-light-Variant.destructive-with-default-icon.png
new file mode 100644
index 000000000..5392349a0
Binary files /dev/null and b/forui/test/golden/alert/zinc-light-Variant.destructive-with-default-icon.png differ
diff --git a/forui/test/golden/alert/zinc-light-Variant.destructive-without-user-icon.png b/forui/test/golden/alert/zinc-light-Variant.destructive-without-user-icon.png
new file mode 100644
index 000000000..6689fab99
Binary files /dev/null and b/forui/test/golden/alert/zinc-light-Variant.destructive-without-user-icon.png differ
diff --git a/forui/test/golden/alert/zinc-light-Variant.primary-with-default-icon.png b/forui/test/golden/alert/zinc-light-Variant.primary-with-default-icon.png
new file mode 100644
index 000000000..7a1844a69
Binary files /dev/null and b/forui/test/golden/alert/zinc-light-Variant.primary-with-default-icon.png differ
diff --git a/forui/test/golden/alert/zinc-light-Variant.primary-without-user-icon.png b/forui/test/golden/alert/zinc-light-Variant.primary-without-user-icon.png
new file mode 100644
index 000000000..6f8496658
Binary files /dev/null and b/forui/test/golden/alert/zinc-light-Variant.primary-without-user-icon.png differ
diff --git a/forui/test/src/widgets/alert/alert_golden_test.dart b/forui/test/src/widgets/alert/alert_golden_test.dart
new file mode 100644
index 000000000..a2ff1a053
--- /dev/null
+++ b/forui/test/src/widgets/alert/alert_golden_test.dart
@@ -0,0 +1,61 @@
+@Tags(['golden'])
+library;
+
+import 'package:flutter/material.dart';
+
+import 'package:flutter_test/flutter_test.dart';
+
+import 'package:forui/forui.dart';
+import 'package:forui/src/widgets/alert/alert.dart';
+import '../../test_scaffold.dart';
+
+void main() {
+ group('FAlert', () {
+ for (final (name, theme, _) in TestScaffold.themes) {
+ for (final variant in Variant.values) {
+ testWidgets('$name with default icon', (tester) async {
+ await tester.pumpWidget(
+ TestScaffold(
+ data: theme,
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 20),
+ child: FAlert(
+ title: const Text('Alert Title'),
+ subtitle: const Text('Alert description with extra text'),
+ style: variant,
+ ),
+ ),
+ ),
+ );
+
+ await expectLater(
+ find.byType(TestScaffold),
+ matchesGoldenFile('alert/$name-$variant-with-default-icon.png'),
+ );
+ });
+
+ testWidgets('$name without user icon', (tester) async {
+ await tester.pumpWidget(
+ TestScaffold(
+ data: theme,
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 20),
+ child: FAlert(
+ icon: FAlertIcon(icon: FAssets.icons.badgeAlert),
+ title: const Text('Alert Title'),
+ subtitle: const Text('Alert description with extra text'),
+ style: variant,
+ ),
+ ),
+ ),
+ );
+
+ await expectLater(
+ find.byType(TestScaffold),
+ matchesGoldenFile('alert/$name-$variant-without-user-icon.png'),
+ );
+ });
+ }
+ }
+ });
+}
diff --git a/forui/test/src/widgets/alert/alert_icon_test.dart b/forui/test/src/widgets/alert/alert_icon_test.dart
new file mode 100644
index 000000000..37dc5de85
--- /dev/null
+++ b/forui/test/src/widgets/alert/alert_icon_test.dart
@@ -0,0 +1,29 @@
+import 'package:flutter/material.dart';
+
+import 'package:flutter_test/flutter_test.dart';
+
+import 'package:forui/forui.dart';
+
+void main() {
+ group('FAlertIconStyle', () {
+ test('invalid height', () {
+ expect(
+ () => FAlertIconStyle(
+ height: 0,
+ color: Colors.white,
+ ),
+ throwsAssertionError,
+ );
+ });
+
+ test('valid height', () {
+ expect(
+ () => FAlertIconStyle(
+ height: 1,
+ color: Colors.white,
+ ),
+ returnsNormally,
+ );
+ });
+ });
+}
diff --git a/samples/lib/main.dart b/samples/lib/main.dart
index 6dae46aaa..f9c49a59d 100644
--- a/samples/lib/main.dart
+++ b/samples/lib/main.dart
@@ -39,6 +39,10 @@ class _AppRouter extends $_AppRouter {
page: EmptyRoute.page,
initial: true,
),
+ AutoRoute(
+ path: '/alert/default',
+ page: AlertRoute.page,
+ ),
AutoRoute(
path: '/badge/default',
page: BadgeRoute.page,
diff --git a/samples/lib/widgets/alert.dart b/samples/lib/widgets/alert.dart
new file mode 100644
index 000000000..f8b4f87e4
--- /dev/null
+++ b/samples/lib/widgets/alert.dart
@@ -0,0 +1,34 @@
+import 'package:flutter/material.dart';
+
+import 'package:auto_route/auto_route.dart';
+import 'package:forui/src/widgets/alert/alert.dart';
+
+import 'package:forui_samples/sample_scaffold.dart';
+
+// ignore_for_file: invalid_use_of_internal_member, implementation_imports
+
+final variants = {
+ for (final value in Variant.values) value.name: value,
+};
+
+@RoutePage()
+class AlertPage extends SampleScaffold {
+ final Variant variant;
+
+ AlertPage({
+ @queryParam super.theme,
+ @queryParam String style = 'primary',
+ }) : variant = variants[style] ?? Variant.primary;
+
+ @override
+ Widget child(BuildContext context) => Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ FAlert(
+ title: const Text('Heads Up!'),
+ subtitle: const Text('You can add components to your app using the cli.'),
+ style: variant,
+ ),
+ ],
+ );
+}