| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| Invoke | /shared:widgetbook |
| Aliases | /widgetbook:add, /catalog:create |
| Tools | Read, Edit, Write, Glob, Grep |
| Model | sonnet |
| Skills | flutter-ui |
/shared:widgetbook โ ํ๋ฉด ๋ถํ ์ ์์ฅ ๋ง๋ค๊ธฐ#
ํ๋ง๋๋ก#
์ฑ์ ๋ค์ด๊ฐ๋ ๋ฒํผยท์นด๋ยท์ ๋ ฅ์ฐฝ ๊ฐ์ UI ๋ถํ๋ค์ ํ๊ณณ์ ๋ชจ์ ๋ณด์ฌ์ฃผ๋ "์ ์์ฅ(์นดํ๋ก๊ทธ)"์ ๋ง๋ค์ด ์ฃผ๋ ๋์ฐ๋ฏธ์ ๋๋ค. ๊ฐ๊ตฌ์ ์ ๊ฐ๋ฉด ์ํ ํ๋๋ฅผ ์๊นยทํฌ๊ธฐ๋ณ๋ก ์ญ ์ง์ดํด ๋ ๊ฒ์ฒ๋ผ, ์ฐ๋ฆฌ ์ฑ ๋ถํ๋ ์ํ๋ณยท๊ธฐ๊ธฐ๋ณ๋ก ํ๋์ ๋ชจ์ ๋ณด๊ฒ ํด ์ค๋๋ค.
๋๊ฐยท์ธ์ ์ฐ๋์#
- ๋์์ด๋ยท๊ธฐํ์๊ฐ ์ฑ์ ๋ค์ด๊ฐ UI ๋ถํ์ด ์ค์ ๋ก ์ด๋ป๊ฒ ๋ณด์ด๋์ง ์ง์ ๋๋ฌ ๋ณด๋ฉฐ ํ์ธํ๊ณ ์ถ์ ๋
- ๊ฐ๋ฐ์๊ฐ ์ ๋ถํ(๋ฒํผ, ์นด๋ ๋ฑ)์ ๋ง๋ค๊ณ ๋์ ์ ์์ฅ์ ๋ฑ๋กํ๊ณ ์ถ์ ๋
/shared:widgetbook๋ช ๋ น์ ์คํํ๊ฑฐ๋, ๋ถํ ์นดํ๋ก๊ทธยทUseCase ์์ฑ ์์ ์ด ํ์ํ ๋ ์๋์ผ๋ก ๋์ํฉ๋๋ค
๋ฌด์์ ํด์ฃผ๋์#
- Widgetbook ์ด๋ผ๋ ์ ์์ฅ ์ฑ(
app/kobic_widgetbook/)์ UI ๋ถํ์ ๋ฑ๋กํด ์ค๋๋ค -
๋ถํ์ ์ฑ๊ฒฉ๋ณ๋ก ์ธ ์นธ์ผ๋ก ๋๋ ์ ๋ฆฌํฉ๋๋ค โ ์ผ๋ฐ ๋ถํ(
[Component]), ํ๋ฉด ๋จ์([Feature]), ์์ยท๊ธ๊ผด ๊ฐ์ ๊ธฐ์ด ์์([Foundation]) - ๊ฐ์ ๋ถํ์ ์ฌ๋ฌ ๊ธฐ๊ธฐ ํฌ๊ธฐ(์์ดํฐยท์์ดํจ๋ยท๋ฐ์คํฌํฑ)์ ํ ๋ง(๋ผ์ดํธ/๋คํฌ), ์ธ์ด(ํ๊ตญ์ดยท์์ดยท์ผ๋ณธ์ด ๋ฑ)๋ก ๋ฐ๊ฟ ๊ฐ๋ฉฐ ๋ณผ ์ ์๊ฒ ์ค์ ํด ์ค๋๋ค
- ์ค์ ๋ฐ์ดํฐ ์์ด๋ ํ๋ฉด์ด ์ฑ์์ ธ ๋ณด์ด๋๋ก ๊ฐ์ง ๋ฐ์ดํฐ(Mock)๋ฅผ ๋ผ์ ์ค๋๋ค
์ด๋ป๊ฒ ์ฐ๋์#
# ์ ์์ฅ ์ฑ์ ๋ธ๋ผ์ฐ์ (Chrome)๋ก ์คํํด์ ์ง์ ๋๋ฌ๋ณด๊ธฐ
flutter run -t lib/main.dart -d chrome
# ๋ถํ์ ์๋ก ๋ฑ๋กยท์์ ํ ๋ค ์ ์์ฅ์ ๋ฐ์(์ฝ๋ ์๋ ์์ฑ)
cd app/kobic_widgetbook & & dart run build_runner build --delete-conflicting-outputs
# ์น์ฉ์ผ๋ก ๋น๋(๊ณต์ ยท๋ฐฐํฌ์ฉ)
flutter build web -t lib/main.dart
๋ช
๋ น ์์ฒด๋ /shared:widgetbook ๋ก ์คํํ๋ฉฐ, ๋ถํ ์ด๋ฆ(component_name)๋ง ์์ผ๋ฉด ๋ฉ๋๋ค. ๋ถ๋ฅ(category)์ ๊ฒฝ๋ก(path)๋ ์ ํ ์ฌํญ์ด๋ผ ๋น์ ๋๋ฉด ์ผ๋ฐ ๋ถํ์ผ๋ก ๋ฑ๋ก๋ฉ๋๋ค.
์์์ ๋ฌด์จ ์ผ์ด ๋ฒ์ด์ง๋์#
- ์ ์์ฅ ์ฑ์ ๊ธฐ๋ณธ ๊ณจ๊ฒฉ(์ง์ ์ , ๊ธฐ๊ธฐ ํฌ๊ธฐยทํ ๋งยท์ธ์ด ์ ํ ๋ฉ๋ด)์ ์ค๋นํฉ๋๋ค.
- ๋ณด์ฌ ์ค ๋ถํ์ "์ ์ ํ์(@UseCase)"๋ฅผ ๋ถ์ฌ, ์ด๋ ์นธ(์ผ๋ฐ/ํ๋ฉด/๊ธฐ์ด)์ ์ง์ดํ ์ง ์ ํฉ๋๋ค.
- ํ ๋ถํ์ ์ฌ๋ฌ ์ํ(๊ธฐ๋ณธยท๋นํ์ฑยท๋ก๋ฉ ๋ฑ)๋ฅผ ๋๋ํ ๋์ด๋์ ๋น๊ตํ๊ธฐ ์ฝ๊ฒ ๋ฌถ์ต๋๋ค.
- ํ๋ฉด ๋จ์ ๋ถํ์ ์ค์ ๋ฐ์ดํฐ ๋์ ๊ฐ์ง ๋ฐ์ดํฐ๋ฅผ ๋ฃ์ด ๊นจ์ง์ง ์๊ฒ ์ฑ์๋๋ค.
- ์ฝ๋ ์๋ ์์ฑ์ ๋๋ ค ์ ์์ฅ์ ์ ๋ถํ์ ๋ฐ์ํ๊ณ , ์ฌ๋ฌ ๊ธฐ๊ธฐ ํฌ๊ธฐ์์ ์ ๋ณด์ด๋์ง ์ ๊ฒํฉ๋๋ค.
โ๏ธ ์์ธ ์ต์ ยท์คํ ๋ช ์ธ (๊ฐ๋ฐ์ / AI ์์ด์ ํธ์ฉ)
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.yamlImport 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: CoButton(
variant: CoreButtonVariant.primary,
onPressed: () {},
child: const Text('Primary'),
),
),
// Secondary Button
WidgetbookButton(
label: 'Secondary Button',
button: CoButton(
variant: CoreButtonVariant.secondary,
onPressed: () {},
child: const Text('Secondary'),
),
),
// Outlined Button
WidgetbookButton(
label: 'Outlined Button',
button: CoButton(
variant: CoreButtonVariant.outline,
onPressed: () {},
child: const Text('Outlined'),
),
),
// Disabled Button
WidgetbookButton(
label: 'Disabled Button',
button: CoButton(
variant: CoreButtonVariant.primary,
onPressed: null,
child: const Text('Disabled'),
),
),
// Loading Button
WidgetbookButton(
label: 'Loading Button',
button: CoButton(
variant: CoreButtonVariant.primary,
onPressed: () {},
child: const Text('Loading'),
),
),
],
);
}
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.dartReference 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.dartChecklist#
- @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