From 80ca8f665ae23b0ac8447833ab6e6c67d3c0765b Mon Sep 17 00:00:00 2001 From: Emily Pauli Date: Sat, 25 Oct 2025 22:59:48 +0200 Subject: [PATCH] Add Widgetbook setup with theme configuration, analysis options, and initial component use cases --- .../widgetbook/button_group_m3e_usecases.dart | 1 + .../analysis_options.yaml | 8 + .../lib/src/navigation_rail_m3e_widget.dart | 8 +- widgetbook/.gitignore | 45 + widgetbook/.metadata | 45 + widgetbook/README.md | 16 + widgetbook/analysis_options.yaml | 28 + .../lib/app_bar_m3e/INDEX-app_bar_m3e.md | 37 + .../lib/app_bar_m3e/app_bar_m3e_usecases.dart | 191 +++ .../sliver_app_bar_m3e_usecases.dart | 248 ++++ .../INDEX-button_group_m3e.md | 21 + .../button_group_m3e_usecases.dart | 363 ++++++ widgetbook/lib/button_m3e/INDEX-button_m3e.md | 24 + .../lib/button_m3e/button_m3e_usecases.dart | 230 ++++ widgetbook/lib/fab_m3e/INDEX-fab_m3e.md | 44 + .../fab_m3e/extended_fab_m3e_usecases.dart | 126 ++ widgetbook/lib/fab_m3e/fab_m3e_usecases.dart | 131 ++ .../lib/fab_m3e/fab_menu_m3e_usecases.dart | 144 +++ .../icon_button_m3e/INDEX-icon_button_m3e.md | 34 + .../icon_button_m3e_usecases.dart | 516 ++++++++ .../icon_button_m3e/icon_button_usecase.dart | 75 ++ .../INDEX-loading_indicator_m3e.md | 29 + ...expressive_loading_indicator_usecases.dart | 148 +++ .../loading_indicator_m3e_usecases.dart | 184 +++ widgetbook/lib/m3e_design/INDEX-m3e_design.md | 28 + widgetbook/lib/main.dart | 40 + widgetbook/lib/main.directories.g.dart | 1113 +++++++++++++++++ .../INDEX-navigation_bar_m3e.md | 34 + .../navigation_bar_m3e_usecases.dart | 176 +++ .../INDEX-navigation_rail_m3e.md | 35 + .../navigation_rail_m3e_usecases.dart | 203 +++ .../rail_badge_m3e_usecases.dart | 42 + .../rail_item_button_m3e_usecases.dart | 74 ++ .../INDEX-progress_indicator_m3e.md | 29 + .../circular_progress_m3e_usecases.dart | 204 +++ .../linear_progress_m3e_usecases.dart | 188 +++ .../progress_with_label_m3e_usecases.dart | 117 ++ widgetbook/lib/slider_m3e/INDEX-slider_m3e.md | 27 + .../slider_m3e/range_slider_m3e_usecases.dart | 191 +++ .../lib/slider_m3e/slider_m3e_usecases.dart | 186 +++ .../INDEX-split_button_m3e.md | 31 + .../split_button_m3e_usecase.dart | 272 ++++ .../lib/toolbar_m3e/INDEX-toolbar_m3e.md | 43 + .../toolbar_icon_button_m3e_usecases.dart | 74 ++ .../lib/toolbar_m3e/toolbar_m3e_usecases.dart | 192 +++ widgetbook/pubspec.yaml | 38 + 46 files changed, 6031 insertions(+), 2 deletions(-) create mode 100644 packages/button_group_m3e/lib/widgetbook/button_group_m3e_usecases.dart create mode 100644 packages/loading_indicator_m3e/analysis_options.yaml create mode 100644 widgetbook/.gitignore create mode 100644 widgetbook/.metadata create mode 100644 widgetbook/README.md create mode 100644 widgetbook/analysis_options.yaml create mode 100644 widgetbook/lib/app_bar_m3e/INDEX-app_bar_m3e.md create mode 100644 widgetbook/lib/app_bar_m3e/app_bar_m3e_usecases.dart create mode 100644 widgetbook/lib/app_bar_m3e/sliver_app_bar_m3e_usecases.dart create mode 100644 widgetbook/lib/button_group_m3e/INDEX-button_group_m3e.md create mode 100644 widgetbook/lib/button_group_m3e/button_group_m3e_usecases.dart create mode 100644 widgetbook/lib/button_m3e/INDEX-button_m3e.md create mode 100644 widgetbook/lib/button_m3e/button_m3e_usecases.dart create mode 100644 widgetbook/lib/fab_m3e/INDEX-fab_m3e.md create mode 100644 widgetbook/lib/fab_m3e/extended_fab_m3e_usecases.dart create mode 100644 widgetbook/lib/fab_m3e/fab_m3e_usecases.dart create mode 100644 widgetbook/lib/fab_m3e/fab_menu_m3e_usecases.dart create mode 100644 widgetbook/lib/icon_button_m3e/INDEX-icon_button_m3e.md create mode 100644 widgetbook/lib/icon_button_m3e/icon_button_m3e_usecases.dart create mode 100644 widgetbook/lib/icon_button_m3e/icon_button_usecase.dart create mode 100644 widgetbook/lib/loading_indicator_m3e/INDEX-loading_indicator_m3e.md create mode 100644 widgetbook/lib/loading_indicator_m3e/expressive_loading_indicator_usecases.dart create mode 100644 widgetbook/lib/loading_indicator_m3e/loading_indicator_m3e_usecases.dart create mode 100644 widgetbook/lib/m3e_design/INDEX-m3e_design.md create mode 100644 widgetbook/lib/main.dart create mode 100644 widgetbook/lib/main.directories.g.dart create mode 100644 widgetbook/lib/navigation_bar_m3e/INDEX-navigation_bar_m3e.md create mode 100644 widgetbook/lib/navigation_bar_m3e/navigation_bar_m3e_usecases.dart create mode 100644 widgetbook/lib/navigation_rail_m3e/INDEX-navigation_rail_m3e.md create mode 100644 widgetbook/lib/navigation_rail_m3e/navigation_rail_m3e_usecases.dart create mode 100644 widgetbook/lib/navigation_rail_m3e/rail_badge_m3e_usecases.dart create mode 100644 widgetbook/lib/navigation_rail_m3e/rail_item_button_m3e_usecases.dart create mode 100644 widgetbook/lib/progress_indicator_m3e/INDEX-progress_indicator_m3e.md create mode 100644 widgetbook/lib/progress_indicator_m3e/circular_progress_m3e_usecases.dart create mode 100644 widgetbook/lib/progress_indicator_m3e/linear_progress_m3e_usecases.dart create mode 100644 widgetbook/lib/progress_indicator_m3e/progress_with_label_m3e_usecases.dart create mode 100644 widgetbook/lib/slider_m3e/INDEX-slider_m3e.md create mode 100644 widgetbook/lib/slider_m3e/range_slider_m3e_usecases.dart create mode 100644 widgetbook/lib/slider_m3e/slider_m3e_usecases.dart create mode 100644 widgetbook/lib/split_button_m3e/INDEX-split_button_m3e.md create mode 100644 widgetbook/lib/split_button_m3e/split_button_m3e_usecase.dart create mode 100644 widgetbook/lib/toolbar_m3e/INDEX-toolbar_m3e.md create mode 100644 widgetbook/lib/toolbar_m3e/toolbar_icon_button_m3e_usecases.dart create mode 100644 widgetbook/lib/toolbar_m3e/toolbar_m3e_usecases.dart create mode 100644 widgetbook/pubspec.yaml diff --git a/packages/button_group_m3e/lib/widgetbook/button_group_m3e_usecases.dart b/packages/button_group_m3e/lib/widgetbook/button_group_m3e_usecases.dart new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/packages/button_group_m3e/lib/widgetbook/button_group_m3e_usecases.dart @@ -0,0 +1 @@ + diff --git a/packages/loading_indicator_m3e/analysis_options.yaml b/packages/loading_indicator_m3e/analysis_options.yaml new file mode 100644 index 0000000..0e3157a --- /dev/null +++ b/packages/loading_indicator_m3e/analysis_options.yaml @@ -0,0 +1,8 @@ +include: ../../analysis_options.yaml + +analyzer: + exclude: + - "**/*.g.dart" + - "**/*.freezed.dart" + - "lib/widgetbook/**" + - "lib/**/widgetbook/**" diff --git a/packages/navigation_rail_m3e/lib/src/navigation_rail_m3e_widget.dart b/packages/navigation_rail_m3e/lib/src/navigation_rail_m3e_widget.dart index 90694d2..59a0a8f 100644 --- a/packages/navigation_rail_m3e/lib/src/navigation_rail_m3e_widget.dart +++ b/packages/navigation_rail_m3e/lib/src/navigation_rail_m3e_widget.dart @@ -415,8 +415,12 @@ class _NavigationRailM3EState extends State for (final dest in section.destinations) { final index = _destinationIndex(widget.sections, dest); children.add(Padding( - padding: const EdgeInsetsDirectional.only( - start: 16, end: 16, top: 8.0, bottom: 8.0), + padding: EdgeInsetsDirectional.only( + start: 16, + end: 16, + top: theme.itemVerticalGap, + bottom: theme.itemVerticalGap, + ), child: RailItem( destination: dest, selected: index == widget.selectedIndex, diff --git a/widgetbook/.gitignore b/widgetbook/.gitignore new file mode 100644 index 0000000..3820a95 --- /dev/null +++ b/widgetbook/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ +/coverage/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/widgetbook/.metadata b/widgetbook/.metadata new file mode 100644 index 0000000..5f4336f --- /dev/null +++ b/widgetbook/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "adc901062556672b4138e18a4dc62a4be8f4b3c2" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + - platform: android + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + - platform: ios + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + - platform: linux + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + - platform: macos + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + - platform: web + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + - platform: windows + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/widgetbook/README.md b/widgetbook/README.md new file mode 100644 index 0000000..7dfdf9b --- /dev/null +++ b/widgetbook/README.md @@ -0,0 +1,16 @@ +# widgetbook_workspace + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/widgetbook/analysis_options.yaml b/widgetbook/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/widgetbook/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/widgetbook/lib/app_bar_m3e/INDEX-app_bar_m3e.md b/widgetbook/lib/app_bar_m3e/INDEX-app_bar_m3e.md new file mode 100644 index 0000000..886ea94 --- /dev/null +++ b/widgetbook/lib/app_bar_m3e/INDEX-app_bar_m3e.md @@ -0,0 +1,37 @@ +# INDEX — app_bar_m3e + +This index lists all components in the app_bar_m3e package and their Widgetbook variants generated under packages/app_bar_m3e/lib/widgetbook. + +## Components and Variants + +- AppBarM3E + - default + - with_icon + - with_label + - without_label + - small + - medium + - large + - long_text + - many_actions + - shape_family + - semantics_label + +- SliverAppBarM3E + - default + - small + - medium + - large + - pinned + - floating + - snap + - long_text + - many_actions + - shape_family + - density + - semantics_label + +## Notes +- Variants use Widgetbook knobs for critical parameters (title, behavior flags), and visual options (density, shape, colors) according to the plan/guide.md. +- Callbacks emit print statements with clear variant context. +- Complex objects are avoided or provided with sensible defaults; future enhancements can expand these demos. diff --git a/widgetbook/lib/app_bar_m3e/app_bar_m3e_usecases.dart b/widgetbook/lib/app_bar_m3e/app_bar_m3e_usecases.dart new file mode 100644 index 0000000..3db42dc --- /dev/null +++ b/widgetbook/lib/app_bar_m3e/app_bar_m3e_usecases.dart @@ -0,0 +1,191 @@ +import 'package:app_bar_m3e/app_bar_m3e.dart'; +import 'package:flutter/material.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; + +// Helper: build a list of action buttons with informative prints +List _buildActions(int count) { + return List.generate(count, (i) { + return IconButton( + tooltip: 'Action \'${i + 1}\'', + icon: + Icon([Icons.search, Icons.tune, Icons.more_vert, Icons.share][i % 4]), + onPressed: () => print('[AppBarM3E] action ${i + 1} pressed'), + ); + }); +} + +@UseCase(name: 'default', type: AppBarM3E) +Widget buildAppBarM3EDefaultUseCase(BuildContext context) { + final centerTitle = + context.knobs.boolean(label: 'centerTitle', initialValue: false); + final title = context.knobs.string(label: 'title', initialValue: 'App Bar'); + final bg = context.knobs.colorOrNull(label: 'backgroundColor'); + final fg = context.knobs.colorOrNull(label: 'foregroundColor'); + final elevation = context.knobs.doubleOrNull.input(label: 'elevation'); + final shapeFamily = context.knobs.object.dropdown( + label: 'shapeFamily', + initialOption: AppBarM3EShapeFamily.round, + options: const [AppBarM3EShapeFamily.round, AppBarM3EShapeFamily.square], + labelBuilder: (v) => v.name, + ); + final density = context.knobs.object.dropdown( + label: 'density', + initialOption: AppBarM3EDensity.regular, + options: const [AppBarM3EDensity.regular, AppBarM3EDensity.compact], + labelBuilder: (v) => v.name, + ); + final toolbarHeight = + context.knobs.doubleOrNull.input(label: 'toolbarHeight'); + final implyLeading = context.knobs + .boolean(label: 'automaticallyImplyLeading', initialValue: true); + final clip = context.knobs.object.dropdown( + label: 'clipBehavior', + initialOption: Clip.none, + options: const [ + Clip.none, + Clip.hardEdge, + Clip.antiAlias, + Clip.antiAliasWithSaveLayer + ], + labelBuilder: (v) => v.name, + ); + final actionsCount = context.knobs.int + .slider(label: 'actions', initialValue: 1, min: 0, max: 5); + + return AppBarM3E( + leading: IconButton( + icon: const Icon(Icons.menu), + onPressed: () => print('[AppBarM3E] leading pressed'), + ), + titleText: title, + centerTitle: centerTitle, + backgroundColor: bg, + foregroundColor: fg, + elevation: elevation, + shapeFamily: shapeFamily, + density: density, + toolbarHeight: toolbarHeight, + automaticallyImplyLeading: implyLeading, + clipBehavior: clip, + actions: _buildActions(actionsCount), + ); +} + +@UseCase(name: 'with_icon', type: AppBarM3E) +Widget buildAppBarM3EWithIconUseCase(BuildContext context) { + return AppBarM3E( + leading: IconButton( + icon: const Icon(Icons.menu), + onPressed: () => print('[AppBarM3E] menu tapped'), + ), + titleText: context.knobs.string(label: 'title', initialValue: 'With icon'), + actions: [ + IconButton( + icon: const Icon(Icons.search), + onPressed: () => print('[AppBarM3E] search tapped'), + ), + ], + ); +} + +@UseCase(name: 'with_label', type: AppBarM3E) +Widget buildAppBarM3EWithLabelUseCase(BuildContext context) { + return AppBarM3E( + title: Text( + context.knobs.string(label: 'label', initialValue: 'Title Widget'), + overflow: TextOverflow.ellipsis, + ), + centerTitle: + context.knobs.boolean(label: 'centerTitle', initialValue: true), + ); +} + +@UseCase(name: 'without_label', type: AppBarM3E) +Widget buildAppBarM3EWithoutLabelUseCase(BuildContext context) { + return AppBarM3E( + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => print('[AppBarM3E] back tapped'), + ), + actions: _buildActions(context.knobs.int + .slider(label: 'actions', initialValue: 2, min: 0, max: 6)), + ); +} + +@UseCase(name: 'small', type: AppBarM3E) +Widget buildAppBarM3ESmallUseCase(BuildContext context) { + final h = context.knobs.double + .slider(label: 'height', initialValue: 56, min: 40, max: 80); + return AppBarM3E( + titleText: 'Small', + toolbarHeight: h, + ); +} + +@UseCase(name: 'medium', type: AppBarM3E) +Widget buildAppBarM3EMediumUseCase(BuildContext context) { + final h = context.knobs.double + .slider(label: 'height', initialValue: 64, min: 48, max: 96); + return AppBarM3E( + titleText: 'Medium', + toolbarHeight: h, + ); +} + +@UseCase(name: 'large', type: AppBarM3E) +Widget buildAppBarM3ELargeUseCase(BuildContext context) { + final h = context.knobs.double + .slider(label: 'height', initialValue: 72, min: 56, max: 120); + return AppBarM3E( + titleText: 'Large', + toolbarHeight: h, + ); +} + +@UseCase(name: 'long_text', type: AppBarM3E) +Widget buildAppBarM3ELongTextUseCase(BuildContext context) { + final repeat = context.knobs.int + .slider(label: 'repeat', initialValue: 3, min: 1, max: 10); + final base = + context.knobs.string(label: 'base', initialValue: 'A very long title'); + return AppBarM3E( + titleText: List.filled(repeat, base).join(' • '), + centerTitle: + context.knobs.boolean(label: 'centerTitle', initialValue: false), + ); +} + +@UseCase(name: 'many_actions', type: AppBarM3E) +Widget buildAppBarM3EManyActionsUseCase(BuildContext context) { + final count = context.knobs.int + .slider(label: 'actions', initialValue: 4, min: 0, max: 10); + return AppBarM3E( + titleText: 'Many actions', + actions: _buildActions(count), + ); +} + +@UseCase(name: 'shape_family', type: AppBarM3E) +Widget buildAppBarM3EShapeFamilyUseCase(BuildContext context) { + final family = context.knobs.object.dropdown( + label: 'shapeFamily', + initialOption: AppBarM3EShapeFamily.round, + options: const [AppBarM3EShapeFamily.round, AppBarM3EShapeFamily.square], + labelBuilder: (v) => v.name, + ); + return AppBarM3E( + titleText: 'Shape: ${family.name}', + shapeFamily: family, + ); +} + +@UseCase(name: 'semantics_label', type: AppBarM3E) +Widget buildAppBarM3ESemanticsLabelUseCase(BuildContext context) { + final label = context.knobs + .string(label: 'semanticLabel', initialValue: 'Primary application bar'); + return AppBarM3E( + titleText: 'Semantics', + semanticLabel: label, + ); +} diff --git a/widgetbook/lib/app_bar_m3e/sliver_app_bar_m3e_usecases.dart b/widgetbook/lib/app_bar_m3e/sliver_app_bar_m3e_usecases.dart new file mode 100644 index 0000000..4d51282 --- /dev/null +++ b/widgetbook/lib/app_bar_m3e/sliver_app_bar_m3e_usecases.dart @@ -0,0 +1,248 @@ +import 'package:app_bar_m3e/app_bar_m3e.dart'; +import 'package:flutter/material.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; + +List _buildActions(int count) => List.generate(count, (i) { + return IconButton( + tooltip: 'Action \'${i + 1}\'', + icon: Icon( + [Icons.search, Icons.tune, Icons.more_vert, Icons.share][i % 4]), + onPressed: () => print('[SliverAppBarM3E] action ${i + 1} pressed'), + ); + }); + +Widget _demoScrollBody({required SliverAppBarM3E appBar}) { + return CustomScrollView( + slivers: [ + appBar, + SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) => ListTile( + title: Text('Item #${index + 1}'), + onTap: () => print('[Sliver] tapped item ${index + 1}'), + ), + childCount: 30, + ), + ), + ], + ); +} + +@UseCase(name: 'default', type: SliverAppBarM3E) +Widget buildSliverAppBarM3EDefaultUseCase(BuildContext context) { + final title = + context.knobs.string(label: 'title', initialValue: 'Sliver App Bar'); + final centerTitle = + context.knobs.boolean(label: 'centerTitle', initialValue: false); + final pinned = context.knobs.boolean(label: 'pinned', initialValue: true); + final floating = + context.knobs.boolean(label: 'floating', initialValue: false); + final snap = context.knobs.boolean(label: 'snap', initialValue: false); + final variant = context.knobs.object.dropdown( + label: 'variant', + initialOption: AppBarM3EVariant.medium, + options: const [ + AppBarM3EVariant.small, + AppBarM3EVariant.medium, + AppBarM3EVariant.large + ], + labelBuilder: (v) => v.name, + ); + final shapeFamily = context.knobs.object.dropdown( + label: 'shapeFamily', + initialOption: AppBarM3EShapeFamily.round, + options: const [AppBarM3EShapeFamily.round, AppBarM3EShapeFamily.square], + labelBuilder: (v) => v.name, + ); + final density = context.knobs.object.dropdown( + label: 'density', + initialOption: AppBarM3EDensity.regular, + options: const [AppBarM3EDensity.regular, AppBarM3EDensity.compact], + labelBuilder: (v) => v.name, + ); + final bg = context.knobs.colorOrNull(label: 'backgroundColor'); + final fg = context.knobs.colorOrNull(label: 'foregroundColor'); + final actionsCount = context.knobs.int + .slider(label: 'actions', initialValue: 1, min: 0, max: 6); + + final appBar = SliverAppBarM3E( + titleText: title, + centerTitle: centerTitle, + pinned: pinned, + floating: floating, + snap: snap, + variant: variant, + shapeFamily: shapeFamily, + density: density, + backgroundColor: bg, + foregroundColor: fg, + actions: _buildActions(actionsCount), + ); + return _demoScrollBody(appBar: appBar); +} + +@UseCase(name: 'small', type: SliverAppBarM3E) +Widget buildSliverAppBarM3ESmallUseCase(BuildContext context) { + final appBar = SliverAppBarM3E( + titleText: 'Small', + variant: AppBarM3EVariant.small, + ); + return _demoScrollBody(appBar: appBar); +} + +@UseCase(name: 'medium', type: SliverAppBarM3E) +Widget buildSliverAppBarM3EMediumUseCase(BuildContext context) { + final appBar = SliverAppBarM3E( + titleText: 'Medium', + variant: AppBarM3EVariant.medium, + ); + return _demoScrollBody(appBar: appBar); +} + +@UseCase(name: 'large', type: SliverAppBarM3E) +Widget buildSliverAppBarM3ELargeUseCase(BuildContext context) { + final appBar = SliverAppBarM3E( + titleText: 'Large', + variant: AppBarM3EVariant.large, + ); + return _demoScrollBody(appBar: appBar); +} + +@UseCase(name: 'pinned', type: SliverAppBarM3E) +Widget buildSliverAppBarM3EPinnedUseCase(BuildContext context) { + final appBar = SliverAppBarM3E( + titleText: 'Pinned', + pinned: true, + floating: false, + snap: false, + variant: context.knobs.object.dropdown( + label: 'variant', + initialOption: AppBarM3EVariant.medium, + options: const [ + AppBarM3EVariant.small, + AppBarM3EVariant.medium, + AppBarM3EVariant.large + ], + labelBuilder: (v) => v.name, + ), + ); + return _demoScrollBody(appBar: appBar); +} + +@UseCase(name: 'floating', type: SliverAppBarM3E) +Widget buildSliverAppBarM3EFloatingUseCase(BuildContext context) { + final snap = context.knobs.boolean(label: 'snap', initialValue: false); + final appBar = SliverAppBarM3E( + titleText: 'Floating', + pinned: false, + floating: true, + snap: snap, + variant: context.knobs.object.dropdown( + label: 'variant', + initialOption: AppBarM3EVariant.medium, + options: const [ + AppBarM3EVariant.small, + AppBarM3EVariant.medium, + AppBarM3EVariant.large + ], + labelBuilder: (v) => v.name, + ), + ); + return _demoScrollBody(appBar: appBar); +} + +@UseCase(name: 'snap', type: SliverAppBarM3E) +Widget buildSliverAppBarM3ESnapUseCase(BuildContext context) { + final appBar = SliverAppBarM3E( + titleText: 'Snap', + pinned: false, + floating: true, + snap: true, + variant: context.knobs.object.dropdown( + label: 'variant', + initialOption: AppBarM3EVariant.medium, + options: const [ + AppBarM3EVariant.small, + AppBarM3EVariant.medium, + AppBarM3EVariant.large + ], + labelBuilder: (v) => v.name, + ), + ); + return _demoScrollBody(appBar: appBar); +} + +@UseCase(name: 'long_text', type: SliverAppBarM3E) +Widget buildSliverAppBarM3ELongTextUseCase(BuildContext context) { + final repeat = context.knobs.int + .slider(label: 'repeat', initialValue: 3, min: 1, max: 10); + final base = + context.knobs.string(label: 'base', initialValue: 'A very long title'); + final appBar = SliverAppBarM3E( + titleText: List.filled(repeat, base).join(' • '), + variant: context.knobs.object.dropdown( + label: 'variant', + initialOption: AppBarM3EVariant.medium, + options: const [ + AppBarM3EVariant.small, + AppBarM3EVariant.medium, + AppBarM3EVariant.large + ], + labelBuilder: (v) => v.name, + ), + ); + return _demoScrollBody(appBar: appBar); +} + +@UseCase(name: 'many_actions', type: SliverAppBarM3E) +Widget buildSliverAppBarM3EManyActionsUseCase(BuildContext context) { + final count = context.knobs.int + .slider(label: 'actions', initialValue: 4, min: 0, max: 10); + final appBar = SliverAppBarM3E( + titleText: 'Many actions', + actions: _buildActions(count), + ); + return _demoScrollBody(appBar: appBar); +} + +@UseCase(name: 'shape_family', type: SliverAppBarM3E) +Widget buildSliverAppBarM3EShapeFamilyUseCase(BuildContext context) { + final family = context.knobs.object.dropdown( + label: 'shapeFamily', + initialOption: AppBarM3EShapeFamily.round, + options: const [AppBarM3EShapeFamily.round, AppBarM3EShapeFamily.square], + labelBuilder: (v) => v.name, + ); + final appBar = SliverAppBarM3E( + titleText: 'Shape: ' + family.name, + shapeFamily: family, + ); + return _demoScrollBody(appBar: appBar); +} + +@UseCase(name: 'density', type: SliverAppBarM3E) +Widget buildSliverAppBarM3EDensityUseCase(BuildContext context) { + final density = context.knobs.object.dropdown( + label: 'density', + initialOption: AppBarM3EDensity.regular, + options: const [AppBarM3EDensity.regular, AppBarM3EDensity.compact], + labelBuilder: (v) => v.name, + ); + final appBar = SliverAppBarM3E( + titleText: 'Density: ' + density.name, + density: density, + ); + return _demoScrollBody(appBar: appBar); +} + +@UseCase(name: 'semantics_label', type: SliverAppBarM3E) +Widget buildSliverAppBarM3ESemanticsLabelUseCase(BuildContext context) { + final label = context.knobs + .string(label: 'semanticLabel', initialValue: 'Scrollable app bar'); + final appBar = SliverAppBarM3E( + titleText: 'Semantics', + semanticLabel: label, + ); + return _demoScrollBody(appBar: appBar); +} diff --git a/widgetbook/lib/button_group_m3e/INDEX-button_group_m3e.md b/widgetbook/lib/button_group_m3e/INDEX-button_group_m3e.md new file mode 100644 index 0000000..ff97869 --- /dev/null +++ b/widgetbook/lib/button_group_m3e/INDEX-button_group_m3e.md @@ -0,0 +1,21 @@ +# Widgetbook Index — button_group_m3e + +This index summarizes the Widgetbook variants implemented for the `button_group_m3e` package. + +## Components and Variants + +- Component: `ButtonGroupM3E` + - Variants: + - default + - with_icon + - without_label + - connected_with_dividers + - vertical + - wrapped_many_items + - equalized_long_text + - empty + +## Notes +- Variants use Widgetbook knobs for critical and visual parameters as per plan/guide.md. +- Themes are provided by the global Widgetbook app. +- Callbacks print informative messages to the console. diff --git a/widgetbook/lib/button_group_m3e/button_group_m3e_usecases.dart b/widgetbook/lib/button_group_m3e/button_group_m3e_usecases.dart new file mode 100644 index 0000000..5874175 --- /dev/null +++ b/widgetbook/lib/button_group_m3e/button_group_m3e_usecases.dart @@ -0,0 +1,363 @@ +import 'package:flutter/material.dart'; +import 'package:m3e_collection/m3e_collection.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; + +// GENERATED USE CASES for ButtonGroupM3E per plan/guide.md +// Notes: +// - Themes are provided globally by the Widgetbook app. +// - Use knobs for critical and visual params; callbacks print helpful messages. +// - Complex objects get meaningful defaults with TODOs. + +List _demoButtonsWithLabels(BuildContext context) => [ + ButtonM3E( + style: ButtonM3EStyle.elevated, + onPressed: () => debugPrint('Pressed: Primary'), + label: const Text('Elevated'), + ), + ButtonM3E( + style: ButtonM3EStyle.outlined, + onPressed: () => debugPrint('Pressed: Secondary'), + label: const Text('Outlined'), + ), + ButtonM3E( + style: ButtonM3EStyle.text, + onPressed: () => debugPrint('Pressed: Tertiary'), + label: const Text('Textbutton'), + ), + ]; + +List _demoButtonsWithIcons(BuildContext context) => [ + ButtonM3E( + style: ButtonM3EStyle.elevated, + onPressed: () => debugPrint('Pressed: Favorite'), + label: const Text('Favorite'), + icon: const Icon(Icons.favorite_border), + ), + ButtonM3E( + style: ButtonM3EStyle.outlined, + onPressed: () => debugPrint('Pressed: Search'), + label: const Text('Search'), + icon: const Icon(Icons.search), + ), + ButtonM3E( + style: ButtonM3EStyle.text, + onPressed: () => debugPrint('Pressed: Settings'), + label: const Text('Settings'), + icon: const Icon(Icons.settings_outlined), + ), + ]; + +ButtonGroupM3EType _knobType( + BuildContext context, { + ButtonGroupM3EType initial = ButtonGroupM3EType.standard, +}) => + context.knobs.object.dropdown( + label: 'type', + initialOption: initial, + options: ButtonGroupM3EType.values, + labelBuilder: (ButtonGroupM3EType v) => v.name, + ); + +ButtonGroupM3EShape _knobShape( + BuildContext context, { + ButtonGroupM3EShape initial = ButtonGroupM3EShape.round, +}) => + context.knobs.object.dropdown( + label: 'shape', + initialOption: initial, + options: ButtonGroupM3EShape.values, + labelBuilder: (ButtonGroupM3EShape v) => v.name, + ); + +ButtonGroupM3ESize _knobSize( + BuildContext context, { + ButtonGroupM3ESize initial = ButtonGroupM3ESize.md, +}) => + context.knobs.object.dropdown( + label: 'size', + initialOption: initial, + options: ButtonGroupM3ESize.values, + labelBuilder: (ButtonGroupM3ESize v) => v.name, + ); + +ButtonGroupM3EDensity _knobDensity( + BuildContext context, { + ButtonGroupM3EDensity initial = ButtonGroupM3EDensity.regular, +}) => + context.knobs.object.dropdown( + label: 'density', + initialOption: initial, + options: ButtonGroupM3EDensity.values, + labelBuilder: (ButtonGroupM3EDensity v) => v.name, + ); + +Axis _knobDirection(BuildContext context, {Axis initial = Axis.horizontal}) => + context.knobs.object.dropdown( + label: 'direction', + initialOption: initial, + options: const [Axis.horizontal, Axis.vertical], + labelBuilder: (Axis v) => v.name, + ); + +WrapAlignment _knobWrapAlignment( + BuildContext context, { + String label = 'alignment', + WrapAlignment initial = WrapAlignment.start, +}) => + context.knobs.object.dropdown( + label: label, + initialOption: initial, + options: WrapAlignment.values, + labelBuilder: (WrapAlignment v) => v.name, + ); + +WrapCrossAlignment _knobCrossAlignment( + BuildContext context, { + WrapCrossAlignment initial = WrapCrossAlignment.center, +}) => + context.knobs.object.dropdown( + label: 'crossAxisAlignment', + initialOption: initial, + options: WrapCrossAlignment.values, + labelBuilder: (WrapCrossAlignment v) => v.name, + ); + +Clip _knobClipBehavior(BuildContext context, {Clip initial = Clip.none}) => + context.knobs.object.dropdown( + label: 'clipBehavior', + initialOption: initial, + options: Clip.values, + labelBuilder: (Clip v) => v.name, + ); + +@UseCase(name: 'default', type: ButtonGroupM3E) +Widget buildButtonGroupM3EUseCase(BuildContext context) { + final ButtonGroupM3EType type = _knobType(context); + final ButtonGroupM3EShape shape = _knobShape(context); + final ButtonGroupM3ESize size = _knobSize(context); + final ButtonGroupM3EDensity density = _knobDensity(context); + final Axis direction = _knobDirection(context); + final bool wrap = context.knobs.boolean(label: 'wrap', initialValue: false); + final bool showDividers = context.knobs.boolean( + label: 'showDividers (connected only)', + initialValue: false, + ); + final bool equalizeWidths = context.knobs.boolean( + label: 'equalizeWidths', + initialValue: false, + ); + final double spacing = context.knobs.double.slider( + label: 'spacing', + initialValue: 8, + min: 0, + max: 32, + divisions: 32, + ); + final double runSpacing = context.knobs.double.slider( + label: 'runSpacing', + initialValue: 8, + min: 0, + max: 32, + divisions: 32, + ); + final Color? dividerColor = context.knobs.colorOrNull( + label: 'dividerColor', + initialValue: null, + ); + final double dividerThickness = context.knobs.double.slider( + label: 'dividerThickness', + initialValue: 1, + min: 0.5, + max: 2.0, + divisions: 15, + ); + final String? semanticLabel = context.knobs.stringOrNull( + label: 'semanticLabel', + initialValue: 'Action group', + ); + final WrapAlignment alignment = + _knobWrapAlignment(context, label: 'alignment'); + final WrapAlignment runAlignment = + _knobWrapAlignment(context, label: 'runAlignment'); + final WrapCrossAlignment cross = _knobCrossAlignment(context); + final Clip clip = _knobClipBehavior(context); + + final String contentMode = context.knobs.object.dropdown( + label: 'content', + initialOption: 'with_label', + options: const ['with_label', 'with_icon', 'without_label'], + labelBuilder: (String v) => v, + ); + + late final List children; + switch (contentMode) { + case 'with_icon': + children = _demoButtonsWithIcons(context); + break; + default: + children = _demoButtonsWithLabels(context); + } + + return Center( + child: ButtonGroupM3E( + type: type, + shape: shape, + size: size, + density: density, + direction: direction, + wrap: wrap, + spacing: spacing, + runSpacing: runSpacing, + alignment: alignment, + runAlignment: runAlignment, + crossAxisAlignment: cross, + showDividers: showDividers, + dividerColor: dividerColor, + dividerThickness: dividerThickness, + equalizeWidths: equalizeWidths, + semanticLabel: semanticLabel, + clipBehavior: clip, + children: children, + ), + ); +} + +@UseCase(name: 'connected', type: ButtonGroupM3E) +Widget buildButtonGroupM3EConnectedUseCase(BuildContext context) { + return Center( + child: ButtonGroupM3E( + type: ButtonGroupM3EType.connected, + shape: _knobShape(context), + size: _knobSize(context), + density: _knobDensity(context), + direction: _knobDirection(context), + equalizeWidths: context.knobs.boolean( + label: 'equalizeWidths', + initialValue: false, + ), + children: _demoButtonsWithLabels(context), + ), + ); +} + +@UseCase(name: 'vertical', type: ButtonGroupM3E) +Widget buildButtonGroupM3EVerticalUseCase(BuildContext context) { + return Center( + child: ButtonGroupM3E( + direction: Axis.vertical, + type: _knobType(context), + shape: _knobShape(context), + size: _knobSize(context), + density: _knobDensity(context), + spacing: context.knobs.double.slider( + label: 'spacing', + initialValue: 8, + min: 0, + max: 32, + divisions: 32, + ), + children: _demoButtonsWithLabels(context), + ), + ); +} + +@UseCase(name: 'wrapped_many_items', type: ButtonGroupM3E) +Widget buildButtonGroupM3EWrappedManyItemsUseCase(BuildContext context) { + final int count = context.knobs.int.slider( + label: 'item count', + initialValue: 12, + min: 0, + max: 40, + divisions: 40, + ); + final List items = List.generate(count, (int i) { + return OutlinedButton( + onPressed: () => debugPrint('Pressed: Item #$i'), + child: Text('Item $i'), + ); + }); + + return Center( + child: SizedBox( + width: 360, + child: ButtonGroupM3E( + type: _knobType(context), + shape: _knobShape(context), + size: _knobSize(context), + density: _knobDensity(context), + direction: _knobDirection(context), + wrap: true, + spacing: context.knobs.double.slider( + label: 'spacing', + initialValue: 8, + min: 0, + max: 24, + divisions: 24, + ), + runSpacing: context.knobs.double.slider( + label: 'runSpacing', + initialValue: 8, + min: 0, + max: 24, + divisions: 24, + ), + alignment: _knobWrapAlignment(context, label: 'alignment'), + runAlignment: _knobWrapAlignment(context, label: 'runAlignment'), + crossAxisAlignment: _knobCrossAlignment(context), + children: items, + ), + ), + ); +} + +@UseCase(name: 'equalized_long_text', type: ButtonGroupM3E) +Widget buildButtonGroupM3EEqualizedLongTextUseCase(BuildContext context) { + final List children = [ + ElevatedButton( + onPressed: () => debugPrint('Pressed: Very long primary label'), + child: const Text('Very long primary label'), + ), + OutlinedButton( + onPressed: () => debugPrint('Pressed: Short'), + child: const Text('Short'), + ), + TextButton( + onPressed: () => debugPrint('Pressed: Mid length'), + child: const Text('Mid length'), + ), + ]; + + return Center( + child: ButtonGroupM3E( + type: _knobType(context), + shape: _knobShape(context), + size: _knobSize(context), + density: _knobDensity(context), + equalizeWidths: true, + children: children, + ), + ); +} + +@UseCase(name: 'empty', type: ButtonGroupM3E) +Widget buildButtonGroupM3EEmptyUseCase(BuildContext context) { + // Boundary: no children → should layout to zero size. + return const Center( + child: ButtonGroupM3E(children: []), + ); +} + +@UseCase(name: 'with_icon', type: ButtonGroupM3E) +Widget buildButtonGroupM3EWithIconUseCase(BuildContext context) { + return Center( + child: ButtonGroupM3E( + type: _knobType(context), + shape: _knobShape(context), + size: _knobSize(context), + density: _knobDensity(context), + direction: _knobDirection(context), + children: _demoButtonsWithIcons(context), + ), + ); +} diff --git a/widgetbook/lib/button_m3e/INDEX-button_m3e.md b/widgetbook/lib/button_m3e/INDEX-button_m3e.md new file mode 100644 index 0000000..800ed19 --- /dev/null +++ b/widgetbook/lib/button_m3e/INDEX-button_m3e.md @@ -0,0 +1,24 @@ +# Widgetbook Index — button_m3e + +This index lists the ButtonM3E component and all provided Widgetbook variants. + +## Components and Variants + +- ButtonM3E + - default + - filled + - tonal + - elevated + - outlined + - text + - disabled + - focused + - selected + - with_icon + - long_text + - empty_label + +## Notes +- Critical parameters are exposed via knobs: size, shape, enabled, toggleable, selected, label text, icon selection. +- Interaction states can be previewed using knobs and, where needed, simulated via WidgetStatesController (focused, selected). +- Themes are injected globally by the Widgetbook app. diff --git a/widgetbook/lib/button_m3e/button_m3e_usecases.dart b/widgetbook/lib/button_m3e/button_m3e_usecases.dart new file mode 100644 index 0000000..bf9d1d1 --- /dev/null +++ b/widgetbook/lib/button_m3e/button_m3e_usecases.dart @@ -0,0 +1,230 @@ +import 'package:button_m3e/button_m3e.dart'; +import 'package:flutter/material.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; + +// GENERATED USE CASES for ButtonM3E per plan/guide.md +// Notes: +// - Themes are provided globally by the Widgetbook app. +// - Use knobs for critical and visual params; callbacks print helpful messages. +// - Complex objects get meaningful defaults with TODOs. + +Widget _buildDemo( + BuildContext context, { + required ButtonM3EStyle style, + bool? forceEnabled, + bool forceFocused = false, + bool? forceToggleable, + bool? forceSelected, + bool forceEmptyLabel = false, + bool forceLongText = false, +}) { + // Visual/critical knobs + final ButtonM3ESize size = context.knobs.object.dropdown( + label: 'size', + initialOption: ButtonM3ESize.md, + options: ButtonM3ESize.values, + labelBuilder: (v) => v.name, + ); + final ButtonM3EShape shape = context.knobs.object.dropdown( + label: 'shape', + initialOption: ButtonM3EShape.round, + options: ButtonM3EShape.values, + labelBuilder: (v) => v.name, + ); + + final bool enabled = forceEnabled ?? + context.knobs.boolean(label: 'enabled', initialValue: true); + final bool toggleable = forceToggleable ?? + context.knobs.boolean(label: 'toggleable', initialValue: false); + final bool selected = forceSelected ?? + context.knobs.boolean(label: 'selected', initialValue: false); + + final bool withIcon = + context.knobs.boolean(label: 'with_icon', initialValue: false); + final String iconChoice = context.knobs.object.dropdown( + label: 'icon', + initialOption: 'favorite', + options: const ['favorite', 'add', 'download', 'none'], + labelBuilder: (v) => v, + ); + + // Content knobs + String labelText; + if (forceEmptyLabel) { + labelText = ''; + } else if (forceLongText) { + labelText = context.knobs.string( + label: 'label (long)', + initialValue: + 'This is a very long label to test truncation and layout behavior', + ); + } else { + labelText = context.knobs.string(label: 'label', initialValue: 'Button'); + } + + // Misc + final bool smallPaddingDeprecated24 = context.knobs.boolean( + label: 'smallPaddingDeprecated24', + initialValue: false, + ); + + // States controller for focused/selected demos + final statesController = WidgetStatesController(); + if (forceFocused) { + statesController.update(WidgetState.focused, true); + } + if (selected && !toggleable) { + statesController.update(WidgetState.selected, true); + } + + // Compute icon based on knobs + Widget? icon; + if (withIcon && iconChoice != 'none') { + switch (iconChoice) { + case 'favorite': + icon = const Icon(Icons.favorite); + break; + case 'add': + icon = const Icon(Icons.add); + break; + case 'download': + icon = const Icon(Icons.download); + break; + default: + icon = null; + } + } + + return Center( + child: ButtonM3E( + style: style, + size: size, + shape: shape, + enabled: enabled, + toggleable: toggleable, + selected: selected, + onSelectedChange: toggleable + ? (val) => debugPrint('ButtonM3E onSelectedChange: newValue=$val') + : null, + onPressed: () => debugPrint( + 'ButtonM3E onPressed: style=${style.name}, size=${size.name}, shape=${shape.name}'), + label: Text(labelText), + icon: icon, + smallPaddingDeprecated24: smallPaddingDeprecated24, + statesController: statesController, + ), + ); +} + +@UseCase(name: 'default', type: ButtonM3E) +Widget buildButtonM3EUseCase(BuildContext context) { + // Default uses filled style; other params adjustable with knobs + return _buildDemo(context, style: ButtonM3EStyle.filled); +} + +@UseCase(name: 'filled', type: ButtonM3E) +Widget buildButtonM3EFilledUseCase(BuildContext context) { + return _buildDemo(context, style: ButtonM3EStyle.filled); +} + +@UseCase(name: 'tonal', type: ButtonM3E) +Widget buildButtonM3ETonalUseCase(BuildContext context) { + return _buildDemo(context, style: ButtonM3EStyle.tonal); +} + +@UseCase(name: 'elevated', type: ButtonM3E) +Widget buildButtonM3EElevatedUseCase(BuildContext context) { + return _buildDemo(context, style: ButtonM3EStyle.elevated); +} + +@UseCase(name: 'outlined', type: ButtonM3E) +Widget buildButtonM3EOutlinedUseCase(BuildContext context) { + return _buildDemo(context, style: ButtonM3EStyle.outlined); +} + +@UseCase(name: 'text', type: ButtonM3E) +Widget buildButtonM3ETextUseCase(BuildContext context) { + return _buildDemo(context, style: ButtonM3EStyle.text); +} + +@UseCase(name: 'disabled', type: ButtonM3E) +Widget buildButtonM3EDisabledUseCase(BuildContext context) { + // Force disabled regardless of knob default + return _buildDemo(context, style: ButtonM3EStyle.filled, forceEnabled: false); +} + +@UseCase(name: 'focused', type: ButtonM3E) +Widget buildButtonM3EFocusedUseCase(BuildContext context) { + // Preview focused visuals using WidgetStatesController + return _buildDemo(context, style: ButtonM3EStyle.filled, forceFocused: true); +} + +@UseCase(name: 'selected', type: ButtonM3E) +Widget buildButtonM3ESelectedUseCase(BuildContext context) { + // Force toggleable + selected for selection visuals + return _buildDemo( + context, + style: ButtonM3EStyle.filled, + forceToggleable: true, + forceSelected: true, + ); +} + +@UseCase(name: 'with_icon', type: ButtonM3E) +Widget buildButtonM3EWithIconUseCase(BuildContext context) { + // Use knobs to enable icon + return _buildDemo(context, style: ButtonM3EStyle.filled); +} + +@UseCase(name: 'long_text', type: ButtonM3E) +Widget buildButtonM3ELongTextUseCase(BuildContext context) { + return _buildDemo( + context, + style: ButtonM3EStyle.filled, + forceLongText: true, + ); +} + +@UseCase(name: 'empty_label', type: ButtonM3E) +Widget buildButtonM3EEmptyLabelUseCase(BuildContext context) { + // Boundary case: empty label when icon is present or not + return _buildDemo( + context, + style: ButtonM3EStyle.filled, + forceEmptyLabel: true, + ); +} + +@UseCase(name: 'sizes', type: ButtonM3E) +Widget buildButtonM3ESizesUseCase(BuildContext context) { + final ButtonM3EStyle style = context.knobs.object.dropdown( + label: 'style', + initialOption: ButtonM3EStyle.filled, + options: ButtonM3EStyle.values, + labelBuilder: (v) => v.name, + ); + final ButtonM3EShape shape = context.knobs.object.dropdown( + label: 'shape', + initialOption: ButtonM3EShape.round, + options: ButtonM3EShape.values, + labelBuilder: (v) => v.name, + ); + final bool withIcon = + context.knobs.boolean(label: 'with_icon', initialValue: false); + + return Wrap( + spacing: 16, + runSpacing: 16, + children: ButtonM3ESize.values.map((s) { + return ButtonM3E( + style: style, + size: s, + shape: shape, + onPressed: () => debugPrint('Pressed size: ${s.name}'), + icon: withIcon ? const Icon(Icons.check) : null, + label: Text('Size: ${s.name}'), + ); + }).toList(), + ); +} diff --git a/widgetbook/lib/fab_m3e/INDEX-fab_m3e.md b/widgetbook/lib/fab_m3e/INDEX-fab_m3e.md new file mode 100644 index 0000000..25b0768 --- /dev/null +++ b/widgetbook/lib/fab_m3e/INDEX-fab_m3e.md @@ -0,0 +1,44 @@ +# Widgetbook Index — fab_m3e + +This index lists all Widgetbook use cases for the fab_m3e package. + +## Components and Variants + +### FabM3E +- default +- disabled +- small +- large +- secondary +- tertiary +- surface +- square_shape +- compact_density +- focused + +### ExtendedFabM3E +- default +- with_icon +- without_label +- disabled +- expand +- long_text +- secondary +- square_shape +- compact_density + +### FabMenuM3E +- default +- initially_open +- direction_down +- direction_left +- direction_right +- overlay_off +- spacing_tight +- many_items +- empty_items + +Notes: +- Each use case uses Widgetbook knobs for critical and visual parameters. +- Callbacks print informative messages to the console. +- Complex parameters are configured with meaningful defaults; TODOs to expand configuration may be added later as needed. diff --git a/widgetbook/lib/fab_m3e/extended_fab_m3e_usecases.dart b/widgetbook/lib/fab_m3e/extended_fab_m3e_usecases.dart new file mode 100644 index 0000000..191dd02 --- /dev/null +++ b/widgetbook/lib/fab_m3e/extended_fab_m3e_usecases.dart @@ -0,0 +1,126 @@ + + +import 'package:flutter/material.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook; +import 'package:fab_m3e/fab_m3e.dart'; + +Widget _buildExtendedFab( + BuildContext context, { + required FabM3EKind kind, + Widget? icon, + String labelText = 'Create', + FabM3ESize size = FabM3ESize.regular, + FabM3EShapeFamily shape = FabM3EShapeFamily.round, + FabM3EDensity density = FabM3EDensity.regular, + bool enabled = true, + bool expand = false, + double? elevation, + String? tooltip, + Object? heroTag, + String? semanticLabel, +}) { + final selectedKind = context.knobs.object.dropdown( + label: 'kind', + initialOption: kind, + options: FabM3EKind.values, + labelBuilder: (v) => v.name, + ); + final selectedSize = context.knobs.object.dropdown( + label: 'size', + initialOption: size, + options: FabM3ESize.values, + labelBuilder: (v) => v.name, + ); + final selectedShape = context.knobs.object.dropdown( + label: 'shape', + initialOption: shape, + options: FabM3EShapeFamily.values, + labelBuilder: (v) => v.name, + ); + final selectedDensity = context.knobs.object.dropdown( + label: 'density', + initialOption: density, + options: FabM3EDensity.values, + labelBuilder: (v) => v.name, + ); + + final label = context.knobs.string(label: 'label', initialValue: labelText); + final withIcon = context.knobs.boolean(label: 'with_icon', initialValue: icon != null); + final isEnabled = context.knobs.boolean(label: 'enabled', initialValue: enabled); + final shouldExpand = context.knobs.boolean(label: 'expand', initialValue: expand); + final dblElevation = context.knobs.doubleOrNull.slider( + label: 'elevation', + initialValue: elevation, + min: 0.0, + max: 24.0, + divisions: 24, + ); + final tip = context.knobs.stringOrNull(label: 'tooltip', initialValue: tooltip); + final semLabel = context.knobs.stringOrNull(label: 'semanticLabel', initialValue: semanticLabel); + final useHero = context.knobs.boolean(label: 'wrap in Hero', initialValue: heroTag != null); + + final Widget fab = ExtendedFabM3E( + label: Text(label, overflow: TextOverflow.ellipsis), + icon: withIcon ? const Icon(Icons.add) : null, + onPressed: isEnabled ? () => print('ExtendedFabM3E pressed (kind=$selectedKind, size=$selectedSize)') : null, + tooltip: tip, + heroTag: useHero ? (heroTag ?? 'extended-fab-hero') : null, + kind: selectedKind, + size: selectedSize, + shapeFamily: selectedShape, + density: selectedDensity, + expand: shouldExpand, + elevation: dblElevation, + semanticLabel: semLabel, + ); + + return Center( + child: SizedBox(width: shouldExpand ? 360 : null, child: fab), + ); +} + +@widgetbook.UseCase(name: 'default', type: ExtendedFabM3E) +Widget buildExtendedFabM3EUseCase(BuildContext context) { + return _buildExtendedFab(context, kind: FabM3EKind.primary, icon: const Icon(Icons.add)); +} + +@widgetbook.UseCase(name: 'with_icon', type: ExtendedFabM3E) +Widget buildExtendedFabM3EWithIconUseCase(BuildContext context) { + return _buildExtendedFab(context, kind: FabM3EKind.primary, icon: const Icon(Icons.add)); +} + +@widgetbook.UseCase(name: 'without_label', type: ExtendedFabM3E) +Widget buildExtendedFabM3EWithoutLabelUseCase(BuildContext context) { + return _buildExtendedFab(context, kind: FabM3EKind.primary, labelText: ''); +} + +@widgetbook.UseCase(name: 'disabled', type: ExtendedFabM3E) +Widget buildExtendedFabM3EDisabledUseCase(BuildContext context) { + return _buildExtendedFab(context, kind: FabM3EKind.primary, icon: const Icon(Icons.add), enabled: false); +} + +@widgetbook.UseCase(name: 'expand', type: ExtendedFabM3E) +Widget buildExtendedFabM3EExpandUseCase(BuildContext context) { + return _buildExtendedFab(context, kind: FabM3EKind.primary, icon: const Icon(Icons.add), expand: true); +} + +@widgetbook.UseCase(name: 'long_text', type: ExtendedFabM3E) +Widget buildExtendedFabM3ELongTextUseCase(BuildContext context) { + return _buildExtendedFab(context, kind: FabM3EKind.primary, icon: const Icon(Icons.add), labelText: 'Compose a very long descriptive label that may overflow'); +} + +@widgetbook.UseCase(name: 'secondary', type: ExtendedFabM3E) +Widget buildExtendedFabM3ESecondaryUseCase(BuildContext context) { + return _buildExtendedFab(context, kind: FabM3EKind.secondary, icon: const Icon(Icons.edit)); +} + +@widgetbook.UseCase(name: 'square_shape', type: ExtendedFabM3E) +Widget buildExtendedFabM3ESquareShapeUseCase(BuildContext context) { + return _buildExtendedFab(context, kind: FabM3EKind.primary, icon: const Icon(Icons.add), shape: FabM3EShapeFamily.square); +} + +@widgetbook.UseCase(name: 'compact_density', type: ExtendedFabM3E) +Widget buildExtendedFabM3ECompactDensityUseCase(BuildContext context) { + return _buildExtendedFab(context, kind: FabM3EKind.primary, icon: const Icon(Icons.add), density: FabM3EDensity.compact); +} diff --git a/widgetbook/lib/fab_m3e/fab_m3e_usecases.dart b/widgetbook/lib/fab_m3e/fab_m3e_usecases.dart new file mode 100644 index 0000000..421bbc7 --- /dev/null +++ b/widgetbook/lib/fab_m3e/fab_m3e_usecases.dart @@ -0,0 +1,131 @@ +import 'package:fab_m3e/fab_m3e.dart'; +import 'package:flutter/material.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook; + +Widget _buildFab( + BuildContext context, { + required FabM3EKind kind, + FabM3ESize size = FabM3ESize.regular, + FabM3EShapeFamily shape = FabM3EShapeFamily.round, + FabM3EDensity density = FabM3EDensity.regular, + bool enabled = true, + bool autofocus = false, + double? elevation, + String? tooltip, + Object? heroTag, + String? semanticLabel, +}) { + final selectedKind = context.knobs.object.dropdown( + label: 'kind', + initialOption: kind, + options: FabM3EKind.values, + labelBuilder: (v) => v.name, + ); + final selectedSize = context.knobs.object.dropdown( + label: 'size', + initialOption: size, + options: FabM3ESize.values, + labelBuilder: (v) => v.name, + ); + final selectedShape = context.knobs.object.dropdown( + label: 'shape', + initialOption: shape, + options: FabM3EShapeFamily.values, + labelBuilder: (v) => v.name, + ); + final selectedDensity = context.knobs.object.dropdown( + label: 'density', + initialOption: density, + options: FabM3EDensity.values, + labelBuilder: (v) => v.name, + ); + final isEnabled = + context.knobs.boolean(label: 'enabled', initialValue: enabled); + final isAutofocus = + context.knobs.boolean(label: 'autofocus', initialValue: autofocus); + final dblElevation = context.knobs.doubleOrNull.slider( + label: 'elevation', + initialValue: elevation, + min: 0.0, + max: 24.0, + divisions: 24, + ); + final tip = + context.knobs.stringOrNull(label: 'tooltip', initialValue: tooltip); + final semLabel = context.knobs + .stringOrNull(label: 'semanticLabel', initialValue: semanticLabel); + final useHero = context.knobs + .boolean(label: 'wrap in Hero', initialValue: heroTag != null); + + final Widget fab = FabM3E( + icon: const Icon(Icons.add), + onPressed: isEnabled + ? () => print('FabM3E pressed (kind=$selectedKind, size=$selectedSize)') + : null, + tooltip: tip, + heroTag: useHero ? (heroTag ?? 'fab-hero') : null, + kind: selectedKind, + size: selectedSize, + shapeFamily: selectedShape, + density: selectedDensity, + elevation: dblElevation, + autofocus: isAutofocus, + semanticLabel: semLabel, + ); + + return Center(child: fab); +} + +@widgetbook.UseCase(name: 'default', type: FabM3E) +Widget buildFabM3EUseCase(BuildContext context) { + return _buildFab(context, kind: FabM3EKind.primary); +} + +@widgetbook.UseCase(name: 'disabled', type: FabM3E) +Widget buildFabM3EDisabledUseCase(BuildContext context) { + return _buildFab(context, kind: FabM3EKind.primary, enabled: false); +} + +@widgetbook.UseCase(name: 'small', type: FabM3E) +Widget buildFabM3ESmallUseCase(BuildContext context) { + return _buildFab(context, kind: FabM3EKind.primary, size: FabM3ESize.small); +} + +@widgetbook.UseCase(name: 'large', type: FabM3E) +Widget buildFabM3ELargeUseCase(BuildContext context) { + return _buildFab(context, kind: FabM3EKind.primary, size: FabM3ESize.large); +} + +@widgetbook.UseCase(name: 'secondary', type: FabM3E) +Widget buildFabM3ESecondaryUseCase(BuildContext context) { + return _buildFab(context, kind: FabM3EKind.secondary); +} + +@widgetbook.UseCase(name: 'tertiary', type: FabM3E) +Widget buildFabM3ETertiaryUseCase(BuildContext context) { + return _buildFab(context, kind: FabM3EKind.tertiary); +} + +@widgetbook.UseCase(name: 'surface', type: FabM3E) +Widget buildFabM3ESurfaceUseCase(BuildContext context) { + return _buildFab(context, kind: FabM3EKind.surface); +} + +@widgetbook.UseCase(name: 'square_shape', type: FabM3E) +Widget buildFabM3ESquareShapeUseCase(BuildContext context) { + return _buildFab(context, + kind: FabM3EKind.primary, shape: FabM3EShapeFamily.square); +} + +@widgetbook.UseCase(name: 'compact_density', type: FabM3E) +Widget buildFabM3ECompactDensityUseCase(BuildContext context) { + return _buildFab(context, + kind: FabM3EKind.primary, density: FabM3EDensity.compact); +} + +@widgetbook.UseCase(name: 'focused', type: FabM3E) +Widget buildFabM3EFocusedUseCase(BuildContext context) { + // Emphasize focus by enabling autofocus + return _buildFab(context, kind: FabM3EKind.primary, autofocus: true); +} diff --git a/widgetbook/lib/fab_m3e/fab_menu_m3e_usecases.dart b/widgetbook/lib/fab_m3e/fab_menu_m3e_usecases.dart new file mode 100644 index 0000000..ad1d3d8 --- /dev/null +++ b/widgetbook/lib/fab_m3e/fab_menu_m3e_usecases.dart @@ -0,0 +1,144 @@ + + +import 'package:flutter/material.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook; +import 'package:fab_m3e/fab_m3e.dart'; + +FabMenuM3E _buildMenu( + BuildContext context, { + required List items, + FabMenuDirection direction = FabMenuDirection.up, + bool overlay = true, + double? spacing, + Alignment alignment = Alignment.bottomRight, + bool popOnItemTap = true, + Object? heroTag, + bool initiallyOpen = false, +}) { + final selectedDirection = context.knobs.object.dropdown( + label: 'direction', + initialOption: direction, + options: FabMenuDirection.values, + labelBuilder: (v) => v.name, + ); + final showOverlay = context.knobs.boolean(label: 'overlay', initialValue: overlay); + final gap = context.knobs.doubleOrNull.slider( + label: 'spacing', + initialValue: spacing, + min: 0, + max: 48, + divisions: 24, + ); + final alignOpt = context.knobs.object.dropdown( + label: 'alignment', + initialOption: 'bottomRight', + options: const ['bottomRight', 'bottomLeft', 'topRight', 'topLeft', 'center'], + labelBuilder: (v) => v, + ); + final Alignment align = switch (alignOpt) { + 'bottomLeft' => Alignment.bottomLeft, + 'topRight' => Alignment.topRight, + 'topLeft' => Alignment.topLeft, + 'center' => Alignment.center, + _ => Alignment.bottomRight, + }; + final popOnTap = context.knobs.boolean(label: 'popOnItemTap', initialValue: popOnItemTap); + final useHero = context.knobs.boolean(label: 'wrap in Hero', initialValue: heroTag != null); + + final controller = FabMenuController(); + if (initiallyOpen) controller.open(); + + return FabMenuM3E( + primaryFab: FabM3E( + icon: AnimatedRotation( + duration: const Duration(milliseconds: 200), + turns: controller.isOpen ? 0.125 : 0, + child: const Icon(Icons.add), + ), + onPressed: controller.toggle, + heroTag: useHero ? (heroTag ?? 'fab-menu-hero') : null, + ), + items: items, + direction: selectedDirection, + overlay: showOverlay, + spacing: gap, + controller: controller, + alignment: align, + popOnItemTap: popOnTap, + ); +} + +List _makeItems(BuildContext context, int count) { + return List.generate(count, (i) { + return FabMenuItem( + icon: Icon([Icons.share, Icons.edit, Icons.delete, Icons.copy][i % 4]), + label: Text('Action ${i + 1}'), + onPressed: () => print('Menu item ${i + 1} pressed'), + semanticLabel: 'Menu action ${i + 1}', + ); + }); +} + +@widgetbook.UseCase(name: 'default', type: FabMenuM3E) +Widget buildFabMenuM3EUseCase(BuildContext context) { + return Stack( + children: [ + Positioned.fill( + child: Container(), + ), + _buildMenu( + context, + items: _makeItems(context, 3), + direction: FabMenuDirection.up, + overlay: true, + ), + ], + ); +} + +@widgetbook.UseCase(name: 'initially_open', type: FabMenuM3E) +Widget buildFabMenuM3EInitiallyOpenUseCase(BuildContext context) { + return _buildMenu( + context, + items: _makeItems(context, 3), + direction: FabMenuDirection.up, + overlay: true, + initiallyOpen: true, + ); +} + +@widgetbook.UseCase(name: 'direction_down', type: FabMenuM3E) +Widget buildFabMenuM3EDirectionDownUseCase(BuildContext context) { + return _buildMenu(context, items: _makeItems(context, 3), direction: FabMenuDirection.down); +} + +@widgetbook.UseCase(name: 'direction_left', type: FabMenuM3E) +Widget buildFabMenuM3EDirectionLeftUseCase(BuildContext context) { + return _buildMenu(context, items: _makeItems(context, 3), direction: FabMenuDirection.left); +} + +@widgetbook.UseCase(name: 'direction_right', type: FabMenuM3E) +Widget buildFabMenuM3EDirectionRightUseCase(BuildContext context) { + return _buildMenu(context, items: _makeItems(context, 3), direction: FabMenuDirection.right); +} + +@widgetbook.UseCase(name: 'overlay_off', type: FabMenuM3E) +Widget buildFabMenuM3EOverlayOffUseCase(BuildContext context) { + return _buildMenu(context, items: _makeItems(context, 3), overlay: false); +} + +@widgetbook.UseCase(name: 'spacing_tight', type: FabMenuM3E) +Widget buildFabMenuM3ESpacingTightUseCase(BuildContext context) { + return _buildMenu(context, items: _makeItems(context, 3), spacing: 4); +} + +@widgetbook.UseCase(name: 'many_items', type: FabMenuM3E) +Widget buildFabMenuM3EManyItemsUseCase(BuildContext context) { + return _buildMenu(context, items: _makeItems(context, 8)); +} + +@widgetbook.UseCase(name: 'empty_items', type: FabMenuM3E) +Widget buildFabMenuM3EEmptyItemsUseCase(BuildContext context) { + return _buildMenu(context, items: const []); +} diff --git a/widgetbook/lib/icon_button_m3e/INDEX-icon_button_m3e.md b/widgetbook/lib/icon_button_m3e/INDEX-icon_button_m3e.md new file mode 100644 index 0000000..eaddccf --- /dev/null +++ b/widgetbook/lib/icon_button_m3e/INDEX-icon_button_m3e.md @@ -0,0 +1,34 @@ +# Widgetbook Index — icon_button_m3e + +This index summarizes all Widgetbook use cases implemented for the `icon_button_m3e` package. + +- Package path: `packages/icon_button_m3e` +- Component inventory: + - IconButtonM3E + +## IconButtonM3E — Variants + +| Variant name | Description | +|----------------------|-------------| +| default | Baseline interactive demo with knobs for variant, size, shape, width, tooltip, selected, feedback, and badge. | +| disabled | Disabled state across variants/sizes. | +| standard | Visual style preset: standard. | +| filled | Visual style preset: filled. | +| tonal | Visual style preset: tonal. | +| outlined | Visual style preset: outlined. | +| sizes | Shows all sizes (xs/sm/md/lg/xl) for the selected style/shape/width. | +| with_badge | Demonstrates numeric badges via knob (0 shows dot). | +| with_badge_string | Demonstrates string badges such as "NEW". | +| badge_edge_cases | Boundary cases: dot (0), small (1), large (99), very large (clamped). | +| with_tooltip | Tooltip demo, including long tooltip text toggle. | +| shapes | Round vs square shapes. | +| widths | Narrow, default, wide widths (disabled for comparison). | +| selected | Toggle selected state with alternate selectedIcon. | +| focused | Autofocused control for focus visuals. | +| error_badge_type | Error case: invalid `badgeValue` type (asserts in debug). | + +## Notes +- Themes are provided globally by the Widgetbook app; use cases do not configure themes. +- Knobs are used for critical and visual parameters following `plan/guide.md`. +- Callbacks log informative messages with `debugPrint` to aid interactive testing. +- Complex objects use sensible defaults and include TODO notes when applicable. diff --git a/widgetbook/lib/icon_button_m3e/icon_button_m3e_usecases.dart b/widgetbook/lib/icon_button_m3e/icon_button_m3e_usecases.dart new file mode 100644 index 0000000..eb50c94 --- /dev/null +++ b/widgetbook/lib/icon_button_m3e/icon_button_m3e_usecases.dart @@ -0,0 +1,516 @@ +// ignore_for_file: depend_on_referenced_packages + +import 'package:flutter/material.dart'; +import 'package:icon_button_m3e/icon_button_m3e.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; + +// GENERATED USE CASES for IconButtonM3E per plan/guide.md +// Notes: +// - Themes are provided globally by the Widgetbook app. +// - Use knobs for critical and visual params; callbacks print helpful messages. +// - Complex objects get meaningful defaults with TODOs. + +@UseCase(name: 'default', type: IconButtonM3E) +Widget buildIconButtonM3EUseCase(BuildContext context) { + final bool isToggle = context.knobs.boolean( + label: 'is toggle (provides selected state)', + initialValue: true, + ); + final bool selected = isToggle + ? context.knobs.boolean(label: 'isSelected', initialValue: false) + : false; + final IconButtonM3EVariant variant = context.knobs.object.dropdown( + label: 'variant', + initialOption: IconButtonM3EVariant.standard, + options: IconButtonM3EVariant.values, + labelBuilder: (v) => v.name, + ); + final IconButtonM3ESize size = context.knobs.object.dropdown( + label: 'size', + initialOption: IconButtonM3ESize.sm, + options: IconButtonM3ESize.values, + labelBuilder: (v) => v.name, + ); + final IconButtonM3EShapeVariant shape = context.knobs.object.dropdown( + label: 'shape', + initialOption: IconButtonM3EShapeVariant.round, + options: IconButtonM3EShapeVariant.values, + labelBuilder: (v) => v.name, + ); + final IconButtonM3EWidth width = context.knobs.object.dropdown( + label: 'width', + initialOption: IconButtonM3EWidth.defaultWidth, + options: IconButtonM3EWidth.values, + labelBuilder: (v) => v.name, + ); + final String? tooltip = context.knobs.stringOrNull( + label: 'tooltip', + initialValue: 'Favorite', + ); + final bool enableFeedback = context.knobs.boolean( + label: 'enableFeedback', + initialValue: true, + ); + + final bool showBadge = context.knobs.boolean( + label: 'show badge', + initialValue: false, + ); + final bool badgeIsNumber = context.knobs.boolean( + label: 'badge is number (off = string)', + initialValue: true, + ); + final Object? badgeValue = showBadge + ? (badgeIsNumber + ? context.knobs.int.slider( + label: 'badge count', + initialValue: 1, + min: 0, + max: 999, + divisions: 30, + ) + : context.knobs.string(label: 'badge label', initialValue: 'NEW')) + : null; + + return Center( + child: IconButtonM3E( + icon: const Icon(Icons.favorite_border), + selectedIcon: const Icon(Icons.favorite), + onPressed: () => debugPrint('IconButtonM3E pressed'), + isSelected: isToggle ? selected : null, + tooltip: tooltip, + enableFeedback: enableFeedback, + variant: variant, + size: size, + shape: shape, + width: width, + badgeValue: badgeValue, + ), + ); +} + +@UseCase(name: 'disabled', type: IconButtonM3E) +Widget buildIconButtonM3EDisabledUseCase(BuildContext context) { + final IconButtonM3EVariant variant = context.knobs.object.dropdown( + label: 'variant', + initialOption: IconButtonM3EVariant.standard, + options: IconButtonM3EVariant.values, + labelBuilder: (v) => v.name, + ); + final IconButtonM3ESize size = context.knobs.object.dropdown( + label: 'size', + initialOption: IconButtonM3ESize.sm, + options: IconButtonM3ESize.values, + labelBuilder: (v) => v.name, + ); + final IconButtonM3EShapeVariant shape = context.knobs.object.dropdown( + label: 'shape', + initialOption: IconButtonM3EShapeVariant.round, + options: IconButtonM3EShapeVariant.values, + labelBuilder: (v) => v.name, + ); + final IconButtonM3EWidth width = context.knobs.object.dropdown( + label: 'width', + initialOption: IconButtonM3EWidth.defaultWidth, + options: IconButtonM3EWidth.values, + labelBuilder: (v) => v.name, + ); + + return Center( + child: IconButtonM3E( + icon: const Icon(Icons.volume_up), + selectedIcon: const Icon(Icons.volume_off), + onPressed: null, // disabled state + variant: variant, + size: size, + shape: shape, + width: width, + tooltip: 'Disabled button', + ), + ); +} + +@UseCase(name: 'standard', type: IconButtonM3E) +Widget buildIconButtonM3EStandardUseCase(BuildContext context) { + return _buildStyleDemo(context, IconButtonM3EVariant.standard); +} + +@UseCase(name: 'filled', type: IconButtonM3E) +Widget buildIconButtonM3EFilledUseCase(BuildContext context) { + return _buildStyleDemo(context, IconButtonM3EVariant.filled); +} + +@UseCase(name: 'tonal', type: IconButtonM3E) +Widget buildIconButtonM3ETonalUseCase(BuildContext context) { + return _buildStyleDemo(context, IconButtonM3EVariant.tonal); +} + +@UseCase(name: 'outlined', type: IconButtonM3E) +Widget buildIconButtonM3EOutlinedUseCase(BuildContext context) { + return _buildStyleDemo(context, IconButtonM3EVariant.outlined); +} + +Widget _buildStyleDemo(BuildContext context, IconButtonM3EVariant variant) { + final IconButtonM3ESize size = context.knobs.object.dropdown( + label: 'size', + initialOption: IconButtonM3ESize.sm, + options: IconButtonM3ESize.values, + labelBuilder: (v) => v.name, + ); + final IconButtonM3EShapeVariant shape = context.knobs.object.dropdown( + label: 'shape', + initialOption: IconButtonM3EShapeVariant.round, + options: IconButtonM3EShapeVariant.values, + labelBuilder: (v) => v.name, + ); + final IconButtonM3EWidth width = context.knobs.object.dropdown( + label: 'width', + initialOption: IconButtonM3EWidth.defaultWidth, + options: IconButtonM3EWidth.values, + labelBuilder: (v) => v.name, + ); + final bool isSelected = context.knobs.boolean( + label: 'isSelected (toggle) ', + initialValue: false, + ); + + final String iconChoice = context.knobs.object.dropdown( + label: 'icon', + initialOption: 'favorite', + options: const ['favorite', 'alarm', 'share', 'settings'], + labelBuilder: (s) => s, + ); + + IconData data; + switch (iconChoice) { + case 'alarm': + data = Icons.alarm; + break; + case 'share': + data = Icons.share; + break; + case 'settings': + data = Icons.settings; + break; + default: + data = isSelected ? Icons.favorite : Icons.favorite_border; + } + + return Center( + child: IconButtonM3E( + icon: Icon(data), + selectedIcon: const Icon(Icons.check), + isSelected: isSelected, + onPressed: () => debugPrint('Pressed style=$variant size=$size'), + variant: variant, + size: size, + shape: shape, + width: width, + tooltip: 'Icon: $iconChoice', + ), + ); +} + +@UseCase(name: 'sizes', type: IconButtonM3E) +Widget buildIconButtonM3ESizesUseCase(BuildContext context) { + final IconButtonM3EVariant variant = context.knobs.object.dropdown( + label: 'variant', + initialOption: IconButtonM3EVariant.standard, + options: IconButtonM3EVariant.values, + labelBuilder: (v) => v.name, + ); + final IconButtonM3EShapeVariant shape = context.knobs.object.dropdown( + label: 'shape', + initialOption: IconButtonM3EShapeVariant.round, + options: IconButtonM3EShapeVariant.values, + labelBuilder: (v) => v.name, + ); + final IconButtonM3EWidth width = context.knobs.object.dropdown( + label: 'width', + initialOption: IconButtonM3EWidth.defaultWidth, + options: IconButtonM3EWidth.values, + labelBuilder: (v) => v.name, + ); + + return Wrap( + spacing: 16, + runSpacing: 16, + children: IconButtonM3ESize.values.map((s) { + return IconButtonM3E( + icon: const Icon(Icons.star_border), + selectedIcon: const Icon(Icons.star), + onPressed: () => debugPrint('Size $s pressed'), + variant: variant, + size: s, + shape: shape, + width: width, + tooltip: 'size: ${s.name}', + ); + }).toList(), + ); +} + +@UseCase(name: 'with_badge', type: IconButtonM3E) +Widget buildIconButtonM3EWithBadgeUseCase(BuildContext context) { + final int count = context.knobs.int.slider( + label: 'count', + initialValue: 1, + min: 0, + max: 999, + divisions: 20, + ); + final IconButtonM3EVariant variant = context.knobs.object.dropdown( + label: 'variant', + initialOption: IconButtonM3EVariant.standard, + options: IconButtonM3EVariant.values, + labelBuilder: (v) => v.name, + ); + final IconButtonM3ESize size = context.knobs.object.dropdown( + label: 'size', + initialOption: IconButtonM3ESize.sm, + options: IconButtonM3ESize.values, + labelBuilder: (v) => v.name, + ); + + return Center( + child: IconButtonM3E( + icon: const Icon(Icons.mail_outline), + selectedIcon: const Icon(Icons.mail), + onPressed: () => debugPrint('Mail pressed'), + variant: variant, + size: size, + badgeValue: count, + tooltip: 'Inbox', + ), + ); +} + +@UseCase(name: 'with_badge_string', type: IconButtonM3E) +Widget buildIconButtonM3EWithBadgeStringUseCase(BuildContext context) { + final String label = context.knobs.string( + label: 'badge label', + initialValue: 'NEW', + ); + final IconButtonM3EVariant variant = context.knobs.object.dropdown( + label: 'variant', + initialOption: IconButtonM3EVariant.filled, + options: IconButtonM3EVariant.values, + labelBuilder: (v) => v.name, + ); + + return Center( + child: IconButtonM3E( + icon: const Icon(Icons.shopping_bag_outlined), + selectedIcon: const Icon(Icons.shopping_bag), + onPressed: () => debugPrint('Bag pressed'), + variant: variant, + badgeValue: label, + tooltip: 'Cart', + ), + ); +} + +@UseCase(name: 'badge_edge_cases', type: IconButtonM3E) +Widget buildIconButtonM3EBadgeEdgeCasesUseCase(BuildContext context) { + return Wrap( + spacing: 16, + runSpacing: 16, + children: const [ + IconButtonM3E( + icon: Icon(Icons.notifications_none), + badgeValue: 0, // dot badge + tooltip: '0 = dot', + ), + IconButtonM3E( + icon: Icon(Icons.notifications_none), + badgeValue: 1, + tooltip: '1', + ), + IconButtonM3E( + icon: Icon(Icons.notifications_none), + badgeValue: 99, + tooltip: '99', + ), + IconButtonM3E( + icon: Icon(Icons.notifications_none), + badgeValue: 999999, // clamped + tooltip: 'big number', + ), + ], + ); +} + +@UseCase(name: 'with_tooltip', type: IconButtonM3E) +Widget buildIconButtonM3EWithTooltipUseCase(BuildContext context) { + final String text = context.knobs.string( + label: 'tooltip', + initialValue: 'Open menu', + ); + final bool long = context.knobs.boolean( + label: 'use long tooltip', + initialValue: false, + ); + final String value = long + ? 'This is a very long tooltip that demonstrates how the control behaves with extended descriptive text. ' + 'Try hovering or long-pressing to read the entire message.' + : text; + return Center( + child: IconButtonM3E( + icon: const Icon(Icons.more_vert), + onPressed: () => debugPrint('More pressed'), + tooltip: value, + ), + ); +} + +@UseCase(name: 'shapes', type: IconButtonM3E) +Widget buildIconButtonM3EShapesUseCase(BuildContext context) { + final IconButtonM3EVariant variant = context.knobs.object.dropdown( + label: 'variant', + initialOption: IconButtonM3EVariant.standard, + options: IconButtonM3EVariant.values, + labelBuilder: (v) => v.name, + ); + final IconButtonM3ESize size = context.knobs.object.dropdown( + label: 'size', + initialOption: IconButtonM3ESize.sm, + options: IconButtonM3ESize.values, + labelBuilder: (v) => v.name, + ); + + return Wrap( + spacing: 16, + children: [ + IconButtonM3E( + icon: const Icon(Icons.crop_5_4), + onPressed: () {}, + variant: variant, + size: size, + shape: IconButtonM3EShapeVariant.round, + tooltip: 'round', + ), + IconButtonM3E( + icon: const Icon(Icons.crop_square), + onPressed: () {}, + variant: variant, + size: size, + shape: IconButtonM3EShapeVariant.square, + tooltip: 'square', + ), + ], + ); +} + +@UseCase(name: 'widths', type: IconButtonM3E) +Widget buildIconButtonM3EWidthsUseCase(BuildContext context) { + final IconButtonM3EVariant variant = context.knobs.object.dropdown( + label: 'variant', + initialOption: IconButtonM3EVariant.standard, + options: IconButtonM3EVariant.values, + labelBuilder: (v) => v.name, + ); + final IconButtonM3ESize size = context.knobs.object.dropdown( + label: 'size', + initialOption: IconButtonM3ESize.sm, + options: IconButtonM3ESize.values, + labelBuilder: (v) => v.name, + ); + + return Wrap( + spacing: 16, + children: const [ + IconButtonM3E( + icon: Icon(Icons.aspect_ratio), + onPressed: null, + width: IconButtonM3EWidth.narrow, + tooltip: 'narrow (disabled) ', + ), + IconButtonM3E( + icon: Icon(Icons.aspect_ratio), + onPressed: null, + width: IconButtonM3EWidth.defaultWidth, + tooltip: 'default (disabled)', + ), + IconButtonM3E( + icon: Icon(Icons.aspect_ratio), + onPressed: null, + width: IconButtonM3EWidth.wide, + tooltip: 'wide (disabled)', + ), + ], + ); +} + +@UseCase(name: 'selected', type: IconButtonM3E) +Widget buildIconButtonM3ESelectedUseCase(BuildContext context) { + final IconButtonM3EVariant variant = context.knobs.object.dropdown( + label: 'variant', + initialOption: IconButtonM3EVariant.standard, + options: IconButtonM3EVariant.values, + labelBuilder: (v) => v.name, + ); + final IconButtonM3ESize size = context.knobs.object.dropdown( + label: 'size', + initialOption: IconButtonM3ESize.sm, + options: IconButtonM3ESize.values, + labelBuilder: (v) => v.name, + ); + + return Center( + child: IconButtonM3E( + icon: const Icon(Icons.radio_button_unchecked), + selectedIcon: const Icon(Icons.radio_button_checked), + isSelected: true, + onPressed: () => debugPrint('Selected toggled'), + variant: variant, + size: size, + tooltip: 'Selected = true', + ), + ); +} + +@UseCase(name: 'focused', type: IconButtonM3E) +Widget buildIconButtonM3EFocusedUseCase(BuildContext context) { + final IconButtonM3EVariant variant = context.knobs.object.dropdown( + label: 'variant', + initialOption: IconButtonM3EVariant.standard, + options: IconButtonM3EVariant.values, + labelBuilder: (v) => v.name, + ); + final IconButtonM3ESize size = context.knobs.object.dropdown( + label: 'size', + initialOption: IconButtonM3ESize.sm, + options: IconButtonM3ESize.values, + labelBuilder: (v) => v.name, + ); + + return Center( + child: Focus( + autofocus: true, + child: IconButtonM3E( + icon: const Icon(Icons.center_focus_strong), + onPressed: () => debugPrint('Focused pressed'), + variant: variant, + size: size, + tooltip: 'Autofocused control', + ), + ), + ); +} + +@UseCase(name: 'error_badge_type', type: IconButtonM3E) +Widget buildIconButtonM3EErrorBadgeTypeUseCase(BuildContext context) { + // This intentionally passes an unsupported type to badgeValue to demonstrate + // assertion behavior in debug mode. In release/profile, this simply omits the badge. + // TODO: Consider exposing a strongly-typed API for badges to avoid runtime asserts. + final Object invalid = const [1, 2, 3]; + return Center( + child: IconButtonM3E( + icon: const Icon(Icons.error_outline), + onPressed: () => debugPrint('Pressed error_badge_type case'), + badgeValue: invalid, // will assert in debug + tooltip: 'Invalid badge value (List)', + ), + ); +} diff --git a/widgetbook/lib/icon_button_m3e/icon_button_usecase.dart b/widgetbook/lib/icon_button_m3e/icon_button_usecase.dart new file mode 100644 index 0000000..f3b1341 --- /dev/null +++ b/widgetbook/lib/icon_button_m3e/icon_button_usecase.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:m3e_collection/m3e_collection.dart'; +import 'package:widgetbook/widgetbook.dart'; + +Widget _buildIconButtonDemo( + BuildContext context, { + required IconButtonM3EVariant variant, +}) { + final size = context.knobs.object.dropdown( + label: 'size', + initialOption: IconButtonM3ESize.md, + options: IconButtonM3ESize.values, + labelBuilder: (v) => v.name, + ); + final shape = context.knobs.object.dropdown( + label: 'shape', + initialOption: IconButtonM3EShapeVariant.round, + options: IconButtonM3EShapeVariant.values, + labelBuilder: (v) => v.name, + ); + final width = context.knobs.object.dropdown( + label: 'width', + initialOption: IconButtonM3EWidth.defaultWidth, + options: IconButtonM3EWidth.values, + labelBuilder: (v) => v.name, + ); + final isSelected = + context.knobs.boolean(label: 'selected', initialValue: false); + final tooltip = + context.knobs.string(label: 'tooltip', initialValue: 'Favorite'); + + final badgeMode = context.knobs.object.dropdown( + label: 'badge', + initialOption: 'none', + options: const ['none', 'dot', 'count', 'text'], + labelBuilder: (v) => v, + ); + + Object? badgeValue; + switch (badgeMode) { + case 'dot': + badgeValue = 0; // shows dot per component logic + break; + case 'count': + badgeValue = context.knobs.int.slider( + label: 'count', + initialValue: 3, + min: 0, + max: 120, + divisions: 120, + ); + break; + case 'text': + badgeValue = + context.knobs.string(label: 'badgeText', initialValue: 'NEW'); + break; + default: + badgeValue = null; + } + + return Center( + child: IconButtonM3E( + icon: const Icon(Icons.favorite_border), + selectedIcon: const Icon(Icons.favorite), + isSelected: isSelected, + tooltip: tooltip, + variant: variant, + size: size, + shape: shape, + width: width, + onPressed: () => print('IconButton pressed: variant=$variant'), + badgeValue: badgeValue, + ), + ); +} diff --git a/widgetbook/lib/loading_indicator_m3e/INDEX-loading_indicator_m3e.md b/widgetbook/lib/loading_indicator_m3e/INDEX-loading_indicator_m3e.md new file mode 100644 index 0000000..4a08770 --- /dev/null +++ b/widgetbook/lib/loading_indicator_m3e/INDEX-loading_indicator_m3e.md @@ -0,0 +1,29 @@ +# Widgetbook Index — loading_indicator_m3e + +This index lists all components in loading_indicator_m3e and their Widgetbook use case variants. + +Component inventory: +- LoadingIndicatorM3E +- ExpressiveLoadingIndicator + +## LoadingIndicatorM3E (lib/src/loading_indicator_m3e.dart) +Use case file: lib/widgetbook/loading_indicator_m3e_usecases.dart +- default — Default style with token-based colors and sizing +- contained — Contained visual variant +- sizes — Small/Medium/Large/Tiny via constraints knob +- custom_colors — Active and container colors via color knobs +- with_padding — Padding and size knobs +- with_semantics — semanticsLabel and semanticsValue knobs +- custom_polygons — Choose polygon set and size via knobs + +## ExpressiveLoadingIndicator (lib/src/expressive_loading_indicator.dart) +Use case file: lib/widgetbook/expressive_loading_indicator_usecases.dart +- default — Defaults from ProgressIndicatorTheme and widget internals +- sizes — Small/Medium/Large/Tiny via constraints knob +- custom_polygons — Choose polygon set and size +- color_and_semantics — Color and semantics knobs +- edge: invalid polygons (debug assert) — Toggle to provide a single polygon (asserts in debug) + +Notes: +- Themes are injected globally by the Widgetbook app; no theme provided here. +- Knobs follow the Comprehensive Knobs API suggested in plan/guide.md. diff --git a/widgetbook/lib/loading_indicator_m3e/expressive_loading_indicator_usecases.dart b/widgetbook/lib/loading_indicator_m3e/expressive_loading_indicator_usecases.dart new file mode 100644 index 0000000..1632435 --- /dev/null +++ b/widgetbook/lib/loading_indicator_m3e/expressive_loading_indicator_usecases.dart @@ -0,0 +1,148 @@ + + +import 'package:flutter/material.dart'; +import 'package:loading_indicator_m3e/loading_indicator_m3e.dart'; +import 'package:material_new_shapes/material_new_shapes.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; + +// GENERATED USE CASES for ExpressiveLoadingIndicator per plan/guide.md +// - Themes are provided globally by the Widgetbook app. +// - Use knobs for critical & visual params. +// - Complex objects get meaningful defaults with TODOs where needed. + +BoxConstraints _tight(double size) => BoxConstraints( + minWidth: size, + minHeight: size, + maxWidth: size, + maxHeight: size, + ); + +List _polygonSet(String id) { + switch (id) { + case 'cookie→oval': + return [MaterialShapes.cookie9Sided, MaterialShapes.oval]; + case 'softBurst→pill→sunny': + return [MaterialShapes.softBurst, MaterialShapes.pill, MaterialShapes.sunny]; + case 'triangle→square→pentagon': + return [MaterialShapes.triangle, MaterialShapes.square, MaterialShapes.pentagon]; + default: + return [ + MaterialShapes.softBurst, + MaterialShapes.cookie9Sided, + MaterialShapes.pentagon, + MaterialShapes.pill, + MaterialShapes.sunny, + MaterialShapes.cookie4Sided, + MaterialShapes.oval, + ]; + } +} + +@UseCase(name: 'default', type: ExpressiveLoadingIndicator) +Widget buildExpressiveLoadingIndicatorUseCase(BuildContext context) { + return const Center( + child: ExpressiveLoadingIndicator(), + ); +} + +@UseCase(name: 'sizes', type: ExpressiveLoadingIndicator) +Widget buildExpressiveLoadingIndicatorSizesUseCase(BuildContext context) { + final String size = context.knobs.object.dropdown( + label: 'size', + initialOption: 'medium', + options: const ['small', 'medium', 'large', 'tiny (edge)'], + labelBuilder: (v) => v, + ); + + final double px = switch (size) { + 'small' => 36, + 'large' => 64, + 'tiny (edge)' => 16, + _ => 48, + }; + + return Center( + child: ExpressiveLoadingIndicator(constraints: _tight(px)), + ); +} + +@UseCase(name: 'custom_polygons', type: ExpressiveLoadingIndicator) +Widget buildExpressiveLoadingIndicatorCustomPolygonsUseCase( + BuildContext context) { + final String setName = context.knobs.object.dropdown( + label: 'polygon set', + initialOption: 'default', + options: const [ + 'default', + 'cookie→oval', + 'softBurst→pill→sunny', + 'triangle→square→pentagon', + ], + labelBuilder: (v) => v, + ); + final double size = context.knobs.double.slider( + label: 'size (px)', + initialValue: 48, + min: 24, + max: 96, + ); + + final polygons = _polygonSet(setName); + + return Center( + child: ExpressiveLoadingIndicator( + polygons: polygons, + constraints: _tight(size), + ), + ); +} + +@UseCase(name: 'color_and_semantics', type: ExpressiveLoadingIndicator) +Widget buildExpressiveLoadingIndicatorColorAndSemanticsUseCase( + BuildContext context) { + final color = context.knobs.color(label: 'color'); + final label = context.knobs.string( + label: 'semanticsLabel', + initialValue: 'Loading', + ); + final value = context.knobs.string( + label: 'semanticsValue', + initialValue: 'In progress', + ); + + return Center( + child: ExpressiveLoadingIndicator( + color: color, + semanticsLabel: label, + semanticsValue: value, + ), + ); +} + +@UseCase(name: 'edge: invalid polygons (debug assert)', type: ExpressiveLoadingIndicator) +Widget buildExpressiveLoadingIndicatorInvalidPolygonsUseCase( + BuildContext context) { + final enableInvalid = context.knobs.boolean( + label: 'trigger invalid (single polygon)', + initialValue: false, + ); + final size = context.knobs.double.slider( + label: 'size (px)', + initialValue: 48, + min: 24, + max: 96, + ); + + // Note: When enabled, this will assert in debug as polygons.length must be > 1. + final polygons = enableInvalid + ? [MaterialShapes.oval] + : _polygonSet('default'); + + return Center( + child: ExpressiveLoadingIndicator( + polygons: polygons, + constraints: _tight(size), + ), + ); +} diff --git a/widgetbook/lib/loading_indicator_m3e/loading_indicator_m3e_usecases.dart b/widgetbook/lib/loading_indicator_m3e/loading_indicator_m3e_usecases.dart new file mode 100644 index 0000000..110e75c --- /dev/null +++ b/widgetbook/lib/loading_indicator_m3e/loading_indicator_m3e_usecases.dart @@ -0,0 +1,184 @@ +import 'package:flutter/material.dart'; +import 'package:loading_indicator_m3e/loading_indicator_m3e.dart'; +import 'package:material_new_shapes/material_new_shapes.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; + +// GENERATED USE CASES for LoadingIndicatorM3E per plan/guide.md +// Notes: +// - Themes are provided globally by the Widgetbook app. +// - Use knobs for critical and visual params; callbacks would print helpful messages (none here). +// - Complex objects get meaningful defaults with TODOs. + +BoxConstraints _sizeToTight(double size) => BoxConstraints( + minWidth: size, + minHeight: size, + maxWidth: size, + maxHeight: size, + ); + +EdgeInsets _paddingAll(double value) => EdgeInsets.all(value); + +List _polygonSet(String id) { + switch (id) { + case 'cookie→oval': + return [ + MaterialShapes.cookie9Sided, + MaterialShapes.oval, + ]; + case 'softBurst→pill→sunny': + return [ + MaterialShapes.softBurst, + MaterialShapes.pill, + MaterialShapes.sunny, + ]; + case 'triangle→square→pentagon': + return [ + MaterialShapes.triangle, + MaterialShapes.square, + MaterialShapes.pentagon, + ]; + default: + // default sequence mirrors the package's internal default + return [ + MaterialShapes.softBurst, + MaterialShapes.cookie9Sided, + MaterialShapes.pentagon, + MaterialShapes.pill, + MaterialShapes.sunny, + MaterialShapes.cookie4Sided, + MaterialShapes.oval, + ]; + } +} + +@UseCase(name: 'default', type: LoadingIndicatorM3E) +Widget buildLoadingIndicatorM3EUseCase(BuildContext context) { + // Keep defaults: tokens-based sizing and colors, subtle container. + return const Center( + child: LoadingIndicatorM3E(), + ); +} + +@UseCase(name: 'contained', type: LoadingIndicatorM3E) +Widget buildLoadingIndicatorM3EContainedUseCase(BuildContext context) { + // Visual family: contained variant uses a stronger container color. + return const Center( + child: LoadingIndicatorM3E(variant: LoadingIndicatorM3EVariant.contained), + ); +} + +@UseCase(name: 'sizes', type: LoadingIndicatorM3E) +Widget buildLoadingIndicatorM3ESizesUseCase(BuildContext context) { + final String size = context.knobs.object.dropdown( + label: 'size', + initialOption: 'medium', + options: const ['small', 'medium', 'large', 'tiny (edge)'], + labelBuilder: (v) => v, + ); + + final double px = switch (size) { + 'small' => 36, + 'large' => 64, + 'tiny (edge)' => 16, // boundary case + _ => 48, // medium + }; + + return Center( + child: LoadingIndicatorM3E( + constraints: _sizeToTight(px), + ), + ); +} + +@UseCase(name: 'custom_colors', type: LoadingIndicatorM3E) +Widget buildLoadingIndicatorM3ECustomColorsUseCase(BuildContext context) { + final variant = context.knobs.object.dropdown( + label: 'variant', + initialOption: LoadingIndicatorM3EVariant.defaultStyle, + options: LoadingIndicatorM3EVariant.values, + labelBuilder: (v) => v.name, + ); + final active = context.knobs.color(label: 'active color'); + final container = context.knobs.color(label: 'container color'); + + return Center( + child: LoadingIndicatorM3E( + variant: variant, + color: active, + containerColor: container, + ), + ); +} + +@UseCase(name: 'with_padding', type: LoadingIndicatorM3E) +Widget buildLoadingIndicatorM3EWithPaddingUseCase(BuildContext context) { + final double pad = context.knobs.double.slider( + label: 'padding', + initialValue: 8, + min: 0, + max: 32, + ); + final double size = context.knobs.double.slider( + label: 'size (px)', + initialValue: 48, + min: 16, + max: 96, + ); + + return Center( + child: LoadingIndicatorM3E( + padding: _paddingAll(pad), + constraints: _sizeToTight(size), + ), + ); +} + +@UseCase(name: 'with_semantics', type: LoadingIndicatorM3E) +Widget buildLoadingIndicatorM3EWithSemanticsUseCase(BuildContext context) { + final String label = context.knobs.string( + label: 'semanticsLabel', + initialValue: 'Loading content', + ); + final String value = context.knobs.string( + label: 'semanticsValue', + initialValue: 'Please wait…', + ); + + return Center( + child: LoadingIndicatorM3E( + semanticLabel: label, + semanticValue: value, + ), + ); +} + +@UseCase(name: 'custom_polygons', type: LoadingIndicatorM3E) +Widget buildLoadingIndicatorM3ECustomPolygonsUseCase(BuildContext context) { + final String setName = context.knobs.object.dropdown( + label: 'polygon set', + initialOption: 'default', + options: const [ + 'default', + 'cookie→oval', + 'softBurst→pill→sunny', + 'triangle→square→pentagon', + ], + labelBuilder: (v) => v, + ); + final double size = context.knobs.double.slider( + label: 'size (px)', + initialValue: 48, + min: 24, + max: 96, + ); + + final polygons = _polygonSet(setName); + + return Center( + child: LoadingIndicatorM3E( + polygons: polygons, + constraints: _sizeToTight(size), + ), + ); +} diff --git a/widgetbook/lib/m3e_design/INDEX-m3e_design.md b/widgetbook/lib/m3e_design/INDEX-m3e_design.md new file mode 100644 index 0000000..56c03fc --- /dev/null +++ b/widgetbook/lib/m3e_design/INDEX-m3e_design.md @@ -0,0 +1,28 @@ +# Widgetbook Index — m3e_design + +This package (m3e_design) provides the design system core for Material 3 Expressive: ThemeExtension(s), tokens (colors, typography, spacing, shapes, motion), and utilities. It does not expose standalone user-facing Widgets. + +Therefore, no component use cases are generated for this package. If downstream packages want to showcase tokens visually, they should do so in their own Widgetbook contexts using this design system. + +## Discovery (Components) + +- Scanned packages/m3e_design/lib for public Widgets (classes extending StatelessWidget/StatefulWidget). +- Result: none found — only ThemeExtension, token data classes, and helpers. + +``` +[ ] Component inventory: + - [ ] (none) +``` + +## Summary Table + +| Component | Variants | +|---|---| +| — No direct widgets in this package — | — | + +## Notes + +- Themes are globally injected in consuming apps; no themes are provided here for use cases. +- Optional previews (typography, palette, spacing, shapes, motion) could be created as internal demo widgets under widgetbook/ if desired. Not included to keep this package lean and avoid exporting demo-only code. + +_Last updated: 2025-10-25_ diff --git a/widgetbook/lib/main.dart b/widgetbook/lib/main.dart new file mode 100644 index 0000000..972093f --- /dev/null +++ b/widgetbook/lib/main.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:m3e_design/m3e_design.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook; + +// Generated by widgetbook_generator +import 'main.directories.g.dart'; + +void main() { + runApp(const WidgetbookApp()); +} + +@widgetbook.App() +class WidgetbookApp extends StatelessWidget { + const WidgetbookApp({super.key}); + + @override + Widget build(BuildContext context) { + final themeLight = + ColorScheme.fromSeed(seedColor: Colors.purple).toM3EThemeData(); + final themeDark = ColorScheme.fromSeed( + seedColor: Colors.purple, brightness: Brightness.dark) + .toM3EThemeData(); + + return Widgetbook.material( + directories: directories, + lightTheme: themeLight, + darkTheme: themeDark, + addons: [ + MaterialThemeAddon( + themes: [ + WidgetbookTheme(name: 'Light', data: themeLight), + WidgetbookTheme(name: 'Dark', data: themeDark), + ], + ), + TextScaleAddon(), + ], + ); + } +} diff --git a/widgetbook/lib/main.directories.g.dart b/widgetbook/lib/main.directories.g.dart new file mode 100644 index 0000000..9f7859e --- /dev/null +++ b/widgetbook/lib/main.directories.g.dart @@ -0,0 +1,1113 @@ +// dart format width=80 +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_import, prefer_relative_imports, directives_ordering + +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// AppGenerator +// ************************************************************************** + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:widgetbook/widgetbook.dart' as _widgetbook; +import 'package:widgetbook_workspace/app_bar_m3e/app_bar_m3e_usecases.dart' + as _widgetbook_workspace_app_bar_m3e_app_bar_m3e_usecases; +import 'package:widgetbook_workspace/app_bar_m3e/sliver_app_bar_m3e_usecases.dart' + as _widgetbook_workspace_app_bar_m3e_sliver_app_bar_m3e_usecases; +import 'package:widgetbook_workspace/button_group_m3e/button_group_m3e_usecases.dart' + as _widgetbook_workspace_button_group_m3e_button_group_m3e_usecases; +import 'package:widgetbook_workspace/button_m3e/button_m3e_usecases.dart' + as _widgetbook_workspace_button_m3e_button_m3e_usecases; +import 'package:widgetbook_workspace/fab_m3e/extended_fab_m3e_usecases.dart' + as _widgetbook_workspace_fab_m3e_extended_fab_m3e_usecases; +import 'package:widgetbook_workspace/fab_m3e/fab_m3e_usecases.dart' + as _widgetbook_workspace_fab_m3e_fab_m3e_usecases; +import 'package:widgetbook_workspace/fab_m3e/fab_menu_m3e_usecases.dart' + as _widgetbook_workspace_fab_m3e_fab_menu_m3e_usecases; +import 'package:widgetbook_workspace/icon_button_m3e/icon_button_m3e_usecases.dart' + as _widgetbook_workspace_icon_button_m3e_icon_button_m3e_usecases; +import 'package:widgetbook_workspace/loading_indicator_m3e/expressive_loading_indicator_usecases.dart' + as _widgetbook_workspace_loading_indicator_m3e_expressive_loading_indicator_usecases; +import 'package:widgetbook_workspace/loading_indicator_m3e/loading_indicator_m3e_usecases.dart' + as _widgetbook_workspace_loading_indicator_m3e_loading_indicator_m3e_usecases; +import 'package:widgetbook_workspace/navigation_bar_m3e/navigation_bar_m3e_usecases.dart' + as _widgetbook_workspace_navigation_bar_m3e_navigation_bar_m3e_usecases; +import 'package:widgetbook_workspace/navigation_rail_m3e/navigation_rail_m3e_usecases.dart' + as _widgetbook_workspace_navigation_rail_m3e_navigation_rail_m3e_usecases; +import 'package:widgetbook_workspace/navigation_rail_m3e/rail_badge_m3e_usecases.dart' + as _widgetbook_workspace_navigation_rail_m3e_rail_badge_m3e_usecases; +import 'package:widgetbook_workspace/navigation_rail_m3e/rail_item_button_m3e_usecases.dart' + as _widgetbook_workspace_navigation_rail_m3e_rail_item_button_m3e_usecases; +import 'package:widgetbook_workspace/progress_indicator_m3e/circular_progress_m3e_usecases.dart' + as _widgetbook_workspace_progress_indicator_m3e_circular_progress_m3e_usecases; +import 'package:widgetbook_workspace/progress_indicator_m3e/linear_progress_m3e_usecases.dart' + as _widgetbook_workspace_progress_indicator_m3e_linear_progress_m3e_usecases; +import 'package:widgetbook_workspace/progress_indicator_m3e/progress_with_label_m3e_usecases.dart' + as _widgetbook_workspace_progress_indicator_m3e_progress_with_label_m3e_usecases; +import 'package:widgetbook_workspace/slider_m3e/range_slider_m3e_usecases.dart' + as _widgetbook_workspace_slider_m3e_range_slider_m3e_usecases; +import 'package:widgetbook_workspace/slider_m3e/slider_m3e_usecases.dart' + as _widgetbook_workspace_slider_m3e_slider_m3e_usecases; +import 'package:widgetbook_workspace/split_button_m3e/split_button_m3e_usecase.dart' + as _widgetbook_workspace_split_button_m3e_split_button_m3e_usecase; +import 'package:widgetbook_workspace/toolbar_m3e/toolbar_icon_button_m3e_usecases.dart' + as _widgetbook_workspace_toolbar_m3e_toolbar_icon_button_m3e_usecases; +import 'package:widgetbook_workspace/toolbar_m3e/toolbar_m3e_usecases.dart' + as _widgetbook_workspace_toolbar_m3e_toolbar_m3e_usecases; + +final directories = <_widgetbook.WidgetbookNode>[ + _widgetbook.WidgetbookComponent( + name: 'AppBarM3E', + useCases: [ + _widgetbook.WidgetbookUseCase( + name: 'default', + builder: _widgetbook_workspace_app_bar_m3e_app_bar_m3e_usecases + .buildAppBarM3EDefaultUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'large', + builder: _widgetbook_workspace_app_bar_m3e_app_bar_m3e_usecases + .buildAppBarM3ELargeUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'long_text', + builder: _widgetbook_workspace_app_bar_m3e_app_bar_m3e_usecases + .buildAppBarM3ELongTextUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'many_actions', + builder: _widgetbook_workspace_app_bar_m3e_app_bar_m3e_usecases + .buildAppBarM3EManyActionsUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'medium', + builder: _widgetbook_workspace_app_bar_m3e_app_bar_m3e_usecases + .buildAppBarM3EMediumUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'semantics_label', + builder: _widgetbook_workspace_app_bar_m3e_app_bar_m3e_usecases + .buildAppBarM3ESemanticsLabelUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'shape_family', + builder: _widgetbook_workspace_app_bar_m3e_app_bar_m3e_usecases + .buildAppBarM3EShapeFamilyUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'small', + builder: _widgetbook_workspace_app_bar_m3e_app_bar_m3e_usecases + .buildAppBarM3ESmallUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'with_icon', + builder: _widgetbook_workspace_app_bar_m3e_app_bar_m3e_usecases + .buildAppBarM3EWithIconUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'with_label', + builder: _widgetbook_workspace_app_bar_m3e_app_bar_m3e_usecases + .buildAppBarM3EWithLabelUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'without_label', + builder: _widgetbook_workspace_app_bar_m3e_app_bar_m3e_usecases + .buildAppBarM3EWithoutLabelUseCase, + ), + ], + ), + _widgetbook.WidgetbookComponent( + name: 'ButtonGroupM3E', + useCases: [ + _widgetbook.WidgetbookUseCase( + name: 'connected', + builder: + _widgetbook_workspace_button_group_m3e_button_group_m3e_usecases + .buildButtonGroupM3EConnectedUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'default', + builder: + _widgetbook_workspace_button_group_m3e_button_group_m3e_usecases + .buildButtonGroupM3EUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'empty', + builder: + _widgetbook_workspace_button_group_m3e_button_group_m3e_usecases + .buildButtonGroupM3EEmptyUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'equalized_long_text', + builder: + _widgetbook_workspace_button_group_m3e_button_group_m3e_usecases + .buildButtonGroupM3EEqualizedLongTextUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'vertical', + builder: + _widgetbook_workspace_button_group_m3e_button_group_m3e_usecases + .buildButtonGroupM3EVerticalUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'with_icon', + builder: + _widgetbook_workspace_button_group_m3e_button_group_m3e_usecases + .buildButtonGroupM3EWithIconUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'wrapped_many_items', + builder: + _widgetbook_workspace_button_group_m3e_button_group_m3e_usecases + .buildButtonGroupM3EWrappedManyItemsUseCase, + ), + ], + ), + _widgetbook.WidgetbookComponent( + name: 'ButtonM3E', + useCases: [ + _widgetbook.WidgetbookUseCase( + name: 'default', + builder: _widgetbook_workspace_button_m3e_button_m3e_usecases + .buildButtonM3EUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'disabled', + builder: _widgetbook_workspace_button_m3e_button_m3e_usecases + .buildButtonM3EDisabledUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'elevated', + builder: _widgetbook_workspace_button_m3e_button_m3e_usecases + .buildButtonM3EElevatedUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'empty_label', + builder: _widgetbook_workspace_button_m3e_button_m3e_usecases + .buildButtonM3EEmptyLabelUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'filled', + builder: _widgetbook_workspace_button_m3e_button_m3e_usecases + .buildButtonM3EFilledUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'focused', + builder: _widgetbook_workspace_button_m3e_button_m3e_usecases + .buildButtonM3EFocusedUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'long_text', + builder: _widgetbook_workspace_button_m3e_button_m3e_usecases + .buildButtonM3ELongTextUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'outlined', + builder: _widgetbook_workspace_button_m3e_button_m3e_usecases + .buildButtonM3EOutlinedUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'selected', + builder: _widgetbook_workspace_button_m3e_button_m3e_usecases + .buildButtonM3ESelectedUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'sizes', + builder: _widgetbook_workspace_button_m3e_button_m3e_usecases + .buildButtonM3ESizesUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'text', + builder: _widgetbook_workspace_button_m3e_button_m3e_usecases + .buildButtonM3ETextUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'tonal', + builder: _widgetbook_workspace_button_m3e_button_m3e_usecases + .buildButtonM3ETonalUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'with_icon', + builder: _widgetbook_workspace_button_m3e_button_m3e_usecases + .buildButtonM3EWithIconUseCase, + ), + ], + ), + _widgetbook.WidgetbookComponent( + name: 'CircularProgressIndicatorM3E', + useCases: [ + _widgetbook.WidgetbookUseCase( + name: 'determinate', + builder: + _widgetbook_workspace_progress_indicator_m3e_circular_progress_m3e_usecases + .buildCircularProgressIndicatorM3EDeterminateUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'flat', + builder: + _widgetbook_workspace_progress_indicator_m3e_circular_progress_m3e_usecases + .buildCircularProgressIndicatorM3EFlatUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'indeterminate', + builder: + _widgetbook_workspace_progress_indicator_m3e_circular_progress_m3e_usecases + .buildCircularProgressIndicatorM3EIndeterminateUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'size_m', + builder: + _widgetbook_workspace_progress_indicator_m3e_circular_progress_m3e_usecases + .buildCircularProgressIndicatorM3ESizeMUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'size_s', + builder: + _widgetbook_workspace_progress_indicator_m3e_circular_progress_m3e_usecases + .buildCircularProgressIndicatorM3ESizeSUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'wavy', + builder: + _widgetbook_workspace_progress_indicator_m3e_circular_progress_m3e_usecases + .buildCircularProgressIndicatorM3EWavyUseCase, + ), + ], + ), + _widgetbook.WidgetbookComponent( + name: 'ExpressiveLoadingIndicator', + useCases: [ + _widgetbook.WidgetbookUseCase( + name: 'color_and_semantics', + builder: + _widgetbook_workspace_loading_indicator_m3e_expressive_loading_indicator_usecases + .buildExpressiveLoadingIndicatorColorAndSemanticsUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'custom_polygons', + builder: + _widgetbook_workspace_loading_indicator_m3e_expressive_loading_indicator_usecases + .buildExpressiveLoadingIndicatorCustomPolygonsUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'default', + builder: + _widgetbook_workspace_loading_indicator_m3e_expressive_loading_indicator_usecases + .buildExpressiveLoadingIndicatorUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'edge: invalid polygons (debug assert)', + builder: + _widgetbook_workspace_loading_indicator_m3e_expressive_loading_indicator_usecases + .buildExpressiveLoadingIndicatorInvalidPolygonsUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'sizes', + builder: + _widgetbook_workspace_loading_indicator_m3e_expressive_loading_indicator_usecases + .buildExpressiveLoadingIndicatorSizesUseCase, + ), + ], + ), + _widgetbook.WidgetbookComponent( + name: 'ExtendedFabM3E', + useCases: [ + _widgetbook.WidgetbookUseCase( + name: 'compact_density', + builder: _widgetbook_workspace_fab_m3e_extended_fab_m3e_usecases + .buildExtendedFabM3ECompactDensityUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'default', + builder: _widgetbook_workspace_fab_m3e_extended_fab_m3e_usecases + .buildExtendedFabM3EUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'disabled', + builder: _widgetbook_workspace_fab_m3e_extended_fab_m3e_usecases + .buildExtendedFabM3EDisabledUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'expand', + builder: _widgetbook_workspace_fab_m3e_extended_fab_m3e_usecases + .buildExtendedFabM3EExpandUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'long_text', + builder: _widgetbook_workspace_fab_m3e_extended_fab_m3e_usecases + .buildExtendedFabM3ELongTextUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'secondary', + builder: _widgetbook_workspace_fab_m3e_extended_fab_m3e_usecases + .buildExtendedFabM3ESecondaryUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'square_shape', + builder: _widgetbook_workspace_fab_m3e_extended_fab_m3e_usecases + .buildExtendedFabM3ESquareShapeUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'with_icon', + builder: _widgetbook_workspace_fab_m3e_extended_fab_m3e_usecases + .buildExtendedFabM3EWithIconUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'without_label', + builder: _widgetbook_workspace_fab_m3e_extended_fab_m3e_usecases + .buildExtendedFabM3EWithoutLabelUseCase, + ), + ], + ), + _widgetbook.WidgetbookComponent( + name: 'FabM3E', + useCases: [ + _widgetbook.WidgetbookUseCase( + name: 'compact_density', + builder: _widgetbook_workspace_fab_m3e_fab_m3e_usecases + .buildFabM3ECompactDensityUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'default', + builder: + _widgetbook_workspace_fab_m3e_fab_m3e_usecases.buildFabM3EUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'disabled', + builder: _widgetbook_workspace_fab_m3e_fab_m3e_usecases + .buildFabM3EDisabledUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'focused', + builder: _widgetbook_workspace_fab_m3e_fab_m3e_usecases + .buildFabM3EFocusedUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'large', + builder: _widgetbook_workspace_fab_m3e_fab_m3e_usecases + .buildFabM3ELargeUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'secondary', + builder: _widgetbook_workspace_fab_m3e_fab_m3e_usecases + .buildFabM3ESecondaryUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'small', + builder: _widgetbook_workspace_fab_m3e_fab_m3e_usecases + .buildFabM3ESmallUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'square_shape', + builder: _widgetbook_workspace_fab_m3e_fab_m3e_usecases + .buildFabM3ESquareShapeUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'surface', + builder: _widgetbook_workspace_fab_m3e_fab_m3e_usecases + .buildFabM3ESurfaceUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'tertiary', + builder: _widgetbook_workspace_fab_m3e_fab_m3e_usecases + .buildFabM3ETertiaryUseCase, + ), + ], + ), + _widgetbook.WidgetbookComponent( + name: 'FabMenuM3E', + useCases: [ + _widgetbook.WidgetbookUseCase( + name: 'default', + builder: _widgetbook_workspace_fab_m3e_fab_menu_m3e_usecases + .buildFabMenuM3EUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'direction_down', + builder: _widgetbook_workspace_fab_m3e_fab_menu_m3e_usecases + .buildFabMenuM3EDirectionDownUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'direction_left', + builder: _widgetbook_workspace_fab_m3e_fab_menu_m3e_usecases + .buildFabMenuM3EDirectionLeftUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'direction_right', + builder: _widgetbook_workspace_fab_m3e_fab_menu_m3e_usecases + .buildFabMenuM3EDirectionRightUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'empty_items', + builder: _widgetbook_workspace_fab_m3e_fab_menu_m3e_usecases + .buildFabMenuM3EEmptyItemsUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'initially_open', + builder: _widgetbook_workspace_fab_m3e_fab_menu_m3e_usecases + .buildFabMenuM3EInitiallyOpenUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'many_items', + builder: _widgetbook_workspace_fab_m3e_fab_menu_m3e_usecases + .buildFabMenuM3EManyItemsUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'overlay_off', + builder: _widgetbook_workspace_fab_m3e_fab_menu_m3e_usecases + .buildFabMenuM3EOverlayOffUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'spacing_tight', + builder: _widgetbook_workspace_fab_m3e_fab_menu_m3e_usecases + .buildFabMenuM3ESpacingTightUseCase, + ), + ], + ), + _widgetbook.WidgetbookComponent( + name: 'IconButtonM3E', + useCases: [ + _widgetbook.WidgetbookUseCase( + name: 'badge_edge_cases', + builder: _widgetbook_workspace_icon_button_m3e_icon_button_m3e_usecases + .buildIconButtonM3EBadgeEdgeCasesUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'default', + builder: _widgetbook_workspace_icon_button_m3e_icon_button_m3e_usecases + .buildIconButtonM3EUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'disabled', + builder: _widgetbook_workspace_icon_button_m3e_icon_button_m3e_usecases + .buildIconButtonM3EDisabledUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'error_badge_type', + builder: _widgetbook_workspace_icon_button_m3e_icon_button_m3e_usecases + .buildIconButtonM3EErrorBadgeTypeUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'filled', + builder: _widgetbook_workspace_icon_button_m3e_icon_button_m3e_usecases + .buildIconButtonM3EFilledUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'focused', + builder: _widgetbook_workspace_icon_button_m3e_icon_button_m3e_usecases + .buildIconButtonM3EFocusedUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'outlined', + builder: _widgetbook_workspace_icon_button_m3e_icon_button_m3e_usecases + .buildIconButtonM3EOutlinedUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'selected', + builder: _widgetbook_workspace_icon_button_m3e_icon_button_m3e_usecases + .buildIconButtonM3ESelectedUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'shapes', + builder: _widgetbook_workspace_icon_button_m3e_icon_button_m3e_usecases + .buildIconButtonM3EShapesUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'sizes', + builder: _widgetbook_workspace_icon_button_m3e_icon_button_m3e_usecases + .buildIconButtonM3ESizesUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'standard', + builder: _widgetbook_workspace_icon_button_m3e_icon_button_m3e_usecases + .buildIconButtonM3EStandardUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'tonal', + builder: _widgetbook_workspace_icon_button_m3e_icon_button_m3e_usecases + .buildIconButtonM3ETonalUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'widths', + builder: _widgetbook_workspace_icon_button_m3e_icon_button_m3e_usecases + .buildIconButtonM3EWidthsUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'with_badge', + builder: _widgetbook_workspace_icon_button_m3e_icon_button_m3e_usecases + .buildIconButtonM3EWithBadgeUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'with_badge_string', + builder: _widgetbook_workspace_icon_button_m3e_icon_button_m3e_usecases + .buildIconButtonM3EWithBadgeStringUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'with_tooltip', + builder: _widgetbook_workspace_icon_button_m3e_icon_button_m3e_usecases + .buildIconButtonM3EWithTooltipUseCase, + ), + ], + ), + _widgetbook.WidgetbookComponent( + name: 'LinearProgressIndicatorM3E', + useCases: [ + _widgetbook.WidgetbookUseCase( + name: 'determinate', + builder: + _widgetbook_workspace_progress_indicator_m3e_linear_progress_m3e_usecases + .buildLinearProgressIndicatorM3EDeterminateUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'indeterminate', + builder: + _widgetbook_workspace_progress_indicator_m3e_linear_progress_m3e_usecases + .buildLinearProgressIndicatorM3EIndeterminateUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'size_m', + builder: + _widgetbook_workspace_progress_indicator_m3e_linear_progress_m3e_usecases + .buildLinearProgressIndicatorM3ESizeMUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'size_s', + builder: + _widgetbook_workspace_progress_indicator_m3e_linear_progress_m3e_usecases + .buildLinearProgressIndicatorM3ESizeSUseCase, + ), + ], + ), + _widgetbook.WidgetbookComponent( + name: 'LoadingIndicatorM3E', + useCases: [ + _widgetbook.WidgetbookUseCase( + name: 'contained', + builder: + _widgetbook_workspace_loading_indicator_m3e_loading_indicator_m3e_usecases + .buildLoadingIndicatorM3EContainedUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'custom_colors', + builder: + _widgetbook_workspace_loading_indicator_m3e_loading_indicator_m3e_usecases + .buildLoadingIndicatorM3ECustomColorsUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'custom_polygons', + builder: + _widgetbook_workspace_loading_indicator_m3e_loading_indicator_m3e_usecases + .buildLoadingIndicatorM3ECustomPolygonsUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'default', + builder: + _widgetbook_workspace_loading_indicator_m3e_loading_indicator_m3e_usecases + .buildLoadingIndicatorM3EUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'sizes', + builder: + _widgetbook_workspace_loading_indicator_m3e_loading_indicator_m3e_usecases + .buildLoadingIndicatorM3ESizesUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'with_padding', + builder: + _widgetbook_workspace_loading_indicator_m3e_loading_indicator_m3e_usecases + .buildLoadingIndicatorM3EWithPaddingUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'with_semantics', + builder: + _widgetbook_workspace_loading_indicator_m3e_loading_indicator_m3e_usecases + .buildLoadingIndicatorM3EWithSemanticsUseCase, + ), + ], + ), + _widgetbook.WidgetbookComponent( + name: 'NavigationBarM3E', + useCases: [ + _widgetbook.WidgetbookUseCase( + name: 'compact_density', + builder: + _widgetbook_workspace_navigation_bar_m3e_navigation_bar_m3e_usecases + .buildNavigationBarM3ECompactDensityUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'default', + builder: + _widgetbook_workspace_navigation_bar_m3e_navigation_bar_m3e_usecases + .buildNavigationBarM3EDefaultUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'no_indicator', + builder: + _widgetbook_workspace_navigation_bar_m3e_navigation_bar_m3e_usecases + .buildNavigationBarM3ENoIndicatorUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'pill_indicator', + builder: + _widgetbook_workspace_navigation_bar_m3e_navigation_bar_m3e_usecases + .buildNavigationBarM3EPillIndicatorUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'playground', + builder: + _widgetbook_workspace_navigation_bar_m3e_navigation_bar_m3e_usecases + .buildNavigationBarM3EPlaygroundUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'small_size', + builder: + _widgetbook_workspace_navigation_bar_m3e_navigation_bar_m3e_usecases + .buildNavigationBarM3ESmallSizeUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'square_shape', + builder: + _widgetbook_workspace_navigation_bar_m3e_navigation_bar_m3e_usecases + .buildNavigationBarM3ESquareShapeUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'underline_indicator', + builder: + _widgetbook_workspace_navigation_bar_m3e_navigation_bar_m3e_usecases + .buildNavigationBarM3EUnderlineIndicatorUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'with_badges', + builder: + _widgetbook_workspace_navigation_bar_m3e_navigation_bar_m3e_usecases + .buildNavigationBarM3EWithBadgesUseCase, + ), + ], + ), + _widgetbook.WidgetbookComponent( + name: 'NavigationRailM3E', + useCases: [ + _widgetbook.WidgetbookUseCase( + name: 'collapsed_standard', + builder: + _widgetbook_workspace_navigation_rail_m3e_navigation_rail_m3e_usecases + .buildNavigationRailM3ECollapsedStandardUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'default', + builder: + _widgetbook_workspace_navigation_rail_m3e_navigation_rail_m3e_usecases + .buildNavigationRailM3EDefaultUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'expanded_modal', + builder: + _widgetbook_workspace_navigation_rail_m3e_navigation_rail_m3e_usecases + .buildNavigationRailM3EExpandedModalUseCase, + ), + ], + ), + _widgetbook.WidgetbookComponent( + name: 'ProgressWithLabelM3E', + useCases: [ + _widgetbook.WidgetbookUseCase( + name: 'default', + builder: + _widgetbook_workspace_progress_indicator_m3e_progress_with_label_m3e_usecases + .buildProgressWithLabelM3EDefaultUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'determinate_with_label', + builder: + _widgetbook_workspace_progress_indicator_m3e_progress_with_label_m3e_usecases + .buildProgressWithLabelM3EDeterminateWithLabelUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'long_label_text', + builder: + _widgetbook_workspace_progress_indicator_m3e_progress_with_label_m3e_usecases + .buildProgressWithLabelM3ELongLabelTextUseCase, + ), + ], + ), + _widgetbook.WidgetbookComponent( + name: 'RailBadgeM3E', + useCases: [ + _widgetbook.WidgetbookUseCase( + name: 'default', + builder: + _widgetbook_workspace_navigation_rail_m3e_rail_badge_m3e_usecases + .buildRailBadgeM3EUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'dense', + builder: + _widgetbook_workspace_navigation_rail_m3e_rail_badge_m3e_usecases + .buildRailBadgeM3EDenseUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'dot', + builder: + _widgetbook_workspace_navigation_rail_m3e_rail_badge_m3e_usecases + .buildRailBadgeM3EDotUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'overflow_999+', + builder: + _widgetbook_workspace_navigation_rail_m3e_rail_badge_m3e_usecases + .buildRailBadgeM3EOverflowUseCase, + ), + ], + ), + _widgetbook.WidgetbookComponent( + name: 'RailItemButtonM3E', + useCases: [ + _widgetbook.WidgetbookUseCase( + name: 'collapsed', + builder: + _widgetbook_workspace_navigation_rail_m3e_rail_item_button_m3e_usecases + .buildRailItemButtonM3ECollapsedUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'expanded', + builder: + _widgetbook_workspace_navigation_rail_m3e_rail_item_button_m3e_usecases + .buildRailItemButtonM3EExpandedUseCase, + ), + ], + ), + _widgetbook.WidgetbookComponent( + name: 'RangeSliderM3E', + useCases: [ + _widgetbook.WidgetbookUseCase( + name: 'default', + builder: _widgetbook_workspace_slider_m3e_range_slider_m3e_usecases + .buildRangeSliderM3EDefaultUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'disabled', + builder: _widgetbook_workspace_slider_m3e_range_slider_m3e_usecases + .buildRangeSliderM3EDisabledUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'discrete', + builder: _widgetbook_workspace_slider_m3e_range_slider_m3e_usecases + .buildRangeSliderM3EDiscreteUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'extremes_0_to_100', + builder: _widgetbook_workspace_slider_m3e_range_slider_m3e_usecases + .buildRangeSliderM3EExtremes0100UseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'min_equals_max', + builder: _widgetbook_workspace_slider_m3e_range_slider_m3e_usecases + .buildRangeSliderM3EMinEqualsMaxUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'negative_range', + builder: _widgetbook_workspace_slider_m3e_range_slider_m3e_usecases + .buildRangeSliderM3ENegativeRangeUseCase, + ), + ], + ), + _widgetbook.WidgetbookComponent( + name: 'SliderM3E', + useCases: [ + _widgetbook.WidgetbookUseCase( + name: 'default', + builder: _widgetbook_workspace_slider_m3e_slider_m3e_usecases + .buildSliderM3EDefaultUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'disabled', + builder: _widgetbook_workspace_slider_m3e_slider_m3e_usecases + .buildSliderM3EDisabledUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'discrete', + builder: _widgetbook_workspace_slider_m3e_slider_m3e_usecases + .buildSliderM3EDiscreteUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'extremes_0_to_100', + builder: _widgetbook_workspace_slider_m3e_slider_m3e_usecases + .buildSliderM3EExtremes0100UseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'min_equals_max', + builder: _widgetbook_workspace_slider_m3e_slider_m3e_usecases + .buildSliderM3EMinEqualsMaxUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'negative_range', + builder: _widgetbook_workspace_slider_m3e_slider_m3e_usecases + .buildSliderM3ENegativeRangeUseCase, + ), + ], + ), + _widgetbook.WidgetbookComponent( + name: 'SliverAppBarM3E', + useCases: [ + _widgetbook.WidgetbookUseCase( + name: 'default', + builder: _widgetbook_workspace_app_bar_m3e_sliver_app_bar_m3e_usecases + .buildSliverAppBarM3EDefaultUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'density', + builder: _widgetbook_workspace_app_bar_m3e_sliver_app_bar_m3e_usecases + .buildSliverAppBarM3EDensityUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'floating', + builder: _widgetbook_workspace_app_bar_m3e_sliver_app_bar_m3e_usecases + .buildSliverAppBarM3EFloatingUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'large', + builder: _widgetbook_workspace_app_bar_m3e_sliver_app_bar_m3e_usecases + .buildSliverAppBarM3ELargeUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'long_text', + builder: _widgetbook_workspace_app_bar_m3e_sliver_app_bar_m3e_usecases + .buildSliverAppBarM3ELongTextUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'many_actions', + builder: _widgetbook_workspace_app_bar_m3e_sliver_app_bar_m3e_usecases + .buildSliverAppBarM3EManyActionsUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'medium', + builder: _widgetbook_workspace_app_bar_m3e_sliver_app_bar_m3e_usecases + .buildSliverAppBarM3EMediumUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'pinned', + builder: _widgetbook_workspace_app_bar_m3e_sliver_app_bar_m3e_usecases + .buildSliverAppBarM3EPinnedUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'semantics_label', + builder: _widgetbook_workspace_app_bar_m3e_sliver_app_bar_m3e_usecases + .buildSliverAppBarM3ESemanticsLabelUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'shape_family', + builder: _widgetbook_workspace_app_bar_m3e_sliver_app_bar_m3e_usecases + .buildSliverAppBarM3EShapeFamilyUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'small', + builder: _widgetbook_workspace_app_bar_m3e_sliver_app_bar_m3e_usecases + .buildSliverAppBarM3ESmallUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'snap', + builder: _widgetbook_workspace_app_bar_m3e_sliver_app_bar_m3e_usecases + .buildSliverAppBarM3ESnapUseCase, + ), + ], + ), + _widgetbook.WidgetbookComponent( + name: 'SplitButtonM3E', + useCases: [ + _widgetbook.WidgetbookUseCase( + name: 'default', + builder: _widgetbook_workspace_split_button_m3e_split_button_m3e_usecase + .buildSplitButtonM3EDefaultUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'disabled', + builder: _widgetbook_workspace_split_button_m3e_split_button_m3e_usecase + .buildSplitButtonM3EDisabledUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'elevated', + builder: _widgetbook_workspace_split_button_m3e_split_button_m3e_usecase + .buildSplitButtonM3EElevatedUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'empty_items', + builder: _widgetbook_workspace_split_button_m3e_split_button_m3e_usecase + .buildSplitButtonM3EEmptyItemsUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'filled', + builder: _widgetbook_workspace_split_button_m3e_split_button_m3e_usecase + .buildSplitButtonM3EFilledUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'lg', + builder: _widgetbook_workspace_split_button_m3e_split_button_m3e_usecase + .buildSplitButtonM3ELGUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'long_label', + builder: _widgetbook_workspace_split_button_m3e_split_button_m3e_usecase + .buildSplitButtonM3ELongLabelUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'many_items', + builder: _widgetbook_workspace_split_button_m3e_split_button_m3e_usecase + .buildSplitButtonM3EManyItemsUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'md', + builder: _widgetbook_workspace_split_button_m3e_split_button_m3e_usecase + .buildSplitButtonM3EMDUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'outlined', + builder: _widgetbook_workspace_split_button_m3e_split_button_m3e_usecase + .buildSplitButtonM3EOutlinedUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'round', + builder: _widgetbook_workspace_split_button_m3e_split_button_m3e_usecase + .buildSplitButtonM3ERoundUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'sm', + builder: _widgetbook_workspace_split_button_m3e_split_button_m3e_usecase + .buildSplitButtonM3ESMUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'square', + builder: _widgetbook_workspace_split_button_m3e_split_button_m3e_usecase + .buildSplitButtonM3ESquareUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'text', + builder: _widgetbook_workspace_split_button_m3e_split_button_m3e_usecase + .buildSplitButtonM3ETextUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'tonal', + builder: _widgetbook_workspace_split_button_m3e_split_button_m3e_usecase + .buildSplitButtonM3ETonalUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'with_icon', + builder: _widgetbook_workspace_split_button_m3e_split_button_m3e_usecase + .buildSplitButtonM3EWithIconUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'with_label', + builder: _widgetbook_workspace_split_button_m3e_split_button_m3e_usecase + .buildSplitButtonM3EWithLabelUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'xl', + builder: _widgetbook_workspace_split_button_m3e_split_button_m3e_usecase + .buildSplitButtonM3EXLUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'xs', + builder: _widgetbook_workspace_split_button_m3e_split_button_m3e_usecase + .buildSplitButtonM3EXSUseCase, + ), + ], + ), + _widgetbook.WidgetbookComponent( + name: 'ToolbarIconButtonM3E', + useCases: [ + _widgetbook.WidgetbookUseCase( + name: 'custom_color_and_size', + builder: + _widgetbook_workspace_toolbar_m3e_toolbar_icon_button_m3e_usecases + .buildToolbarIconButtonM3ECustomStyleUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'default', + builder: + _widgetbook_workspace_toolbar_m3e_toolbar_icon_button_m3e_usecases + .buildToolbarIconButtonM3EDefaultUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'destructive', + builder: + _widgetbook_workspace_toolbar_m3e_toolbar_icon_button_m3e_usecases + .buildToolbarIconButtonM3EDestructiveUseCase, + ), + ], + ), + _widgetbook.WidgetbookComponent( + name: 'ToolbarM3E', + useCases: [ + _widgetbook.WidgetbookUseCase( + name: 'centered_title', + builder: _widgetbook_workspace_toolbar_m3e_toolbar_m3e_usecases + .buildToolbarM3ECenteredTitleUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'compact', + builder: _widgetbook_workspace_toolbar_m3e_toolbar_m3e_usecases + .buildToolbarM3ECompactUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'default', + builder: _widgetbook_workspace_toolbar_m3e_toolbar_m3e_usecases + .buildToolbarM3EDefaultUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'large', + builder: _widgetbook_workspace_toolbar_m3e_toolbar_m3e_usecases + .buildToolbarM3ELargeUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'long_title', + builder: _widgetbook_workspace_toolbar_m3e_toolbar_m3e_usecases + .buildToolbarM3ELongTitleUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'medium', + builder: _widgetbook_workspace_toolbar_m3e_toolbar_m3e_usecases + .buildToolbarM3EMediumUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'primary', + builder: _widgetbook_workspace_toolbar_m3e_toolbar_m3e_usecases + .buildToolbarM3EPrimaryUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'regular', + builder: _widgetbook_workspace_toolbar_m3e_toolbar_m3e_usecases + .buildToolbarM3ERegularUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'small', + builder: _widgetbook_workspace_toolbar_m3e_toolbar_m3e_usecases + .buildToolbarM3ESmallUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'surface', + builder: _widgetbook_workspace_toolbar_m3e_toolbar_m3e_usecases + .buildToolbarM3ESurfaceUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'tonal', + builder: _widgetbook_workspace_toolbar_m3e_toolbar_m3e_usecases + .buildToolbarM3ETonalUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'with_leading', + builder: _widgetbook_workspace_toolbar_m3e_toolbar_m3e_usecases + .buildToolbarM3EWithLeadingUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'with_overflow', + builder: _widgetbook_workspace_toolbar_m3e_toolbar_m3e_usecases + .buildToolbarM3EWithOverflowUseCase, + ), + _widgetbook.WidgetbookUseCase( + name: 'with_trailing_actions', + builder: _widgetbook_workspace_toolbar_m3e_toolbar_m3e_usecases + .buildToolbarM3EWithActionsUseCase, + ), + ], + ), +]; diff --git a/widgetbook/lib/navigation_bar_m3e/INDEX-navigation_bar_m3e.md b/widgetbook/lib/navigation_bar_m3e/INDEX-navigation_bar_m3e.md new file mode 100644 index 0000000..4624b0b --- /dev/null +++ b/widgetbook/lib/navigation_bar_m3e/INDEX-navigation_bar_m3e.md @@ -0,0 +1,34 @@ +# INDEX — navigation_bar_m3e + +This index summarizes Widgetbook use cases for components in packages/navigation_bar_m3e. + +## Components and Variants + +### NavigationBarM3E +- default +- pill_indicator +- underline_indicator +- none_indicator +- small_size +- medium_size +- round_shape +- square_shape +- regular_density +- compact_density +- with_3_destinations +- with_4_destinations +- with_5_destinations +- with_badges + +### NavBadgeM3E +- default +- dot +- edge_max_count +- custom_colors +- offset_tweak +- with_semantic_label + +Notes +- All use cases follow plan/guide.md: @UseCase annotations and build[Component][Variant]UseCase signatures. +- Knobs are provided for critical parameters and visual options. +- Callbacks print helpful messages where applicable. diff --git a/widgetbook/lib/navigation_bar_m3e/navigation_bar_m3e_usecases.dart b/widgetbook/lib/navigation_bar_m3e/navigation_bar_m3e_usecases.dart new file mode 100644 index 0000000..da9037b --- /dev/null +++ b/widgetbook/lib/navigation_bar_m3e/navigation_bar_m3e_usecases.dart @@ -0,0 +1,176 @@ +import 'package:flutter/material.dart'; +import 'package:navigation_bar_m3e/navigation_bar_m3e.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; + +// Helper: Build demo destinations (3–5), optionally with badges +List _demoDestinations( + int count, { + bool withBadges = false, +}) { + final clamped = count.clamp(3, 5); + final base = <(IconData, IconData, String)>[ + (Icons.home_outlined, Icons.home_rounded, 'Home'), + (Icons.search, Icons.search_rounded, 'Search'), + (Icons.notifications_none_rounded, Icons.notifications_rounded, 'Alerts'), + (Icons.person_outline_rounded, Icons.person_rounded, 'Profile'), + (Icons.settings_outlined, Icons.settings_rounded, 'Settings'), + ]; + + return List.generate(clamped, (i) { + final (icon, selectedIcon, label) = base[i]; + return NavigationDestinationM3E( + icon: Icon(icon), + selectedIcon: Icon(selectedIcon), + label: label, + badgeCount: withBadges && i == 2 ? 12 : null, + badgeDot: withBadges && i == 1, + semanticLabel: withBadges ? 'Tab $label with badge' : 'Tab $label', + ); + }); +} + +// A flexible "playground" builder used by the variants below +Widget _buildNavBar( + BuildContext context, { + NavBarM3EIndicatorStyle? indicatorStyle, + NavBarM3ESize? size, + NavBarM3EShapeFamily? shapeFamily, + NavBarM3EDensity? density, + bool? withBadges, + Color? backgroundColor, + Color? indicatorColor, +}) { + final count = context.knobs.int.slider( + label: 'destinations', + initialValue: 4, + min: 3, + max: 5, + ); + + final idx = context.knobs.int.slider( + label: 'selectedIndex', + initialValue: 0, + min: 0, + max: count - 1, + ); + + final labelBehavior = context.knobs.object.dropdown( + label: 'labelBehavior', + initialOption: NavBarM3ELabelBehavior.alwaysShow, + options: const [ + NavBarM3ELabelBehavior.alwaysShow, + NavBarM3ELabelBehavior.onlySelected, + NavBarM3ELabelBehavior.alwaysHide, + ], + labelBuilder: (v) => v.name, + ); + + final safeArea = context.knobs.boolean(label: 'safeArea', initialValue: true); + + final bg = context.knobs.colorOrNull(label: 'backgroundColor'); + final ind = context.knobs.colorOrNull(label: 'indicatorColor'); + + return NavigationBarM3E( + destinations: _demoDestinations(count, withBadges: withBadges ?? false), + selectedIndex: idx, + onDestinationSelected: (i) => + debugPrint('NavigationBarM3E: selected index = $i'), + labelBehavior: labelBehavior, + size: size ?? NavBarM3ESize.medium, + shapeFamily: shapeFamily ?? NavBarM3EShapeFamily.round, + density: density ?? NavBarM3EDensity.regular, + indicatorStyle: indicatorStyle ?? NavBarM3EIndicatorStyle.pill, + safeArea: safeArea, + backgroundColor: backgroundColor ?? bg, + indicatorColor: indicatorColor ?? ind, + ); +} + +@UseCase(name: 'default', type: NavigationBarM3E) +Widget buildNavigationBarM3EDefaultUseCase(BuildContext context) { + return _buildNavBar(context); +} + +@UseCase(name: 'playground', type: NavigationBarM3E) +Widget buildNavigationBarM3EPlaygroundUseCase(BuildContext context) { + final indicator = context.knobs.object.dropdown( + label: 'indicatorStyle', + initialOption: NavBarM3EIndicatorStyle.pill, + options: const [ + NavBarM3EIndicatorStyle.pill, + NavBarM3EIndicatorStyle.underline, + NavBarM3EIndicatorStyle.none, + ], + labelBuilder: (v) => v.name, + ); + + final size = context.knobs.object.dropdown( + label: 'size', + initialOption: NavBarM3ESize.medium, + options: const [NavBarM3ESize.small, NavBarM3ESize.medium], + labelBuilder: (v) => v.name, + ); + + final shape = context.knobs.object.dropdown( + label: 'shapeFamily', + initialOption: NavBarM3EShapeFamily.round, + options: const [NavBarM3EShapeFamily.round, NavBarM3EShapeFamily.square], + labelBuilder: (v) => v.name, + ); + + final density = context.knobs.object.dropdown( + label: 'density', + initialOption: NavBarM3EDensity.regular, + options: const [NavBarM3EDensity.regular, NavBarM3EDensity.compact], + labelBuilder: (v) => v.name, + ); + + final withBadges = + context.knobs.boolean(label: 'withBadges', initialValue: false); + + return _buildNavBar( + context, + indicatorStyle: indicator, + size: size, + shapeFamily: shape, + density: density, + withBadges: withBadges, + ); +} + +@UseCase(name: 'pill_indicator', type: NavigationBarM3E) +Widget buildNavigationBarM3EPillIndicatorUseCase(BuildContext context) { + return _buildNavBar(context, indicatorStyle: NavBarM3EIndicatorStyle.pill); +} + +@UseCase(name: 'underline_indicator', type: NavigationBarM3E) +Widget buildNavigationBarM3EUnderlineIndicatorUseCase(BuildContext context) { + return _buildNavBar(context, + indicatorStyle: NavBarM3EIndicatorStyle.underline); +} + +@UseCase(name: 'no_indicator', type: NavigationBarM3E) +Widget buildNavigationBarM3ENoIndicatorUseCase(BuildContext context) { + return _buildNavBar(context, indicatorStyle: NavBarM3EIndicatorStyle.none); +} + +@UseCase(name: 'small_size', type: NavigationBarM3E) +Widget buildNavigationBarM3ESmallSizeUseCase(BuildContext context) { + return _buildNavBar(context, size: NavBarM3ESize.small); +} + +@UseCase(name: 'square_shape', type: NavigationBarM3E) +Widget buildNavigationBarM3ESquareShapeUseCase(BuildContext context) { + return _buildNavBar(context, shapeFamily: NavBarM3EShapeFamily.square); +} + +@UseCase(name: 'compact_density', type: NavigationBarM3E) +Widget buildNavigationBarM3ECompactDensityUseCase(BuildContext context) { + return _buildNavBar(context, density: NavBarM3EDensity.compact); +} + +@UseCase(name: 'with_badges', type: NavigationBarM3E) +Widget buildNavigationBarM3EWithBadgesUseCase(BuildContext context) { + return _buildNavBar(context, withBadges: true); +} diff --git a/widgetbook/lib/navigation_rail_m3e/INDEX-navigation_rail_m3e.md b/widgetbook/lib/navigation_rail_m3e/INDEX-navigation_rail_m3e.md new file mode 100644 index 0000000..d111af0 --- /dev/null +++ b/widgetbook/lib/navigation_rail_m3e/INDEX-navigation_rail_m3e.md @@ -0,0 +1,35 @@ +# INDEX — navigation_rail_m3e + +This index summarizes Widgetbook use cases for the `navigation_rail_m3e` package. + +## Components and Variants + +- NavigationRailM3E + - default + - collapsed + - always_collapsed + - always_expanded + - modal + - labels_only_selected + - labels_always_hide + - three_destinations + - five_destinations_with_badges + - with_fab_slot + - with_trailing + +- RailItemButtonM3E + - default + - expanded + - selected + - with_badge + +- RailBadgeM3E + - default + - dot + - overflow_999+ + - dense + +Notes +- All use cases are placed under: `packages/navigation_rail_m3e/lib/src/widgetbook/`. +- Use cases follow plan/guide.md: `@UseCase(name: '...', type: ComponentType)` and method names `build[Component][Variant]UseCase`. +- Critical parameters are exposed via knobs; callbacks print useful messages. diff --git a/widgetbook/lib/navigation_rail_m3e/navigation_rail_m3e_usecases.dart b/widgetbook/lib/navigation_rail_m3e/navigation_rail_m3e_usecases.dart new file mode 100644 index 0000000..814c1ea --- /dev/null +++ b/widgetbook/lib/navigation_rail_m3e/navigation_rail_m3e_usecases.dart @@ -0,0 +1,203 @@ +import 'package:flutter/material.dart'; +import 'package:navigation_rail_m3e/navigation_rail_m3e.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; + +List _buildSections( + BuildContext context, { + required int sectionCount, + required int itemsPerSection, + required bool withBadges, + required bool useShortItems, +}) { + final icons = [ + Icons.inbox_outlined, + Icons.send_outlined, + Icons.star_outline, + Icons.archive_outlined, + Icons.delete_outline, + Icons.settings_outlined, + ]; + + return List.generate(sectionCount, (s) { + final destinations = List.generate(itemsPerSection, (i) { + final idx = (s * itemsPerSection + i) % icons.length; + return NavigationRailM3EDestination( + icon: Icon(icons[idx]), + selectedIcon: Icon( + icons[idx].codePoint == Icons.inbox_outlined.codePoint + ? Icons.inbox + : icons[idx]), + label: 'Item ${s + 1}.${i + 1}', + semanticLabel: 'Item ${s + 1}.${i + 1}', + badgeCount: withBadges + ? ((i % 3 == 0) + ? 0 + : (i % 4 == 0) + ? 1001 + : (i + 1) * (s + 1)) + : null, + short: useShortItems && (i % 2 == 0), + ); + }); + return NavigationRailM3ESection( + header: Text('Section ${s + 1}'), + destinations: destinations, + ); + }); +} + +Widget _buildRailDemo( + BuildContext context, { + NavigationRailM3EType? forcedType, + NavigationRailM3EModality? forcedModality, +}) { + // Content knobs + final sectionsCount = context.knobs.int.slider( + label: 'sections', + initialValue: 2, + min: 1, + max: 3, + ); + final itemsPerSection = context.knobs.int.slider( + label: 'items per section', + initialValue: 3, + min: 1, + max: 6, + ); + final withBadges = + context.knobs.boolean(label: 'with badges', initialValue: true); + final useShortItems = + context.knobs.boolean(label: 'use short items', initialValue: false); + + final sections = _buildSections( + context, + sectionCount: sectionsCount, + itemsPerSection: itemsPerSection, + withBadges: withBadges, + useShortItems: useShortItems, + ); + final totalItems = + sections.fold(0, (sum, s) => sum + s.destinations.length); + + // Behavior knobs + final type = forcedType ?? + context.knobs.object.dropdown( + label: 'type', + options: NavigationRailM3EType.values, + initialOption: NavigationRailM3EType.expanded, + labelBuilder: (v) => v.name, + ); + final modality = forcedModality ?? + context.knobs.object.dropdown( + label: 'modality', + options: NavigationRailM3EModality.values, + initialOption: NavigationRailM3EModality.standard, + labelBuilder: (v) => v.name, + ); + final labelBehavior = + context.knobs.object.dropdown( + label: 'labelBehavior', + options: NavigationRailM3ELabelBehavior.values, + initialOption: NavigationRailM3ELabelBehavior.alwaysShow, + labelBuilder: (v) => v.name, + ); + final hideWhenCollapsed = context.knobs.boolean( + label: 'hideWhenCollapsed', + initialValue: false, + ); + final scrollable = context.knobs.boolean( + label: 'scrollable', + initialValue: true, + ); + final expandedWidth = context.knobs.double.slider( + label: 'expandedWidth', + initialValue: 280, + min: 220, + max: 360, + divisions: 14, + ); + final selectedIndex = context.knobs.int.slider( + label: 'selectedIndex', + initialValue: 0, + min: 0, + max: (totalItems == 0 ? 0 : totalItems - 1), + ); + + // Slots knobs + final withFab = context.knobs.boolean(label: 'with FAB', initialValue: true); + final withTrailing = + context.knobs.boolean(label: 'with trailing', initialValue: false); + final trailingAtBottom = + context.knobs.boolean(label: 'trailingAtBottom', initialValue: true); + + final fab = withFab + ? NavigationRailM3EFabSlot( + icon: const Icon(Icons.add), + label: 'Create', + onPressed: () => print('Rail FAB pressed'), + tooltip: 'Create', + ) + : null; + + final trailing = withTrailing + ? IconButton( + tooltip: 'Settings', + onPressed: () => print('Trailing pressed'), + icon: const Icon(Icons.settings_outlined), + ) + : null; + + return Row( + children: [ + NavigationRailM3E( + type: type, + modality: modality, + sections: sections, + selectedIndex: selectedIndex.clamp(0, (totalItems - 1).clamp(0, 9999)), + onDestinationSelected: (i) => print('Selected index: $i'), + fab: fab, + hideWhenCollapsed: hideWhenCollapsed, + expandedWidth: expandedWidth, + onDismissModal: () => print('Dismiss modal'), + onTypeChanged: (t) => print('Type changed -> ${t.name}'), + labelBehavior: labelBehavior, + scrollable: scrollable, + trailing: trailing, + trailingAtBottom: trailingAtBottom, + ), + // Fake content area to the right + Expanded( + child: Container( + height: double.infinity, + color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.2), + alignment: Alignment.center, + child: const Text('Content area'), + ), + ) + ], + ); +} + +@UseCase(name: 'default', type: NavigationRailM3E) +Widget buildNavigationRailM3EDefaultUseCase(BuildContext context) { + return _buildRailDemo(context); +} + +@UseCase(name: 'collapsed_standard', type: NavigationRailM3E) +Widget buildNavigationRailM3ECollapsedStandardUseCase(BuildContext context) { + return _buildRailDemo( + context, + forcedType: NavigationRailM3EType.collapsed, + forcedModality: NavigationRailM3EModality.standard, + ); +} + +@UseCase(name: 'expanded_modal', type: NavigationRailM3E) +Widget buildNavigationRailM3EExpandedModalUseCase(BuildContext context) { + return _buildRailDemo( + context, + forcedType: NavigationRailM3EType.expanded, + forcedModality: NavigationRailM3EModality.modal, + ); +} diff --git a/widgetbook/lib/navigation_rail_m3e/rail_badge_m3e_usecases.dart b/widgetbook/lib/navigation_rail_m3e/rail_badge_m3e_usecases.dart new file mode 100644 index 0000000..6053b70 --- /dev/null +++ b/widgetbook/lib/navigation_rail_m3e/rail_badge_m3e_usecases.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook; +import 'package:navigation_rail_m3e/navigation_rail_m3e.dart'; + +// Note: RailBadgeM3E shows nothing when count is null; dot when 0; count otherwise. + +@widgetbook.UseCase(name: 'default', type: RailBadgeM3E) +Widget buildRailBadgeM3EUseCase(BuildContext context) { + final count = context.knobs.intOrNull.slider( + label: 'count', + initialValue: 7, + min: 0, + max: 1200, + divisions: 120, + ); + final maxDigits = context.knobs.int.slider( + label: 'maxDigits', + initialValue: 3, + min: 1, + max: 4, + ); + final dense = context.knobs.boolean(label: 'dense', initialValue: false); + return Center( + child: RailBadgeM3E(count: count, maxDigits: maxDigits, dense: dense), + ); +} + +@widgetbook.UseCase(name: 'dot', type: RailBadgeM3E) +Widget buildRailBadgeM3EDotUseCase(BuildContext context) { + return const Center(child: RailBadgeM3E(count: 0)); +} + +@widgetbook.UseCase(name: 'overflow_999+', type: RailBadgeM3E) +Widget buildRailBadgeM3EOverflowUseCase(BuildContext context) { + return const Center(child: RailBadgeM3E(count: 1200, maxDigits: 3)); +} + +@widgetbook.UseCase(name: 'dense', type: RailBadgeM3E) +Widget buildRailBadgeM3EDenseUseCase(BuildContext context) { + return const Center(child: RailBadgeM3E(count: 42, dense: true)); +} diff --git a/widgetbook/lib/navigation_rail_m3e/rail_item_button_m3e_usecases.dart b/widgetbook/lib/navigation_rail_m3e/rail_item_button_m3e_usecases.dart new file mode 100644 index 0000000..55cf940 --- /dev/null +++ b/widgetbook/lib/navigation_rail_m3e/rail_item_button_m3e_usecases.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:navigation_rail_m3e/navigation_rail_m3e.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; + +Widget _buildRailItemButtonDemo( + BuildContext context, { + required bool expanded, +}) { + final isSelected = + context.knobs.boolean(label: 'isSelected', initialValue: false); + final label = context.knobs + .string(label: 'label', initialValue: expanded ? 'Inbox' : 'Inbox'); + final semantic = + context.knobs.stringOrNull(label: 'semanticLabel', initialValue: 'Inbox'); + final labelBehavior = + context.knobs.object.dropdown( + label: 'labelBehavior', + options: NavigationRailM3ELabelBehavior.values, + initialOption: NavigationRailM3ELabelBehavior.alwaysShow, + labelBuilder: (v) => v.name, + ); + final badge = context.knobs.intOrNull.slider( + label: 'badgeCount', + initialValue: null, + min: 0, + max: 200, + divisions: 20, + ); + final suppressInk = + context.knobs.boolean(label: 'suppressInk', initialValue: false); + final useAltIcon = + context.knobs.boolean(label: 'useAltIcon', initialValue: false); + + final icon = Icon(useAltIcon ? Icons.star : Icons.inbox_outlined); + final selectedIcon = Icon(useAltIcon ? Icons.star : Icons.inbox); + + return Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 300), + child: DecoratedBox( + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.withValues(alpha: 0.3)), + borderRadius: BorderRadius.circular(12), + ), + child: Padding( + padding: const EdgeInsets.all(8), + child: RailItemButtonM3E( + icon: icon, + selectedIcon: selectedIcon, + isSelected: isSelected, + onPressed: () => print('RailItemButtonM3E pressed'), + expanded: expanded, + labelBehavior: labelBehavior, + label: label, + semanticLabel: semantic, + suppressInk: suppressInk, + badgeCount: badge, + ), + ), + ), + ), + ); +} + +@UseCase(name: 'collapsed', type: RailItemButtonM3E) +Widget buildRailItemButtonM3ECollapsedUseCase(BuildContext context) { + return _buildRailItemButtonDemo(context, expanded: false); +} + +@UseCase(name: 'expanded', type: RailItemButtonM3E) +Widget buildRailItemButtonM3EExpandedUseCase(BuildContext context) { + return _buildRailItemButtonDemo(context, expanded: true); +} diff --git a/widgetbook/lib/progress_indicator_m3e/INDEX-progress_indicator_m3e.md b/widgetbook/lib/progress_indicator_m3e/INDEX-progress_indicator_m3e.md new file mode 100644 index 0000000..f984861 --- /dev/null +++ b/widgetbook/lib/progress_indicator_m3e/INDEX-progress_indicator_m3e.md @@ -0,0 +1,29 @@ +# Widgetbook Index — progress_indicator_m3e + +This index lists all components in the `progress_indicator_m3e` package and their Widgetbook variants. + +## Components and Variants + +- CircularProgressIndicatorM3E + - indeterminate + - determinate + - flat + - wavy + - size_s + - size_m + +- LinearProgressIndicatorM3E + - indeterminate + - determinate + - size_s + - size_m + +- ProgressWithLabelM3E + - default + - determinate_with_label + - long_label_text + +Notes +- Knobs are provided for critical params (progress/value, size, shape, rotation/phase/inset as applicable). +- Themes are provided globally by the Widgetbook app (no local Theme wrapping). +- Complex parameters such as custom colors are left at sensible defaults (see TODOs in code). diff --git a/widgetbook/lib/progress_indicator_m3e/circular_progress_m3e_usecases.dart b/widgetbook/lib/progress_indicator_m3e/circular_progress_m3e_usecases.dart new file mode 100644 index 0000000..c0aac8a --- /dev/null +++ b/widgetbook/lib/progress_indicator_m3e/circular_progress_m3e_usecases.dart @@ -0,0 +1,204 @@ + + +import 'package:flutter/material.dart'; +import 'package:progress_indicator_m3e/progress_indicator_m3e.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; + +// Use cases for CircularProgressIndicatorM3E per plan/guide.md +// - Themes are injected globally by the Widgetbook app. +// - Knobs for value (when determinate), size, shape, rotation. +// - TODO: Consider adding color knobs for active/track colors. + +@UseCase(name: 'indeterminate', type: CircularProgressIndicatorM3E) +Widget buildCircularProgressIndicatorM3EIndeterminateUseCase( + BuildContext context) { + final CircularProgressM3ESize size = context.knobs.object.dropdown( + label: 'size', + initialOption: CircularProgressM3ESize.m, + options: CircularProgressM3ESize.values, + labelBuilder: (v) => v.name, + ); + final ProgressM3EShape shape = context.knobs.object.dropdown( + label: 'shape', + initialOption: ProgressM3EShape.wavy, + options: ProgressM3EShape.values, + labelBuilder: (v) => v.name, + ); + final double rotation = context.knobs.double.slider( + label: 'rotation (rad)', + initialValue: 0.0, + min: 0.0, + max: 6.28318, + divisions: 36, + ); + + return Center( + child: CircularProgressIndicatorM3E( + value: null, // indeterminate + size: size, + shape: shape, + rotation: rotation, + ), + ); +} + +@UseCase(name: 'determinate', type: CircularProgressIndicatorM3E) +Widget buildCircularProgressIndicatorM3EDeterminateUseCase( + BuildContext context) { + final double value = context.knobs.double.slider( + label: 'value', + initialValue: 0.6, + min: 0.0, + max: 1.0, + divisions: 100, + ); + final CircularProgressM3ESize size = context.knobs.object.dropdown( + label: 'size', + initialOption: CircularProgressM3ESize.m, + options: CircularProgressM3ESize.values, + labelBuilder: (v) => v.name, + ); + final ProgressM3EShape shape = context.knobs.object.dropdown( + label: 'shape', + initialOption: ProgressM3EShape.wavy, + options: ProgressM3EShape.values, + labelBuilder: (v) => v.name, + ); + + return Center( + child: CircularProgressIndicatorM3E( + value: value, + size: size, + shape: shape, + ), + ); +} + +@UseCase(name: 'flat', type: CircularProgressIndicatorM3E) +Widget buildCircularProgressIndicatorM3EFlatUseCase(BuildContext context) { + final double? valueOrNull = context.knobs.boolean( + label: 'determinate', + initialValue: true, + ) + ? context.knobs.double.slider( + label: 'value', + initialValue: 0.75, + min: 0.0, + max: 1.0, + divisions: 100, + ) + : null; + final CircularProgressM3ESize size = context.knobs.object.dropdown( + label: 'size', + initialOption: CircularProgressM3ESize.m, + options: CircularProgressM3ESize.values, + labelBuilder: (v) => v.name, + ); + + return Center( + child: CircularProgressIndicatorM3E( + value: valueOrNull, + size: size, + shape: ProgressM3EShape.flat, + ), + ); +} + +@UseCase(name: 'wavy', type: CircularProgressIndicatorM3E) +Widget buildCircularProgressIndicatorM3EWavyUseCase(BuildContext context) { + final double? valueOrNull = context.knobs.boolean( + label: 'determinate', + initialValue: false, + ) + ? context.knobs.double.slider( + label: 'value', + initialValue: 0.4, + min: 0.0, + max: 1.0, + divisions: 100, + ) + : null; + final CircularProgressM3ESize size = context.knobs.object.dropdown( + label: 'size', + initialOption: CircularProgressM3ESize.m, + options: CircularProgressM3ESize.values, + labelBuilder: (v) => v.name, + ); + final double rotation = context.knobs.double.slider( + label: 'rotation (rad)', + initialValue: 0.0, + min: 0.0, + max: 6.28318, + divisions: 36, + ); + + return Center( + child: CircularProgressIndicatorM3E( + value: valueOrNull, + size: size, + shape: ProgressM3EShape.wavy, + rotation: rotation, + ), + ); +} + +@UseCase(name: 'size_s', type: CircularProgressIndicatorM3E) +Widget buildCircularProgressIndicatorM3ESizeSUseCase(BuildContext context) { + final ProgressM3EShape shape = context.knobs.object.dropdown( + label: 'shape', + initialOption: ProgressM3EShape.wavy, + options: ProgressM3EShape.values, + labelBuilder: (v) => v.name, + ); + final double? valueOrNull = context.knobs.boolean( + label: 'determinate', + initialValue: true, + ) + ? context.knobs.double.slider( + label: 'value', + initialValue: 1.0, + min: 0.0, + max: 1.0, + divisions: 100, + ) + : null; + + return Center( + child: CircularProgressIndicatorM3E( + value: valueOrNull, + size: CircularProgressM3ESize.s, + shape: shape, + ), + ); +} + +@UseCase(name: 'size_m', type: CircularProgressIndicatorM3E) +Widget buildCircularProgressIndicatorM3ESizeMUseCase(BuildContext context) { + final ProgressM3EShape shape = context.knobs.object.dropdown( + label: 'shape', + initialOption: ProgressM3EShape.wavy, + options: ProgressM3EShape.values, + labelBuilder: (v) => v.name, + ); + final double? valueOrNull = context.knobs.boolean( + label: 'determinate', + initialValue: true, + ) + ? context.knobs.double.slider( + label: 'value', + initialValue: 0.0, // boundary case + min: 0.0, + max: 1.0, + divisions: 100, + ) + : null; + + return Center( + child: CircularProgressIndicatorM3E( + value: valueOrNull, + size: CircularProgressM3ESize.m, + shape: shape, + ), + ); +} diff --git a/widgetbook/lib/progress_indicator_m3e/linear_progress_m3e_usecases.dart b/widgetbook/lib/progress_indicator_m3e/linear_progress_m3e_usecases.dart new file mode 100644 index 0000000..33c12df --- /dev/null +++ b/widgetbook/lib/progress_indicator_m3e/linear_progress_m3e_usecases.dart @@ -0,0 +1,188 @@ + + +import 'package:flutter/material.dart'; +import 'package:progress_indicator_m3e/progress_indicator_m3e.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; + +// Use cases for LinearProgressIndicatorM3E per plan/guide.md +// - Themes are injected globally by the Widgetbook app. +// - Knobs for value (when determinate), size, shape, phase (wavy), and inset. +// - TODO: Consider adding color knobs for active/track colors. + +@UseCase(name: 'indeterminate', type: LinearProgressIndicatorM3E) +Widget buildLinearProgressIndicatorM3EIndeterminateUseCase( + BuildContext context) { + final LinearProgressM3ESize size = context.knobs.object.dropdown( + label: 'size', + initialOption: LinearProgressM3ESize.m, + options: LinearProgressM3ESize.values, + labelBuilder: (v) => v.name, + ); + final ProgressM3EShape shape = context.knobs.object.dropdown( + label: 'shape', + initialOption: ProgressM3EShape.wavy, + options: ProgressM3EShape.values, + labelBuilder: (v) => v.name, + ); + final double phase = context.knobs.double.slider( + label: 'phase (rad) — wavy only', + initialValue: 0.0, + min: 0.0, + max: 6.28318, + divisions: 36, + ); + final double inset = context.knobs.double.slider( + label: 'inset', + initialValue: 4.0, + min: 0.0, + max: 24.0, + divisions: 24, + ); + + return Padding( + padding: const EdgeInsets.all(16), + child: LinearProgressIndicatorM3E( + value: null, // indeterminate + size: size, + shape: shape, + phase: phase, + inset: inset, + ), + ); +} + +@UseCase(name: 'determinate', type: LinearProgressIndicatorM3E) +Widget buildLinearProgressIndicatorM3EDeterminateUseCase( + BuildContext context) { + final double value = context.knobs.double.slider( + label: 'value', + initialValue: 0.5, + min: 0.0, + max: 1.0, + divisions: 100, + ); + final LinearProgressM3ESize size = context.knobs.object.dropdown( + label: 'size', + initialOption: LinearProgressM3ESize.m, + options: LinearProgressM3ESize.values, + labelBuilder: (v) => v.name, + ); + final ProgressM3EShape shape = context.knobs.object.dropdown( + label: 'shape', + initialOption: ProgressM3EShape.wavy, + options: ProgressM3EShape.values, + labelBuilder: (v) => v.name, + ); + final double phase = context.knobs.double.slider( + label: 'phase (rad) — wavy only', + initialValue: 0.0, + min: 0.0, + max: 6.28318, + divisions: 36, + ); + final double inset = context.knobs.double.slider( + label: 'inset', + initialValue: 4.0, + min: 0.0, + max: 24.0, + divisions: 24, + ); + + return Padding( + padding: const EdgeInsets.all(16), + child: LinearProgressIndicatorM3E( + value: value, + size: size, + shape: shape, + phase: phase, + inset: inset, + ), + ); +} + +@UseCase(name: 'size_s', type: LinearProgressIndicatorM3E) +Widget buildLinearProgressIndicatorM3ESizeSUseCase(BuildContext context) { + final double? valueOrNull = context.knobs.boolean( + label: 'determinate', + initialValue: true, + ) + ? context.knobs.double.slider( + label: 'value', + initialValue: 0.0, // boundary case + min: 0.0, + max: 1.0, + divisions: 100, + ) + : null; + final ProgressM3EShape shape = context.knobs.object.dropdown( + label: 'shape', + initialOption: ProgressM3EShape.flat, + options: ProgressM3EShape.values, + labelBuilder: (v) => v.name, + ); + final double inset = context.knobs.double.slider( + label: 'inset', + initialValue: 0.0, + min: 0.0, + max: 24.0, + divisions: 24, + ); + + return Padding( + padding: const EdgeInsets.all(16), + child: LinearProgressIndicatorM3E( + value: valueOrNull, + size: LinearProgressM3ESize.s, + shape: shape, + inset: inset, + ), + ); +} + +@UseCase(name: 'size_m', type: LinearProgressIndicatorM3E) +Widget buildLinearProgressIndicatorM3ESizeMUseCase(BuildContext context) { + final double? valueOrNull = context.knobs.boolean( + label: 'determinate', + initialValue: true, + ) + ? context.knobs.double.slider( + label: 'value', + initialValue: 1.0, // boundary case + min: 0.0, + max: 1.0, + divisions: 100, + ) + : null; + final ProgressM3EShape shape = context.knobs.object.dropdown( + label: 'shape', + initialOption: ProgressM3EShape.wavy, + options: ProgressM3EShape.values, + labelBuilder: (v) => v.name, + ); + final double phase = context.knobs.double.slider( + label: 'phase (rad) — wavy only', + initialValue: 0.0, + min: 0.0, + max: 6.28318, + divisions: 36, + ); + final double inset = context.knobs.double.slider( + label: 'inset', + initialValue: 8.0, + min: 0.0, + max: 24.0, + divisions: 24, + ); + + return Padding( + padding: const EdgeInsets.all(16), + child: LinearProgressIndicatorM3E( + value: valueOrNull, + size: LinearProgressM3ESize.m, + shape: shape, + phase: phase, + inset: inset, + ), + ); +} diff --git a/widgetbook/lib/progress_indicator_m3e/progress_with_label_m3e_usecases.dart b/widgetbook/lib/progress_indicator_m3e/progress_with_label_m3e_usecases.dart new file mode 100644 index 0000000..9c50586 --- /dev/null +++ b/widgetbook/lib/progress_indicator_m3e/progress_with_label_m3e_usecases.dart @@ -0,0 +1,117 @@ +import 'package:flutter/material.dart'; +import 'package:progress_indicator_m3e/progress_indicator_m3e.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; + +// Use cases for ProgressWithLabelM3E per plan/guide.md +// - Themes are injected globally by the Widgetbook app. +// - Knobs for value, size, and label style/text. + +@UseCase(name: 'default', type: ProgressWithLabelM3E) +Widget buildProgressWithLabelM3EDefaultUseCase(BuildContext context) { + final double value = context.knobs.double.slider( + label: 'value', + initialValue: 0.6, + min: 0.0, + max: 1.0, + divisions: 100, + ); + final CircularProgressM3ESize size = context.knobs.object.dropdown( + label: 'size', + initialOption: CircularProgressM3ESize.m, + options: CircularProgressM3ESize.values, + labelBuilder: (v) => v.name, + ); + + return Center( + child: ProgressWithLabelM3E( + value: value, + size: size, + ), + ); +} + +@UseCase(name: 'determinate_with_label', type: ProgressWithLabelM3E) +Widget buildProgressWithLabelM3EDeterminateWithLabelUseCase( + BuildContext context) { + final double value = context.knobs.double.slider( + label: 'value', + initialValue: 0.85, + min: 0.0, + max: 1.0, + divisions: 100, + ); + final CircularProgressM3ESize size = context.knobs.object.dropdown( + label: 'size', + initialOption: CircularProgressM3ESize.m, + options: CircularProgressM3ESize.values, + labelBuilder: (v) => v.name, + ); + final double fontSize = context.knobs.double.slider( + label: 'fontSize', + initialValue: 12.0, + min: 8.0, + max: 32.0, + divisions: 24, + ); + + return Center( + child: ProgressWithLabelM3E( + value: value, + size: size, + textStyle: TextStyle(fontSize: fontSize), + ), + ); +} + +@UseCase(name: 'long_label_text', type: ProgressWithLabelM3E) +Widget buildProgressWithLabelM3ELongLabelTextUseCase(BuildContext context) { + final double value = context.knobs.double.slider( + label: 'value', + initialValue: 0.33, + min: 0.0, + max: 1.0, + divisions: 100, + ); + final CircularProgressM3ESize size = context.knobs.object.dropdown( + label: 'size', + initialOption: CircularProgressM3ESize.m, + options: CircularProgressM3ESize.values, + labelBuilder: (v) => v.name, + ); + final String label = context.knobs.string( + label: 'label (overrides %)', + initialValue: + 'Downloading super-duper long file name with many-many characters... 33%', + ); + final bool useCustom = context.knobs.boolean( + label: 'use custom text instead of percent', + initialValue: true, + ); + // Note: The stock ProgressWithLabelM3E renders percentage by default. + // To show a long label, we overlay an extra Text widget for demonstration. + + return SizedBox( + width: size.diameterWavy, + height: size.diameterWavy, + child: Stack( + alignment: Alignment.center, + children: [ + CircularProgressIndicatorM3E(value: value, size: size), + if (useCustom) + Padding( + padding: const EdgeInsets.all(4.0), + child: Text( + label, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.labelSmall, + maxLines: 3, + overflow: TextOverflow.ellipsis, + ), + ) + else + ProgressWithLabelM3E(value: value, size: size), + ], + ), + ); +} diff --git a/widgetbook/lib/slider_m3e/INDEX-slider_m3e.md b/widgetbook/lib/slider_m3e/INDEX-slider_m3e.md new file mode 100644 index 0000000..9e0d90c --- /dev/null +++ b/widgetbook/lib/slider_m3e/INDEX-slider_m3e.md @@ -0,0 +1,27 @@ +# INDEX — slider_m3e + +This index lists all Widgetbook use cases generated for the slider_m3e package. + +## Components and Variants + +- SliderM3E + - default + - discrete + - disabled + - extremes_0_to_100 + - negative_range + - min_equals_max + +- RangeSliderM3E + - default + - discrete + - disabled + - extremes_0_to_100 + - negative_range + - min_equals_max + +Notes +- All variants follow plan/guide.md rules with @UseCase annotations and required method signatures. +- Knobs are provided for critical parameters (values, min, max, divisions) and key visuals (size, emphasis, shapeFamily, density, value indicator). +- Callbacks print helpful messages to the console. +- Range labels are included where useful; complex configurations are kept simple with TODOs where appropriate. diff --git a/widgetbook/lib/slider_m3e/range_slider_m3e_usecases.dart b/widgetbook/lib/slider_m3e/range_slider_m3e_usecases.dart new file mode 100644 index 0000000..e66c068 --- /dev/null +++ b/widgetbook/lib/slider_m3e/range_slider_m3e_usecases.dart @@ -0,0 +1,191 @@ +import 'package:flutter/material.dart'; +import 'package:slider_m3e/slider_m3e.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; + +Widget _buildRangeSliderM3EDemo( + BuildContext context, { + double? forcedMin, + double? forcedMax, + int? forcedDivisions, + bool disabled = false, + bool zeroRange = false, +}) { + // Critical params via knobs + final minKnob = forcedMin ?? + context.knobs.double.slider( + label: 'min', + initialValue: 0, + min: -100, + max: 100, + divisions: 40, + ); + final maxKnob = forcedMax ?? + context.knobs.double.slider( + label: 'max', + initialValue: 100, + min: -100, + max: 200, + divisions: 60, + ); + + double min = minKnob; + double max = maxKnob; + + // Force min == max when zeroRange is requested + if (zeroRange) { + final fixed = context.knobs.double.slider( + label: 'fixed value (min==max)', + initialValue: 25, + min: -100, + max: 100, + divisions: 40, + ); + min = fixed; + max = fixed; + } + + if (min >= max) { + // Ensure valid range + max = min + 1; + } + + // Pick start/end values within [min, max] + final start = context.knobs.double.slider( + label: 'start', + initialValue: min + (max - min) * 0.25, + min: min, + max: max, + divisions: 100, + ); + final end = context.knobs.double.slider( + label: 'end', + initialValue: min + (max - min) * 0.75, + min: min, + max: max, + divisions: 100, + ); + + final divisions = forcedDivisions ?? + context.knobs.intOrNull.slider( + label: 'divisions', + initialValue: null, + min: 1, + max: 20, + divisions: 19, + ); + + // Visual params via knobs + final size = context.knobs.object.dropdown( + label: 'size', + initialOption: SliderM3ESize.medium, + options: SliderM3ESize.values, + labelBuilder: (v) => v.name, + ); + final emphasis = context.knobs.object.dropdown( + label: 'emphasis', + initialOption: SliderM3EEmphasis.primary, + options: SliderM3EEmphasis.values, + labelBuilder: (v) => v.name, + ); + final shape = context.knobs.object.dropdown( + label: 'shapeFamily', + initialOption: SliderM3EShapeFamily.round, + options: SliderM3EShapeFamily.values, + labelBuilder: (v) => v.name, + ); + final density = context.knobs.object.dropdown( + label: 'density', + initialOption: SliderM3EDensity.regular, + options: SliderM3EDensity.values, + labelBuilder: (v) => v.name, + ); + + final showValueIndicator = context.knobs.boolean( + label: 'showValueIndicator', + initialValue: false, + ); + + // Labels & semantics + final hasLabels = context.knobs.boolean(label: 'use RangeLabels', initialValue: false); + final startLabel = hasLabels + ? context.knobs.string(label: 'start label', initialValue: 'Start') + : null; + final endLabel = hasLabels + ? context.knobs.string(label: 'end label', initialValue: 'End') + : null; + final labels = hasLabels && startLabel != null && endLabel != null + ? RangeLabels(startLabel, endLabel) + : null; + + final semanticLabel = zeroRange + ? null + : context.knobs.stringOrNull( + label: 'semanticLabel', + initialValue: 'Progress', + ); + + final onChanged = disabled + ? null + : (RangeValues v) => print('RangeSliderM3E onChanged -> ${v.start} - ${v.end}'); + final onChangeStart = disabled + ? null + : (RangeValues v) => print('RangeSliderM3E onChangeStart -> ${v.start} - ${v.end}'); + final onChangeEnd = disabled + ? null + : (RangeValues v) => print('RangeSliderM3E onChangeEnd -> ${v.start} - ${v.end}'); + + return Padding( + padding: const EdgeInsets.all(16), + child: RangeSliderM3E( + values: RangeValues( + start.clamp(min, max), + end.clamp(min, max), + ), + onChanged: onChanged, + onChangeStart: onChangeStart, + onChangeEnd: onChangeEnd, + min: min, + max: max, + divisions: divisions, + labels: labels, + semanticLabel: semanticLabel, + size: size, + emphasis: emphasis, + shapeFamily: shape, + density: density, + showValueIndicator: showValueIndicator, + ), + ); +} + +@UseCase(name: 'default', type: RangeSliderM3E) +Widget buildRangeSliderM3EDefaultUseCase(BuildContext context) { + return _buildRangeSliderM3EDemo(context); +} + +@UseCase(name: 'discrete', type: RangeSliderM3E) +Widget buildRangeSliderM3EDiscreteUseCase(BuildContext context) { + return _buildRangeSliderM3EDemo(context, forcedDivisions: 6); +} + +@UseCase(name: 'disabled', type: RangeSliderM3E) +Widget buildRangeSliderM3EDisabledUseCase(BuildContext context) { + return _buildRangeSliderM3EDemo(context, disabled: true); +} + +@UseCase(name: 'extremes_0_to_100', type: RangeSliderM3E) +Widget buildRangeSliderM3EExtremes0100UseCase(BuildContext context) { + return _buildRangeSliderM3EDemo(context, forcedMin: 0, forcedMax: 100); +} + +@UseCase(name: 'negative_range', type: RangeSliderM3E) +Widget buildRangeSliderM3ENegativeRangeUseCase(BuildContext context) { + return _buildRangeSliderM3EDemo(context, forcedMin: -100, forcedMax: 0); +} + +@UseCase(name: 'min_equals_max', type: RangeSliderM3E) +Widget buildRangeSliderM3EMinEqualsMaxUseCase(BuildContext context) { + // Zero-range; interactions disabled to avoid semantic division by zero + return _buildRangeSliderM3EDemo(context, zeroRange: true, disabled: true); +} diff --git a/widgetbook/lib/slider_m3e/slider_m3e_usecases.dart b/widgetbook/lib/slider_m3e/slider_m3e_usecases.dart new file mode 100644 index 0000000..11bb3a4 --- /dev/null +++ b/widgetbook/lib/slider_m3e/slider_m3e_usecases.dart @@ -0,0 +1,186 @@ +import 'package:flutter/material.dart'; +import 'package:slider_m3e/slider_m3e.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; + +Widget _buildSliderM3EDemo( + BuildContext context, { + double? forcedMin, + double? forcedMax, + int? forcedDivisions, + bool disabled = false, + bool zeroRange = false, +}) { + // Critical params via knobs + final minKnob = forcedMin ?? + context.knobs.double.slider( + label: 'min', + initialValue: 0, + min: -100, + max: 100, + divisions: 40, + ); + final maxKnob = forcedMax ?? + context.knobs.double.slider( + label: 'max', + initialValue: 100, + min: -100, + max: 200, + divisions: 60, + ); + + double min = minKnob; + double max = maxKnob; + + // Special handling when zeroRange is requested + if (zeroRange) { + // Force min == max and disable interactions to avoid semantic division by zero + final fixed = context.knobs.double.slider( + label: 'fixed value (min==max)', + initialValue: 50, + min: -100, + max: 100, + divisions: 40, + ); + min = fixed; + max = fixed; + } + + if (min >= max) { + // Guard against invalid ranges; ensure there is at least a small span + max = min + 1; + } + + final value = context.knobs.double.slider( + label: 'value', + initialValue: (min + max) / 2, + min: min, + max: max, + divisions: 100, + ); + + final divisions = forcedDivisions ?? + context.knobs.intOrNull.slider( + label: 'divisions', + initialValue: null, + min: 1, + max: 20, + divisions: 19, + ); + + // Visual params via knobs + final size = context.knobs.object.dropdown( + label: 'size', + initialOption: SliderM3ESize.medium, + options: SliderM3ESize.values, + labelBuilder: (v) => v.name, + ); + final emphasis = context.knobs.object.dropdown( + label: 'emphasis', + initialOption: SliderM3EEmphasis.primary, + options: SliderM3EEmphasis.values, + labelBuilder: (v) => v.name, + ); + final shape = context.knobs.object.dropdown( + label: 'shapeFamily', + initialOption: SliderM3EShapeFamily.round, + options: SliderM3EShapeFamily.values, + labelBuilder: (v) => v.name, + ); + final density = context.knobs.object.dropdown( + label: 'density', + initialOption: SliderM3EDensity.regular, + options: SliderM3EDensity.values, + labelBuilder: (v) => v.name, + ); + + final showValueIndicator = context.knobs.boolean( + label: 'showValueIndicator', + initialValue: false, + ); + final withStartIcon = context.knobs.boolean( + label: 'startIcon', + initialValue: false, + ); + final withEndIcon = context.knobs.boolean( + label: 'endIcon', + initialValue: false, + ); + + // Content params + final label = context.knobs.stringOrNull( + label: 'label', + initialValue: null, + ); + // Avoid semantic callback when zeroRange to prevent divide-by-zero in tokens formatting + final semanticLabel = zeroRange + ? null + : context.knobs.stringOrNull( + label: 'semanticLabel', + initialValue: 'Progress', + ); + + final onChanged = disabled + ? null + : (double v) => print('SliderM3E onChanged -> $v'); + final onChangeStart = disabled + ? null + : (double v) => print('SliderM3E onChangeStart -> $v'); + final onChangeEnd = disabled + ? null + : (double v) => print('SliderM3E onChangeEnd -> $v'); + + return Padding( + padding: const EdgeInsets.all(16), + child: SliderM3E( + value: value, + onChanged: onChanged, + onChangeStart: onChangeStart, + onChangeEnd: onChangeEnd, + min: min, + max: max, + divisions: divisions, + label: label, + semanticLabel: semanticLabel, + size: size, + emphasis: emphasis, + shapeFamily: shape, + density: density, + showValueIndicator: showValueIndicator, + startIcon: withStartIcon ? const Icon(Icons.remove) : null, + endIcon: withEndIcon ? const Icon(Icons.add) : null, + ), + ); +} + +@UseCase(name: 'default', type: SliderM3E) +Widget buildSliderM3EDefaultUseCase(BuildContext context) { + return _buildSliderM3EDemo(context); +} + +@UseCase(name: 'discrete', type: SliderM3E) +Widget buildSliderM3EDiscreteUseCase(BuildContext context) { + // Provide a default divisions count; still knob-customizable via 'divisions' + return _buildSliderM3EDemo(context, forcedDivisions: 5); +} + +@UseCase(name: 'disabled', type: SliderM3E) +Widget buildSliderM3EDisabledUseCase(BuildContext context) { + return _buildSliderM3EDemo(context, disabled: true); +} + +@UseCase(name: 'extremes_0_to_100', type: SliderM3E) +Widget buildSliderM3EExtremes0100UseCase(BuildContext context) { + return _buildSliderM3EDemo(context, forcedMin: 0, forcedMax: 100); +} + +@UseCase(name: 'negative_range', type: SliderM3E) +Widget buildSliderM3ENegativeRangeUseCase(BuildContext context) { + return _buildSliderM3EDemo(context, forcedMin: -100, forcedMax: 0); +} + +@UseCase(name: 'min_equals_max', type: SliderM3E) +Widget buildSliderM3EMinEqualsMaxUseCase(BuildContext context) { + // Show a zero-range slider (min == max), interactions disabled to avoid semantic math + return _buildSliderM3EDemo(context, zeroRange: true, disabled: true); +} diff --git a/widgetbook/lib/split_button_m3e/INDEX-split_button_m3e.md b/widgetbook/lib/split_button_m3e/INDEX-split_button_m3e.md new file mode 100644 index 0000000..7b362ea --- /dev/null +++ b/widgetbook/lib/split_button_m3e/INDEX-split_button_m3e.md @@ -0,0 +1,31 @@ +# Widgetbook Index — split_button_m3e + +This index summarizes all Widgetbook use cases defined for SplitButtonM3E. + +Component: SplitButtonM3E + +Variants +- default +- filled +- tonal +- elevated +- outlined +- text +- xs +- sm +- md +- lg +- xl +- round +- square +- with_icon +- with_label +- long_label +- many_items +- empty_items +- disabled + +Notes +- Knobs provided for critical parameters: items count, selection (via choosing menu item), emphasis, size, shape, trailingAlignment, enabled, tooltips, and content (label/icon). +- Callbacks print informative messages to the console when the primary action is pressed or a menu item is selected. +- Complex objects use sensible defaults; see TODO comments for potential icon picker enhancement. diff --git a/widgetbook/lib/split_button_m3e/split_button_m3e_usecase.dart b/widgetbook/lib/split_button_m3e/split_button_m3e_usecase.dart new file mode 100644 index 0000000..56c749d --- /dev/null +++ b/widgetbook/lib/split_button_m3e/split_button_m3e_usecase.dart @@ -0,0 +1,272 @@ + + +import 'package:flutter/material.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook; +import 'package:split_button_m3e/split_button_m3e.dart'; + +Widget _buildSplitButtonDemo( + BuildContext context, { + SplitButtonM3EEmphasis? emphasisFixed, + SplitButtonM3ESize? sizeFixed, + SplitButtonM3EShape? shapeFixed, + bool? enabledFixed, + String? labelFixed, + IconData? iconFixed, + int? itemsFixedCount, +}) { + final emphasis = emphasisFixed ?? + context.knobs.object.dropdown( + label: 'emphasis', + initialOption: SplitButtonM3EEmphasis.filled, + options: SplitButtonM3EEmphasis.values, + labelBuilder: (v) => v.name, + ); + + final size = sizeFixed ?? context.knobs.object.dropdown( + label: 'size', + initialOption: SplitButtonM3ESize.md, + options: SplitButtonM3ESize.values, + labelBuilder: (v) => v.name, + ); + + final shape = shapeFixed ?? + context.knobs.object.dropdown( + label: 'shape', + initialOption: SplitButtonM3EShape.round, + options: SplitButtonM3EShape.values, + labelBuilder: (v) => v.name, + ); + + final trailingAlignment = context.knobs.object + .dropdown( + label: 'trailingAlignment', + initialOption: SplitButtonM3ETrailingAlignment.opticalCenter, + options: SplitButtonM3ETrailingAlignment.values, + labelBuilder: (v) => v.name, + ); + + final enabled = enabledFixed ?? + context.knobs.boolean(label: 'enabled', initialValue: true); + + // Content knobs + final effectiveLabel = labelFixed ?? + context.knobs.string(label: 'label', initialValue: 'Save'); + + final includeIcon = iconFixed != null + ? true + : context.knobs.boolean(label: 'leadingIcon', initialValue: true); + + final effectiveIcon = iconFixed ?? Icons.save_outlined; // TODO: icon picker knob + + // Tooltips knobs (helpful for semantics and tests) + final leadingTooltip = context.knobs + .string(label: 'leadingTooltip', initialValue: 'Save'); + final trailingTooltip = context.knobs + .string(label: 'trailingTooltip', initialValue: 'Open menu'); + + // Items knobs + final itemCount = itemsFixedCount ?? context.knobs.int.slider( + label: 'items count', + initialValue: 4, + min: 0, + max: 120, + divisions: 120, + ); + + final disableEveryNth = context.knobs.int.slider( + label: 'disable every Nth (0 = none)', + initialValue: 0, + min: 0, + max: 10, + divisions: 10, + ); + + final items = List.generate(itemCount, (i) { + final disabled = disableEveryNth == 0 ? false : (i % disableEveryNth == 0); + return SplitButtonM3EItem( + value: 'value_$i', + child: 'Option $i', + enabled: !disabled, + ); + }); + + return Center( + child: SplitButtonM3E( + size: size, + shape: shape, + emphasis: emphasis, + label: effectiveLabel.isEmpty ? null : effectiveLabel, + leadingIcon: includeIcon ? effectiveIcon : null, + onPressed: enabled + ? () => print( + 'Primary pressed: size=${size.name}, shape=${shape.name}, emphasis=${emphasis.name}', + ) + : null, + items: items, + onSelected: (v) => print('Menu selected → $v'), + trailingAlignment: trailingAlignment, + leadingTooltip: leadingTooltip.isEmpty ? null : leadingTooltip, + trailingTooltip: trailingTooltip.isEmpty ? null : trailingTooltip, + enabled: enabled, + ), + ); +} + +// Default +@widgetbook.UseCase(name: 'default', type: SplitButtonM3E) +Widget buildSplitButtonM3EDefaultUseCase(BuildContext context) { + return _buildSplitButtonDemo(context); +} + +// Emphasis variants +@widgetbook.UseCase(name: 'filled', type: SplitButtonM3E) +Widget buildSplitButtonM3EFilledUseCase(BuildContext context) { + return _buildSplitButtonDemo( + context, + emphasisFixed: SplitButtonM3EEmphasis.filled, + ); +} + +@widgetbook.UseCase(name: 'tonal', type: SplitButtonM3E) +Widget buildSplitButtonM3ETonalUseCase(BuildContext context) { + return _buildSplitButtonDemo( + context, + emphasisFixed: SplitButtonM3EEmphasis.tonal, + ); +} + +@widgetbook.UseCase(name: 'elevated', type: SplitButtonM3E) +Widget buildSplitButtonM3EElevatedUseCase(BuildContext context) { + return _buildSplitButtonDemo( + context, + emphasisFixed: SplitButtonM3EEmphasis.elevated, + ); +} + +@widgetbook.UseCase(name: 'outlined', type: SplitButtonM3E) +Widget buildSplitButtonM3EOutlinedUseCase(BuildContext context) { + return _buildSplitButtonDemo( + context, + emphasisFixed: SplitButtonM3EEmphasis.outlined, + ); +} + +@widgetbook.UseCase(name: 'text', type: SplitButtonM3E) +Widget buildSplitButtonM3ETextUseCase(BuildContext context) { + return _buildSplitButtonDemo( + context, + emphasisFixed: SplitButtonM3EEmphasis.text, + ); +} + +// Size variants +@widgetbook.UseCase(name: 'xs', type: SplitButtonM3E) +Widget buildSplitButtonM3EXSUseCase(BuildContext context) { + return _buildSplitButtonDemo( + context, + sizeFixed: SplitButtonM3ESize.xs, + ); +} + +@widgetbook.UseCase(name: 'sm', type: SplitButtonM3E) +Widget buildSplitButtonM3ESMUseCase(BuildContext context) { + return _buildSplitButtonDemo( + context, + sizeFixed: SplitButtonM3ESize.sm, + ); +} + +@widgetbook.UseCase(name: 'md', type: SplitButtonM3E) +Widget buildSplitButtonM3EMDUseCase(BuildContext context) { + return _buildSplitButtonDemo( + context, + sizeFixed: SplitButtonM3ESize.md, + ); +} + +@widgetbook.UseCase(name: 'lg', type: SplitButtonM3E) +Widget buildSplitButtonM3ELGUseCase(BuildContext context) { + return _buildSplitButtonDemo( + context, + sizeFixed: SplitButtonM3ESize.lg, + ); +} + +@widgetbook.UseCase(name: 'xl', type: SplitButtonM3E) +Widget buildSplitButtonM3EXLUseCase(BuildContext context) { + return _buildSplitButtonDemo( + context, + sizeFixed: SplitButtonM3ESize.xl, + ); +} + +// Shape variants +@widgetbook.UseCase(name: 'round', type: SplitButtonM3E) +Widget buildSplitButtonM3ERoundUseCase(BuildContext context) { + return _buildSplitButtonDemo( + context, + shapeFixed: SplitButtonM3EShape.round, + ); +} + +@widgetbook.UseCase(name: 'square', type: SplitButtonM3E) +Widget buildSplitButtonM3ESquareUseCase(BuildContext context) { + return _buildSplitButtonDemo( + context, + shapeFixed: SplitButtonM3EShape.square, + ); +} + +// Content variants +@widgetbook.UseCase(name: 'with_icon', type: SplitButtonM3E) +Widget buildSplitButtonM3EWithIconUseCase(BuildContext context) { + return _buildSplitButtonDemo( + context, + labelFixed: '', + iconFixed: Icons.save_outlined, + ); +} + +@widgetbook.UseCase(name: 'with_label', type: SplitButtonM3E) +Widget buildSplitButtonM3EWithLabelUseCase(BuildContext context) { + return _buildSplitButtonDemo( + context, + labelFixed: 'Save', + iconFixed: null, + ); +} + +@widgetbook.UseCase(name: 'long_label', type: SplitButtonM3E) +Widget buildSplitButtonM3ELongLabelUseCase(BuildContext context) { + return _buildSplitButtonDemo( + context, + labelFixed: 'Save document and sync to cloud', + iconFixed: Icons.cloud_upload_outlined, + ); +} + +// Edge cases +@widgetbook.UseCase(name: 'many_items', type: SplitButtonM3E) +Widget buildSplitButtonM3EManyItemsUseCase(BuildContext context) { + return _buildSplitButtonDemo( + context, + itemsFixedCount: 60, + ); +} + +@widgetbook.UseCase(name: 'empty_items', type: SplitButtonM3E) +Widget buildSplitButtonM3EEmptyItemsUseCase(BuildContext context) { + return _buildSplitButtonDemo( + context, + itemsFixedCount: 0, + ); +} + +@widgetbook.UseCase(name: 'disabled', type: SplitButtonM3E) +Widget buildSplitButtonM3EDisabledUseCase(BuildContext context) { + return _buildSplitButtonDemo( + context, + enabledFixed: false, + ); +} diff --git a/widgetbook/lib/toolbar_m3e/INDEX-toolbar_m3e.md b/widgetbook/lib/toolbar_m3e/INDEX-toolbar_m3e.md new file mode 100644 index 0000000..9b93b1c --- /dev/null +++ b/widgetbook/lib/toolbar_m3e/INDEX-toolbar_m3e.md @@ -0,0 +1,43 @@ +# Widgetbook Index — toolbar_m3e + +This file summarizes the Widgetbook use cases for the `toolbar_m3e` package. + +Component inventory: +- ToolbarM3E — primary toolbar component +- ToolbarIconButtonM3E — wrapper for toolbar action icon buttons +- ToolbarM3EWidget — internal placeholder/demo (not exported); no use cases generated + +## ToolbarM3E +Variants implemented: +- default +- surface +- tonal +- primary +- small +- medium +- large +- compact +- regular +- with_leading +- with_trailing_actions +- with_overflow +- long_title +- centered_title + +Notes: +- Critical parameters (variant, size, density, shapeFamily, titles, action count) are exposed via knobs. +- Callbacks print informative messages to console. +- Theme is assumed to be injected globally by the host app. + +## ToolbarIconButtonM3E +Variants implemented: +- default +- destructive +- custom_color_and_size + +Notes: +- Knobs include enabled, iconSize, and color where applicable. +- Destructive variant showcases error-colored action. + +## ToolbarM3EWidget +- Not exported by the package; treated as internal. Skipped for widgetbook use cases. diff --git a/widgetbook/lib/toolbar_m3e/toolbar_icon_button_m3e_usecases.dart b/widgetbook/lib/toolbar_m3e/toolbar_icon_button_m3e_usecases.dart new file mode 100644 index 0000000..9977699 --- /dev/null +++ b/widgetbook/lib/toolbar_m3e/toolbar_icon_button_m3e_usecases.dart @@ -0,0 +1,74 @@ + + +import 'package:flutter/material.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; +import 'package:toolbar_m3e/toolbar_m3e.dart'; + +@UseCase(name: 'default', type: ToolbarIconButtonM3E) +Widget buildToolbarIconButtonM3EDefaultUseCase(BuildContext context) { + final enabled = context.knobs.boolean(label: 'enabled', initialValue: true); + final iconSize = context.knobs.double.slider( + label: 'iconSize', + initialValue: 24, + min: 12, + max: 48, + divisions: 12, + ); + final color = context.knobs.colorOrNull(label: 'color', initialValue: null); + + return Center( + child: ToolbarIconButtonM3E( + action: ToolbarActionM3E( + icon: Icons.search, + tooltip: 'Search', + label: 'Search', + enabled: enabled, + onPressed: () => print('ToolbarIconButtonM3E -> Search pressed'), + ), + iconSize: iconSize, + color: color, + ), + ); +} + +@UseCase(name: 'destructive', type: ToolbarIconButtonM3E) +Widget buildToolbarIconButtonM3EDestructiveUseCase(BuildContext context) { + final enabled = context.knobs.boolean(label: 'enabled', initialValue: true); + return Center( + child: ToolbarIconButtonM3E( + action: ToolbarActionM3E( + icon: Icons.delete, + tooltip: 'Delete', + label: 'Delete', + isDestructive: true, + enabled: enabled, + onPressed: () => print('ToolbarIconButtonM3E -> Delete pressed'), + ), + color: Theme.of(context).colorScheme.error, + ), + ); +} + +@UseCase(name: 'custom_color_and_size', type: ToolbarIconButtonM3E) +Widget buildToolbarIconButtonM3ECustomStyleUseCase(BuildContext context) { + final color = context.knobs.color(label: 'color', initialValue: Colors.teal); + final iconSize = context.knobs.double.slider( + label: 'iconSize', + initialValue: 32, + min: 16, + max: 56, + ); + return Center( + child: ToolbarIconButtonM3E( + action: ToolbarActionM3E( + icon: Icons.share, + tooltip: 'Share', + label: 'Share', + onPressed: () => print('ToolbarIconButtonM3E -> Share pressed'), + ), + color: color, + iconSize: iconSize, + ), + ); +} diff --git a/widgetbook/lib/toolbar_m3e/toolbar_m3e_usecases.dart b/widgetbook/lib/toolbar_m3e/toolbar_m3e_usecases.dart new file mode 100644 index 0000000..bab159a --- /dev/null +++ b/widgetbook/lib/toolbar_m3e/toolbar_m3e_usecases.dart @@ -0,0 +1,192 @@ +import 'package:flutter/material.dart'; +import 'package:toolbar_m3e/toolbar_m3e.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; + +// Helper to build a ToolbarM3E with comprehensive knobs per guide +Widget _buildToolbarDemo( + BuildContext context, { + ToolbarM3EVariant? forcedVariant, + ToolbarM3ESize? forcedSize, + ToolbarM3EDensity? forcedDensity, + String? forcedTitle, + bool withLeading = false, + int? forcedActionCount, + bool longTitle = false, + bool centerTitle = false, +}) { + final variant = forcedVariant ?? + context.knobs.object.dropdown( + label: 'variant', + initialOption: ToolbarM3EVariant.surface, + options: ToolbarM3EVariant.values, + labelBuilder: (v) => v.name, + ); + final size = forcedSize ?? + context.knobs.object.dropdown( + label: 'size', + initialOption: ToolbarM3ESize.medium, + options: ToolbarM3ESize.values, + labelBuilder: (v) => v.name, + ); + final density = forcedDensity ?? + context.knobs.object.dropdown( + label: 'density', + initialOption: ToolbarM3EDensity.regular, + options: ToolbarM3EDensity.values, + labelBuilder: (v) => v.name, + ); + final shape = context.knobs.object.dropdown( + label: 'shapeFamily', + initialOption: ToolbarM3EShapeFamily.round, + options: ToolbarM3EShapeFamily.values, + labelBuilder: (v) => v.name, + ); + + final title = forcedTitle ?? + context.knobs.string( + label: 'title', + initialValue: longTitle + ? 'An exceptionally long toolbar title that will likely overflow the available space' + : 'Toolbar Title', + ); + final hasSubtitle = + context.knobs.boolean(label: 'subtitle?', initialValue: false); + final subtitleText = hasSubtitle + ? context.knobs.string(label: 'subtitle', initialValue: 'Subheading') + : null; + + final actionCount = forcedActionCount ?? + context.knobs.int.slider( + label: 'actions', + initialValue: 3, + min: 0, + max: 8, + divisions: 8, + ); + + final maxInline = context.knobs.int.slider( + label: 'maxInlineActions', + initialValue: 4, + min: 1, + max: 6, + divisions: 5, + ); + + List buildActions(int count) { + return List.generate(count, (i) { + return ToolbarActionM3E( + icon: i == count - 1 ? Icons.delete : Icons.more_horiz, + isDestructive: i == count - 1, + tooltip: 'Action #${i + 1}', + label: 'Action #${i + 1}', + onPressed: () => + print('Toolbar action pressed -> index=$i (of $count)'), + ); + }); + } + + return Material( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ToolbarM3E( + leading: withLeading + ? IconButton( + onPressed: () => print('Leading pressed'), + icon: const Icon(Icons.menu), + ) + : null, + titleText: title, + subtitleText: subtitleText, + actions: buildActions(actionCount), + maxInlineActions: maxInline, + overflowIcon: const Icon(Icons.more_vert), + centerTitle: centerTitle, + variant: variant, + size: size, + density: density, + shapeFamily: shape, + // backgroundColor / foregroundColor left to tokens; can add knob if needed + safeArea: false, + ), + // Some filler content to visualize elevation and background separation + Container( + height: 120, + alignment: Alignment.center, + child: const Text('Content area below the toolbar'), + ), + ], + ), + ); +} + +@UseCase(name: 'default', type: ToolbarM3E) +Widget buildToolbarM3EDefaultUseCase(BuildContext context) { + return _buildToolbarDemo(context); +} + +@UseCase(name: 'surface', type: ToolbarM3E) +Widget buildToolbarM3ESurfaceUseCase(BuildContext context) { + return _buildToolbarDemo(context, forcedVariant: ToolbarM3EVariant.surface); +} + +@UseCase(name: 'tonal', type: ToolbarM3E) +Widget buildToolbarM3ETonalUseCase(BuildContext context) { + return _buildToolbarDemo(context, forcedVariant: ToolbarM3EVariant.tonal); +} + +@UseCase(name: 'primary', type: ToolbarM3E) +Widget buildToolbarM3EPrimaryUseCase(BuildContext context) { + return _buildToolbarDemo(context, forcedVariant: ToolbarM3EVariant.primary); +} + +@UseCase(name: 'small', type: ToolbarM3E) +Widget buildToolbarM3ESmallUseCase(BuildContext context) { + return _buildToolbarDemo(context, forcedSize: ToolbarM3ESize.small); +} + +@UseCase(name: 'medium', type: ToolbarM3E) +Widget buildToolbarM3EMediumUseCase(BuildContext context) { + return _buildToolbarDemo(context, forcedSize: ToolbarM3ESize.medium); +} + +@UseCase(name: 'large', type: ToolbarM3E) +Widget buildToolbarM3ELargeUseCase(BuildContext context) { + return _buildToolbarDemo(context, forcedSize: ToolbarM3ESize.large); +} + +@UseCase(name: 'compact', type: ToolbarM3E) +Widget buildToolbarM3ECompactUseCase(BuildContext context) { + return _buildToolbarDemo(context, forcedDensity: ToolbarM3EDensity.compact); +} + +@UseCase(name: 'regular', type: ToolbarM3E) +Widget buildToolbarM3ERegularUseCase(BuildContext context) { + return _buildToolbarDemo(context, forcedDensity: ToolbarM3EDensity.regular); +} + +@UseCase(name: 'with_leading', type: ToolbarM3E) +Widget buildToolbarM3EWithLeadingUseCase(BuildContext context) { + return _buildToolbarDemo(context, withLeading: true); +} + +@UseCase(name: 'with_trailing_actions', type: ToolbarM3E) +Widget buildToolbarM3EWithActionsUseCase(BuildContext context) { + return _buildToolbarDemo(context, forcedActionCount: 3); +} + +@UseCase(name: 'with_overflow', type: ToolbarM3E) +Widget buildToolbarM3EWithOverflowUseCase(BuildContext context) { + return _buildToolbarDemo(context, forcedActionCount: 8); +} + +@UseCase(name: 'long_title', type: ToolbarM3E) +Widget buildToolbarM3ELongTitleUseCase(BuildContext context) { + return _buildToolbarDemo(context, longTitle: true, centerTitle: false); +} + +@UseCase(name: 'centered_title', type: ToolbarM3E) +Widget buildToolbarM3ECenteredTitleUseCase(BuildContext context) { + return _buildToolbarDemo(context, longTitle: false, centerTitle: true); +} diff --git a/widgetbook/pubspec.yaml b/widgetbook/pubspec.yaml new file mode 100644 index 0000000..e1b31b3 --- /dev/null +++ b/widgetbook/pubspec.yaml @@ -0,0 +1,38 @@ +name: widgetbook_workspace +description: Widgetbook workspace for the material_3_expressive monorepo. +publish_to: none + +environment: + sdk: ">=3.5.0 <4.0.0" + +dependencies: + button_group_m3e: + path: ../packages/button_group_m3e + flutter: + sdk: flutter + loading_indicator_m3e: + path: ../packages/loading_indicator_m3e + m3e_collection: + path: ../packages/m3e_collection + material_new_shapes: ^1.0.0 + navigation_bar_m3e: + path: ../packages/navigation_bar_m3e + navigation_rail_m3e: + path: ../packages/navigation_rail_m3e + progress_indicator_m3e: + path: ../packages/progress_indicator_m3e + split_button_m3e: + path: ../packages/split_button_m3e + widgetbook: ^3.18.0 + widgetbook_annotation: ^3.7.0 + app_bar_m3e: + path: ../packages/app_bar_m3e + fab_m3e: + path: ../packages/fab_m3e + +dev_dependencies: + build_runner: ^2.4.13 + widgetbook_generator: ^3.18.0 + +flutter: + uses-material-design: true