9.8 KiB
Widgetbook Use-Case Generation Instructions
Workspace And Asset Contract
The catalog is a standalone Flutter workspace under widgetbook/ with a path
dependency on the launcher package. Production components live under
lib/app/design_system; stories, fixtures, and generated directories stay in
widgetbook/lib.
cd widgetbook
fvm flutter pub get
fvm dart run build_runner build
fvm flutter run -d windows
The catalog provides forest and temple themes, 1024×640, 1280×720, and 1600×900 Windows viewports, and text scaling from 100% through 200%. The production launcher composition targets a fixed minimum window of 1280×720 to preserve the mockup layout.
The launcher compositions must follow mockup.html: a 240-pixel game rail,
brand and account header, hero copy over the key art, a 360-pixel floating news
panel, and the realm/progress/play strip above the status bar. The login shell
uses the mockup's split artwork and 480-pixel form panel. Widgetbook stories
exercise these production compositions; they are not alternate app layouts.
Temporary launcher artwork uses a 1536×1024 source cropped with BoxFit.cover.
Final artwork should be at least 2560×1440, keep essential content out of the
outer 12% on every edge, and leave the central/right content areas readable
under a dark gradient. The MoonWell brand mark is composed from Flutter vector
icons and text and must remain legible between 96 and 360 logical pixels.
Generate comprehensive Widgetbook use cases for Flutter components that showcase different variants and configurations effectively.
Core Requirements
UseCase Annotation Structure
Each use case must have a properly configured @UseCase annotation:
@UseCase(
name: 'variantName', // Required: Unique identifier for this variant
type: ComponentType, // Required: The Flutter widget class being showcased
)
Naming Rules:
- Single use case:
name: 'default' - Multiple use cases: Use descriptive names like
'with_label','disabled','loading' - Names must be unique within the same component type
Method Signature Requirements
Exact signature pattern:
Widget build[ComponentName][VariantName]UseCase(BuildContext context) {
// Implementation
}
Naming conventions:
- Single use case:
buildProgressIndicatorUseCase - Multiple use cases:
buildProgressIndicatorWithLabelUseCase,buildProgressIndicatorDisabledUseCase - Always return
Widget - Always accept exactly one parameter:
BuildContext context
Parameter Configuration Strategy
Priority System
- Critical parameters (required, affects core functionality): Always use knobs
- Visual parameters (colors, sizes, styles): Use knobs when they demonstrate component flexibility
- Behavioral parameters (enabled/disabled, loading states): Use knobs for interactive demonstration
- Callback parameters: Implement with descriptive print statements
- Complex objects: Hardcode with meaningful defaults, add TODO comments
Knob Selection Logic
- Bounded numeric values: Use sliders (opacity: 0.0-1.0, progress: 0-100)
- Unbounded numeric values: Use input fields (dimensions, counts)
- Enums/predefined options: Use list knobs
- Text content: Use string inputs
- Feature toggles: Use boolean checkboxes
Comprehensive Knobs API
Basic Types
// Strings
context.knobs.string(label: 'text', initialValue: 'Hello World')
context.knobs.stringOrNull(label: 'optionalText', initialValue: null)
// Booleans
context.knobs.boolean(label: 'enabled', initialValue: true)
context.knobs.booleanOrNull(label: 'optionalFlag', initialValue: null)
// Integers
context.knobs.int.input(label: 'count', initialValue: 5)
context.knobs.int.slider(label: 'progress', initialValue: 50, min: 0, max: 100, divisions: 10)
context.knobs.intOrNull.input(label: 'optionalCount', initialValue: null)
context.knobs.intOrNull.slider(label: 'progress', initialValue: null, min: 0, max: 100, divisions: 10)
// Doubles
context.knobs.double.input(label: 'value', initialValue: 1.5)
context.knobs.double.slider(label: 'opacity', initialValue: 0.8, min: 0.0, max: 1.0, divisions: 20)
context.knobs.doubleOrNull.input(label: 'value', initialValue: null)
context.knobs.doubleOrNull.slider(label: 'optionalOpacity', initialValue: null, min: 0.0, max: 1.0)
Advanced Types
// Dropdown lists
context.knobs.object.dropdown<TextAlign>(
label: 'textAlign',
initialOption: TextAlign.center,
options: [TextAlign.left, TextAlign.center, TextAlign.right],
labelBuilder: (value) => value.name,
)
// Colors
context.knobs.color(label: 'primaryColor', initialValue: Colors.blue)
context.knobs.colorOrNull(label: 'optionalColor', initialValue: null)
// DateTime
context.knobs.dateTime(
label: 'selectedDate',
initialValue: DateTime.now(),
start: DateTime.now().subtract(const Duration(days: 365)),
end: DateTime.now().add(const Duration(days: 365)),
)
// Duration
context.knobs.duration(label: 'animationDuration', initialValue: const Duration(milliseconds: 300))
Advanced Component Handling
State Management Integration
For components requiring external state:
// Example: Component requiring a provider
@UseCase(name: 'with_data', type: DataWidget)
Widget buildDataWidgetWithDataUseCase(BuildContext context) {
return MockDataProvider(
data: _generateMockData(),
child: DataWidget(
onItemSelected: (item) => print('Selected item: ${item.id}'),
showLoading: context.knobs.boolean(label: 'showLoading', initialValue: false),
),
);
}
Complex Parameter Handling
// For unmappable parameters
final customObject = CustomConfiguration(
// Hardcoded meaningful defaults
apiEndpoint: 'https://api.example.com',
timeout: const Duration(seconds: 30),
); // TODO: User should configure CustomConfiguration manually
// For asset references
final iconPath = 'assets/icons/star.svg'; // Verify asset exists in pubspec.yaml
Callback Implementation Patterns
// Simple callbacks
onPressed: () => print('Button pressed'),
// Callbacks with data
onChanged: (value) => print('Value changed to: $value'),
// Complex callbacks
onFormSubmitted: (formData) {
print('Form submitted with data:');
print(' - Name: ${formData.name}');
print(' - Email: ${formData.email}');
},
// Async callbacks
onSave: () async {
print('Save operation started');
await Future.delayed(const Duration(seconds: 1));
print('Save operation completed');
},
Theme handling
Themes are globally injected and must not be provided.
Use Case Variant Strategies
Single Component, Multiple Variants
Create variants that showcase different states and configurations:
// Default state
@UseCase(name: 'default', type: CustomButton)
Widget buildCustomButtonUseCase(BuildContext context) { /* ... */ }
// Loading state
@UseCase(name: 'loading', type: CustomButton)
Widget buildCustomButtonLoadingUseCase(BuildContext context) { /* ... */ }
// Disabled state
@UseCase(name: 'disabled', type: CustomButton)
Widget buildCustomButtonDisabledUseCase(BuildContext context) { /* ... */ }
// With icon
@UseCase(name: 'with icon', type: CustomButton)
Widget buildCustomButtonWithIconUseCase(BuildContext context) { /* ... */ }
Responsive and Theme Variants
// Different sizes
@UseCase(name: 'small', type: ProfileCard)
@UseCase(name: 'medium', type: ProfileCard)
@UseCase(name: 'large', type: ProfileCard)
Quality Standards
Code Quality
- No descriptive comments: Code should be self-documenting
- Consistent formatting: Follow Dart style guidelines
- Meaningful defaults: Initial values should represent realistic usage
- Error handling: Wrap potentially failing operations in try-catch where appropriate
Use Case Coverage
Ensure use cases demonstrate:
- Default/primary functionality
- Edge cases (empty states, maximum values)
- Interactive behaviors (hover, focus, disabled states)
- Visual variants (different styles, sizes, colors)
- Error states when applicable
Testing Considerations
// Include realistic data volumes
final items = List.generate(50, (index) => 'Item ${index + 1}');
// Test boundary conditions
final progress = context.knobs.double.slider(
label: 'progress',
initialValue: 0.7,
min: 0.0,
max: 1.0,
divisions: 100,
); // Allows testing 0%, 100%, and intermediate values
Complete Example Template
import 'package:flutter/material.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';
@UseCase(
name: 'interactive',
type: CustomSlider,
)
Widget buildCustomSliderInteractiveUseCase(BuildContext context) {
return CustomSlider(
value: context.knobs.double.slider(
label: 'value',
initialValue: 0.5,
min: 0.0,
max: 1.0,
divisions: 20,
),
min: context.knobs.double.input(
label: 'min',
initialValue: 0.0,
),
max: context.knobs.double.input(
label: 'max',
initialValue: 1.0,
),
enabled: context.knobs.boolean(
label: 'enabled',
initialValue: true,
),
showLabels: context.knobs.boolean(
label: 'showLabels',
initialValue: true,
),
activeColor: context.knobs.color(
label: 'activeColor',
initialValue: Colors.blue,
),
onChanged: (value) => print('Slider value changed to: $value'),
onChangeStart: (value) => print('Slider interaction started at: $value'),
onChangeEnd: (value) => print('Slider interaction ended at: $value'),
);
}
Pre-Generation Checklist
Before generating use cases, verify:
- Component class name and import path
- Required vs optional parameters
- Parameter types and constraints
- Available enum values for list knobs
- Asset dependencies and paths
- State management requirements
- Theme/localization dependencies
- Callback signatures and expected behavior