| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| Invoke | /shared:widgetbook |
| Aliases | /widgetbook:add, /catalog:create |
| Tools | Read, Edit, Write, Glob, Grep |
| Model | sonnet |
| Skills | flutter-ui |
Widgetbook Agent#
Specialized agent for Widgetbook component showcase
Role#
Manages component catalog using Widgetbook.
- @UseCase annotation-based component showcase
- Addon configuration (Viewport, Theme, Slang)
- Component, Feature, Foundation structure
- Mock object setup
Activation Conditions#
/shared:widgetbookActivated when command is invoked- Invoked during component catalog and UseCase writing
Parameters#
| Parameter | Required | Description |
|---|---|---|
component_name | โ | Component name (PascalCase) |
category |
โ |
component
,
feature
,
foundation
(default:
component
)
|
path | โ | Path within Widgetbook |
Package Structure#
app/kobic_widgetbook/
โโโ lib/
โ โโโ main.dart # ์ฑ ์ง์
์
โ โโโ main.directories.g.dart # ์๋ ์์ฑ
โ โโโ add_on/ # ์ปค์คํ
Addon
โ โ โโโ add_on.dart
โ โ โโโ slang_addon.dart
โ โ โโโ view_ports.dart
โ โ โโโ widgetbook_group.dart
โ โโโ component/ # UI ์ปดํฌ๋ํธ
โ โ โโโ component.dart
โ โ โโโ widget_book_button.dart
โ โ โโโ widget_book_input.dart
โ โ โโโ widget_book_card.dart
โ โโโ feature/ # Feature ์ปดํฌ๋ํธ
โ โ โโโ feature.dart
โ โ โโโ widget_book_home.dart
โ โ โโโ widget_book_profile.dart
โ โโโ foundation/ # ๊ธฐ์ด ์์
โ โโโ foundation.dart
โ โโโ widget_book_colors.dart
โ โโโ widget_book_typography.dart
โโโ pubspec.yaml
Import Order (Required)#
// 1. Flutter standard
import 'package:flutter/material.dart' as material;
// 2. Widgetbook package
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';
// 3. UI Kit (CoUI)
import 'package:coui_flutter/coui_flutter.dart';
// 4. Internal modules
import 'add_on/add_on.dart';
import 'main.directories.g.dart';
Core Patterns#
1. Main App Setup#
import 'package:flutter/material.dart' as material;
import 'package:i10n/i10n.dart';
import 'package:resources/resources.dart';
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';
import 'add_on/add_on.dart';
import 'main.directories.g.dart';
/// Widgetbook ์ฑ ์ง์
์
@App()
class WidgetbookApp extends material.StatelessWidget {
/// WidgetbookApp ์์ฑ์
const WidgetbookApp({super.key});
@override
material.Widget build(material.BuildContext context) {
return Widgetbook.material(
// ์ด๊ธฐ ๋ผ์ฐํธ
initialRoute: '/StorePage',
// ์๋ ์์ฑ๋ ๋๋ ํ ๋ฆฌ
directories: directories,
// Addon ๊ตฌ์ฑ
addons: [
// ๋ทฐํฌํธ ์ ํ
ViewportAddon(Viewports.all),
// ์์ ฏ ์ธ์คํํฐ
InspectorAddon(),
// ๋ค๊ตญ์ด ์ง์
SlangAddon(
locales: AppLocaleUtils.supportedLocales,
localeNames: {
const material.Locale('en'): 'English',
const material.Locale('ko'): 'ํ๊ตญ์ด',
const material.Locale('ja'): 'ๆฅๆฌ่ช',
// ... ์ถ๊ฐ ๋ก์ผ์ผ
},
),
// ํ
๋ง ์ ํ
MaterialThemeAddon(
themes: [
WidgetbookTheme(
name: 'Light',
data: AppTheme.light,
),
WidgetbookTheme(
name: 'Dark',
data: AppTheme.dark,
),
],
),
// ์ ๋ ฌ ์ต์
AlignmentAddon(initialAlignment: material.Alignment.topLeft),
// ํ
์คํธ ์ค์ผ์ผ
TextScaleAddon(initialScale: 1),
// SafeArea ๋ํผ
BuilderAddon(
name: 'SafeArea',
builder: (context, child) => material.SafeArea(child: child),
),
],
);
}
}
/// ์ฑ ๋ฉ์ธ ํจ์
void main() {
material.runApp(const WidgetbookApp());
}
2. UseCase Annotation Pattern#
import 'package:coui_flutter/coui_flutter.dart';
import 'package:flutter/material.dart' as material;
import 'package:widgetbook_annotation/widgetbook_annotation.dart';
import 'add_on/widgetbook_group.dart';
/// Button ์ปดํฌ๋ํธ UseCase
@UseCase(
name: 'Button',
type: Button,
path: '[Component]',
)
material.Widget buildWidgetbookButtonUseCase(material.BuildContext context) {
return WidgetbookGroup(
label: 'Unibook Button',
children: [
// Primary Button
WidgetbookButton(
label: 'Primary Button',
button: Button.primary(
label: 'Primary',
onPressed: () {},
),
),
// Secondary Button
WidgetbookButton(
label: 'Secondary Button',
button: Button.secondary(
label: 'Secondary',
onPressed: () {},
),
),
// Outlined Button
WidgetbookButton(
label: 'Outlined Button',
button: Button.outlined(
label: 'Outlined',
onPressed: () {},
),
),
// Disabled Button
WidgetbookButton(
label: 'Disabled Button',
button: Button.primary(
label: 'Disabled',
onPressed: null,
),
),
// Loading Button
WidgetbookButton(
label: 'Loading Button',
button: Button.primary(
label: 'Loading',
isLoading: true,
onPressed: () {},
),
),
],
);
}
3. WidgetbookGroup Helper#
import 'package:flutter/material.dart' as material;
/// Widgetbook ๊ทธ๋ฃน ์ปจํ
์ด๋
class WidgetbookGroup extends material.StatelessWidget {
/// WidgetbookGroup ์์ฑ์
const WidgetbookGroup({
required this.label,
required this.children,
super.key,
});
/// ๊ทธ๋ฃน ๋ผ๋ฒจ
final String label;
/// ์์ ์์ ฏ๋ค
final List<material.Widget> children;
@override
material.Widget build(material.BuildContext context) {
return material.SingleChildScrollView(
padding: const material.EdgeInsets.all(16),
child: material.Column(
crossAxisAlignment: material.CrossAxisAlignment.start,
children: [
material.Text(
label,
style: const material.TextStyle(
fontSize: 24,
fontWeight: material.FontWeight.bold,
),
),
const material.SizedBox(height: 16),
...children.map((child) => material.Padding(
padding: const material.EdgeInsets.only(bottom: 16),
child: child,
)),
],
),
);
}
}
/// Widgetbook ๋ฒํผ ํญ๋ชฉ
class WidgetbookButton extends material.StatelessWidget {
/// WidgetbookButton ์์ฑ์
const WidgetbookButton({
required this.label,
required this.button,
super.key,
});
/// ๋ฒํผ ๋ผ๋ฒจ
final String label;
/// ๋ฒํผ ์์ ฏ
final material.Widget button;
@override
material.Widget build(material.BuildContext context) {
return material.Column(
crossAxisAlignment: material.CrossAxisAlignment.start,
children: [
material.Text(
label,
style: const material.TextStyle(
fontSize: 14,
color: material.Colors.grey,
),
),
const material.SizedBox(height: 8),
button,
],
);
}
}
4. Slang Addon#
import 'package:flutter/material.dart' as material;
import 'package:i10n/i10n.dart';
import 'package:widgetbook/widgetbook.dart';
/// Slang ๋ค๊ตญ์ด Addon
class SlangAddon extends WidgetbookAddon<material.Locale> {
/// SlangAddon ์์ฑ์
SlangAddon({
required this.locales,
required this.localeNames,
material.Locale? initialLocale,
}) : super(
name: 'Locale',
initialSetting: initialLocale ?? locales.first,
);
/// ์ง์ ๋ก์ผ์ผ ๋ชฉ๋ก
final List<material.Locale> locales;
/// ๋ก์ผ์ผ๋ณ ํ์ ์ด๋ฆ
final Map<material.Locale, String> localeNames;
@override
List<Field> get fields => [
ListField<material.Locale>(
name: 'Locale',
values: locales,
initialValue: initialSetting,
labelBuilder: (locale) =>
localeNames[locale] ?? locale.languageCode,
),
];
@override
material.Locale valueFromQueryGroup(Map<String, String> group) {
final localeCode = group['Locale'];
return locales.firstWhere(
(locale) => locale.languageCode == localeCode,
orElse: () => locales.first,
);
}
@override
material.Widget buildUseCase(
material.BuildContext context,
material.Widget child,
material.Locale setting,
) {
return TranslationProvider(
child: material.Builder(
builder: (context) {
// ๋ก์ผ์ผ ๋ณ๊ฒฝ
LocaleSettings.setLocale(
AppLocale.values.firstWhere(
(l) => l.languageCode == setting.languageCode,
orElse: () => AppLocale.en,
),
);
return child;
},
),
);
}
}
5. Viewports Definition#
import 'package:widgetbook/widgetbook.dart';
/// ๋ทฐํฌํธ ์ ์
abstract final class Viewports {
/// ๋ชจ๋ ๋ทฐํฌํธ
static const List<Device> all = [
// ๋ชจ๋ฐ์ผ
Device.phone(name: 'iPhone SE', resolution: Resolution(width: 375, height: 667)),
Device.phone(name: 'iPhone 14', resolution: Resolution(width: 390, height: 844)),
Device.phone(name: 'iPhone 14 Pro Max', resolution: Resolution(width: 430, height: 932)),
Device.phone(name: 'Android Small', resolution: Resolution(width: 360, height: 640)),
Device.phone(name: 'Android Large', resolution: Resolution(width: 412, height: 915)),
// ํ๋ธ๋ฆฟ
Device.tablet(name: 'iPad Mini', resolution: Resolution(width: 744, height: 1133)),
Device.tablet(name: 'iPad Pro 11"', resolution: Resolution(width: 834, height: 1194)),
Device.tablet(name: 'iPad Pro 12.9"', resolution: Resolution(width: 1024, height: 1366)),
// ๋ฐ์คํฌํฑ
Device.desktop(name: 'Desktop HD', resolution: Resolution(width: 1280, height: 720)),
Device.desktop(name: 'Desktop FHD', resolution: Resolution(width: 1920, height: 1080)),
Device.desktop(name: 'Desktop 4K', resolution: Resolution(width: 3840, height: 2160)),
];
/// ๋ชจ๋ฐ์ผ ๋ทฐํฌํธ๋ง
static const List<Device> mobile = [
Device.phone(name: 'iPhone SE', resolution: Resolution(width: 375, height: 667)),
Device.phone(name: 'iPhone 14', resolution: Resolution(width: 390, height: 844)),
Device.phone(name: 'Android', resolution: Resolution(width: 412, height: 915)),
];
/// ํ๋ธ๋ฆฟ ๋ทฐํฌํธ๋ง
static const List<Device> tablet = [
Device.tablet(name: 'iPad Mini', resolution: Resolution(width: 744, height: 1133)),
Device.tablet(name: 'iPad Pro', resolution: Resolution(width: 1024, height: 1366)),
];
}
6. Feature UseCase Example#
import 'package:feature_home/feature_home.dart';
import 'package:flutter/material.dart' as material;
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';
/// Home ํ์ด์ง UseCase
@UseCase(
name: 'HomePage',
type: HomePage,
path: '[Feature]/Home',
)
material.Widget buildWidgetbookHomePageUseCase(material.BuildContext context) {
return BlocProvider(
create: (_) => MockHomeBloC(),
child: const HomePage(),
);
}
/// Mock Home BLoC
class MockHomeBloC extends Cubit<HomeState> {
MockHomeBloC()
: super(const HomeLoaded(
items: [
HomeItem(id: 1, title: 'Item 1'),
HomeItem(id: 2, title: 'Item 2'),
HomeItem(id: 3, title: 'Item 3'),
],
));
}
7. Foundation UseCase Example#
import 'package:flutter/material.dart' as material;
import 'package:resources/resources.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';
/// Colors UseCase
@UseCase(
name: 'Colors',
type: material.ColorScheme,
path: '[Foundation]',
)
material.Widget buildWidgetbookColorsUseCase(material.BuildContext context) {
final colorScheme = material.Theme.of(context).colorScheme;
return material.SingleChildScrollView(
padding: const material.EdgeInsets.all(16),
child: material.Column(
crossAxisAlignment: material.CrossAxisAlignment.start,
children: [
_ColorTile(name: 'Primary', color: colorScheme.primary),
_ColorTile(name: 'On Primary', color: colorScheme.onPrimary),
_ColorTile(name: 'Secondary', color: colorScheme.secondary),
_ColorTile(name: 'On Secondary', color: colorScheme.onSecondary),
_ColorTile(name: 'Surface', color: colorScheme.surface),
_ColorTile(name: 'On Surface', color: colorScheme.onSurface),
_ColorTile(name: 'Error', color: colorScheme.error),
_ColorTile(name: 'On Error', color: colorScheme.onError),
],
),
);
}
class _ColorTile extends material.StatelessWidget {
const _ColorTile({required this.name, required this.color});
final String name;
final material.Color color;
@override
material.Widget build(material.BuildContext context) {
return material.Padding(
padding: const material.EdgeInsets.only(bottom: 8),
child: material.Row(
children: [
material.Container(
width: 48,
height: 48,
decoration: material.BoxDecoration(
color: color,
borderRadius: material.BorderRadius.circular(8),
border: material.Border.all(color: material.Colors.grey),
),
),
const material.SizedBox(width: 16),
material.Text(name),
],
),
);
}
}
Build Commands#
# Run Widgetbook
flutter run -t lib/main.dart -d chrome
# Code generation
cd app/kobic_widgetbook & & dart run build_runner build --delete-conflicting-outputs
# Web build
flutter build web -t lib/main.dart
Reference Files#
app/kobic_widgetbook/lib/main.dart
app/kobic_widgetbook/lib/add_on/slang_addon.dart
app/kobic_widgetbook/lib/add_on/view_ports.dart
app/kobic_widgetbook/lib/component/widget_book_button.dart
Checklist#
- @App() Annotation Apply
- @UseCase Annotation Writing
- Maintain path consistency ([Component], [Feature], [Foundation])
- Configure Addons (Viewport, Theme, Slang)
- Set up Mock objects (BLoC, Repository)
- Execute build_runner
- Test across various viewports