Skip to content

Commit 99d948f

Browse files
erickzanardoclaude
andauthored
feat: adding very good ui skill (#24)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 60d8f55 commit 99d948f

6 files changed

Lines changed: 177 additions & 0 deletions

File tree

.claude-plugin/plugin.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"testing",
4747
"theming",
4848
"very-good-cli",
49+
"ui-package",
4950
"wcag",
5051
"widget-testing",
5152
"sdk-upgrade",

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ skills/
3838
static-security/reference.md
3939
testing/SKILL.md
4040
testing/reference.md
41+
ui-package/SKILL.md
4142
```
4243

4344
## Skill File Format

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ For more details, see the [Very Good Claude Marketplace][marketplace_link].
3737
| [**Bloc**](skills/bloc/SKILL.md) | State management with Bloc/Cubit — sealed events & states, `BlocProvider`/`BlocBuilder` widgets, event transformers, and testing with `blocTest()` & `mocktail` |
3838
| [**Layered Architecture**](skills/layered-architecture/SKILL.md) | VGV layered architecture — four-layer package structure (Data, Repository, Business Logic, Presentation), dependency rules, data flow, and bootstrap wiring |
3939
| [**Security**](skills/static-security/SKILL.md) | Flutter-specific static security review — secrets management, `flutter_secure_storage`, certificate pinning, `Random.secure()`, `formz` validation, dependency vulnerability scanning with `osv-scanner`, and OWASP Mobile Top 10 guidance |
40+
| [**UI Package**](skills/ui-package/SKILL.md) | Flutter UI package creation — custom widget libraries with `ThemeExtension`-based theming, design tokens, barrel file exports, widget tests, Widgetbook catalog, and consistent API conventions |
4041
| [**License Compliance**](skills/license-compliance/SKILL.md) | Dependency license auditing — categorizes licenses (permissive, weak/strong copyleft, unknown), flags non-compliant or missing licenses, and produces a structured compliance report using Very Good CLI |
4142
| [**Dart/Flutter SDK Upgrade**](skills/dart-flutter-sdk-upgrade/SKILL.md) | Bump Dart and Flutter SDK constraints across packages — CI workflow versions, pubspec.yaml environment constraints, and PR preparation for SDK upgrades |
4243

@@ -76,6 +77,7 @@ You can also invoke skills directly as slash commands:
7677
/vgv-navigation
7778
/vgv-static-security
7879
/vgv-testing
80+
/vgv-ui-package
7981
/vgv-license-compliance
8082
/vgv-dart-flutter-sdk-upgrade
8183
```

config/cspell.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"Bidirectionality",
55
"Bienvenido",
66
"bypassable",
7+
"dartdoc",
78
"CSPRNG",
89
"dismissable",
910
"elemento",
@@ -14,6 +15,7 @@
1415
"GHSA",
1516
"goldens",
1617
"hoverable",
18+
"lerp",
1719
"jailbroken",
1820
"LTRB",
1921
"mapbox",
@@ -27,6 +29,8 @@
2729
"serialization",
2830
"stdio",
2931
"WCAG",
32+
"widgetbook",
33+
"Widgetbook",
3034
"xxlg"
3135
],
3236
"flagWords": []

