forked from mirrors/material_3_expressive
Add Widgetbook setup with theme configuration, analysis options, and initial component use cases
This commit is contained in:
parent
d3ad4b9c9d
commit
80ca8f665a
46 changed files with 6031 additions and 2 deletions
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
8
packages/loading_indicator_m3e/analysis_options.yaml
Normal file
8
packages/loading_indicator_m3e/analysis_options.yaml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
include: ../../analysis_options.yaml
|
||||||
|
|
||||||
|
analyzer:
|
||||||
|
exclude:
|
||||||
|
- "**/*.g.dart"
|
||||||
|
- "**/*.freezed.dart"
|
||||||
|
- "lib/widgetbook/**"
|
||||||
|
- "lib/**/widgetbook/**"
|
||||||
|
|
@ -415,8 +415,12 @@ class _NavigationRailM3EState extends State<NavigationRailM3E>
|
||||||
for (final dest in section.destinations) {
|
for (final dest in section.destinations) {
|
||||||
final index = _destinationIndex(widget.sections, dest);
|
final index = _destinationIndex(widget.sections, dest);
|
||||||
children.add(Padding(
|
children.add(Padding(
|
||||||
padding: const EdgeInsetsDirectional.only(
|
padding: EdgeInsetsDirectional.only(
|
||||||
start: 16, end: 16, top: 8.0, bottom: 8.0),
|
start: 16,
|
||||||
|
end: 16,
|
||||||
|
top: theme.itemVerticalGap,
|
||||||
|
bottom: theme.itemVerticalGap,
|
||||||
|
),
|
||||||
child: RailItem(
|
child: RailItem(
|
||||||
destination: dest,
|
destination: dest,
|
||||||
selected: index == widget.selectedIndex,
|
selected: index == widget.selectedIndex,
|
||||||
|
|
|
||||||
45
widgetbook/.gitignore
vendored
Normal file
45
widgetbook/.gitignore
vendored
Normal file
|
|
@ -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
|
||||||
45
widgetbook/.metadata
Normal file
45
widgetbook/.metadata
Normal file
|
|
@ -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'
|
||||||
16
widgetbook/README.md
Normal file
16
widgetbook/README.md
Normal file
|
|
@ -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.
|
||||||
28
widgetbook/analysis_options.yaml
Normal file
28
widgetbook/analysis_options.yaml
Normal file
|
|
@ -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
|
||||||
37
widgetbook/lib/app_bar_m3e/INDEX-app_bar_m3e.md
Normal file
37
widgetbook/lib/app_bar_m3e/INDEX-app_bar_m3e.md
Normal file
|
|
@ -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.
|
||||||
191
widgetbook/lib/app_bar_m3e/app_bar_m3e_usecases.dart
Normal file
191
widgetbook/lib/app_bar_m3e/app_bar_m3e_usecases.dart
Normal file
|
|
@ -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<Widget> _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<AppBarM3EShapeFamily>(
|
||||||
|
label: 'shapeFamily',
|
||||||
|
initialOption: AppBarM3EShapeFamily.round,
|
||||||
|
options: const [AppBarM3EShapeFamily.round, AppBarM3EShapeFamily.square],
|
||||||
|
labelBuilder: (v) => v.name,
|
||||||
|
);
|
||||||
|
final density = context.knobs.object.dropdown<AppBarM3EDensity>(
|
||||||
|
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<Clip>(
|
||||||
|
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<AppBarM3EShapeFamily>(
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
248
widgetbook/lib/app_bar_m3e/sliver_app_bar_m3e_usecases.dart
Normal file
248
widgetbook/lib/app_bar_m3e/sliver_app_bar_m3e_usecases.dart
Normal file
|
|
@ -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<Widget> _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<AppBarM3EVariant>(
|
||||||
|
label: 'variant',
|
||||||
|
initialOption: AppBarM3EVariant.medium,
|
||||||
|
options: const [
|
||||||
|
AppBarM3EVariant.small,
|
||||||
|
AppBarM3EVariant.medium,
|
||||||
|
AppBarM3EVariant.large
|
||||||
|
],
|
||||||
|
labelBuilder: (v) => v.name,
|
||||||
|
);
|
||||||
|
final shapeFamily = context.knobs.object.dropdown<AppBarM3EShapeFamily>(
|
||||||
|
label: 'shapeFamily',
|
||||||
|
initialOption: AppBarM3EShapeFamily.round,
|
||||||
|
options: const [AppBarM3EShapeFamily.round, AppBarM3EShapeFamily.square],
|
||||||
|
labelBuilder: (v) => v.name,
|
||||||
|
);
|
||||||
|
final density = context.knobs.object.dropdown<AppBarM3EDensity>(
|
||||||
|
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<AppBarM3EVariant>(
|
||||||
|
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<AppBarM3EVariant>(
|
||||||
|
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<AppBarM3EVariant>(
|
||||||
|
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<AppBarM3EVariant>(
|
||||||
|
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<AppBarM3EShapeFamily>(
|
||||||
|
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<AppBarM3EDensity>(
|
||||||
|
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);
|
||||||
|
}
|
||||||
21
widgetbook/lib/button_group_m3e/INDEX-button_group_m3e.md
Normal file
21
widgetbook/lib/button_group_m3e/INDEX-button_group_m3e.md
Normal file
|
|
@ -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.
|
||||||
363
widgetbook/lib/button_group_m3e/button_group_m3e_usecases.dart
Normal file
363
widgetbook/lib/button_group_m3e/button_group_m3e_usecases.dart
Normal file
|
|
@ -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<Widget> _demoButtonsWithLabels(BuildContext context) => <Widget>[
|
||||||
|
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<Widget> _demoButtonsWithIcons(BuildContext context) => <Widget>[
|
||||||
|
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<ButtonGroupM3EType>(
|
||||||
|
label: 'type',
|
||||||
|
initialOption: initial,
|
||||||
|
options: ButtonGroupM3EType.values,
|
||||||
|
labelBuilder: (ButtonGroupM3EType v) => v.name,
|
||||||
|
);
|
||||||
|
|
||||||
|
ButtonGroupM3EShape _knobShape(
|
||||||
|
BuildContext context, {
|
||||||
|
ButtonGroupM3EShape initial = ButtonGroupM3EShape.round,
|
||||||
|
}) =>
|
||||||
|
context.knobs.object.dropdown<ButtonGroupM3EShape>(
|
||||||
|
label: 'shape',
|
||||||
|
initialOption: initial,
|
||||||
|
options: ButtonGroupM3EShape.values,
|
||||||
|
labelBuilder: (ButtonGroupM3EShape v) => v.name,
|
||||||
|
);
|
||||||
|
|
||||||
|
ButtonGroupM3ESize _knobSize(
|
||||||
|
BuildContext context, {
|
||||||
|
ButtonGroupM3ESize initial = ButtonGroupM3ESize.md,
|
||||||
|
}) =>
|
||||||
|
context.knobs.object.dropdown<ButtonGroupM3ESize>(
|
||||||
|
label: 'size',
|
||||||
|
initialOption: initial,
|
||||||
|
options: ButtonGroupM3ESize.values,
|
||||||
|
labelBuilder: (ButtonGroupM3ESize v) => v.name,
|
||||||
|
);
|
||||||
|
|
||||||
|
ButtonGroupM3EDensity _knobDensity(
|
||||||
|
BuildContext context, {
|
||||||
|
ButtonGroupM3EDensity initial = ButtonGroupM3EDensity.regular,
|
||||||
|
}) =>
|
||||||
|
context.knobs.object.dropdown<ButtonGroupM3EDensity>(
|
||||||
|
label: 'density',
|
||||||
|
initialOption: initial,
|
||||||
|
options: ButtonGroupM3EDensity.values,
|
||||||
|
labelBuilder: (ButtonGroupM3EDensity v) => v.name,
|
||||||
|
);
|
||||||
|
|
||||||
|
Axis _knobDirection(BuildContext context, {Axis initial = Axis.horizontal}) =>
|
||||||
|
context.knobs.object.dropdown<Axis>(
|
||||||
|
label: 'direction',
|
||||||
|
initialOption: initial,
|
||||||
|
options: const <Axis>[Axis.horizontal, Axis.vertical],
|
||||||
|
labelBuilder: (Axis v) => v.name,
|
||||||
|
);
|
||||||
|
|
||||||
|
WrapAlignment _knobWrapAlignment(
|
||||||
|
BuildContext context, {
|
||||||
|
String label = 'alignment',
|
||||||
|
WrapAlignment initial = WrapAlignment.start,
|
||||||
|
}) =>
|
||||||
|
context.knobs.object.dropdown<WrapAlignment>(
|
||||||
|
label: label,
|
||||||
|
initialOption: initial,
|
||||||
|
options: WrapAlignment.values,
|
||||||
|
labelBuilder: (WrapAlignment v) => v.name,
|
||||||
|
);
|
||||||
|
|
||||||
|
WrapCrossAlignment _knobCrossAlignment(
|
||||||
|
BuildContext context, {
|
||||||
|
WrapCrossAlignment initial = WrapCrossAlignment.center,
|
||||||
|
}) =>
|
||||||
|
context.knobs.object.dropdown<WrapCrossAlignment>(
|
||||||
|
label: 'crossAxisAlignment',
|
||||||
|
initialOption: initial,
|
||||||
|
options: WrapCrossAlignment.values,
|
||||||
|
labelBuilder: (WrapCrossAlignment v) => v.name,
|
||||||
|
);
|
||||||
|
|
||||||
|
Clip _knobClipBehavior(BuildContext context, {Clip initial = Clip.none}) =>
|
||||||
|
context.knobs.object.dropdown<Clip>(
|
||||||
|
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<String>(
|
||||||
|
label: 'content',
|
||||||
|
initialOption: 'with_label',
|
||||||
|
options: const <String>['with_label', 'with_icon', 'without_label'],
|
||||||
|
labelBuilder: (String v) => v,
|
||||||
|
);
|
||||||
|
|
||||||
|
late final List<Widget> 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<Widget> items = List<Widget>.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<Widget> children = <Widget>[
|
||||||
|
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: <Widget>[]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
24
widgetbook/lib/button_m3e/INDEX-button_m3e.md
Normal file
24
widgetbook/lib/button_m3e/INDEX-button_m3e.md
Normal file
|
|
@ -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.
|
||||||
230
widgetbook/lib/button_m3e/button_m3e_usecases.dart
Normal file
230
widgetbook/lib/button_m3e/button_m3e_usecases.dart
Normal file
|
|
@ -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(),
|
||||||
|
);
|
||||||
|
}
|
||||||
44
widgetbook/lib/fab_m3e/INDEX-fab_m3e.md
Normal file
44
widgetbook/lib/fab_m3e/INDEX-fab_m3e.md
Normal file
|
|
@ -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.
|
||||||
126
widgetbook/lib/fab_m3e/extended_fab_m3e_usecases.dart
Normal file
126
widgetbook/lib/fab_m3e/extended_fab_m3e_usecases.dart
Normal file
|
|
@ -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<FabM3EKind>(
|
||||||
|
label: 'kind',
|
||||||
|
initialOption: kind,
|
||||||
|
options: FabM3EKind.values,
|
||||||
|
labelBuilder: (v) => v.name,
|
||||||
|
);
|
||||||
|
final selectedSize = context.knobs.object.dropdown<FabM3ESize>(
|
||||||
|
label: 'size',
|
||||||
|
initialOption: size,
|
||||||
|
options: FabM3ESize.values,
|
||||||
|
labelBuilder: (v) => v.name,
|
||||||
|
);
|
||||||
|
final selectedShape = context.knobs.object.dropdown<FabM3EShapeFamily>(
|
||||||
|
label: 'shape',
|
||||||
|
initialOption: shape,
|
||||||
|
options: FabM3EShapeFamily.values,
|
||||||
|
labelBuilder: (v) => v.name,
|
||||||
|
);
|
||||||
|
final selectedDensity = context.knobs.object.dropdown<FabM3EDensity>(
|
||||||
|
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);
|
||||||
|
}
|
||||||
131
widgetbook/lib/fab_m3e/fab_m3e_usecases.dart
Normal file
131
widgetbook/lib/fab_m3e/fab_m3e_usecases.dart
Normal file
|
|
@ -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<FabM3EKind>(
|
||||||
|
label: 'kind',
|
||||||
|
initialOption: kind,
|
||||||
|
options: FabM3EKind.values,
|
||||||
|
labelBuilder: (v) => v.name,
|
||||||
|
);
|
||||||
|
final selectedSize = context.knobs.object.dropdown<FabM3ESize>(
|
||||||
|
label: 'size',
|
||||||
|
initialOption: size,
|
||||||
|
options: FabM3ESize.values,
|
||||||
|
labelBuilder: (v) => v.name,
|
||||||
|
);
|
||||||
|
final selectedShape = context.knobs.object.dropdown<FabM3EShapeFamily>(
|
||||||
|
label: 'shape',
|
||||||
|
initialOption: shape,
|
||||||
|
options: FabM3EShapeFamily.values,
|
||||||
|
labelBuilder: (v) => v.name,
|
||||||
|
);
|
||||||
|
final selectedDensity = context.knobs.object.dropdown<FabM3EDensity>(
|
||||||
|
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);
|
||||||
|
}
|
||||||
144
widgetbook/lib/fab_m3e/fab_menu_m3e_usecases.dart
Normal file
144
widgetbook/lib/fab_m3e/fab_menu_m3e_usecases.dart
Normal file
|
|
@ -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<FabMenuItem> 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<FabMenuDirection>(
|
||||||
|
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<String>(
|
||||||
|
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<FabMenuItem> _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 []);
|
||||||
|
}
|
||||||
34
widgetbook/lib/icon_button_m3e/INDEX-icon_button_m3e.md
Normal file
34
widgetbook/lib/icon_button_m3e/INDEX-icon_button_m3e.md
Normal file
|
|
@ -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.
|
||||||
516
widgetbook/lib/icon_button_m3e/icon_button_m3e_usecases.dart
Normal file
516
widgetbook/lib/icon_button_m3e/icon_button_m3e_usecases.dart
Normal file
|
|
@ -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)',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
75
widgetbook/lib/icon_button_m3e/icon_button_usecase.dart
Normal file
75
widgetbook/lib/icon_button_m3e/icon_button_usecase.dart
Normal file
|
|
@ -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<IconButtonM3ESize>(
|
||||||
|
label: 'size',
|
||||||
|
initialOption: IconButtonM3ESize.md,
|
||||||
|
options: IconButtonM3ESize.values,
|
||||||
|
labelBuilder: (v) => v.name,
|
||||||
|
);
|
||||||
|
final shape = context.knobs.object.dropdown<IconButtonM3EShapeVariant>(
|
||||||
|
label: 'shape',
|
||||||
|
initialOption: IconButtonM3EShapeVariant.round,
|
||||||
|
options: IconButtonM3EShapeVariant.values,
|
||||||
|
labelBuilder: (v) => v.name,
|
||||||
|
);
|
||||||
|
final width = context.knobs.object.dropdown<IconButtonM3EWidth>(
|
||||||
|
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<String>(
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -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<RoundedPolygon> _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<String>(
|
||||||
|
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<String>(
|
||||||
|
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
|
||||||
|
? <RoundedPolygon>[MaterialShapes.oval]
|
||||||
|
: _polygonSet('default');
|
||||||
|
|
||||||
|
return Center(
|
||||||
|
child: ExpressiveLoadingIndicator(
|
||||||
|
polygons: polygons,
|
||||||
|
constraints: _tight(size),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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<RoundedPolygon> _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<String>(
|
||||||
|
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<LoadingIndicatorM3EVariant>(
|
||||||
|
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<String>(
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
28
widgetbook/lib/m3e_design/INDEX-m3e_design.md
Normal file
28
widgetbook/lib/m3e_design/INDEX-m3e_design.md
Normal file
|
|
@ -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_
|
||||||
40
widgetbook/lib/main.dart
Normal file
40
widgetbook/lib/main.dart
Normal file
|
|
@ -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(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
1113
widgetbook/lib/main.directories.g.dart
Normal file
1113
widgetbook/lib/main.directories.g.dart
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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.
|
||||||
|
|
@ -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<NavigationDestinationM3E> _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<NavBarM3ELabelBehavior>(
|
||||||
|
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<NavBarM3EIndicatorStyle>(
|
||||||
|
label: 'indicatorStyle',
|
||||||
|
initialOption: NavBarM3EIndicatorStyle.pill,
|
||||||
|
options: const [
|
||||||
|
NavBarM3EIndicatorStyle.pill,
|
||||||
|
NavBarM3EIndicatorStyle.underline,
|
||||||
|
NavBarM3EIndicatorStyle.none,
|
||||||
|
],
|
||||||
|
labelBuilder: (v) => v.name,
|
||||||
|
);
|
||||||
|
|
||||||
|
final size = context.knobs.object.dropdown<NavBarM3ESize>(
|
||||||
|
label: 'size',
|
||||||
|
initialOption: NavBarM3ESize.medium,
|
||||||
|
options: const [NavBarM3ESize.small, NavBarM3ESize.medium],
|
||||||
|
labelBuilder: (v) => v.name,
|
||||||
|
);
|
||||||
|
|
||||||
|
final shape = context.knobs.object.dropdown<NavBarM3EShapeFamily>(
|
||||||
|
label: 'shapeFamily',
|
||||||
|
initialOption: NavBarM3EShapeFamily.round,
|
||||||
|
options: const [NavBarM3EShapeFamily.round, NavBarM3EShapeFamily.square],
|
||||||
|
labelBuilder: (v) => v.name,
|
||||||
|
);
|
||||||
|
|
||||||
|
final density = context.knobs.object.dropdown<NavBarM3EDensity>(
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -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<NavigationRailM3ESection> _buildSections(
|
||||||
|
BuildContext context, {
|
||||||
|
required int sectionCount,
|
||||||
|
required int itemsPerSection,
|
||||||
|
required bool withBadges,
|
||||||
|
required bool useShortItems,
|
||||||
|
}) {
|
||||||
|
final icons = <IconData>[
|
||||||
|
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<int>(0, (sum, s) => sum + s.destinations.length);
|
||||||
|
|
||||||
|
// Behavior knobs
|
||||||
|
final type = forcedType ??
|
||||||
|
context.knobs.object.dropdown<NavigationRailM3EType>(
|
||||||
|
label: 'type',
|
||||||
|
options: NavigationRailM3EType.values,
|
||||||
|
initialOption: NavigationRailM3EType.expanded,
|
||||||
|
labelBuilder: (v) => v.name,
|
||||||
|
);
|
||||||
|
final modality = forcedModality ??
|
||||||
|
context.knobs.object.dropdown<NavigationRailM3EModality>(
|
||||||
|
label: 'modality',
|
||||||
|
options: NavigationRailM3EModality.values,
|
||||||
|
initialOption: NavigationRailM3EModality.standard,
|
||||||
|
labelBuilder: (v) => v.name,
|
||||||
|
);
|
||||||
|
final labelBehavior =
|
||||||
|
context.knobs.object.dropdown<NavigationRailM3ELabelBehavior>(
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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));
|
||||||
|
}
|
||||||
|
|
@ -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<NavigationRailM3ELabelBehavior>(
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
@ -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).
|
||||||
|
|
@ -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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
27
widgetbook/lib/slider_m3e/INDEX-slider_m3e.md
Normal file
27
widgetbook/lib/slider_m3e/INDEX-slider_m3e.md
Normal file
|
|
@ -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.
|
||||||
191
widgetbook/lib/slider_m3e/range_slider_m3e_usecases.dart
Normal file
191
widgetbook/lib/slider_m3e/range_slider_m3e_usecases.dart
Normal file
|
|
@ -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<SliderM3ESize>(
|
||||||
|
label: 'size',
|
||||||
|
initialOption: SliderM3ESize.medium,
|
||||||
|
options: SliderM3ESize.values,
|
||||||
|
labelBuilder: (v) => v.name,
|
||||||
|
);
|
||||||
|
final emphasis = context.knobs.object.dropdown<SliderM3EEmphasis>(
|
||||||
|
label: 'emphasis',
|
||||||
|
initialOption: SliderM3EEmphasis.primary,
|
||||||
|
options: SliderM3EEmphasis.values,
|
||||||
|
labelBuilder: (v) => v.name,
|
||||||
|
);
|
||||||
|
final shape = context.knobs.object.dropdown<SliderM3EShapeFamily>(
|
||||||
|
label: 'shapeFamily',
|
||||||
|
initialOption: SliderM3EShapeFamily.round,
|
||||||
|
options: SliderM3EShapeFamily.values,
|
||||||
|
labelBuilder: (v) => v.name,
|
||||||
|
);
|
||||||
|
final density = context.knobs.object.dropdown<SliderM3EDensity>(
|
||||||
|
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);
|
||||||
|
}
|
||||||
186
widgetbook/lib/slider_m3e/slider_m3e_usecases.dart
Normal file
186
widgetbook/lib/slider_m3e/slider_m3e_usecases.dart
Normal file
|
|
@ -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<SliderM3ESize>(
|
||||||
|
label: 'size',
|
||||||
|
initialOption: SliderM3ESize.medium,
|
||||||
|
options: SliderM3ESize.values,
|
||||||
|
labelBuilder: (v) => v.name,
|
||||||
|
);
|
||||||
|
final emphasis = context.knobs.object.dropdown<SliderM3EEmphasis>(
|
||||||
|
label: 'emphasis',
|
||||||
|
initialOption: SliderM3EEmphasis.primary,
|
||||||
|
options: SliderM3EEmphasis.values,
|
||||||
|
labelBuilder: (v) => v.name,
|
||||||
|
);
|
||||||
|
final shape = context.knobs.object.dropdown<SliderM3EShapeFamily>(
|
||||||
|
label: 'shapeFamily',
|
||||||
|
initialOption: SliderM3EShapeFamily.round,
|
||||||
|
options: SliderM3EShapeFamily.values,
|
||||||
|
labelBuilder: (v) => v.name,
|
||||||
|
);
|
||||||
|
final density = context.knobs.object.dropdown<SliderM3EDensity>(
|
||||||
|
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);
|
||||||
|
}
|
||||||
31
widgetbook/lib/split_button_m3e/INDEX-split_button_m3e.md
Normal file
31
widgetbook/lib/split_button_m3e/INDEX-split_button_m3e.md
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Widgetbook Index — split_button_m3e
|
||||||
|
|
||||||
|
This index summarizes all Widgetbook use cases defined for SplitButtonM3E.
|
||||||
|
|
||||||
|
Component: SplitButtonM3E<T>
|
||||||
|
|
||||||
|
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.
|
||||||
272
widgetbook/lib/split_button_m3e/split_button_m3e_usecase.dart
Normal file
272
widgetbook/lib/split_button_m3e/split_button_m3e_usecase.dart
Normal file
|
|
@ -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<SplitButtonM3EEmphasis>(
|
||||||
|
label: 'emphasis',
|
||||||
|
initialOption: SplitButtonM3EEmphasis.filled,
|
||||||
|
options: SplitButtonM3EEmphasis.values,
|
||||||
|
labelBuilder: (v) => v.name,
|
||||||
|
);
|
||||||
|
|
||||||
|
final size = sizeFixed ?? context.knobs.object.dropdown<SplitButtonM3ESize>(
|
||||||
|
label: 'size',
|
||||||
|
initialOption: SplitButtonM3ESize.md,
|
||||||
|
options: SplitButtonM3ESize.values,
|
||||||
|
labelBuilder: (v) => v.name,
|
||||||
|
);
|
||||||
|
|
||||||
|
final shape = shapeFixed ??
|
||||||
|
context.knobs.object.dropdown<SplitButtonM3EShape>(
|
||||||
|
label: 'shape',
|
||||||
|
initialOption: SplitButtonM3EShape.round,
|
||||||
|
options: SplitButtonM3EShape.values,
|
||||||
|
labelBuilder: (v) => v.name,
|
||||||
|
);
|
||||||
|
|
||||||
|
final trailingAlignment = context.knobs.object
|
||||||
|
.dropdown<SplitButtonM3ETrailingAlignment>(
|
||||||
|
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<String>(
|
||||||
|
value: 'value_$i',
|
||||||
|
child: 'Option $i',
|
||||||
|
enabled: !disabled,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Center(
|
||||||
|
child: SplitButtonM3E<String>(
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
43
widgetbook/lib/toolbar_m3e/INDEX-toolbar_m3e.md
Normal file
43
widgetbook/lib/toolbar_m3e/INDEX-toolbar_m3e.md
Normal file
|
|
@ -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.
|
||||||
|
|
@ -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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
192
widgetbook/lib/toolbar_m3e/toolbar_m3e_usecases.dart
Normal file
192
widgetbook/lib/toolbar_m3e/toolbar_m3e_usecases.dart
Normal file
|
|
@ -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<ToolbarM3EVariant>(
|
||||||
|
label: 'variant',
|
||||||
|
initialOption: ToolbarM3EVariant.surface,
|
||||||
|
options: ToolbarM3EVariant.values,
|
||||||
|
labelBuilder: (v) => v.name,
|
||||||
|
);
|
||||||
|
final size = forcedSize ??
|
||||||
|
context.knobs.object.dropdown<ToolbarM3ESize>(
|
||||||
|
label: 'size',
|
||||||
|
initialOption: ToolbarM3ESize.medium,
|
||||||
|
options: ToolbarM3ESize.values,
|
||||||
|
labelBuilder: (v) => v.name,
|
||||||
|
);
|
||||||
|
final density = forcedDensity ??
|
||||||
|
context.knobs.object.dropdown<ToolbarM3EDensity>(
|
||||||
|
label: 'density',
|
||||||
|
initialOption: ToolbarM3EDensity.regular,
|
||||||
|
options: ToolbarM3EDensity.values,
|
||||||
|
labelBuilder: (v) => v.name,
|
||||||
|
);
|
||||||
|
final shape = context.knobs.object.dropdown<ToolbarM3EShapeFamily>(
|
||||||
|
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<ToolbarActionM3E> 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);
|
||||||
|
}
|
||||||
38
widgetbook/pubspec.yaml
Normal file
38
widgetbook/pubspec.yaml
Normal file
|
|
@ -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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue