Add Widgetbook setup with theme configuration, analysis options, and initial component use cases

This commit is contained in:
Emily Pauli 2025-10-25 22:59:48 +02:00
commit 80ca8f665a
46 changed files with 6031 additions and 2 deletions

View 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.

View 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,
);
}

View 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);
}