From 357dfe5dcad6eb551116e765975c336c2ce955cb Mon Sep 17 00:00:00 2001 From: Matthias Ngeo Date: Thu, 3 Oct 2024 23:53:44 +0800 Subject: [PATCH] Introduce FIcon (#210) * Introduce FIcon * Add documentation and tests * Fix failing lint * Fix more failing lints * Commit from GitHub Actions (Forui Presubmit) * Commit from GitHub Actions (Forui Samples Presubmit) * Fix docs * Ops --------- Co-authored-by: Pante --- docs/pages/docs/_meta.json | 2 +- docs/pages/docs/alert.mdx | 2 +- docs/pages/docs/bottom-navigation-bar.mdx | 84 +---- docs/pages/docs/button.mdx | 4 +- docs/pages/docs/icon-library.mdx | 57 +++ docs/pages/docs/icon.mdx | 234 +++++++++++-- forui/CHANGELOG.md | 15 +- forui/example/lib/main.dart | 12 +- forui/example/pubspec.yaml | 2 +- forui/lib/forui.dart | 1 + forui/lib/foundation.dart | 2 +- forui/lib/src/foundation/layout.dart | 19 - forui/lib/src/foundation/rendering.dart | 20 ++ forui/lib/src/theme/style.dart | 14 +- forui/lib/src/widgets/{alert => }/alert.dart | 150 ++++++-- forui/lib/src/widgets/alert/alert_icon.dart | 82 ----- forui/lib/src/widgets/alert/alert_styles.dart | 84 ----- .../bottom_navigation_bar_item.dart | 40 +-- forui/lib/src/widgets/button/button.dart | 23 +- .../src/widgets/button/button_content.dart | 127 +++++-- forui/lib/src/widgets/button/button_icon.dart | 89 ----- .../lib/src/widgets/button/button_styles.dart | 24 +- .../lib/src/widgets/header/header_action.dart | 17 +- forui/lib/src/widgets/icon.dart | 324 ++++++++++++++++++ forui/lib/widgets/alert.dart | 4 +- forui/lib/widgets/button.dart | 1 - forui/lib/widgets/icon.dart | 8 + .../test/golden/icon/icon-data-zinc-dark.png | Bin 0 -> 21192 bytes .../test/golden/icon/icon-data-zinc-light.png | Bin 0 -> 21173 bytes forui/test/golden/icon/icon-style.png | Bin 0 -> 28178 bytes .../golden/icon/image-original-zinc-dark.png | Bin 0 -> 23734 bytes .../golden/icon/image-original-zinc-light.png | Bin 0 -> 23722 bytes .../golden/icon/image-recolored-zinc-dark.png | Bin 0 -> 23751 bytes .../icon/image-recolored-zinc-light.png | Bin 0 -> 23709 bytes forui/test/golden/icon/raw-zinc-dark.png | Bin 0 -> 21045 bytes forui/test/golden/icon/raw-zinc-light.png | Bin 0 -> 21041 bytes .../test/golden/icon/svg-asset-zinc-dark.png | Bin 0 -> 24325 bytes .../test/golden/icon/svg-asset-zinc-light.png | Bin 0 -> 24295 bytes forui/test/resources/forus-labs.png | Bin 0 -> 40712 bytes .../src/widgets/alert/alert_icon_test.dart | 29 -- .../{alert => }/alert_golden_test.dart | 6 +- .../test/src/widgets/avatar_golden_test.dart | 2 +- .../bottom_navigation_bar_golden_test.dart | 55 +-- .../widgets/button/button_golden_test.dart | 25 +- .../src/widgets/button/button_icon_test.dart | 31 -- .../header/nested_header_golden_test.dart | 4 +- .../header/root_header_golden_test.dart | 4 +- forui/test/src/widgets/icon_golden_test.dart | 109 ++++++ samples/assets/forus-labs.png | Bin 0 -> 40712 bytes samples/lib/main.dart | 5 +- samples/lib/widgets/alert.dart | 2 +- .../lib/widgets/bottom_navigation_bar.dart | 82 +---- samples/lib/widgets/button.dart | 4 +- samples/lib/widgets/header.dart | 12 +- samples/lib/widgets/icon.dart | 142 ++++++++ samples/lib/widgets/scaffold.dart | 10 +- samples/pubspec.lock | 12 +- samples/pubspec.yaml | 1 + 58 files changed, 1220 insertions(+), 756 deletions(-) create mode 100644 docs/pages/docs/icon-library.mdx delete mode 100644 forui/lib/src/foundation/layout.dart rename forui/lib/src/widgets/{alert => }/alert.dart (59%) delete mode 100644 forui/lib/src/widgets/alert/alert_icon.dart delete mode 100644 forui/lib/src/widgets/alert/alert_styles.dart delete mode 100644 forui/lib/src/widgets/button/button_icon.dart create mode 100644 forui/lib/src/widgets/icon.dart create mode 100644 forui/lib/widgets/icon.dart create mode 100644 forui/test/golden/icon/icon-data-zinc-dark.png create mode 100644 forui/test/golden/icon/icon-data-zinc-light.png create mode 100644 forui/test/golden/icon/icon-style.png create mode 100644 forui/test/golden/icon/image-original-zinc-dark.png create mode 100644 forui/test/golden/icon/image-original-zinc-light.png create mode 100644 forui/test/golden/icon/image-recolored-zinc-dark.png create mode 100644 forui/test/golden/icon/image-recolored-zinc-light.png create mode 100644 forui/test/golden/icon/raw-zinc-dark.png create mode 100644 forui/test/golden/icon/raw-zinc-light.png create mode 100644 forui/test/golden/icon/svg-asset-zinc-dark.png create mode 100644 forui/test/golden/icon/svg-asset-zinc-light.png create mode 100644 forui/test/resources/forus-labs.png delete mode 100644 forui/test/src/widgets/alert/alert_icon_test.dart rename forui/test/src/widgets/{alert => }/alert_golden_test.dart (91%) delete mode 100644 forui/test/src/widgets/button/button_icon_test.dart create mode 100644 forui/test/src/widgets/icon_golden_test.dart create mode 100644 samples/assets/forus-labs.png create mode 100644 samples/lib/widgets/icon.dart 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 image, { + Color? color, + double? size, + String? semanticLabel, + Key? key, + }) = _ImageProviderIcon; + + /// Creates a [FIcon] from a [ValueWidgetBuilder]. + /// + /// To access widget-specific data, i.e. [FButtonData] inside a [FButton]: + /// ```dart + /// FButton.icon( + /// icon: FIcon.raw( + /// builder: (context, style, _) { + /// final data = FButtonData.of(context); + /// final someWidget = ... // Create someWidget using data here + /// return someWidget; + /// }, + /// ); + /// ``` + const factory FIcon.raw({required ValueWidgetBuilder builder, Widget? child, Key? key}) = _BuilderIcon; + + const FIcon._({required this.color, this.size, this.semanticLabel, super.key}); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(ColorProperty('color', color)) + ..add(DoubleProperty('size', size)) + ..add(StringProperty('semanticLabel', semanticLabel)); + } +} + +class _Icon extends FIcon { + final SvgAsset icon; + final bool matchTextDirection; + final BoxFit fit; + final AlignmentGeometry alignment; + final bool allowDrawingOutsideViewBox; + final WidgetBuilder? placeholderBuilder; + final Clip clipBehavior; + + const _Icon( + this.icon, { + this.matchTextDirection = false, + this.fit = BoxFit.contain, + this.alignment = Alignment.center, + this.allowDrawingOutsideViewBox = false, + this.placeholderBuilder, + this.clipBehavior = Clip.hardEdge, + super.color, + super.size, + super.semanticLabel, + super.key, + }) : super._(); + + @override + Widget build(BuildContext context) { + final data = FIconStyle.of(context); + return icon.call( + matchTextDirection: matchTextDirection, + semanticsLabel: semanticLabel, + fit: fit, + alignment: alignment, + allowDrawingOutsideViewBox: allowDrawingOutsideViewBox, + placeholderBuilder: placeholderBuilder, + excludeFromSemantics: true, + clipBehavior: clipBehavior, + colorFilter: ColorFilter.mode(color ?? data.color, BlendMode.srcIn), + height: size ?? data.size, + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('icon', icon)) + ..add(FlagProperty('matchTextDirection', value: matchTextDirection, ifTrue: 'match text direction')) + ..add(EnumProperty('fit', fit)) + ..add(DiagnosticsProperty('alignment', alignment)) + ..add( + FlagProperty( + 'allowDrawingOutsideViewBox', + value: allowDrawingOutsideViewBox, + ifTrue: 'allow drawing outside view box', + ), + ) + ..add(ObjectFlagProperty.has('placeholderBuilder', placeholderBuilder)) + ..add(StringProperty('semanticLabel', semanticLabel)) + ..add(EnumProperty('clipBehavior', clipBehavior)); + } +} + +class _IconDataIcon extends FIcon { + final IconData icon; + final double? fill; + final double? weight; + final double? grade; + final double? opticalSize; + final List? shadows; + final TextDirection? textDirection; + final bool applyTextScaling; + + const _IconDataIcon( + this.icon, { + this.fill, + this.weight, + this.grade, + this.opticalSize, + this.shadows, + this.textDirection, + this.applyTextScaling = true, + super.color, + super.size, + super.semanticLabel, + super.key, + }) : super._(); + + @override + Widget build(BuildContext context) { + final data = FIconStyle.of(context); + return Icon( + icon, + fill: fill, + weight: weight, + grade: grade, + opticalSize: opticalSize, + shadows: shadows, + semanticLabel: semanticLabel, + textDirection: textDirection, + applyTextScaling: applyTextScaling, + color: color ?? data.color, + size: size ?? data.size, + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('icon', icon)) + ..add(DoubleProperty('fill', fill)) + ..add(DoubleProperty('weight', weight)) + ..add(DoubleProperty('grade', grade)) + ..add(DoubleProperty('opticalSize', opticalSize)) + ..add(IterableProperty('shadows', shadows)) + ..add(StringProperty('semanticLabel', semanticLabel)) + ..add(EnumProperty('direction', textDirection)) + ..add(FlagProperty('applyTextScaling', value: applyTextScaling, ifTrue: 'apply text scaling')); + } +} + +class _ImageProviderIcon extends FIcon { + final ImageProvider image; + + const _ImageProviderIcon( + this.image, { + super.color, + super.size, + super.semanticLabel, + super.key, + }) : super._(); + + @override + Widget build(BuildContext context) { + final data = FIconStyle.of(context); + return Semantics( + label: semanticLabel, + child: Image( + image: image, + height: size ?? data.size, + width: size ?? data.size, + color: color == Colors.transparent ? null : (color ?? data.color), + fit: BoxFit.scaleDown, + excludeFromSemantics: true, + ), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('image', image)); + } +} + +class _BuilderIcon extends FIcon { + final ValueWidgetBuilder builder; + final Widget? child; + + const _BuilderIcon({required this.builder, this.child, super.key}) : super._(color: null, size: null); + + @override + Widget build(BuildContext context) => builder(context, FIconStyle.of(context), child); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(ObjectFlagProperty.has('builder', builder)); + } +} diff --git a/forui/lib/widgets/alert.dart b/forui/lib/widgets/alert.dart index 41d406f60..c1879c0df 100644 --- a/forui/lib/widgets/alert.dart +++ b/forui/lib/widgets/alert.dart @@ -5,6 +5,4 @@ /// See https://forui.dev/docs/alert for working examples. library forui.widgets.alert; -export '../src/widgets/alert/alert.dart' hide InheritedData, Variant; -export '../src/widgets/alert/alert_icon.dart'; -export '../src/widgets/alert/alert_styles.dart'; +export '../src/widgets/alert.dart' hide Variant; diff --git a/forui/lib/widgets/button.dart b/forui/lib/widgets/button.dart index 3bd9c4941..d4bd9edf0 100644 --- a/forui/lib/widgets/button.dart +++ b/forui/lib/widgets/button.dart @@ -7,5 +7,4 @@ library forui.widgets.button; export '../src/widgets/button/button.dart' hide Variant; export '../src/widgets/button/button_content.dart' hide Content, IconContent; -export '../src/widgets/button/button_icon.dart'; export '../src/widgets/button/button_styles.dart'; diff --git a/forui/lib/widgets/icon.dart b/forui/lib/widgets/icon.dart new file mode 100644 index 000000000..19a3ebe05 --- /dev/null +++ b/forui/lib/widgets/icon.dart @@ -0,0 +1,8 @@ +/// {@category Widgets} +/// +/// An icon that may inherit its style from its enclosing widget if supported. +/// +/// See https://forui.dev/docs/icon for working examples. +library forui.widgets.icon; + +export '../src/widgets/icon.dart'; diff --git a/forui/test/golden/icon/icon-data-zinc-dark.png b/forui/test/golden/icon/icon-data-zinc-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..ff2820d85bd7cb287966dea266009f3d08d8d1dc GIT binary patch literal 21192 zcmeHPT}V@582)Bcr>WUWO+cKO`Hl&1ow=crmxE}*cmP74dVoRL}!i=F0A z4P+JO_XOKQ-7yF26j_Je9MgdbhXV*fl@EjfAqczy-arVX08#)cph^I5pg?T2*x={a zvzPA$fF|z2Iols@#QBuHQ_%*6!Ttw#v$7O#n|ASt$$^kfdU zR)?uvlV!lW7rQ?tjeRvw`6`b{GTN)ETHBQD2im+xb480gBH6w>)tQKZ%#Ld<2?Jqo z440b@tJQ-c?03?`GCi$*dG>=R+BMy|*e#?kbLN`(E$na;#^T~)wMW&x8vXc%nGU1T zSfgjV4kvzWwfu)!!<8um57ZI({V*}0E;eR5%8^np?qQW7eR|I{+jIT?3&oQDbCzVfK>?0f9=6w z3oAzKCS4}h!HM8R9771Q18c%o4OOJ1Agjoup)JrBXbXz~oapy6T}Un@mqWnw59%Ud z3)o`Mmc_^CdoAf(#X{$#wkwQFMj(C=KLX{4@ITeCUPp<-x|Hc1HjBhtd@vNoc^&?pIl zBJ4rX2gwX91j~jGr4MR*$h0YHArTh+aTVxh-G9e>U&NF^((ifMx##YlU4HlT`<`=t z=XZB4*KAaVMuieWl%_00J|PMVAt7@42BxL+^=uIHA+zQi^`vb;^N~3Svg%C*a;DVC z%W4UUAtr;apz7YIw>l+>jc~c%`_7jnzf#5C$;*`$YqinLC46}~?~&0udAQfIaA4T5 zo3=Z9ox!vov9+C6NnGG&T0cM+H9>o%Svk_DAIg_ClY@%-!=-@@p%vG| zMs*64>P^)5w7zfeW|F@;f}dK$>Gg&HaeFdL4Aey^B>NLGaWi{|BtRJ?hWOkky@V{6BxGdUY#z6kJyX?(D+C=t29;)YPvIk3V%fhdnW$ z!Cz=D@A9XOjn?d+?r&+iFSHFrUS_|;m1X|!@@%&DI&XCcvSzFL{d3NLE>$ydaT9xJ z&f&0f^^J`?w(a8*G|fH#(#Vc8&q{7IR&?wvXVXh3#;r+%GwJpPkX-6&Nfzf{;IrNz z<04)Kv#>O?#kq2!TzgIrT6id!l`6D_nu36H@qL1oPh=pu6evjU($ikh0$O+rU?dWk zrWlDZ5(&Bxw{#!_w1Ad1O3Q?*tCEZLCH9Wn!@KI@Eow2ww@VC>pjiL_HsK8cAOHj* ztLqI{qI|=IsEzFZ0)R*GxY|5|K`;mg1qKMb0p0*_fHyD}fD}LqAO(;DNP#rC)>`?Q z+mW!`eEvgI8EK?^AemLL#QJ-?0cp(qqhF0DA3PM09-==RXJv1}lxa4!=`EN301t2* AeE?)miI`}>72S&4hCz1Fjye|y$WjQahu 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 literal 0 HcmV?d00001 diff --git a/forui/test/golden/icon/image-recolored-zinc-dark.png b/forui/test/golden/icon/image-recolored-zinc-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..70b1159b1f33c7dbb1f3ba1025992107ded4ec8b GIT binary patch literal 23751 zcmeHPX|kU*dqNEHYoJD4CuMFfJ8eM$1>CZYWL@qOxRJNXdi-sH}mIsbFce`fBGgkx3~ z(qAZifkL6A4#gQ7FmPt5<4*L;hr*;h|&tY+Pfi;=%>yi)F^2WeUnTqJrarQswO%c$2c zVplF6tlju0vvI{fvA`Ya0m@&7Gf&E2^XA)E>Zzb&*7?t zmbbT10;ml`5+-6Ay7~a zPz@*)v;t@a&H$t44%F&uTL$gXLbBbd97_eQg;fENjz>TLU*iujM4W^vU&T{krMOp zM_P9jztmp)2(9hyeDAp$)@HxX*0WOOfxVNed%*YVGl8w^=2(R!IurmF2fxp?hKnq|7Dvsja3zC zCdA3oNQBFc+~Q*NDoIIpe!*Tvua3e5^LsMwo_bm-FV4w6G|I3FGRnA6*4yH}+m$!x zM^Dygt&f}ThD9Xc~IODyO53K2N5|Wm+$bIe%=1)ej*W9Q=U`9Fixi_WC zb)E?fJ`>X2BgJh8-y4XNREZ}>kFW#gM+NfMCZ#6k=H~q7p;Kwo+0kar)|g!6PT zGwjo^lI(4MhTp$$QXFADI{tumF)u@b$K&bjnmCk{oUGhFjqH(qlWZZB$3E-!;YmBn zltjS0lq^C-70YC=k&%KvGbk`>>QPo=e}6w92ksRuzeM|9>ky5RVazU~aPRNnryC8ZNz0e4eNwmEXw|Np+&F#I0fAs$vnb`;kW|F*zxz{*>)mxiV%* zmfC~3 zx8_+BSGQ+BMu3 zVvKgk7%QVE9$x1M}S%@ zvc`zJns|fBwiAHk0C^&j9$G!m%cR*#1{-DARmVg>wr4YgEpPAqPJa8DZ_I2aMxG)U zR|bd-*I)pj9bcEY!J^bYIX=Ebn_UwpL3V2>Cd_nPzNXc%bGIK$iPT{?Z3jL-VP$w) z%%#;rxBif@a*aGD=ku%TEMN(vy#kFX4v%*QRnT&Q_FN-Nso^G?cw5&aq!_0M=;O{v z-w4hm%32x3)c#B=(RS@DVv>Q2a00bbj-*F?R1+!PWRuVl-^6dls}W53GrkjTzM(GT z@0()Nbm1?|nnXJx0IU79Jw?|fnKNXm0QTYJg;QcK_ zo#2mcc2yCl(!<~5ZKWR~t&HRtyG(vOrQ_4{*nm^c9+1tN2?O_+>KyGU^*Ru%=UQ(m zE5b%f+?!DUAKUMOOB+-50&555wqh}*_R)3oCkxsQ=0_fBSXQR$dN9u6#=BgJW)vnN zKHe%%_Sv&%vQPFR3X9!rCWd@h+B~J@gw4qTnyhuYKb7fA=P;|&{70B$G1KSncHEtt zn{#POowhXCy{A&QXOt^N2klEk%wVLq3>`B~@nHYr;G6M~w+ZRK()S|v5XwQ!%c^-k z`Zf93t>mcLlKbtROfP#9cF)AigR-^v-!SBUVGDEf zf&OdPDte_=+J=H$nlnwv7h7|?o(2w{8$|Zozd2;MP;dq5;3p4+y*@p=Hbv#Pdy>yT zA7VbY0}kp+^ubH2E?flS)&fxhez9i5_IST2_AohooE;$GO(Rk`Re-I#v@#Ea!wIg$ z4yW~1>OS#h8YTE4|Bg_jcvfe}j`opYg{IcHi^XScs9X!J9WB>;sTPp&4Y!D61~P5R(n4GwmD!ZXN=e<;Loa5OCz;amUvdN zW@^u)g&C%*SW5M&fmMlIc2M4e0RD)Vy!Mt6^Ig|ypW zQ%v^O3%i7Apkcd%;$(!0np%B*{XmCCvEYvj|{g}&yF1$=(45n8sv zdv{s@GuSD?!S`wKfh)nsfUDb+)-0p?qg82s=P=p3yTtD4k8UGfl9FgsqrI+!3=hOH$-c~OWSoHiA;{>)+*e3dpXegn zQ_>R+a|-Q)ZKC+&f^Rg^{SUMaPd+Q!+OQLtIWQsMn=$dVLQi=0&kxSm%1z3V423yD3A!Kx-H6J~YCNY5KpNcAX#``SO;l|xE68!hoU!Eyn_M2G|=1SAAh zgO~%*H5Y+^_9#LE+9R|_Xpf7o0M!810M!8101$vy0IdL80kndDo)vih@z*H8CXyYZ z9aKUx+z{@|6BglVu{-X>PuitO?z2}d4?&tZ;b3gZeUElBby6)?_ zKXd&%JDYXiZ2Ja5kaf1lEKehdlm~)HNJ@SUu2j?ctH5DJ@M)VPNXcu(A#k!P_=v58 zB=`^|&yx^j2V!e^*a4rx?mD~uAs;DEi9O`Z@m*4io%Oa>ln~3ZH0U?$?2nYxW1hN6L z0YRV?Kq-JyAXWm%2C*Uj`(i^wO~=T}O(~;{x5{^+z^e8`85XkbW1GN7|Xp zJ&;_c_Dyf$X}_W^p^6*JomX4?T}4!nZ)#CH8L(F6paCHuaB+uG`P+X^RI5|I%)Vs5 zN(SnPcV_OCvb8X}NyAOKSPG#?4jBB|zJ+mS2MITCx0Y{3f!zWx{s3O!(`r z{zrVX>vW}sloQ=|yO_@)7j-g=Yc4JVplI%65urx_@ z{xRRG3+L<6qPLCwzTC@?IGL*)9~eEC=VOrG9#ED0Zit;7J2feVwQ|r%x$j8PiR4cj zPQ29hoc&Tq?yT@`didkJyct{6Blr@HmX*Owbm)Id|7egN)E8-y_YnZZV^eBM2wb*e)encasMNpcbD8~+I`5a>0J`|Ew_Q;Xabb0lfftx1;=57;r zERy7PFBsmD2K~hxwHA7}UIqNnlSni*AHTGLcDr^`S;ySGccExc9GI0og`QJdsvR3U z*mP6vgy-*$f}|{p)x94XI_=c>6x`h7z z{xlQbIC#2@z`BN+hBK)9+5gzR!H~FxHu@!?cdxTwT<>X)-|8JtI3L05JVm!o-O4yx4H||%l`2(4*PUw=Dvf5{pO~_&kD-5a<#IJ z6UP%xI3!95|5%Z0jlnjub@-`DXL2}on*{2F;M+!Qa6~2@z{C{- z=dFpetrYb}rz=-hmJw&a%uME&#>?2rB{~;(tb4FN4;T?Vi9+{0e}0$w;%pnuq;EDc z%{($9{1lj38*#8g$Jp4oKkW$G=jYWjLaKf)2p#EYD)*9Y9DJ2t!}wZ4g7u|UJHFzz zaqtn-6z+#x7@BMy{;12vk7soZ47Qe8{9!zpmdgsxo;wKuGhP*iZs|n~<;1=H#^rLW zr@JtX)1%!_7@6)D6O~K`?)aIT5Ao1#!7(dEQg-r5xfZT9buX7X zwh;c%xtK({PxlRaZRFh??>3pkNiKZhP~b3dPkVudXO0L|0caWp-(uYthNGKgFs3>~ zJaj}c(n7g>v&QQjO*<+mS}&c3WOluDJ@8>r$2o6nKr#-_pZx+`7j-n* ztFvMb9m#l)h?gF3Q@rGPOx}b~mC44rB;_K;97|2xsDxnZp}pB^W4x?(Vr^|Lr!uTY zW`pT?j8J7`vhJUn5_)Uq*~1`K?B|duc@OINFE-6TTvIYosrS{pQwC(@J(b|37&+Ol z6Fm6R-9&}*Zm5yNtYQ5wp&Vxve4+Wxo9_-*1O5TDC`=5#BAXGWEzhL_Ru2f~#$-iP zli4ji?jHh@cq)DSs5K_#xLWMBq-<%Y%I?wXh3ReBy=Q8zkWExn47D-fGIksyN$r( zkVm_#nx`1i8r#UUtD#^r>CzsfU6q<^h0^y=S&`ExUCvqSrN5G57-!m{xN2w%@4e{s zuzU21ATz8+$&Vp8-_S@=G6_35W28E8RVFsqx!9U*k@~Y>ewO?2)&^9GJ>E)+$aekG zr6GHEX%^_qbn?wJ8S_#{O`UK~j}#-Ib~e>VZ+EOS=AgOx_JhpHOesd9;Q%SXKeRr* zZ$8g(=wC9di){qAmze~7MtEnDKmSuk#Bc#KR08~}dh}5@Zh=3=^rQRgr|wg_ zNb3STV7T(G17imRqpAx)cDJ=xJM9oUzh{UHgQ9u8@-`d(b zOJ@ZI@SELPfy3ix7BhbpZmYf3v`Q$oyIsN58l;}t7Yn{DE_RKE^$^CW%okXRj;8}# z@@aSUa{&T#06HH0aP%olNw#sIskvFxeKx<{w-Of;qGu4$BR4lq#z=)X$S%7(KdO9-Hdr-&bqa z`z5t5Zfb-Po}@Y-s*!R$BjoLM?l0Q&-HAr;8rCfCrqO6m7$Z{=83azZXH(pX+`2k<7Zfb@WA}!= zZo7tcIs^(IZ{Y$|XRs|>d5S+d)4w5FF4In3RTcF>B+ra{MY(6IjZ&zC7WU`!yzsc3 zv<4+%Iv2PU)akyJ_|Dc;OYmqDPIa!r5241HAU6vpj{3PL5jc<4`Ezr)u7^K@)Xqm$ zn940PWQRKBn2m{7bu>vT{;>-JL>5N;|_T-nVkA?uC~mT%wV2Y;3G2 z9v_quEx4$&f4?-yVUA}Dlg_9Vkz^mu1DLJ~bWcc(I#^OY(1QPP!zQQG-wX%%V82PaT8V4T084rCHkQh_!1?=Y0oH{WX9|Ooy?Yn?h&?a2 z-A$418(LT;h2AD3633a$Adg}CB_~IT-5zH(Quf4o7FMBBgS71s3NqS6juoC1#W3Mn zl>6Ujz46yxEy2+PZ2mnL!qBzQwL-Ps@&Bws1Az&F34tlJ0Qidei_}#iYawg@_pCj8 zjqi^jirVP^0)V^!;_nt92mYQM5G1*p2ulHS9|Fy;F`G6b^}Ci|j-CAKKQF%EWpUVo zHW3*D0s;a8K_D9-8(_!~D*GT6vyAL>pHu-vMLF!D=aEP;3cp2XWS-qAVg#jf(RRlc@0Se#WoMI6%mdrIiOLiuXdYQuI|!IBzTuvZB;}Uld*)7HIH{);a~oF%vP22e zwf5AV>B44v#7K~#Ep_0e1TCTFU< z=092@8#pXThbBS?03n?ZKmZ6rG$0y?U(evYO-}K#+9}^@ey6RW)71m_?XBH;H+%p5%tp<`?f(5j$hugI;p(<4&Xq#0 zC#XgL8aO#4l5yW%ugJ1865nkbvszN^&3Jg>Wb(bD&D6gc0%T!|JQ+!##px_R{j7>0 zLJ$cB5`U3{ffmpLS}X;!uv`v>BCYS>Ei5R~Ijo781!TF%awQAJD5-7VINmoGKw(p= zi%Pc;0zd!=uRjGnyi#ud S&rPa1>rDF9E-jZ#PyPWpBn*B4 literal 0 HcmV?d00001 diff --git a/forui/test/golden/icon/raw-zinc-light.png b/forui/test/golden/icon/raw-zinc-light.png new file mode 100644 index 0000000000000000000000000000000000000000..1a1dd23c7fcf57e618ea8c9438bdfbb77c87e00f GIT binary patch literal 21041 zcmeI4J!lhQ7{{Nhu}x~z#)?WM(vSFo;!v$PIW*xU%@S(uAVTG&opcPO;s=Bd5~-j? z90Z3>-K=pC#0&;In5lG7@dL4_gMv;{1S!G0-23)!5dsE5?srSxd&%8}|NWl-^S;l$ zm+Pb1bhLd}yNEhg<=S1mQKmya{Q~k6UwDB<6(Qd5}qoG z^hw4{j$gd@?QJqD{jo~r)B5GJM_0P~@17hrCWeN3?L*w{dF`E{+w$OuwXtKi9@71V zYGJc($9hV-DFd5!==ShbrA4=k9jA3u=8P@6-N(tb6vzTCngxOgZ&55@xg-mP0y&hS zg+hT57Xwj+0z+tK777I>T+Bli3QW(bS|}7KEc~y9h0>cxlTPiekgUDRy`G34j(Dl= zwpV@|q}xRuO+TY{p;tKr++%D`lNH!w`8u{SJ`g*58&jE-u2CY zUywzsA`Qs$<;;cq?-LtGWIsc%7Nhuwzy~m`P$Pgf$86CY&0PhC8%WL20xi(uDDW5K zTY`unB8Uhgk><~x@fN%VZ^2unx16unSFWx w@AYME)~%;&@%+v-i%Q+V3f@}=-1&cd=TGha(Gyu%nLX!RHgh6tE~c!R-)z{-Jpcdz literal 0 HcmV?d00001 diff --git a/forui/test/golden/icon/svg-asset-zinc-dark.png b/forui/test/golden/icon/svg-asset-zinc-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..0b50611dc376ea5042fecf3fe61b085ef9ce50b1 GIT binary patch literal 24325 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 literal 0 HcmV?d00001 diff --git a/forui/test/golden/icon/svg-asset-zinc-light.png b/forui/test/golden/icon/svg-asset-zinc-light.png new file mode 100644 index 0000000000000000000000000000000000000000..b3f0851e72a87a3e2a092494453ea0559d745478 GIT binary patch literal 24295 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 ahVPOSP*cC4yCR|Q3{RWr6Hi_F<$nP6ESQ!6 literal 0 HcmV?d00001 diff --git a/forui/test/resources/forus-labs.png b/forui/test/resources/forus-labs.png new file mode 100644 index 0000000000000000000000000000000000000000..f69658a2fc723d18cab83cf726853aeb0c577e7c GIT binary patch literal 40712 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 literal 0 HcmV?d00001 diff --git a/forui/test/src/widgets/alert/alert_icon_test.dart b/forui/test/src/widgets/alert/alert_icon_test.dart deleted file mode 100644 index b94e50592..000000000 --- a/forui/test/src/widgets/alert/alert_icon_test.dart +++ /dev/null @@ -1,29 +0,0 @@ -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( - size: 0, - color: Colors.white, - ), - throwsAssertionError, - ); - }); - - test('valid height', () { - expect( - () => FAlertIconStyle( - size: 1, - color: Colors.white, - ), - returnsNormally, - ); - }); - }); -} diff --git a/forui/test/src/widgets/alert/alert_golden_test.dart b/forui/test/src/widgets/alert_golden_test.dart similarity index 91% rename from forui/test/src/widgets/alert/alert_golden_test.dart rename to forui/test/src/widgets/alert_golden_test.dart index a2ff1a053..e3cd5b263 100644 --- a/forui/test/src/widgets/alert/alert_golden_test.dart +++ b/forui/test/src/widgets/alert_golden_test.dart @@ -6,8 +6,8 @@ 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'; +import 'package:forui/src/widgets/alert.dart'; +import '../test_scaffold.dart'; void main() { group('FAlert', () { @@ -41,7 +41,7 @@ void main() { child: Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: FAlert( - icon: FAlertIcon(icon: FAssets.icons.badgeAlert), + icon: FIcon(FAssets.icons.badgeAlert), title: const Text('Alert Title'), subtitle: const Text('Alert description with extra text'), style: variant, diff --git a/forui/test/src/widgets/avatar_golden_test.dart b/forui/test/src/widgets/avatar_golden_test.dart index 4c90f00b5..7e5be8f04 100644 --- a/forui/test/src/widgets/avatar_golden_test.dart +++ b/forui/test/src/widgets/avatar_golden_test.dart @@ -37,6 +37,7 @@ void main() { await tester.pumpAndSettle(); } }); + await expectLater( find.byType(TestScaffold), matchesGoldenFile('avatar/$name-with-image.png'), @@ -45,7 +46,6 @@ void main() { /// We will not be testing for the fallback behavior due to this issue on flutter /// https://github.com/flutter/flutter/issues/107416 - testWidgets('$name with raw content', (tester) async { await tester.pumpWidget( TestScaffold( diff --git a/forui/test/src/widgets/bottom_navigation_bar_golden_test.dart b/forui/test/src/widgets/bottom_navigation_bar_golden_test.dart index 28d3bcfd1..d04ae1413 100644 --- a/forui/test/src/widgets/bottom_navigation_bar_golden_test.dart +++ b/forui/test/src/widgets/bottom_navigation_bar_golden_test.dart @@ -20,23 +20,23 @@ void main() { index: 2, 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.radio), label: const Text('Library'), ), FBottomNavigationBarItem( - icon: FAssets.icons.search, + icon: FIcon(FAssets.icons.radio), label: const Text('Search'), ), ], @@ -49,51 +49,6 @@ void main() { matchesGoldenFile('bottom-navigation-bar/forui-icon-$name.png'), ); }); - - testWidgets('custom icon - $name', (tester) async { - Widget icon(BuildContext _, FBottomNavigationBarData data, Widget? __) => Container( - height: data.itemStyle.iconSize, - width: data.itemStyle.iconSize, - color: data.selected ? data.itemStyle.activeIconColor : data.itemStyle.inactiveIconColor, - ); - - await tester.pumpWidget( - TestScaffold( - data: theme, - background: background, - child: FBottomNavigationBar( - index: 2, - children: [ - FBottomNavigationBarItem.custom( - iconBuilder: icon, - label: const Text('Home'), - ), - FBottomNavigationBarItem.custom( - iconBuilder: icon, - label: const Text('Browse'), - ), - FBottomNavigationBarItem.custom( - iconBuilder: icon, - label: const Text('Radio'), - ), - FBottomNavigationBarItem.custom( - iconBuilder: icon, - label: const Text('Library'), - ), - FBottomNavigationBarItem.custom( - iconBuilder: icon, - label: const Text('Search'), - ), - ], - ), - ), - ); - - await expectLater( - find.byType(TestScaffold), - matchesGoldenFile('bottom-navigation-bar/custom-icon-$name.png'), - ); - }); } }); } diff --git a/forui/test/src/widgets/button/button_golden_test.dart b/forui/test/src/widgets/button/button_golden_test.dart index fa5c969b9..e1970b1b6 100644 --- a/forui/test/src/widgets/button/button_golden_test.dart +++ b/forui/test/src/widgets/button/button_golden_test.dart @@ -23,8 +23,9 @@ void main() { child: FButton( label: const Text('Button'), style: variant, - prefix: FButtonIcon(icon: FAssets.icons.circlePlay), - suffix: FButtonIcon(icon: FAssets.icons.circleStop), + // We don't have access to widget specific fields :( + prefix: FIcon(FAssets.icons.circlePlay), + suffix: FIcon(FAssets.icons.circleStop), onPress: () {}, ), ), @@ -46,8 +47,8 @@ void main() { child: FButton( label: const Text('Button'), style: variant, - prefix: FButtonIcon(icon: FAssets.icons.circlePlay), - suffix: FButtonIcon(icon: FAssets.icons.circleStop), + prefix: FIcon(FAssets.icons.circlePlay), + suffix: FIcon(FAssets.icons.circleStop), onPress: () {}, ), ), @@ -77,8 +78,8 @@ void main() { child: FButton( label: const Text('Button'), style: variant, - prefix: FButtonIcon(icon: FAssets.icons.circlePlay), - suffix: FButtonIcon(icon: FAssets.icons.circleStop), + prefix: FIcon(FAssets.icons.circlePlay), + suffix: FIcon(FAssets.icons.circleStop), onPress: () {}, ), ), @@ -108,8 +109,8 @@ void main() { child: FButton( label: const Text('Button'), style: variant, - prefix: FButtonIcon(icon: FAssets.icons.circlePlay), - suffix: FButtonIcon(icon: FAssets.icons.circleStop), + prefix: FIcon(FAssets.icons.circlePlay), + suffix: FIcon(FAssets.icons.circleStop), onPress: null, ), ), @@ -209,9 +210,7 @@ void main() { child: FButton.icon( onPress: () {}, style: variant, - child: FButtonIcon( - icon: FAssets.icons.chevronRight, - ), + child: FIcon(FAssets.icons.chevronRight), ), ), ), @@ -234,9 +233,7 @@ void main() { child: FButton.icon( onPress: null, style: variant, - child: FButtonIcon( - icon: FAssets.icons.chevronRight, - ), + child: FIcon(FAssets.icons.chevronRight), ), ), ), diff --git a/forui/test/src/widgets/button/button_icon_test.dart b/forui/test/src/widgets/button/button_icon_test.dart deleted file mode 100644 index ef394e8e2..000000000 --- a/forui/test/src/widgets/button/button_icon_test.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:flutter_test/flutter_test.dart'; - -import 'package:forui/forui.dart'; - -void main() { - group('FButtonIconStyle', () { - test('invalid height', () { - expect( - () => FButtonIconStyle( - size: 0, - enabledColor: Colors.white, - disabledColor: Colors.white, - ), - throwsAssertionError, - ); - }); - - test('valid height', () { - expect( - () => FButtonIconStyle( - size: 1, - enabledColor: Colors.white, - disabledColor: Colors.white, - ), - returnsNormally, - ); - }); - }); -} diff --git a/forui/test/src/widgets/header/nested_header_golden_test.dart b/forui/test/src/widgets/header/nested_header_golden_test.dart index bca8ffa74..9a8911280 100644 --- a/forui/test/src/widgets/header/nested_header_golden_test.dart +++ b/forui/test/src/widgets/header/nested_header_golden_test.dart @@ -22,13 +22,13 @@ void main() { leftActions: [ FHeaderAction.back(onPress: () {}), FHeaderAction( - icon: FAssets.icons.alarmClock, + icon: FIcon(FAssets.icons.alarmClock), onPress: null, ), ], rightActions: [ FHeaderAction( - icon: FAssets.icons.plus, + icon: FIcon(FAssets.icons.plus), onPress: () {}, ), FHeaderAction.x(onPress: () {}), diff --git a/forui/test/src/widgets/header/root_header_golden_test.dart b/forui/test/src/widgets/header/root_header_golden_test.dart index bc1645ad4..43c6ab024 100644 --- a/forui/test/src/widgets/header/root_header_golden_test.dart +++ b/forui/test/src/widgets/header/root_header_golden_test.dart @@ -24,11 +24,11 @@ void main() { title: const Text(title), actions: [ FHeaderAction( - icon: FAssets.icons.alarmClock, + icon: FIcon(FAssets.icons.alarmClock), onPress: null, ), FHeaderAction( - icon: FAssets.icons.plus, + icon: FIcon(FAssets.icons.plus), onPress: () {}, ), ], diff --git a/forui/test/src/widgets/icon_golden_test.dart b/forui/test/src/widgets/icon_golden_test.dart new file mode 100644 index 000000000..6d5916815 --- /dev/null +++ b/forui/test/src/widgets/icon_golden_test.dart @@ -0,0 +1,109 @@ +@Tags(['golden']) +library; + +import 'dart:io'; + +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; + +import 'package:forui/forui.dart'; +import '../test_scaffold.dart'; + +void main() { + group('FIcon', () { + testWidgets('with parent IconStyle', (tester) async { + await tester.pumpWidget( + TestScaffold( + data: FThemes.zinc.light, + child: FInheritedIconStyle( + style: const FIconStyle(color: Colors.red, size: 48), + child: FIcon(FAssets.icons.laugh), + ), + ), + ); + + await expectLater(find.byType(TestScaffold), matchesGoldenFile('icon/icon-style.png')); + }); + + for (final (name, theme, _) in TestScaffold.themes) { + testWidgets('$name with SvgAsset', (tester) async { + await tester.pumpWidget( + TestScaffold( + data: theme, + child: FIcon(FAssets.icons.laugh), + ), + ); + + await expectLater(find.byType(TestScaffold), matchesGoldenFile('icon/svg-asset-$name.png')); + }); + + testWidgets('$name with IconData', (tester) async { + await tester.pumpWidget( + TestScaffold( + data: theme, + // This will always be a square since we don't include material's icon in the pubspec.yaml. + child: const FIcon.data(Icons.add), + ), + ); + + await expectLater(find.byType(TestScaffold), matchesGoldenFile('icon/icon-data-$name.png')); + }); + + testWidgets('$name with ImageProvider', (tester) async { + final image = TestScaffold( + data: theme, + child: FIcon.image(FileImage(File('./test/resources/forus-labs.png'))), + ); + + await tester.runAsync(() async { + await tester.pumpWidget(image); + for (final element in find.byType(Image).evaluate()) { + final Image widget = element.widget as Image; + final ImageProvider image = widget.image; + await precacheImage(image, element); + await tester.pumpAndSettle(); + } + }); + + await expectLater(find.byType(TestScaffold), matchesGoldenFile('icon/image-recolored-$name.png')); + }); + + testWidgets('$name with ImageProvider and no recoloring', (tester) async { + final image = TestScaffold( + data: theme, + child: FIcon.image(FileImage(File('./test/resources/forus-labs.png')), color: Colors.transparent), + ); + + await tester.runAsync(() async { + await tester.pumpWidget(image); + for (final element in find.byType(Image).evaluate()) { + final Image widget = element.widget as Image; + final ImageProvider image = widget.image; + await precacheImage(image, element); + await tester.pumpAndSettle(); + } + }); + + await expectLater(find.byType(TestScaffold), matchesGoldenFile('icon/image-original-$name.png')); + }); + + testWidgets('$name with raw builder', (tester) async { + await tester.pumpWidget( + TestScaffold( + data: theme, + child: FIcon.raw( + builder: (context, style, _) => Container( + color: style.color, + height: style.size, + width: style.size, + ), + ), + ), + ); + + await expectLater(find.byType(TestScaffold), matchesGoldenFile('icon/raw-$name.png')); + }); + } + }); +} diff --git a/samples/assets/forus-labs.png b/samples/assets/forus-labs.png new file mode 100644 index 0000000000000000000000000000000000000000..f69658a2fc723d18cab83cf726853aeb0c577e7c GIT binary patch literal 40712 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 literal 0 HcmV?d00001 diff --git a/samples/lib/main.dart b/samples/lib/main.dart index 708d19c20..c937285ea 100644 --- a/samples/lib/main.dart +++ b/samples/lib/main.dart @@ -43,7 +43,6 @@ class _AppRouter extends RootStackRouter { AutoRoute(path: '/avatar/invalid', page: AvatarInvalidRoute.page), AutoRoute(path: '/badge/default', page: BadgeRoute.page), AutoRoute(path: '/bottom-navigation-bar/default', page: BottomNavigationBarRoute.page), - AutoRoute(path: '/bottom-navigation-bar/custom', page: CustomBottomNavigationBarRoute.page), AutoRoute(path: '/button/text', page: ButtonTextRoute.page), AutoRoute(path: '/button/icon', page: ButtonIconRoute.page), AutoRoute(path: '/button/only-icon', page: ButtonOnlyIconRoute.page), @@ -60,6 +59,10 @@ class _AppRouter extends RootStackRouter { AutoRoute(path: '/header/default', page: RootHeaderRoute.page), AutoRoute(path: '/header/nested', page: NestedHeaderRoute.page), AutoRoute(path: '/header/nested-x', page: XNestedHeaderRoute.page), + AutoRoute(path: '/icon/default', page: IconRoute.page), + AutoRoute(path: '/icon/comparison', page: ComparisonIconRoute.page), + AutoRoute(path: '/icon/custom', page: CustomIconRoute.page), + AutoRoute(path: '/icon/image', page: ImageIconRoute.page), AutoRoute(path: '/label/vertical', page: VerticalLabelRoute.page), AutoRoute(path: '/label/horizontal', page: HorizontalLabelRoute.page), AutoRoute(path: '/popover/default', page: PopoverRoute.page), diff --git a/samples/lib/widgets/alert.dart b/samples/lib/widgets/alert.dart index f8b4f87e4..d674ba1aa 100644 --- a/samples/lib/widgets/alert.dart +++ b/samples/lib/widgets/alert.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:auto_route/auto_route.dart'; -import 'package:forui/src/widgets/alert/alert.dart'; +import 'package:forui/src/widgets/alert.dart'; import 'package:forui_samples/sample_scaffold.dart'; diff --git a/samples/lib/widgets/bottom_navigation_bar.dart b/samples/lib/widgets/bottom_navigation_bar.dart index 434ce1407..3f2643180 100644 --- a/samples/lib/widgets/bottom_navigation_bar.dart +++ b/samples/lib/widgets/bottom_navigation_bar.dart @@ -34,95 +34,23 @@ class _DemoState extends State<_Demo> { 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, - label: const Text('Search'), - ), - ], - ); -} - -@RoutePage() -class CustomBottomNavigationBarPage extends SampleScaffold { - CustomBottomNavigationBarPage({ - @queryParam super.theme, - }); - - @override - Widget child(BuildContext context) => const Padding( - padding: EdgeInsets.all(15.0), - child: _CustomDemo(), - ); -} - -class _CustomDemo extends StatefulWidget { - const _CustomDemo(); - - @override - State<_CustomDemo> createState() => _CustomDemoState(); -} - -class _CustomDemoState extends State<_CustomDemo> { - int index = 1; - - @override - Widget build(BuildContext context) => FBottomNavigationBar( - index: index, - onChange: (index) => setState(() => this.index = index), - children: [ - FBottomNavigationBarItem.custom( - iconBuilder: (_, data, __) => Icon( - Icons.home_outlined, - size: data.itemStyle.iconSize, - color: data.selected ? data.itemStyle.activeIconColor : data.itemStyle.inactiveIconColor, - ), - label: const Text('Home'), - ), - FBottomNavigationBarItem.custom( - 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.custom( - iconBuilder: (_, data, __) => Icon( - Icons.radio_outlined, - size: data.itemStyle.iconSize, - color: data.selected ? data.itemStyle.activeIconColor : data.itemStyle.inactiveIconColor, - ), - label: const Text('Radio'), - ), - FBottomNavigationBarItem.custom( - 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.custom( - iconBuilder: (_, data, __) => Icon( - Icons.search_outlined, - size: data.itemStyle.iconSize, - color: data.selected ? data.itemStyle.activeIconColor : data.itemStyle.inactiveIconColor, - ), + icon: FIcon(FAssets.icons.search), label: const Text('Search'), ), ], diff --git a/samples/lib/widgets/button.dart b/samples/lib/widgets/button.dart index fa5d6dc8b..bcf7532b7 100644 --- a/samples/lib/widgets/button.dart +++ b/samples/lib/widgets/button.dart @@ -45,7 +45,7 @@ class ButtonIconPage extends SampleScaffold { @override Widget child(BuildContext context) => IntrinsicWidth( child: FButton( - prefix: FButtonIcon(icon: FAssets.icons.mail), + prefix: FIcon(FAssets.icons.mail), label: const Text('Login with Email'), style: variant, onPress: () {}, @@ -62,7 +62,7 @@ class ButtonOnlyIconPage extends SampleScaffold { @override Widget child(BuildContext context) => IntrinsicWidth( child: FButton.icon( - child: FButtonIcon(icon: FAssets.icons.chevronRight), + child: FIcon(FAssets.icons.chevronRight), onPress: () {}, ), ); diff --git a/samples/lib/widgets/header.dart b/samples/lib/widgets/header.dart index 41412d02c..613ab6f22 100644 --- a/samples/lib/widgets/header.dart +++ b/samples/lib/widgets/header.dart @@ -16,11 +16,11 @@ class RootHeaderPage extends SampleScaffold { title: const Text('Edit Alarm'), actions: [ FHeaderAction( - icon: FAssets.icons.alarmClock, + icon: FIcon(FAssets.icons.alarmClock), onPress: () {}, ), FHeaderAction( - icon: FAssets.icons.plus, + icon: FIcon(FAssets.icons.plus), onPress: () {}, ), ], @@ -41,11 +41,11 @@ class NestedHeaderPage extends SampleScaffold { ], rightActions: [ FHeaderAction( - icon: FAssets.icons.info, + icon: FIcon(FAssets.icons.info), onPress: () {}, ), FHeaderAction( - icon: FAssets.icons.plus, + icon: FIcon(FAssets.icons.plus), onPress: () {}, ), ], @@ -63,11 +63,11 @@ class XNestedHeaderPage extends SampleScaffold { title: const Text('Climate'), leftActions: [ FHeaderAction( - icon: FAssets.icons.thermometer, + icon: FIcon(FAssets.icons.thermometer), onPress: () {}, ), FHeaderAction( - icon: FAssets.icons.wind, + icon: FIcon(FAssets.icons.wind), onPress: null, ), ], diff --git a/samples/lib/widgets/icon.dart b/samples/lib/widgets/icon.dart new file mode 100644 index 000000000..8c8d68e9f --- /dev/null +++ b/samples/lib/widgets/icon.dart @@ -0,0 +1,142 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:auto_route/auto_route.dart'; +import 'package:forui/forui.dart'; + +import 'package:forui_samples/sample_scaffold.dart'; + +String path(String str) => kIsWeb ? 'assets/$str' : str; + +@RoutePage() +class IconPage extends SampleScaffold { + final String variant; + + IconPage({ + @queryParam super.theme, + @queryParam this.variant = 'svg', + }); + + @override + Widget child(BuildContext context) => IntrinsicWidth( + child: FButton.icon( + style: FButtonStyle.secondary, + child: switch (variant) { + 'data' => const FIcon.data(Icons.wifi), + _ => FIcon(FAssets.icons.wifi), + }, + onPress: () {}, + ), + ); +} + +@RoutePage() +class ComparisonIconPage extends SampleScaffold { + ComparisonIconPage({ + @queryParam super.theme, + }); + + @override + Widget child(BuildContext context) => Row( + mainAxisAlignment: MainAxisAlignment.center, + 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: () {}, + ), + ], + ); +} + +@RoutePage() +class ImageIconPage extends SampleScaffold { + ImageIconPage({ + @queryParam super.theme, + }); + + @override + Widget child(BuildContext context) => Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FButton.icon( + style: FButtonStyle.primary, + child: FIcon.image(AssetImage(path('forus-labs.png')), color: Colors.transparent), + onPress: () {}, + ), + const SizedBox(width: 10), + FButton.icon( + style: FButtonStyle.primary, + child: FIcon.image(AssetImage(path('forus-labs.png'))), + onPress: () {}, + ), + ], + ); +} + +@RoutePage() +class CustomIconPage extends SampleScaffold { + CustomIconPage({ + @queryParam super.theme, + }); + + @override + Widget child(BuildContext context) => IntrinsicWidth( + child: FButton.icon( + child: FIcon.raw( + builder: (context, style, child) { + final FButtonData(:enabled) = FButtonData.of(context); + return enabled ? _Icon(style: style) : const FIcon.data(Icons.menu); + }, + ), + onPress: () {}, + ), + ); +} + +class _Icon extends StatefulWidget { + final FIconStyle style; + + const _Icon({required this.style}); + + @override + State<_Icon> createState() => _IconState(); +} + +class _IconState extends State<_Icon> 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) => AnimatedIcon( + icon: AnimatedIcons.home_menu, + progress: animation, + color: widget.style.color, + size: widget.style.size, + semanticLabel: 'Home menu', + ); + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } +} diff --git a/samples/lib/widgets/scaffold.dart b/samples/lib/widgets/scaffold.dart index 217ea121f..cc417ac31 100644 --- a/samples/lib/widgets/scaffold.dart +++ b/samples/lib/widgets/scaffold.dart @@ -31,7 +31,7 @@ final headers = [ title: const Text('Settings'), actions: [ FHeaderAction( - icon: FAssets.icons.ellipsis, + icon: FIcon(FAssets.icons.ellipsis), onPress: () {}, ), ], @@ -99,19 +99,19 @@ class _DemoState extends State<_Demo> { 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'), ), ], diff --git a/samples/pubspec.lock b/samples/pubspec.lock index 24be77b54..33fd75b2c 100644 --- a/samples/pubspec.lock +++ b/samples/pubspec.lock @@ -98,10 +98,10 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: dd09dd4e2b078992f42aac7f1a622f01882a8492fef08486b27ddde929c19f04 + sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" url: "https://pub.dev" source: hosted - version: "2.4.12" + version: "2.4.13" build_runner_core: dependency: transitive description: @@ -436,10 +436,10 @@ packages: dependency: transitive description: name: mime - sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" url: "https://pub.dev" source: hosted - version: "1.0.6" + version: "2.0.0" nitrogen_flutter_svg: dependency: transitive description: @@ -737,10 +737,10 @@ packages: dependency: transitive description: name: web - sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.1.0" web_socket: dependency: transitive description: diff --git a/samples/pubspec.yaml b/samples/pubspec.yaml index 3a33a0c86..79b21a714 100644 --- a/samples/pubspec.yaml +++ b/samples/pubspec.yaml @@ -75,6 +75,7 @@ flutter: assets: - assets/avatar.png + - assets/forus-labs.png # To add custom fonts to your application, add a fonts section here, # in this "flutter" section. Each entry in this list should have a