diff --git a/docs/pages/docs/_meta.json b/docs/pages/docs/_meta.json
index bed9fbf34..823f943f4 100644
--- a/docs/pages/docs/_meta.json
+++ b/docs/pages/docs/_meta.json
@@ -1,7 +1,7 @@
{
"index": "Getting Started",
"themes": "Themes",
- "icon": "Icon",
+ "icon-library": "Icon",
"api_reference": {
"title": "API Reference ↗",
"href": "https://pub.dev/documentation/forui",
diff --git a/docs/pages/docs/alert.mdx b/docs/pages/docs/alert.mdx
index 1bfb17b2f..25e49497f 100644
--- a/docs/pages/docs/alert.mdx
+++ b/docs/pages/docs/alert.mdx
@@ -30,7 +30,7 @@ Displays a callout for user attention.
```dart
FAlert(
- icon: FAlertIcon(icon: FAssets.icons.badgeAlert),
+ icon: FIcon(FAssets.icons.badgeAlert),
title: const Text('Heads Up!'),
subtitle: const Text('You can add components to your app using the cli.'),
);
diff --git a/docs/pages/docs/bottom-navigation-bar.mdx b/docs/pages/docs/bottom-navigation-bar.mdx
index 571916f52..ffd74245a 100644
--- a/docs/pages/docs/bottom-navigation-bar.mdx
+++ b/docs/pages/docs/bottom-navigation-bar.mdx
@@ -34,23 +34,23 @@ It is used to navigate between a small number of views, typically between three
onChange: (index) => setState(() => this.index = index),
children: [
FBottomNavigationBarItem(
- icon: FAssets.icons.home,
+ icon: FIcon(FAssets.icons.home),
label: const Text('Home'),
),
FBottomNavigationBarItem(
- icon: FAssets.icons.layoutGrid,
+ icon: FIcon(FAssets.icons.layoutGrid),
label: const Text('Browse'),
),
FBottomNavigationBarItem(
- icon: FAssets.icons.radio,
+ icon: FIcon(FAssets.icons.radio),
label: const Text('Radio'),
),
FBottomNavigationBarItem(
- icon: FAssets.icons.libraryBig,
+ icon: FIcon(FAssets.icons.libraryBig),
label: const Text('Library'),
),
FBottomNavigationBarItem(
- icon: FAssets.icons.search,
+ icon: FIcon(FAssets.icons.search),
label: const Text('Search'),
),
],
@@ -74,81 +74,9 @@ FBottomNavigationBar(
onChange: (index) => {},
children: [
FBottomNavigationBarItem(
- icon: FAssets.icons.home,
+ icon: FIcon(FAssets.icons.home),
label: const Text('Home'),
),
],
)
```
-
-## Examples
-
-### Custom Icon
-
-
-
-
-
-
- ```dart
- class Application extends StatefulWidget {
- const Application({super.key});
-
- @override
- State createState() => _ApplicationState();
- }
-
- class _ApplicationState extends State {
- int index = 1;
-
- @override
- Widget build(BuildContext context) => FBottomNavigationBar(
- index: index,
- onChange: (index) => setState(() => this.index = index),
- children: [
- FBottomNavigationBarItem.customIcon(
- iconBuilder: (_, data, __) => Icon(
- Icons.home_outlined,
- size: data.itemStyle.iconSize,
- color: data.selected ? data.itemStyle.activeIconColor : data.itemStyle.inactiveIconColor,
- ),
- label: const Text('Home'),
- ),
- FBottomNavigationBarItem.customIcon(
- iconBuilder: (_, data, __) => Icon(
- Icons.browse_gallery_outlined,
- size: data.itemStyle.iconSize,
- color: data.selected ? data.itemStyle.activeIconColor : data.itemStyle.inactiveIconColor,
- ),
- label: const Text('Browse'),
- ),
- FBottomNavigationBarItem.customIcon(
- iconBuilder: (_, data, __) => Icon(
- Icons.radio_outlined,
- size: data.itemStyle.iconSize,
- color: data.selected ? data.itemStyle.activeIconColor : data.itemStyle.inactiveIconColor,
- ),
- label: const Text('Radio'),
- ),
- FBottomNavigationBarItem.customIcon(
- iconBuilder: (_, data, __) => Icon(
- Icons.library_books_outlined,
- size: data.itemStyle.iconSize,
- color: data.selected ? data.itemStyle.activeIconColor : data.itemStyle.inactiveIconColor,
- ),
- label: const Text('Library'),
- ),
- FBottomNavigationBarItem.customIcon(
- iconBuilder: (_, data, __) => Icon(
- Icons.search_outlined,
- size: data.itemStyle.iconSize,
- color: data.selected ? data.itemStyle.activeIconColor : data.itemStyle.inactiveIconColor,
- ),
- label: const Text('Search'),
- ),
- ],
- );
- }
- ```
-
-
diff --git a/docs/pages/docs/button.mdx b/docs/pages/docs/button.mdx
index 0ae156848..76366fa91 100644
--- a/docs/pages/docs/button.mdx
+++ b/docs/pages/docs/button.mdx
@@ -122,7 +122,7 @@ FButton.raw(
```dart {2}
FButton(
- prefix: FButtonIcon(icon: FAssets.icons.mail),
+ prefix: FIcon(icon: FAssets.icons.mail),
label: const Text('Login with Email'),
onPress: () {},
),
@@ -138,7 +138,7 @@ FButton.raw(
```dart {2}
FButton.icon(
- icon: FButtonIcon(icon: FAssets.icons.chevronRight),
+ icon: FIcon(icon: FAssets.icons.chevronRight),
onPress: () {},
),
```
diff --git a/docs/pages/docs/icon-library.mdx b/docs/pages/docs/icon-library.mdx
new file mode 100644
index 000000000..64584e1c6
--- /dev/null
+++ b/docs/pages/docs/icon-library.mdx
@@ -0,0 +1,57 @@
+import {Callout} from "nextra/components";
+
+
+# Icon
+
+Forui Assets is flutter library that provides a set of high-quality icons from [Lucide](https://lucide.dev/).
+
+
+## Installation
+
+
+ Forui Assets is bundled with forui package. You don't need to install it separately if you installed `forui`.
+
+
+From your Flutter project directory, run the following command to install Forui Assets.
+
+```bash filename="bash"
+flutter pub install forui_assets
+```
+
+
+## Usage
+
+
+ The best way to find a list of icons is to visit the [Lucide](https://lucide.dev/icons/) website.
+ We periodically update the icons in the Forui Assets package.
+ If you notice a missing icon in Forui Assets, please [open an issue](https://github.com/forus-labs/forui/issues/new).
+
+
+
+ While you can use an icon from `forui_assets` directly, it is recommended to wrap it in an [FIcon](/docs/icon) to
+ automatically configure its color and size.
+
+
+```dart
+import 'package:forui/forui.dart';
+
+// alternatively; if you've only installed forui_assets.
+import 'package:forui_assets/forui_assets.dart';
+
+// Dog icon as a Widget. It is recommended to wrap icons in FIcon if you're using Forui.
+final dogIconWidget = FIcon(FAssets.icons.dog);
+
+// Bird icon as a Widget.
+final birdIconWidget = FAssets.icons.bird();
+
+// White cat icon with a size of 24x24.
+final catIconWidget = FAssets.icons.cat(
+ width: 24,
+ height: 24,
+ colorFilter: const ColorFilter.mode(Color(0xFFFFFFFF), BlendMode.srcIn),
+);
+
+// Saving an icon to a variable for later use.
+final rabbitSvgAsset = FAssets.icons.rabbit;
+final rabbitIconWidget = rabbitSvgAsset();
+```
\ No newline at end of file
diff --git a/docs/pages/docs/icon.mdx b/docs/pages/docs/icon.mdx
index 6a5102688..1b691ae01 100644
--- a/docs/pages/docs/icon.mdx
+++ b/docs/pages/docs/icon.mdx
@@ -1,49 +1,221 @@
-import {Callout} from "nextra/components";
-
+import { Tabs } from 'nextra/components';
+import { Callout } from "nextra/components";
+import { Widget } from "../../components/widget";
+import LinkBadge from "../../components/link-badge/link-badge";
+import LinkBadgeGroup from "../../components/link-badge/link-badge-group";
# Icon
+An icon that inherits its style from an enclosing, supported widget, such as a [button](/docs/button).
+
+
+
+
+
+
+
+
+
+
+ ```dart
+ Row(
+ children: [
+ FButton.icon(
+ style: FButtonStyle.primary,
+ child: FIcon(FAssets.icons.bird),
+ onPress: () {},
+ ),
+ const SizedBox(width: 10),
+ FButton.icon(
+ style: FButtonStyle.secondary,
+ child: FIcon(FAssets.icons.bird),
+ onPress: () {},
+ ),
+ ]
+ );
+ ```
+
+
-Forui Icons is flutter library that provides a set of high-quality icons from [Lucide](https://lucide.dev/).
-
-
-## Installation
-
- Forui Icons is bundled with forui package. You don't need to install it separately if you installed `forui`.
-
+## Usage
-From your Flutter project directory, run the following command to install Forui Icon.
+### `FIcon(...)`
-```bash filename="bash"
-flutter pub install forui_assets
+```dart
+FIcon(
+ FAssets.icons.bird,
+ color: Colors.red,
+ size: 24,
+ semanticLabel: 'Label',
+);
```
-
-## Usage
+### `FIcon.data(...)`
- The best way to find a list of icons is to visit the [Lucide](https://lucide.dev/icons/) website.
- We periodically update the icons in the Forui Icons package.
- If you notice a missing icon in Forui Icons, please [open an issue](https://github.com/forus-labs/forui/issues/new).
+ This should be used with icons in other libraries, such as Cupertino and Material, that embed their icons as
+ `IconData`s.
```dart
-import 'package:forui/forui.dart';
+FIcon.data(
+ Icons.abc,
+ color: Colors.red,
+ size: 24,
+ semanticLabel: 'Label',
+);
+```
+
+### `FIcon.image(...)`
-// alternatively; if you've only installed forui_assets.
-import 'package:forui_assets/forui_assets.dart';
+```dart
+FIcon.image(
+ NetworkImage('https://raw.githubusercontent.com/forus-labs/forui/main/samples/assets/avatar.png'),
+ color: Colors.red,
+ size: 24,
+ semanticLabel: 'Label',
+);
+```
-// Bird icon as a Widget.
-final birdIconWidget = FAssets.icons.bird();
+### `FIcon.raw(...)`
-// White cat icon with a size of 24x24.
-final catIconWidget = FAssets.icons.cat(
- width: 24,
- height: 24,
- colorFilter: const ColorFilter.mode(Color(0xFFFFFFFF), BlendMode.srcIn),
+```dart
+FIcon.raw(
+ builder: (context, style, child) => Container(
+ color: style.color,
+ height: style.size,
+ width: style.size,
+ child: child!,
+ ),
+ child: const Text('Button'),
);
+```
+
+## Examples
+
+### Bundled/SVG
+
+
+
+
+
+
+ ```dart {3}
+ FButton.icon(
+ style: FButtonStyle.secondary,
+ child: FIcon(FAssets.icons.wifi),
+ onPress: () {},
+ );
+ ```
+
+
+
+### `IconData`
+
+
+
+
+
+
+
+
+ ```dart {3}
+ FButton.icon(
+ style: FButtonStyle.secondary,
+ child: const FIcon.data(Icons.wifi),
+ onPress: () {},
+ );
+ ```
+
+
+
+### Image
+
+
+ It is important that the image's background is transparent. An image with a non-transparent background will be
+ completely filled with a single color.
+
-// Saving an icon to a variable for later use.
-final rabbitSvgAsset = FAssets.icons.rabbit;
-final rabbitIconWidget = rabbitSvgAsset();
-```
\ No newline at end of file
+
+
+
+
+
+ ```dart {7, 14}
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ // Original color
+ FButton.icon(
+ style: FButtonStyle.primary,
+ child: FIcon.image(AssetImage(path('forus-labs.png')), color: Colors.transparent),
+ onPress: () {},
+ ),
+ const SizedBox(width: 10),
+ // Recolored
+ FButton.icon(
+ style: FButtonStyle.primary,
+ child: FIcon.image(AssetImage(path('forus-labs.png'))),
+ onPress: () {},
+ ),
+ ],
+ );
+ ```
+
+
+
+### Raw
+
+
+
+
+
+
+ ```dart {24-35}
+ class _Example extends StatefulWidget {
+ @override
+ State<_Example> createState() => _ExampleState();
+ }
+
+ class _ExampleState extends State<_Example> with SingleTickerProviderStateMixin {
+ late AnimationController controller;
+ late Animation animation;
+
+ @override
+ void initState() {
+ super.initState();
+ controller = AnimationController(
+ vsync: this,
+ duration: const Duration(seconds: 3),
+ )
+ ..forward()
+ ..repeat(reverse: true);
+ animation = Tween(begin: 0.0, end: 1.0).animate(controller);
+ }
+
+ @override
+ Widget build(BuildContext context) => FButton.icon(
+ child: FIcon.raw(builder: (context, style, child) {
+ // You can access widget specific inherited data inside here.
+ final FButtonData(:enabled) = FButtonData.of(context);
+ return enabled ?
+ AnimatedIcon(
+ icon: AnimatedIcons.home_menu,
+ progress: animation,
+ color: style.color,
+ size: style.size,
+ semanticLabel: 'Home menu',
+ ) : const FIcon.data(Icons.menu);
+ }),
+ onPress: () {},
+ );
+
+ @override
+ void dispose() {
+ controller.dispose();
+ super.dispose();
+ }
+ }
+ ```
+
+
\ No newline at end of file
diff --git a/forui/CHANGELOG.md b/forui/CHANGELOG.md
index e6c7c940b..42ec6f9ec 100644
--- a/forui/CHANGELOG.md
+++ b/forui/CHANGELOG.md
@@ -3,8 +3,6 @@
### Additions
* Add `FSlider`
-* Add `FBottomNavigationBarItem.custom(...)`.
-
* Add `FButtonCustomStyle.enabledHoverBoxDecoration`.
* Add `FTextField.contentInsertionConfiguration`.
@@ -16,6 +14,8 @@
* **Breaking** Add `FColorScheme.disabledColorBrightness` - this will only affect users that create a `FColorScheme`
from scratch.
+* Add `FIcon`.
+
### Changes
* Change button to change color when hovering over it.
@@ -37,6 +37,17 @@
* **Breaking** Rename `FTextField.onSave` to `FTextField.onSaved`.
+* **Breaking** Remove FAlertIcon & FAlertIconStyle - use `FIcon` instead.
+
+* **Breaking** Remove FButtonIcon & FAlertIconStyle - use `FIcon` instead.
+
+* * **Breaking** Change FButtonCustomStyle to better represent the style's layout - this will only affect users that
+ create a custom `FButtonCustomStyle`.
+
+* **Breaking** Change `FBottomNavigationBarItem.icon` from `SvgAsset` to `Widget` - wrap the asset in ` FIcon` instead.
+
+* **Breaking** Change `FHeaderAction.icon` from `SvgAsset` to `Widget` - wrap the asset in ` FIcon` instead.
+
### Fixes
* Fix `FBottomNavigationBar` items hit region being smaller than intended.
diff --git a/forui/example/lib/main.dart b/forui/example/lib/main.dart
index 914bf0283..2baf702d2 100644
--- a/forui/example/lib/main.dart
+++ b/forui/example/lib/main.dart
@@ -41,7 +41,7 @@ class _ApplicationState extends State {
title: const Text('Example'),
actions: [
FHeaderAction(
- icon: FAssets.icons.plus,
+ icon: FIcon(FAssets.icons.plus),
onPress: () {},
),
],
@@ -52,23 +52,23 @@ class _ApplicationState extends State {
onChange: (index) => setState(() => this.index = index),
children: [
FBottomNavigationBarItem(
- icon: FAssets.icons.home,
+ icon: FIcon(FAssets.icons.home),
label: const Text('Home'),
),
FBottomNavigationBarItem(
- icon: FAssets.icons.layoutGrid,
+ icon: FIcon(FAssets.icons.layoutGrid),
label: const Text('Categories'),
),
FBottomNavigationBarItem(
- icon: FAssets.icons.search,
+ icon: FIcon(FAssets.icons.search),
label: const Text('Search'),
),
FBottomNavigationBarItem(
- icon: FAssets.icons.settings,
+ icon: FIcon(FAssets.icons.settings),
label: const Text('Settings'),
),
FBottomNavigationBarItem(
- icon: FAssets.icons.castle,
+ icon: FIcon(FAssets.icons.castle),
label: const Text('Sandbox'),
),
],
diff --git a/forui/example/pubspec.yaml b/forui/example/pubspec.yaml
index b29afe800..da90e7713 100644
--- a/forui/example/pubspec.yaml
+++ b/forui/example/pubspec.yaml
@@ -47,7 +47,7 @@ flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
- uses-material-design: false
+ uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
diff --git a/forui/lib/forui.dart b/forui/lib/forui.dart
index 248cc0135..4ceb8001f 100644
--- a/forui/lib/forui.dart
+++ b/forui/lib/forui.dart
@@ -17,6 +17,7 @@ export 'widgets/checkbox.dart';
export 'widgets/dialog.dart';
export 'widgets/divider.dart';
export 'widgets/header.dart';
+export 'widgets/icon.dart';
export 'widgets/label.dart';
export 'widgets/popover.dart';
export 'widgets/progress.dart';
diff --git a/forui/lib/foundation.dart b/forui/lib/foundation.dart
index a614fa2aa..bf706ca5f 100644
--- a/forui/lib/foundation.dart
+++ b/forui/lib/foundation.dart
@@ -1,6 +1,6 @@
/// Low-level utilities and services.
library forui.foundation;
-export 'src/foundation/layout.dart';
+export 'src/foundation/rendering.dart' hide Alignments, RenderBoxes;
export 'src/foundation/portal/portal.dart';
export 'src/foundation/portal/portal_shift.dart';
diff --git a/forui/lib/src/foundation/layout.dart b/forui/lib/src/foundation/layout.dart
deleted file mode 100644
index 95a12e05f..000000000
--- a/forui/lib/src/foundation/layout.dart
+++ /dev/null
@@ -1,19 +0,0 @@
-/// Possible way to layout a sequence of items.
-enum Layout {
- /// Lays out the items horizontally from left to right.
- ltr(vertical: false),
-
- /// Lays out the items horizontally from right to left.
- rtl(vertical: false),
-
- /// Lays out the items vertically from bottom to top.
- ttb(vertical: true),
-
- /// Lays out the items vertically from top to bottom.
- btt(vertical: true);
-
- /// Whether the layout is vertical.
- final bool vertical;
-
- const Layout({required this.vertical});
-}
diff --git a/forui/lib/src/foundation/rendering.dart b/forui/lib/src/foundation/rendering.dart
index 9c987942f..4000c397a 100644
--- a/forui/lib/src/foundation/rendering.dart
+++ b/forui/lib/src/foundation/rendering.dart
@@ -2,6 +2,26 @@ import 'package:flutter/rendering.dart';
import 'package:meta/meta.dart';
+/// Possible way to layout a sequence of items.
+enum Layout {
+ /// Lays out the items horizontally from left to right.
+ ltr(vertical: false),
+
+ /// Lays out the items horizontally from right to left.
+ rtl(vertical: false),
+
+ /// Lays out the items vertically from bottom to top.
+ ttb(vertical: true),
+
+ /// Lays out the items vertically from top to bottom.
+ btt(vertical: true);
+
+ /// Whether the layout is vertical.
+ final bool vertical;
+
+ const Layout({required this.vertical});
+}
+
@internal
extension RenderBoxes on RenderBox {
BoxParentData get data => parentData! as BoxParentData;
diff --git a/forui/lib/src/theme/style.dart b/forui/lib/src/theme/style.dart
index fecd58f8b..834b3999b 100644
--- a/forui/lib/src/theme/style.dart
+++ b/forui/lib/src/theme/style.dart
@@ -4,7 +4,6 @@ import 'package:flutter/widgets.dart';
import 'package:meta/meta.dart';
import 'package:forui/forui.dart';
-import 'package:forui/theme.dart';
/// A set of miscellaneous properties that is part of a [FThemeData].
///
@@ -20,6 +19,9 @@ final class FStyle with Diagnosticable {
/// The style for the form field when it has an error.
final FFormFieldErrorStyle errorFormFieldStyle;
+ /// The icon style.
+ final FIconStyle iconStyle;
+
/// The border radius. Defaults to `BorderRadius.circular(8)`.
final BorderRadius borderRadius;
@@ -37,6 +39,7 @@ final class FStyle with Diagnosticable {
required this.enabledFormFieldStyle,
required this.disabledFormFieldStyle,
required this.errorFormFieldStyle,
+ required this.iconStyle,
this.borderRadius = const BorderRadius.all(Radius.circular(8)),
this.borderWidth = 1,
this.pagePadding = const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
@@ -63,6 +66,10 @@ final class FStyle with Diagnosticable {
errorColor: colorScheme.error,
typography: typography,
),
+ iconStyle: FIconStyle(
+ color: colorScheme.primary,
+ size: 20,
+ ),
);
/// Returns a copy of this [FStyle] with the given properties replaced.
@@ -71,6 +78,7 @@ final class FStyle with Diagnosticable {
FFormFieldStyle? enabledFormFieldStyle,
FFormFieldStyle? disabledFormFieldStyle,
FFormFieldErrorStyle? errorFormFieldStyle,
+ FIconStyle? iconStyle,
BorderRadius? borderRadius,
double? borderWidth,
EdgeInsets? pagePadding,
@@ -79,6 +87,7 @@ final class FStyle with Diagnosticable {
enabledFormFieldStyle: enabledFormFieldStyle ?? this.enabledFormFieldStyle,
disabledFormFieldStyle: disabledFormFieldStyle ?? this.disabledFormFieldStyle,
errorFormFieldStyle: errorFormFieldStyle ?? this.errorFormFieldStyle,
+ iconStyle: iconStyle ?? this.iconStyle,
borderRadius: borderRadius ?? this.borderRadius,
borderWidth: borderWidth ?? this.borderWidth,
pagePadding: pagePadding ?? this.pagePadding,
@@ -91,6 +100,7 @@ final class FStyle with Diagnosticable {
..add(DiagnosticsProperty('enabledFormFieldStyle', enabledFormFieldStyle))
..add(DiagnosticsProperty('disabledFormFieldStyle', disabledFormFieldStyle))
..add(DiagnosticsProperty('errorFormFieldStyle', errorFormFieldStyle))
+ ..add(DiagnosticsProperty('iconStyle', iconStyle))
..add(DiagnosticsProperty('borderRadius', borderRadius, defaultValue: BorderRadius.circular(8)))
..add(DoubleProperty('borderWidth', borderWidth, defaultValue: 1))
..add(DiagnosticsProperty('pagePadding', pagePadding, defaultValue: const EdgeInsets.all(4)));
@@ -104,6 +114,7 @@ final class FStyle with Diagnosticable {
enabledFormFieldStyle == other.enabledFormFieldStyle &&
disabledFormFieldStyle == other.disabledFormFieldStyle &&
errorFormFieldStyle == other.errorFormFieldStyle &&
+ iconStyle == other.iconStyle &&
borderRadius == other.borderRadius &&
borderWidth == other.borderWidth &&
pagePadding == other.pagePadding;
@@ -113,6 +124,7 @@ final class FStyle with Diagnosticable {
enabledFormFieldStyle.hashCode ^
disabledFormFieldStyle.hashCode ^
errorFormFieldStyle.hashCode ^
+ iconStyle.hashCode ^
borderRadius.hashCode ^
borderWidth.hashCode ^
pagePadding.hashCode;
diff --git a/forui/lib/src/widgets/alert/alert.dart b/forui/lib/src/widgets/alert.dart
similarity index 59%
rename from forui/lib/src/widgets/alert/alert.dart
rename to forui/lib/src/widgets/alert.dart
index a3e11e15d..68e1d765b 100644
--- a/forui/lib/src/widgets/alert/alert.dart
+++ b/forui/lib/src/widgets/alert.dart
@@ -14,6 +14,8 @@ import 'package:forui/forui.dart';
/// * [FAlertStyle] for customizing an alert's appearance.
class FAlert extends StatelessWidget {
/// The icon. Defaults to [FAssets.icons.circleAlert].
+ ///
+ /// [icon] is wrapped in [FIconStyle], and therefore works with [FIcon]s.
final Widget icon;
/// The title.
@@ -42,7 +44,7 @@ class FAlert extends StatelessWidget {
this.subtitle,
this.style = FAlertStyle.primary,
super.key,
- }) : icon = icon ?? FAlertIcon(icon: FAssets.icons.circleAlert);
+ }) : icon = icon ?? FIcon(FAssets.icons.circleAlert);
@override
Widget build(BuildContext context) {
@@ -61,7 +63,10 @@ class FAlert extends StatelessWidget {
children: [
Row(
children: [
- InheritedData(style: style, child: icon),
+ FInheritedIconStyle(
+ style: FIconStyle(color: style.iconColor, size: style.iconSize),
+ child: icon,
+ ),
Flexible(
child: Padding(
padding: const EdgeInsets.only(left: 8),
@@ -76,7 +81,7 @@ class FAlert extends StatelessWidget {
if (subtitle != null)
Row(
children: [
- SizedBox(width: style.icon.size),
+ SizedBox(width: style.iconSize),
Flexible(
child: Padding(
padding: const EdgeInsets.only(top: 3, left: 8),
@@ -101,6 +106,84 @@ class FAlert extends StatelessWidget {
}
}
+/// [FAlertCustomStyle]'s style.
+final 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),
+ iconColor: colorScheme.foreground,
+ 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,
+ ),
+ ),
+ destructive = FAlertCustomStyle(
+ padding: const EdgeInsets.fromLTRB(16, 12, 16, 12),
+ iconColor: colorScheme.destructive,
+ 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,
+ ),
+ );
+
+ @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.
+ @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;
+}
+
/// A [FAlert]'s style.
///
/// A style can be either one of the pre-defined styles in [FAlertStyle] or a [FAlertCustomStyle]. The pre-defined
@@ -131,8 +214,16 @@ final class FAlertCustomStyle extends FAlertStyle with Diagnosticable {
/// The padding. Defaults to `const EdgeInsets.all(16)`.
final EdgeInsets padding;
- /// The icon's style.
- final FAlertIconStyle icon;
+ /// The icon's color.
+ ///
+ /// Defaults to 20.
+ final Color iconColor;
+
+ /// The icon's size. Defaults to 20.
+ ///
+ /// ## Contract
+ /// Throws [AssertionError] if `iconSize` is not positive.
+ final double iconSize;
/// The title's [TextStyle].
final TextStyle titleTextStyle;
@@ -143,25 +234,28 @@ final class FAlertCustomStyle extends FAlertStyle with Diagnosticable {
/// Creates a [FAlertCustomStyle].
FAlertCustomStyle({
required this.decoration,
- required this.icon,
+ required this.iconColor,
required this.titleTextStyle,
required this.subtitleTextStyle,
this.padding = const EdgeInsets.all(16),
- });
+ this.iconSize = 20,
+ }) : assert(0 < iconSize, 'iconSize is $iconSize, but it should be positive.');
/// Returns a copy of this [FAlertCustomStyle] with the given properties replaced.
@useResult
FAlertCustomStyle copyWith({
BoxDecoration? decoration,
EdgeInsets? padding,
- FAlertIconStyle? icon,
+ Color? iconColor,
+ double? iconSize,
TextStyle? titleTextStyle,
TextStyle? subtitleTextStyle,
}) =>
FAlertCustomStyle(
decoration: decoration ?? this.decoration,
padding: padding ?? this.padding,
- icon: icon ?? this.icon,
+ iconColor: iconColor ?? this.iconColor,
+ iconSize: iconSize ?? this.iconSize,
titleTextStyle: titleTextStyle ?? this.titleTextStyle,
subtitleTextStyle: subtitleTextStyle ?? this.subtitleTextStyle,
);
@@ -172,7 +266,8 @@ final class FAlertCustomStyle extends FAlertStyle with Diagnosticable {
properties
..add(DiagnosticsProperty('decoration', decoration))
..add(DiagnosticsProperty('padding', padding))
- ..add(DiagnosticsProperty('icon', icon))
+ ..add(DiagnosticsProperty('iconColor', iconColor))
+ ..add(DiagnosticsProperty('iconSize', iconSize))
..add(DiagnosticsProperty('titleTextStyle', titleTextStyle))
..add(DiagnosticsProperty('subtitleTextStyle', subtitleTextStyle));
}
@@ -184,37 +279,16 @@ final class FAlertCustomStyle extends FAlertStyle with Diagnosticable {
runtimeType == other.runtimeType &&
decoration == other.decoration &&
padding == other.padding &&
- icon == other.icon &&
+ iconColor == other.iconColor &&
+ iconSize == other.iconSize &&
titleTextStyle == other.titleTextStyle &&
subtitleTextStyle == other.subtitleTextStyle;
@override
int get hashCode =>
- decoration.hashCode ^ padding.hashCode ^ icon.hashCode ^ titleTextStyle.hashCode ^ subtitleTextStyle.hashCode;
-}
-
-@internal
-class InheritedData extends InheritedWidget {
- @useResult
- static FAlertCustomStyle of(BuildContext context) {
- final theme = context.dependOnInheritedWidgetOfExactType();
- return theme?.style ?? context.theme.alertStyles.primary;
- }
-
- final FAlertCustomStyle style;
-
- const InheritedData({
- required this.style,
- required super.child,
- super.key,
- });
-
- @override
- bool updateShouldNotify(covariant InheritedData old) => style != old.style;
-
- @override
- void debugFillProperties(DiagnosticPropertiesBuilder properties) {
- super.debugFillProperties(properties);
- properties.add(DiagnosticsProperty('style', style));
- }
+ decoration.hashCode ^
+ padding.hashCode ^
+ iconColor.hashCode ^
+ iconSize.hashCode & titleTextStyle.hashCode ^
+ subtitleTextStyle.hashCode;
}
diff --git a/forui/lib/src/widgets/alert/alert_icon.dart b/forui/lib/src/widgets/alert/alert_icon.dart
deleted file mode 100644
index 94766d4cd..000000000
--- a/forui/lib/src/widgets/alert/alert_icon.dart
+++ /dev/null
@@ -1,82 +0,0 @@
-import 'package:flutter/foundation.dart';
-import 'package:flutter/widgets.dart';
-
-import 'package:meta/meta.dart';
-
-import 'package:forui/forui.dart';
-import 'package:forui/src/widgets/alert/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) = InheritedData.of(context);
-
- return this.icon(
- height: icon.size,
- 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 size. Defaults to 20.
- final double size;
-
- /// Creates a [FButtonIconStyle].
- ///
- /// ## Contract
- /// Throws [AssertionError] if:
- /// * `height` <= 0.0
- /// * `height` is Nan
- FAlertIconStyle({
- required this.color,
- this.size = 20,
- }) : assert(0 < size, 'The dimension is $size, but it should be positive.');
-
- /// Returns a copy of this [FAlertIconStyle] with the given properties replaced.
- @useResult
- FAlertIconStyle copyWith({
- Color? color,
- double? size,
- }) =>
- FAlertIconStyle(
- color: color ?? this.color,
- size: size ?? this.size,
- );
-
- @override
- void debugFillProperties(DiagnosticPropertiesBuilder properties) {
- super.debugFillProperties(properties);
- properties
- ..add(ColorProperty('color', color))
- ..add(DoubleProperty('size', size, defaultValue: 20));
- }
-
- @override
- bool operator ==(Object other) =>
- identical(this, other) ||
- other is FButtonIconStyle &&
- runtimeType == other.runtimeType &&
- color == other.enabledColor &&
- size == other.size;
-
- @override
- int get hashCode => color.hashCode ^ size.hashCode;
-}
diff --git a/forui/lib/src/widgets/alert/alert_styles.dart b/forui/lib/src/widgets/alert/alert_styles.dart
deleted file mode 100644
index bad695a77..000000000
--- a/forui/lib/src/widgets/alert/alert_styles.dart
+++ /dev/null
@@ -1,84 +0,0 @@
-import 'package:flutter/foundation.dart';
-import 'package:flutter/widgets.dart';
-
-import 'package:meta/meta.dart';
-
-import 'package:forui/forui.dart';
-
-/// [FAlertCustomStyle]'s style.
-final 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.
- @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/bottom_navigation_bar/bottom_navigation_bar_item.dart b/forui/lib/src/widgets/bottom_navigation_bar/bottom_navigation_bar_item.dart
index f8cf887c7..6830d4de7 100644
--- a/forui/lib/src/widgets/bottom_navigation_bar/bottom_navigation_bar_item.dart
+++ b/forui/lib/src/widgets/bottom_navigation_bar/bottom_navigation_bar_item.dart
@@ -7,35 +7,21 @@ import 'package:forui/forui.dart';
/// A [FBottomNavigationBar] item.
class FBottomNavigationBarItem extends StatelessWidget {
- static ValueWidgetBuilder _icon(SvgAsset icon) => (_, data, __) => icon(
- height: data.itemStyle.iconSize,
- colorFilter: ColorFilter.mode(
- data.selected ? data.itemStyle.activeIconColor : data.itemStyle.inactiveIconColor,
- BlendMode.srcIn,
- ),
- );
-
/// The style.
final FBottomNavigationBarItemStyle? style;
- /// The icon's builder.
- final ValueWidgetBuilder iconBuilder;
+ /// The icon.
+ ///
+ /// [icon] is wrapped in [FIconStyle], and therefore works with [FIcon]s.
+ final Widget icon;
/// The label.
final Widget label;
/// Creates a [FBottomNavigationBarItem].
- FBottomNavigationBarItem({
+ const FBottomNavigationBarItem({
required this.label,
- required SvgAsset icon,
- this.style,
- super.key,
- }) : iconBuilder = _icon(icon);
-
- /// Creates a [FBottomNavigationBarItem] with a custom icon.
- const FBottomNavigationBarItem.custom({
- required this.label,
- required this.iconBuilder,
+ required this.icon,
this.style,
super.key,
});
@@ -51,7 +37,15 @@ class FBottomNavigationBarItem extends StatelessWidget {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
- ExcludeSemantics(child: iconBuilder(context, data, null)),
+ ExcludeSemantics(
+ child: FInheritedIconStyle(
+ style: FIconStyle(
+ color: data.selected ? data.itemStyle.activeIconColor : data.itemStyle.inactiveIconColor,
+ size: data.itemStyle.iconSize,
+ ),
+ child: icon,
+ ),
+ ),
const SizedBox(height: 2),
DefaultTextStyle.merge(
style: selected ? style.activeTextStyle : style.inactiveTextStyle,
@@ -66,9 +60,7 @@ class FBottomNavigationBarItem extends StatelessWidget {
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
- properties
- ..add(DiagnosticsProperty('style', style))
- ..add(ObjectFlagProperty.has('iconBuilder', iconBuilder));
+ properties.add(DiagnosticsProperty('style', style));
}
}
diff --git a/forui/lib/src/widgets/button/button.dart b/forui/lib/src/widgets/button/button.dart
index 7655bd49b..ed3ae3a43 100644
--- a/forui/lib/src/widgets/button/button.dart
+++ b/forui/lib/src/widgets/button/button.dart
@@ -62,14 +62,14 @@ class FButton extends StatelessWidget {
/// Creates a [FButton] that contains a [prefix], [label], and [suffix].
///
+ /// [prefix] and [suffix] are wrapped in [FIconStyle], and therefore works with [FIcon]s.
+ ///
/// The button layout is as follows, assuming the locale is read from left to right:
/// ```
/// |---------------------------------------|
/// | [prefixIcon] [label] [suffixIcon] |
/// |---------------------------------------|
/// ```
- ///
- /// [FButtonIcon] provides a convenient way to transform a bundled SVG icon into a [prefix] and [suffix].
FButton({
required this.onPress,
required Widget label,
@@ -81,13 +81,11 @@ class FButton extends StatelessWidget {
Widget? prefix,
Widget? suffix,
super.key,
- }) : child = Content(
- prefix: prefix,
- suffix: suffix,
- label: label,
- );
+ }) : child = Content(prefix: prefix, suffix: suffix, label: label);
/// Creates a [FButton] that contains only an icon.
+ ///
+ /// [child] is wrapped in [FIconStyle], and therefore works with [FIcon]s.
FButton.icon({
required this.onPress,
required Widget child,
@@ -206,9 +204,6 @@ class FButtonCustomStyle extends FButtonStyle with Diagnosticable {
/// The content's style.
final FButtonContentStyle content;
- /// The icon's style.
- final FButtonIconStyle icon;
-
/// The icon content's style.
final FButtonIconContentStyle iconContent;
@@ -218,8 +213,7 @@ class FButtonCustomStyle extends FButtonStyle with Diagnosticable {
required this.enabledHoverBoxDecoration,
required this.disabledBoxDecoration,
required this.content,
- required this.icon,
- this.iconContent = const FButtonIconContentStyle(),
+ required this.iconContent,
});
/// Returns a copy of this [FButtonCustomStyle] with the given properties replaced.
@@ -229,7 +223,6 @@ class FButtonCustomStyle extends FButtonStyle with Diagnosticable {
BoxDecoration? enabledHoverBoxDecoration,
BoxDecoration? disabledBoxDecoration,
FButtonContentStyle? content,
- FButtonIconStyle? icon,
FButtonIconContentStyle? iconContent,
}) =>
FButtonCustomStyle(
@@ -237,7 +230,6 @@ class FButtonCustomStyle extends FButtonStyle with Diagnosticable {
enabledHoverBoxDecoration: enabledHoverBoxDecoration ?? this.enabledHoverBoxDecoration,
disabledBoxDecoration: disabledBoxDecoration ?? this.disabledBoxDecoration,
content: content ?? this.content,
- icon: icon ?? this.icon,
iconContent: iconContent ?? this.iconContent,
);
@@ -249,7 +241,6 @@ class FButtonCustomStyle extends FButtonStyle with Diagnosticable {
..add(DiagnosticsProperty('enabledHoverBoxDecoration', enabledHoverBoxDecoration))
..add(DiagnosticsProperty('disabledBoxDecoration', disabledBoxDecoration))
..add(DiagnosticsProperty('content', content))
- ..add(DiagnosticsProperty('icon', icon))
..add(DiagnosticsProperty('iconContent', iconContent));
}
@@ -262,7 +253,6 @@ class FButtonCustomStyle extends FButtonStyle with Diagnosticable {
enabledHoverBoxDecoration == other.enabledHoverBoxDecoration &&
disabledBoxDecoration == other.disabledBoxDecoration &&
content == other.content &&
- icon == other.icon &&
iconContent == other.iconContent;
@override
@@ -271,7 +261,6 @@ class FButtonCustomStyle extends FButtonStyle with Diagnosticable {
enabledHoverBoxDecoration.hashCode ^
disabledBoxDecoration.hashCode ^
content.hashCode ^
- icon.hashCode ^
iconContent.hashCode;
}
diff --git a/forui/lib/src/widgets/button/button_content.dart b/forui/lib/src/widgets/button/button_content.dart
index 14b2f0daa..086735f0c 100644
--- a/forui/lib/src/widgets/button/button_content.dart
+++ b/forui/lib/src/widgets/button/button_content.dart
@@ -27,17 +27,23 @@ class Content extends StatelessWidget {
padding: content.padding,
child: DefaultTextStyle.merge(
style: enabled ? content.enabledTextStyle : content.disabledTextStyle,
- child: Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: separate(
- [
- if (prefix != null) prefix!,
- label,
- if (suffix != null) suffix!,
- ],
- by: [
- const SizedBox(width: 10),
- ],
+ child: FInheritedIconStyle(
+ style: FIconStyle(
+ color: enabled ? content.enabledIconColor : content.disabledIconColor,
+ size: content.iconSize,
+ ),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: separate(
+ [
+ if (prefix != null) prefix!,
+ label,
+ if (suffix != null) suffix!,
+ ],
+ by: [
+ const SizedBox(width: 10),
+ ],
+ ),
),
),
),
@@ -53,11 +59,17 @@ class IconContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
- final FButtonData(:style, enabled: _) = FButtonData.of(context);
+ final FButtonData(:style, :enabled) = FButtonData.of(context);
return Padding(
padding: style.iconContent.padding,
- child: child,
+ child: FInheritedIconStyle(
+ style: FIconStyle(
+ color: enabled ? style.iconContent.enabled : style.iconContent.disabled,
+ size: style.iconContent.size,
+ ),
+ child: child,
+ ),
);
}
}
@@ -73,11 +85,23 @@ final class FButtonContentStyle with Diagnosticable {
/// The padding.
final EdgeInsets padding;
+ /// The icon's color when this button is enabled.
+ final Color enabledIconColor;
+
+ /// The icon's color when this button is disabled.
+ final Color disabledIconColor;
+
+ /// The icon's size. Defaults to 20.
+ final double iconSize;
+
/// Creates a [FButtonContentStyle].
FButtonContentStyle({
required this.enabledTextStyle,
required this.disabledTextStyle,
required this.padding,
+ required this.enabledIconColor,
+ required this.disabledIconColor,
+ this.iconSize = 20,
});
/// Creates a [FButtonContentStyle] that inherits its properties from the given [enabled] and [disabled].
@@ -85,11 +109,7 @@ final class FButtonContentStyle with Diagnosticable {
required FTypography typography,
required Color enabled,
required Color disabled,
- }) : padding = const EdgeInsets.symmetric(
- horizontal: 16,
- vertical: 12.5,
- ),
- enabledTextStyle = typography.base.copyWith(
+ }) : enabledTextStyle = typography.base.copyWith(
color: enabled,
fontWeight: FontWeight.w500,
height: 1,
@@ -98,7 +118,14 @@ final class FButtonContentStyle with Diagnosticable {
color: disabled,
fontWeight: FontWeight.w500,
height: 1,
- );
+ ),
+ padding = const EdgeInsets.symmetric(
+ horizontal: 16,
+ vertical: 12.5,
+ ),
+ enabledIconColor = enabled,
+ disabledIconColor = disabled,
+ iconSize = 20;
/// Returns a copy of this [FButtonContentStyle] with the given properties replaced.
@useResult
@@ -106,11 +133,17 @@ final class FButtonContentStyle with Diagnosticable {
TextStyle? enabledTextStyle,
TextStyle? disabledTextStyle,
EdgeInsets? padding,
+ Color? enabledIconColor,
+ Color? disabledIconColor,
+ double? iconSize,
}) =>
FButtonContentStyle(
enabledTextStyle: enabledTextStyle ?? this.enabledTextStyle,
disabledTextStyle: disabledTextStyle ?? this.disabledTextStyle,
padding: padding ?? this.padding,
+ enabledIconColor: enabledIconColor ?? this.enabledIconColor,
+ disabledIconColor: disabledIconColor ?? this.disabledIconColor,
+ iconSize: iconSize ?? this.iconSize,
);
@override
@@ -119,7 +152,10 @@ final class FButtonContentStyle with Diagnosticable {
properties
..add(DiagnosticsProperty('enabledTextStyle', enabledTextStyle))
..add(DiagnosticsProperty('disabledTextStyle', disabledTextStyle))
- ..add(DiagnosticsProperty('padding', padding));
+ ..add(DiagnosticsProperty('padding', padding))
+ ..add(ColorProperty('enabledColor', enabledIconColor))
+ ..add(ColorProperty('disabledColor', disabledIconColor))
+ ..add(DoubleProperty('size', iconSize, defaultValue: 20));
}
@override
@@ -129,10 +165,19 @@ final class FButtonContentStyle with Diagnosticable {
runtimeType == other.runtimeType &&
enabledTextStyle == other.enabledTextStyle &&
disabledTextStyle == other.disabledTextStyle &&
- padding == other.padding;
+ padding == other.padding &&
+ enabledIconColor == other.enabledIconColor &&
+ disabledIconColor == other.disabledIconColor &&
+ iconSize == other.iconSize;
@override
- int get hashCode => enabledTextStyle.hashCode ^ disabledTextStyle.hashCode ^ padding.hashCode;
+ int get hashCode =>
+ enabledTextStyle.hashCode ^
+ disabledTextStyle.hashCode ^
+ padding.hashCode ^
+ enabledIconColor.hashCode ^
+ disabledIconColor.hashCode ^
+ iconSize.hashCode;
}
/// [FButton] icon content's style.
@@ -140,24 +185,52 @@ final class FButtonIconContentStyle with Diagnosticable {
/// The padding.
final EdgeInsets padding;
+ /// The icon's color when this button is enabled.
+ final Color enabled;
+
+ /// The icon's color when this button is disabled.
+ final Color disabled;
+
+ /// The icon's size. Defaults to 20.
+ final double size;
+
/// Creates a [FButtonIconContentStyle].
- const FButtonIconContentStyle({this.padding = const EdgeInsets.all(7.5)});
+ const FButtonIconContentStyle({
+ required this.enabled,
+ required this.disabled,
+ this.padding = const EdgeInsets.all(7.5),
+ this.size = 20,
+ });
/// Returns a copy of this [FButtonIconContentStyle] with the given properties replaced.
@useResult
- FButtonIconContentStyle copyWith({EdgeInsets? padding}) => FButtonIconContentStyle(padding: padding ?? this.padding);
+ FButtonIconContentStyle copyWith({EdgeInsets? padding}) => FButtonIconContentStyle(
+ padding: padding ?? this.padding,
+ enabled: enabled,
+ disabled: disabled,
+ size: size,
+ );
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
- properties.add(DiagnosticsProperty('padding', padding));
+ properties
+ ..add(DiagnosticsProperty('padding', padding))
+ ..add(ColorProperty('enabledColor', enabled))
+ ..add(ColorProperty('disabledColor', disabled))
+ ..add(DoubleProperty('size', size, defaultValue: 20));
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
- other is FButtonIconContentStyle && runtimeType == other.runtimeType && padding == other.padding;
+ other is FButtonIconContentStyle &&
+ runtimeType == other.runtimeType &&
+ padding == other.padding &&
+ enabled == other.enabled &&
+ disabled == other.disabled &&
+ size == other.size;
@override
- int get hashCode => padding.hashCode;
+ int get hashCode => padding.hashCode ^ enabled.hashCode ^ disabled.hashCode ^ size.hashCode;
}
diff --git a/forui/lib/src/widgets/button/button_icon.dart b/forui/lib/src/widgets/button/button_icon.dart
deleted file mode 100644
index 5e13d94f7..000000000
--- a/forui/lib/src/widgets/button/button_icon.dart
+++ /dev/null
@@ -1,89 +0,0 @@
-import 'package:flutter/foundation.dart';
-import 'package:flutter/widgets.dart';
-
-import 'package:meta/meta.dart';
-
-import 'package:forui/forui.dart';
-
-/// A [FButton]'s icon.
-class FButtonIcon extends StatelessWidget {
- /// The icon.
- final SvgAsset icon;
-
- /// Creates a [FButtonIcon] from the given SVG [icon].
- const FButtonIcon({required this.icon, super.key});
-
- @override
- Widget build(BuildContext context) {
- final FButtonData(style: FButtonCustomStyle(:icon), :enabled) = FButtonData.of(context);
-
- return this.icon(
- height: icon.size,
- colorFilter: ColorFilter.mode(enabled ? icon.enabledColor : icon.disabledColor, BlendMode.srcIn),
- );
- }
-
- @override
- void debugFillProperties(DiagnosticPropertiesBuilder properties) {
- super.debugFillProperties(properties);
- properties.add(DiagnosticsProperty('icon', icon));
- }
-}
-
-/// [FButtonIcon]'s style.
-final class FButtonIconStyle with Diagnosticable {
- /// The icon's color when this button is enabled.
- final Color enabledColor;
-
- /// The icon's color when this button is disabled.
- final Color disabledColor;
-
- /// The icon's size. Defaults to 20.
- final double size;
-
- /// Creates a [FButtonIconStyle].
- ///
- /// ## Contract
- /// Throws [AssertionError] if:
- /// * `size` <= 0.0
- /// * `size` is Nan
- FButtonIconStyle({
- required this.enabledColor,
- required this.disabledColor,
- this.size = 20,
- }) : assert(0 < size, 'The size is $size, but it should be in the range "0 < size".');
-
- /// Returns a copy of this [FBadgeContentStyle] with the given properties replaced.
- @useResult
- FButtonIconStyle copyWith({
- Color? enabledColor,
- Color? disabledColor,
- double? size,
- }) =>
- FButtonIconStyle(
- enabledColor: enabledColor ?? this.enabledColor,
- disabledColor: disabledColor ?? this.disabledColor,
- size: size ?? this.size,
- );
-
- @override
- void debugFillProperties(DiagnosticPropertiesBuilder properties) {
- super.debugFillProperties(properties);
- properties
- ..add(ColorProperty('enabledColor', enabledColor))
- ..add(ColorProperty('disabledColor', disabledColor))
- ..add(DoubleProperty('size', size, defaultValue: 20));
- }
-
- @override
- bool operator ==(Object other) =>
- identical(this, other) ||
- other is FButtonIconStyle &&
- runtimeType == other.runtimeType &&
- enabledColor == other.enabledColor &&
- disabledColor == other.disabledColor &&
- size == other.size;
-
- @override
- int get hashCode => enabledColor.hashCode ^ disabledColor.hashCode ^ size.hashCode;
-}
diff --git a/forui/lib/src/widgets/button/button_styles.dart b/forui/lib/src/widgets/button/button_styles.dart
index 2d4114541..ba28f95bf 100644
--- a/forui/lib/src/widgets/button/button_styles.dart
+++ b/forui/lib/src/widgets/button/button_styles.dart
@@ -48,9 +48,9 @@ final class FButtonStyles with Diagnosticable {
enabled: colorScheme.primaryForeground,
disabled: colorScheme.primaryForeground.withOpacity(0.5),
),
- icon: FButtonIconStyle(
- enabledColor: colorScheme.primaryForeground,
- disabledColor: colorScheme.primaryForeground.withOpacity(0.5),
+ iconContent: FButtonIconContentStyle(
+ enabled: colorScheme.primaryForeground,
+ disabled: colorScheme.primaryForeground.withOpacity(0.5),
),
),
secondary = FButtonCustomStyle(
@@ -71,9 +71,9 @@ final class FButtonStyles with Diagnosticable {
enabled: colorScheme.secondaryForeground,
disabled: colorScheme.secondaryForeground.withOpacity(0.5),
),
- icon: FButtonIconStyle(
- enabledColor: colorScheme.secondaryForeground,
- disabledColor: colorScheme.secondaryForeground.withOpacity(0.5),
+ iconContent: FButtonIconContentStyle(
+ enabled: colorScheme.secondaryForeground,
+ disabled: colorScheme.secondaryForeground.withOpacity(0.5),
),
),
destructive = FButtonCustomStyle(
@@ -94,9 +94,9 @@ final class FButtonStyles with Diagnosticable {
enabled: colorScheme.destructiveForeground,
disabled: colorScheme.destructiveForeground.withOpacity(0.5),
),
- icon: FButtonIconStyle(
- enabledColor: colorScheme.destructiveForeground,
- disabledColor: colorScheme.destructiveForeground.withOpacity(0.5),
+ iconContent: FButtonIconContentStyle(
+ enabled: colorScheme.destructiveForeground,
+ disabled: colorScheme.destructiveForeground.withOpacity(0.5),
),
),
outline = FButtonCustomStyle(
@@ -120,9 +120,9 @@ final class FButtonStyles with Diagnosticable {
enabled: colorScheme.secondaryForeground,
disabled: colorScheme.secondaryForeground.withOpacity(0.5),
),
- icon: FButtonIconStyle(
- enabledColor: colorScheme.secondaryForeground,
- disabledColor: colorScheme.secondaryForeground.withOpacity(0.5),
+ iconContent: FButtonIconContentStyle(
+ enabled: colorScheme.secondaryForeground,
+ disabled: colorScheme.secondaryForeground.withOpacity(0.5),
),
);
diff --git a/forui/lib/src/widgets/header/header_action.dart b/forui/lib/src/widgets/header/header_action.dart
index d18229e75..2ff24130f 100644
--- a/forui/lib/src/widgets/header/header_action.dart
+++ b/forui/lib/src/widgets/header/header_action.dart
@@ -11,7 +11,9 @@ class FHeaderAction extends StatelessWidget {
final String? semanticLabel;
/// The icon.
- final SvgAsset icon;
+ ///
+ /// [icon] is wrapped in [FIconStyle], and therefore works with [FIcon]s.
+ final Widget icon;
/// A callback for when the button is pressed.
///
@@ -41,7 +43,7 @@ class FHeaderAction extends StatelessWidget {
Key? key,
}) =>
FHeaderAction(
- icon: FAssets.icons.arrowLeft,
+ icon: FIcon(FAssets.icons.arrowLeft),
onPress: onPress,
style: style,
semanticLabel: semanticLabel,
@@ -55,7 +57,7 @@ class FHeaderAction extends StatelessWidget {
Key? key,
}) =>
FHeaderAction(
- icon: FAssets.icons.x,
+ icon: FIcon(FAssets.icons.x),
onPress: onPress,
style: style,
key: key,
@@ -70,9 +72,12 @@ class FHeaderAction extends StatelessWidget {
semanticLabel: semanticLabel,
onPress: onPress,
onLongPress: onLongPress,
- child: icon(
- height: style.size,
- colorFilter: ColorFilter.mode(enabled ? style.enabledColor : style.disabledColor, BlendMode.srcIn),
+ child: FInheritedIconStyle(
+ style: FIconStyle(
+ color: enabled ? style.enabledColor : style.disabledColor,
+ size: style.size,
+ ),
+ child: icon,
),
);
}
diff --git a/forui/lib/src/widgets/icon.dart b/forui/lib/src/widgets/icon.dart
new file mode 100644
index 000000000..2bd7ecf59
--- /dev/null
+++ b/forui/lib/src/widgets/icon.dart
@@ -0,0 +1,324 @@
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+
+import 'package:forui/forui.dart';
+
+/// The [FIconStyle] that this [FInheritedIconStyle]'s widget subtree should inherit.
+class FInheritedIconStyle extends InheritedWidget {
+ /// The icon's data.
+ final FIconStyle style;
+
+ /// Creates a [FIconStyle].
+ const FInheritedIconStyle({
+ required this.style,
+ required super.child,
+ super.key,
+ });
+
+ @override
+ bool updateShouldNotify(FInheritedIconStyle old) => style != old.style;
+
+ @override
+ void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+ super.debugFillProperties(properties);
+ properties.add(DiagnosticsProperty('style', style));
+ }
+}
+
+/// The default properties of [FIcon]s in a widget subtree.
+class FIconStyle with Diagnosticable {
+ /// The icon style from the closest instance of [FInheritedIconStyle] that encloses the given context, or
+ /// [FStyle.iconStyle] otherwise.
+ static FIconStyle of(BuildContext context) =>
+ context.dependOnInheritedWidgetOfExactType()?.style ?? context.theme.style.iconStyle;
+
+ /// The icon's color.
+ final Color color;
+
+ /// The icon's size.
+ ///
+ /// ## Contract
+ /// Throws [AssertionError] if `size` is not positive.
+ final double size;
+
+ /// Creates a [FIconStyle].
+ const FIconStyle({
+ required this.color,
+ required this.size,
+ }) : assert(0 < size, 'size is $size, but it should be positive.');
+
+ @override
+ void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+ super.debugFillProperties(properties);
+ properties
+ ..add(ColorProperty('color', color))
+ ..add(DoubleProperty('size', size));
+ }
+}
+
+/// A graphical icon widget that inherits its style from an [FIconStyle], if any.
+///
+/// [FIconStyle] allows icons to be automatically configured by an enclosing widget. It is explicitly mentioned in
+/// a widget's documentation, such as [FButton.icon], if it provides an [FIconStyle]. [FIcon] defaults to
+/// [FStyle.iconStyle] otherwise.
+///
+/// See:
+/// * [FIconStyle] for the properties that can be inherited.
+/// * [FAssets.icons] for bundled Forui icons.
+abstract class FIcon extends StatelessWidget {
+ /// The icon's color.
+ final Color? color;
+
+ /// The icon's size.
+ ///
+ /// ## Contract
+ /// Throws [AssertionError] if `size` <= 0.0
+ final double? size;
+
+ /// The icon's semantic label.
+ final String? semanticLabel;
+
+ /// Creates a [FIcon] from a [SvgAsset].
+ const factory FIcon(
+ SvgAsset icon, {
+ bool matchTextDirection,
+ BoxFit fit,
+ AlignmentGeometry alignment,
+ bool allowDrawingOutsideViewBox,
+ WidgetBuilder? placeholderBuilder,
+ Clip clipBehavior,
+ Color color,
+ double size,
+ String? semanticLabel,
+ Key? key,
+ }) = _Icon;
+
+ /// Creates a [FIcon] from an [IconData].
+ ///
+ /// See [Icon] for more information.
+ const factory FIcon.data(
+ IconData data, {
+ double fill,
+ double weight,
+ double grade,
+ double opticalSize,
+ List shadows,
+ TextDirection textDirection,
+ bool applyTextScaling,
+ Color color,
+ double size,
+ String semanticLabel,
+ Key? key,
+ }) = _IconDataIcon;
+
+ /// Creates a [FIcon] from an [ImageProvider].
+ ///
+ /// **Note:** Provided images should always have a transparent background. Otherwise, the entire icon will be [color].
+ ///
+ /// Set [color] to [Colors.transparent] to avoid recoloring the image.
+ ///
+ /// See [ImageIcon] for more information.
+ const factory FIcon.image(
+ ImageProvider