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