skills/ui-package/SKILL.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
---
2+
name: vgv-ui-package
3+
description: Best practices for building a Flutter UI package on top of Material — custom components, ThemeExtension-based theming, consistent APIs, and widget tests. Use when user says "create a ui package". Supports app_ui_package template.
4+
allowed-tools: Edit,mcp__very-good-cli__create
5+
---
6+
7+
# UI Package
8+
9+
Best practices for creating a Flutter UI package — a reusable widget library that builds on top of `package:flutter/material.dart`, extending it with app-specific components, custom design tokens via `ThemeExtension`, and a consistent API surface.
10+
11+
> **Theming foundation:** This skill focuses on UI package structure, widget APIs, and testing. For foundational Material 3 theming (`ColorScheme`, `TextTheme`, component themes, spacing constants, light/dark mode), see the **Material Theming** skill (`/vgv-material-theming`). The two skills are complementary — Material Theming covers how to set up and use `ThemeData`; this skill covers how to extend it with `ThemeExtension` tokens and package reusable widgets around it.
12+
13+
## Core Standards
14+
15+
Apply these standards to ALL UI package work:
16+
17+
- **Build on Material** — depend on `flutter/material.dart` and compose Material widgets; do not rebuild primitives that Material already provides
18+
- **One widget per file** — each public widget lives in its own file named after the widget in snake_case (e.g., `app_button.dart`)
19+
- **Barrel file for public API** — expose all public widgets and theme classes through a single barrel file (e.g., `lib/my_ui.dart`) that also re-exports `material.dart`
20+
- **Extend theming with `ThemeExtension`** — use Material's `ThemeData`, `ColorScheme`, and `TextTheme` as the base (see Material Theming skill); add app-specific tokens (spacing, custom colors) via `ThemeExtension<T>`
21+
- **Every widget has a corresponding widget test** — behavioral tests verify interactions, callbacks, and state changes
22+
- **Prefix all public classes** — use a consistent prefix (e.g., `App`, `Vg`) to avoid naming collisions with Material widgets
23+
- **Use `const` constructors everywhere possible** — all widget constructors must be `const` when feasible
24+
- **Document every public member** — every public class, constructor parameter, and method has a dartdoc comment
25+
26+
## Package Structure
27+
28+
```text
29+
my_ui/
30+
├── lib/
31+
│ ├── my_ui.dart # Barrel file — re-exports material.dart + all public API
32+
│ └── src/
33+
│ ├── theme/
34+
│ │ ├── app_theme.dart # AppTheme class with light/dark ThemeData builders
35+
│ │ ├── app_colors.dart # AppColors ThemeExtension for custom color tokens
36+
│ │ ├── app_spacing.dart # AppSpacing ThemeExtension for spacing tokens
37+
│ │ └── app_text_styles.dart # Optional: extra text styles beyond Material's TextTheme
38+
│ ├── widgets/
39+
│ │ ├── app_button.dart
40+
│ │ ├── app_text_field.dart
41+
│ │ ├── app_card.dart
42+
│ │ └── ...
43+
│ └── extensions/
44+
│ └── build_context_extensions.dart # context.appColors, context.appSpacing shortcuts
45+
├── test/
46+
│ ├── src/
47+
│ │ ├── theme/
48+
│ │ │ └── app_theme_test.dart
49+
│ │ └── widgets/
50+
│ │ ├── app_button_test.dart
51+
│ │ └── ...
52+
│ └── helpers/
53+
│ └── pump_app.dart # Test helper wrapping widgets in MaterialApp + theme
54+
├── widgetbook/ # Widgetbook catalog submodule (sandbox + showcase)
55+
│ └── ...
56+
└── pubspec.yaml
57+
```
58+
59+
## Building Widgets
60+
61+
### Widget API Guidelines
62+
63+
- Compose Material widgets — use `FilledButton`, `OutlinedButton`, `TextField`, `Card`, etc. as building blocks
64+
- Accept only the minimum required parameters — avoid "kitchen sink" constructors
65+
- Use named parameters for everything except `key` and `child`/`children`
66+
- Provide sensible defaults derived from the theme when a parameter is not supplied
67+
- Expose callbacks with `ValueChanged<T>` or `VoidCallback` — do not use raw `Function`
68+
- Use `Widget?` for optional slot-based composition (leading, trailing icons, etc.)
69+
70+
## Anti-Patterns
71+
72+
| Anti-Pattern | Correct Approach |
73+
| ------------ | ---------------- |
74+
| Rebuilding widgets Material already provides (e.g., custom button from `GestureDetector` + `DecoratedBox`) | Compose Material widgets (`FilledButton`, `OutlinedButton`) and style them |
75+
| Creating a parallel theme system with custom `InheritedWidget` | Use Material's `ThemeData` as the base and `ThemeExtension` for custom tokens |
76+
| Hardcoding `Color(0xFF...)` in widget code | Use `Theme.of(context).colorScheme` for standard colors and `context.appColors` for custom tokens |
77+
| Duplicating Material's `ColorScheme` roles in a custom class | Only create `ThemeExtension` tokens for values Material does not cover (e.g., success, warning, info) |
78+
| Using `dynamic` or `Object` for callback types | Use `VoidCallback`, `ValueChanged<T>`, or specific function typedefs |
79+
| Exposing internal implementation files directly | Use a barrel file; keep all files under `src/` private |
80+
81+
## Creating the Package
82+
83+
Use the Very Good CLI MCP tool to scaffold the `app_ui_package`.

