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,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).

View file

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

View file

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

View file

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