diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index a033baf..f709157 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -46,6 +46,7 @@ "testing", "theming", "very-good-cli", + "ui-package", "wcag", "widget-testing", "sdk-upgrade", diff --git a/CLAUDE.md b/CLAUDE.md index 714dde0..894304d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -37,6 +37,7 @@ skills/ static-security/reference.md testing/SKILL.md testing/reference.md + ui-package/SKILL.md ``` ## Skill File Format diff --git a/README.md b/README.md index f7e68b8..ada72f6 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ For more details, see the [Very Good Claude Marketplace][marketplace_link]. | [**Bloc**](skills/bloc/SKILL.md) | State management with Bloc/Cubit — sealed events & states, `BlocProvider`/`BlocBuilder` widgets, event transformers, and testing with `blocTest()` & `mocktail` | | [**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 | | [**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 | +| [**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 | | [**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 | | [**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 | @@ -76,6 +77,7 @@ You can also invoke skills directly as slash commands: /vgv-navigation /vgv-static-security /vgv-testing +/vgv-ui-package /vgv-license-compliance /vgv-dart-flutter-sdk-upgrade ``` diff --git a/config/cspell.json b/config/cspell.json index eba95a4..8c58b5d 100644 --- a/config/cspell.json +++ b/config/cspell.json @@ -4,6 +4,7 @@ "Bidirectionality", "Bienvenido", "bypassable", + "dartdoc", "CSPRNG", "dismissable", "elemento", @@ -14,6 +15,7 @@ "GHSA", "goldens", "hoverable", + "lerp", "jailbroken", "LTRB", "mapbox", @@ -26,6 +28,8 @@ "serialization", "stdio", "WCAG", + "widgetbook", + "Widgetbook", "xxlg" ], "flagWords": [] diff --git a/skills/ui-package/SKILL.md b/skills/ui-package/SKILL.md new file mode 100644 index 0000000..1ee2cd8 --- /dev/null +++ b/skills/ui-package/SKILL.md @@ -0,0 +1,83 @@ +--- +name: vgv-ui-package +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. +allowed-tools: Edit,mcp__very-good-cli__create +--- + +# UI Package + +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. + +> **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. + +## Core Standards + +Apply these standards to ALL UI package work: + +- **Build on Material** — depend on `flutter/material.dart` and compose Material widgets; do not rebuild primitives that Material already provides +- **One widget per file** — each public widget lives in its own file named after the widget in snake_case (e.g., `app_button.dart`) +- **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` +- **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` +- **Every widget has a corresponding widget test** — behavioral tests verify interactions, callbacks, and state changes +- **Prefix all public classes** — use a consistent prefix (e.g., `App`, `Vg`) to avoid naming collisions with Material widgets +- **Use `const` constructors everywhere possible** — all widget constructors must be `const` when feasible +- **Document every public member** — every public class, constructor parameter, and method has a dartdoc comment + +## Package Structure + +```text +my_ui/ +├── lib/ +│ ├── my_ui.dart # Barrel file — re-exports material.dart + all public API +│ └── src/ +│ ├── theme/ +│ │ ├── app_theme.dart # AppTheme class with light/dark ThemeData builders +│ │ ├── app_colors.dart # AppColors ThemeExtension for custom color tokens +│ │ ├── app_spacing.dart # AppSpacing ThemeExtension for spacing tokens +│ │ └── app_text_styles.dart # Optional: extra text styles beyond Material's TextTheme +│ ├── widgets/ +│ │ ├── app_button.dart +│ │ ├── app_text_field.dart +│ │ ├── app_card.dart +│ │ └── ... +│ └── extensions/ +│ └── build_context_extensions.dart # context.appColors, context.appSpacing shortcuts +├── test/ +│ ├── src/ +│ │ ├── theme/ +│ │ │ └── app_theme_test.dart +│ │ └── widgets/ +│ │ ├── app_button_test.dart +│ │ └── ... +│ └── helpers/ +│ └── pump_app.dart # Test helper wrapping widgets in MaterialApp + theme +├── widgetbook/ # Widgetbook catalog submodule (sandbox + showcase) +│ └── ... +└── pubspec.yaml +``` + +## Building Widgets + +### Widget API Guidelines + +- Compose Material widgets — use `FilledButton`, `OutlinedButton`, `TextField`, `Card`, etc. as building blocks +- Accept only the minimum required parameters — avoid "kitchen sink" constructors +- Use named parameters for everything except `key` and `child`/`children` +- Provide sensible defaults derived from the theme when a parameter is not supplied +- Expose callbacks with `ValueChanged` or `VoidCallback` — do not use raw `Function` +- Use `Widget?` for optional slot-based composition (leading, trailing icons, etc.) + +## Anti-Patterns + +| Anti-Pattern | Correct Approach | +| ------------ | ---------------- | +| Rebuilding widgets Material already provides (e.g., custom button from `GestureDetector` + `DecoratedBox`) | Compose Material widgets (`FilledButton`, `OutlinedButton`) and style them | +| Creating a parallel theme system with custom `InheritedWidget` | Use Material's `ThemeData` as the base and `ThemeExtension` for custom tokens | +| Hardcoding `Color(0xFF...)` in widget code | Use `Theme.of(context).colorScheme` for standard colors and `context.appColors` for custom tokens | +| Duplicating Material's `ColorScheme` roles in a custom class | Only create `ThemeExtension` tokens for values Material does not cover (e.g., success, warning, info) | +| Using `dynamic` or `Object` for callback types | Use `VoidCallback`, `ValueChanged`, or specific function typedefs | +| Exposing internal implementation files directly | Use a barrel file; keep all files under `src/` private | + +## Creating the Package + +Use the Very Good CLI MCP tool to scaffold the `app_ui_package`. diff --git a/skills/ui-package/reference.md b/skills/ui-package/reference.md new file mode 100644 index 0000000..129a772 --- /dev/null +++ b/skills/ui-package/reference.md @@ -0,0 +1,86 @@ +# UI Package — Reference + +Concrete examples and step-by-step workflows for the UI Package skill. + +--- + +## ThemeExtension Key Classes + +| Class | Purpose | +| ----- | ------- | +| `AppColors extends ThemeExtension` | Custom color tokens beyond `ColorScheme` (success, warning, info + on-variants) | +| `AppSpacing extends ThemeExtension` | Spacing scale (xxs through xxlg) with `copyWith` and `lerp` | +| `AppTheme` | Composes `ThemeData` with `ColorScheme.fromSeed` + custom extensions, for light and dark variants | +| `AppThemeBuildContext` extension | Shorthand `context.appColors` and `context.appSpacing` | + +Every `ThemeExtension` must implement `copyWith` and `lerp` for theme animation support. + +## Test Helper + +```dart +extension PumpApp on WidgetTester { + Future pumpApp( + Widget widget, { + ThemeData? theme, + }) { + return pumpWidget( + MaterialApp( + theme: theme ?? AppTheme.light, + home: Scaffold(body: widget), + ), + ); + } +} +``` + +## Barrel File Example + +```dart +/// My UI — a custom Flutter widget library built on Material. +library; + +export 'package:flutter/material.dart'; + +export 'src/extensions/build_context_extensions.dart'; +export 'src/theme/app_colors.dart'; +export 'src/theme/app_spacing.dart'; +export 'src/theme/app_theme.dart'; +export 'src/widgets/app_button.dart'; +export 'src/widgets/app_card.dart'; +export 'src/widgets/app_text_field.dart'; +``` + +## Widgetbook Catalog + +### Key Concepts + +- **Use cases**: top-level functions annotated with `@widgetbook.UseCase(name:, type:)`, one file per widget in `use_cases/` +- **Use-case decorator**: a `UseCaseDecorator` widget that wraps every use case with a consistent background +- **Theme addon**: `ThemeAddon` wired to `AppTheme.light` and `AppTheme.dark` for switching themes in the catalog +- **Code generation**: Widgetbook uses `build_runner` to scan annotations and generate `widgetbook.directories.g.dart` + +### Commands + +| Command | Purpose | +| ------- | ------- | +| `cd widgetbook && dart run build_runner build --delete-conflicting-outputs` | Regenerate use-case directories after adding/modifying use cases | +| `cd widgetbook && flutter run -d chrome` | Run the catalog locally | + +## Common Workflows + +### Adding a New Widget + +1. Create `lib/src/widgets/app_.dart` with a `const` constructor and documentation +2. Compose Material widgets internally; read custom tokens via `context.appColors` / `context.appSpacing` +3. Export the file from the barrel file (`lib/my_ui.dart`) +4. Create `test/src/widgets/app__test.dart` with widget tests +5. Add use cases in `widgetbook/lib/widgetbook/use_cases/app_.dart` covering all variants +6. Re-run `dart run build_runner build --delete-conflicting-outputs` in `widgetbook/` + +### Adding a New Custom Token + +1. Add the token to the appropriate `ThemeExtension` class (`AppColors` or `AppSpacing`) +2. Update `copyWith` and `lerp` methods +3. Update `AppTheme.light` and `AppTheme.dark` to include the new token value +4. Update existing tests that construct the extension directly +5. Use the new token in widgets via the `BuildContext` extension