skills/ui-package/reference.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# UI Package — Reference
2+
3+
Concrete examples and step-by-step workflows for the UI Package skill.
4+
5+
---
6+
7+
## ThemeExtension Key Classes
8+
9+
| Class | Purpose |
10+
| ----- | ------- |
11+
| `AppColors extends ThemeExtension<AppColors>` | Custom color tokens beyond `ColorScheme` (success, warning, info + on-variants) |
12+
| `AppSpacing extends ThemeExtension<AppSpacing>` | Spacing scale (xxs through xxlg) with `copyWith` and `lerp` |
13+
| `AppTheme` | Composes `ThemeData` with `ColorScheme.fromSeed` + custom extensions, for light and dark variants |
14+
| `AppThemeBuildContext` extension | Shorthand `context.appColors` and `context.appSpacing` |
15+
16+
Every `ThemeExtension` must implement `copyWith` and `lerp` for theme animation support.
17+
18+
## Test Helper
19+
20+
```dart
21+
extension PumpApp on WidgetTester {
22+
Future<void> pumpApp(
23+
Widget widget, {
24+
ThemeData? theme,
25+
}) {
26+
return pumpWidget(
27+
MaterialApp(
28+
theme: theme ?? AppTheme.light,
29+
home: Scaffold(body: widget),
30+
),
31+
);
32+
}
33+
}
34+
```
35+
36+
## Barrel File Example
37+
38+
```dart
39+
/// My UI — a custom Flutter widget library built on Material.
40+
library;
41+
42+
export 'package:flutter/material.dart';
43+
44+
export 'src/extensions/build_context_extensions.dart';
45+
export 'src/theme/app_colors.dart';
46+
export 'src/theme/app_spacing.dart';
47+
export 'src/theme/app_theme.dart';
48+
export 'src/widgets/app_button.dart';
49+
export 'src/widgets/app_card.dart';
50+
export 'src/widgets/app_text_field.dart';
51+
```
52+
53+
## Widgetbook Catalog
54+
55+
### Key Concepts
56+
57+
- **Use cases**: top-level functions annotated with `@widgetbook.UseCase(name:, type:)`, one file per widget in `use_cases/`
58+
- **Use-case decorator**: a `UseCaseDecorator` widget that wraps every use case with a consistent background
59+
- **Theme addon**: `ThemeAddon` wired to `AppTheme.light` and `AppTheme.dark` for switching themes in the catalog
60+
- **Code generation**: Widgetbook uses `build_runner` to scan annotations and generate `widgetbook.directories.g.dart`
61+
62+
### Commands
63+
64+
| Command | Purpose |
65+
| ------- | ------- |
66+
| `cd widgetbook && dart run build_runner build --delete-conflicting-outputs` | Regenerate use-case directories after adding/modifying use cases |
67+
| `cd widgetbook && flutter run -d chrome` | Run the catalog locally |
68+
69+
## Common Workflows
70+
71+
### Adding a New Widget
72+
73+
1. Create `lib/src/widgets/app_<name>.dart` with a `const` constructor and documentation
74+
2. Compose Material widgets internally; read custom tokens via `context.appColors` / `context.appSpacing`
75+
3. Export the file from the barrel file (`lib/my_ui.dart`)
76+
4. Create `test/src/widgets/app_<name>_test.dart` with widget tests
77+
5. Add use cases in `widgetbook/lib/widgetbook/use_cases/app_<name>.dart` covering all variants
78+
6. Re-run `dart run build_runner build --delete-conflicting-outputs` in `widgetbook/`
79+
80+
### Adding a New Custom Token
81+
82+
1. Add the token to the appropriate `ThemeExtension` class (`AppColors` or `AppSpacing`)
83+
2. Update `copyWith` and `lerp` methods
84+
3. Update `AppTheme.light` and `AppTheme.dark` to include the new token value
85+
4. Update existing tests that construct the extension directly
86+
5. Use the new token in widgets via the `BuildContext` extension

0 commit comments

Comments
 (0)