WIP: feat(ui): Migrate to new UI implementation
This commit is contained in:
@@ -0,0 +1,330 @@
|
||||
# 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`.
|
||||
|
||||
```powershell
|
||||
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:
|
||||
|
||||
```dart
|
||||
@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:**
|
||||
|
||||
```dart
|
||||
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
|
||||
|
||||
1. **Critical parameters** (required, affects core functionality): Always use knobs
|
||||
2. **Visual parameters** (colors, sizes, styles): Use knobs when they demonstrate component flexibility
|
||||
3. **Behavioral parameters** (enabled/disabled, loading states): Use knobs for interactive demonstration
|
||||
4. **Callback parameters**: Implement with descriptive print statements
|
||||
5. **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
|
||||
|
||||
```dart
|
||||
// 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
|
||||
|
||||
```dart
|
||||
// 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:
|
||||
|
||||
```dart
|
||||
// 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
|
||||
|
||||
```dart
|
||||
// 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
|
||||
|
||||
```dart
|
||||
// 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:
|
||||
|
||||
```dart
|
||||
// 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
|
||||
|
||||
```dart
|
||||
// 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
|
||||
|
||||
```dart
|
||||
// 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
|
||||
|
||||
```dart
|
||||
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
|
||||
Reference in New Issue
Block a user