| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| Invoke | /shared:resources |
| Aliases | /ui:theme, /widget:create |
| Tools | Read, Edit, Write, Glob, Grep |
| Model | sonnet |
| Skills | flutter-ui |
/shared:resources โ ํ๋ฉด ๊ณตํต๋ถํยท๋์์ธ ๋ด๋น#
UI ๋ถํ, ํ ๋ง(์ยท๊ธ๊ผด), ๊ณตํต ์์ ฏ์ ๋ง๋ค๊ณ ๊ด๋ฆฌํ๋ ์ ๋ฌธ ์์ด์ ํธ
ํ๋ง๋๋ก#
์ฑ ์ ์ฒด์์ ๋๊ฐ์ด ์ฐ์ด๋ ๊ณตํต ๋์์ธ ๋ถํ์ ๋ง๋ค์ด ์ฃผ๋ ๋ด๋น์์ ๋๋ค. ์ธํ ๋ฆฌ์ด์ ๋น์ ํ๋ฉด, ์ง์ง๋ง๋ค ๋ฐ๋ก ์ฌ์ง ์๊ณ ํ ๋ฒ ์ ํด ๋๋ฉด ๋ชจ๋ ๋ฐฉ์ ๋๊ฐ์ด ์ ์ฉ๋๋ "ํ์ค ๊ฐ๊ตฌยท์์ยท์กฐ๋ช ์ธํธ"๋ฅผ ๋ง๋๋ ์ผ์ด์์. ๋ฒํผ ๋ชจ์, ๋ฐ์/์ด๋์ด ํ ๋ง, ํ(Table)๋ ํ์ ์ฐฝ ๊ฐ์ ๋ถํ์ ํ๊ณณ์์ ์ฑ๊น๋๋ค.
๋๊ฐยท์ธ์ ์ฐ๋์#
- ์ฑ์ ์์ยท๊ธ๊ผดยท์ ์ฒด ํ ๋ง(๋ฐ์ ๋ชจ๋ / ์ด๋์ด ๋ชจ๋)๋ฅผ ์ ํ๊ฑฐ๋ ๋ฐ๊ฟ ๋
- ์ฌ๋ฌ ํ๋ฉด์์ ๋ฐ๋ณตํด์ ์ฐ๋ ๊ณตํต ์์ ฏ(๋ฐฐ๋, ์นด๋, ํ์ ์ฐฝ, ํ ๋ฑ)์ด ํ์ํ ๋
- CoUI ๋์์ธ ์์คํ ์ ๋ฒํผยท๋ฑ์ง ๊ฐ์ ๋ถํ์ ์ฐ๋ฆฌ ์ฑ์ ๋ง๊ฒ ์ปค์คํฐ๋ง์ด์ฆํ ๋
-
/shared:resources๋ช ๋ น์ ๋ถ๋ฅด๋ฉด ์ด ๋ด๋น์๊ฐ ์๋ํฉ๋๋ค. (/ui:theme,/widget:create๋ก๋ ํธ์ถ ๊ฐ๋ฅ)
๋ฌด์์ ํด์ฃผ๋์#
package/resources ํจํค์ง ์์ ๊ณตํต ์์์ ๋ง๋ค์ด ์ ๋ฆฌํฉ๋๋ค. ์ค์ ๋ก ๋ง๋ค์ด์ง๋ ๊ฒ๋ค:
- AppTheme โ ์ฑ์ ๋ฐ์ ๋ชจ๋ / ์ด๋์ด ๋ชจ๋ ํ
๋ง (
app_theme.dart) -
์์ยท๊ธ๊ผด ์ ์ โ ์์ํ(
app_color_scheme.dart)์ Pretendard ๊ธ๊ผด ๊ธฐ๋ฐ ํ์ดํฌ๊ทธ๋ํผ(app_typography.dart) -
๊ณตํต ์์ ฏ โ ํ(
table_builder.dart), ํ์ธ ํ์ (confirm_dialog.dart), ๋ฐฐ๋ยท์นด๋ ๋ฑ - ํธ์ ๋จ์ถ ์ฝ๋ โ ํ๋ฉด ์ด๋์๋ ํ ๋งยท์ยท๊ธ๊ผด์ ์ฝ๊ฒ ์ ๊ทผํ๊ฒ ํด ์ฃผ๋ BuildContext ํ์ฅ ๋ฉ์๋
-
์ปค์คํ
SVG ์์ด์ฝ โ Figma์์ exportํ, CoUI ํ์ค ์์ด์ฝ(Lucide)์ ์๋ ๋ธ๋๋/์ปค์คํ
์์ด์ฝ์
assets/svg/์ ๋ณด๊ดํ๊ณflutter_gen์ผ๋ก ํ์ ์ธ์ดํํ๊ฒ ์ ๊ทผ (Assets.svg.xxx)
์ด๋ป๊ฒ ์ฐ๋์#
# ํ
๋ง(์ยท๊ธ๊ผด) ๋ง๋ค๊ธฐ
/shared:resources component_type=theme component_name=AppTheme
# ๊ณตํต ์์ ฏ ๋ง๋ค๊ธฐ (์: ํ ๋ถํ)
/shared:resources component_type=widget component_name=TableBuilder category=table
# ํธ์ ๋จ์ถ ์ฝ๋(ํ์ฅ ๋ฉ์๋) ๋ง๋ค๊ธฐ
/shared:resources component_type=extension component_name=ThemeContext
-
component_type(ํ์):theme(ํ ๋ง) /widget(๊ณตํต ์์ ฏ) /extension(ํ์ฅ ๋ฉ์๋) ์ค ํ๋ component_name(ํ์): ๋ง๋ค ๋ถํ ์ด๋ฆ (PascalCase, ์:TableBuilder)-
category(์ ํ): ์์ ฏ ์ข ๋ฅ โbanner,card,dialog,table๋ฑ
์์์ ๋ฌด์จ ์ผ์ด ๋ฒ์ด์ง๋์#
์์ฒญํ ๋ถํ ์ข ๋ฅ์ ๋ง์ถฐ ์ ํด์ง ํจํด๋๋ก ์ฝ๋๋ฅผ ๋ง๋ค๊ณ , ๋ง์ง๋ง์ ๋น ์ง ๊ฒ ์๋์ง ์ ๊ฒํฉ๋๋ค.
- ํ ๋ง ๋ง๋ค๊ธฐ โ ๋ฐ์/์ด๋์ด ๋ชจ๋ ์์ํ๋ฅผ ์์๋ก ์ ์ํ๊ณ , Pretendard ๊ธ๊ผด์ ์ ํ ๋ค, ๋์ ๋ฌถ์ด ์ฑ ์ ์ฒด ํ ๋ง(AppTheme)๋ก ์์ฑํฉ๋๋ค.
- ๊ณตํต ์์ ฏ ๋ง๋ค๊ธฐ โ ํยทํ์ ์ฐฝ ๊ฐ์ ์ฌ์ฌ์ฉ ๋ถํ์ ์ ํด์ง ๊ตฌ์กฐ๋ก ๋ง๋ญ๋๋ค. (์: ์ ๋ ฌยท์ปฌ๋ผ ํฌ๊ธฐ์กฐ์ ยท์์๋ณ๊ฒฝ์ด ๋๋ ๋ฒ์ฉ ํ)
-
ํธ์ ๋จ์ถ ์ฝ๋ ๋ง๋ค๊ธฐ โ ํ๋ฉด ์ฝ๋์์
context.colorScheme์ฒ๋ผ ์งง๊ฒ ํ ๋งยท์ยท๊ธ๊ผด์ ์ ๊ทผํ๋๋ก ํ์ฅ ๋ฉ์๋๋ฅผ ์ถ๊ฐํฉ๋๋ค. -
CoUI ํ์ค ํ์ธ โ ๋ฒํผยท๋ฑ์งยท์์ด์ฝ ๊ฐ์ ๋ถํ์ CoUI์ ์ต์ ํ์ค ๋ฐฉ์(
CoButton(variant: ...)๋ฑ)์ ๋ง๋์ง ํ์ธํ๊ณ ์ ์ฉํฉ๋๋ค. - ๋ง๋ฌด๋ฆฌ ์ ๊ฒ โ ๋ฐ์/์ด๋์ด ํ ๋ง๊ฐ ๋ชจ๋ ์ ์๋๋์ง, ์ฃผ์์ด ๋ฌ๋ ธ๋์ง ๋ฑ ์ฒดํฌ๋ฆฌ์คํธ๋ก ๋น ์ง ๋ถ๋ถ์ ํ์ธํฉ๋๋ค.
โ๏ธ ์์ธ ์ต์ ยท์คํ ๋ช ์ธ (๊ฐ๋ฐ์ / AI ์์ด์ ํธ์ฉ)
Role#
Creates and manages UI elements in the Resources package.
- AppTheme (Light/Dark mode)
- CoUI Flutter component customization
- Common widgets (Banner, Card, Dialog, Table)
- BuildContext extension methods
Activation Conditions#
/shared:resourcesActivated when command is invoked- Invoked during UI component, theme, and extension method work
Parameters#
| Parameter | Required | Description |
|---|---|---|
component_type | โ | theme, widget, extension |
component_name | โ | Component name (PascalCase) |
category | โ | Widget category (banner, card, dialog, table etc.) |
Package Structure#
package/resources/
โโโ assets/
โ โโโ svg/ # ์ปค์คํ
/๋ธ๋๋ SVG ์์ด์ฝ ์๋ณธ (Lucide์ ์๋ ๊ฒ๋ง)
โ โโโ action_bell.svg
โ โโโ bottom_navi_home_off.svg
โโโ pubspec.yaml # flutter_gen_runner + flutter_gen ์ค์
โโโ lib/
โโโ resources.dart # Export ํ์ผ
โโโ src/
โโโ core/
โ โโโ extensions/ # BuildContext ํ์ฅ
โ โ โโโ theme_context_extension.dart
โ โโโ locale/ # ๋ก์ผ์ผ ์ ํธ๋ฆฌํฐ
โ โโโ size/ # ์ฌ์ด์ฆ ์์
โ โโโ theme/ # ํ
๋ง ์ ์
โ โ โโโ app_theme.dart
โ โ โโโ app_color_scheme.dart
โ โ โโโ app_typography.dart
โ โ โโโ app_component_theme.dart
โ โโโ util/ # ์ ํธ๋ฆฌํฐ
โ โโโ generated/
โ โโโ assets.gen.dart # flutter_gen ์๋ ์์ฑ โ Assets.svg.xxx
โโโ widgets/ # ๊ณตํต ์์ ฏ
โโโ banner/
โโโ card/
โโโ console/
โโโ dialog/
โโโ navigation/
โโโ table/Custom SVG Icon Workflow#
CoUI's CoIcon(CoLucideIcons.x) covers the standard icon set. When a Figma design uses a
brand/custom icon with no Lucide equivalent:
- Export the icon from Figma as SVG
- Add it to
package/resources/assets/svg/(checkassets.gen.dartfirst โ don't duplicate an existing entry) - Regenerate:
melos run generate:assets(re-runsflutter_gen_runner) - Reference via the generated accessor:
Assets.svg.iconBestseller.svg(width: 24, height: 24)
Never hardcode an SvgPicture.asset('assets/svg/...') path directly โ always go through the
generated Assets.svg.* accessor so renames/moves are caught at compile time.
Import Order (Required)#
// 1. Flutter standard
import 'package:flutter/material.dart';
// 2. CoUI Flutter
import 'package:coui_flutter/coui_flutter.dart';
// 3. ๋ด๋ถ ๋ชจ๋
import '../theme/app_color_scheme.dart';
import '../theme/app_typography.dart';
Core Patterns#
1. AppTheme Definition#
import 'package:coui_flutter/coui_flutter.dart';
import 'package:flutter/material.dart';
import 'app_color_scheme.dart';
import 'app_component_theme.dart';
import 'app_typography.dart';
/// ์ฑ ํ
๋ง ์ ์
///
/// CoUI์ [ThemeData]๋ฅผ ํ์ฅํ์ฌ Light/Dark ๋ชจ๋๋ฅผ ์ ๊ณตํฉ๋๋ค.
abstract final class AppTheme {
/// default ๋ค๋น๊ฒ์ด์
๋ฐ ๋์ด
static const double defaultNavBarHeight = 60;
// ============ Light Theme ============
static const ColorScheme _lightColorScheme = AppColorScheme.light;
static const Typography _lightTypography = AppTypography.pretendard;
/// Light ํ
๋ง
static ThemeData get light => ThemeData.new(
colorScheme: _lightColorScheme,
componentTheme: AppComponentTheme.from(
colorScheme: _lightColorScheme,
typography: _lightTypography,
),
typography: _lightTypography,
);
// ============ Dark Theme ============
static const ColorScheme _darkColorScheme = AppColorScheme.dark;
static const Typography _darkTypography = AppTypography.pretendard;
/// Dark ํ
๋ง
static ThemeData get dark => ThemeData.new(
colorScheme: _darkColorScheme,
componentTheme: AppComponentTheme.from(
colorScheme: _darkColorScheme,
typography: _darkTypography,
),
typography: _darkTypography,
);
}
2. ColorScheme Definition#
import 'package:coui_flutter/coui_flutter.dart';
/// ์ฑ ์์ ์คํด ์ ์
abstract final class AppColorScheme {
/// Light ๋ชจ๋ ์์ ์คํด
static const ColorScheme light = ColorScheme(
brightness: Brightness.light,
primary: Color(0xFF1976D2),
onPrimary: Color(0xFFFFFFFF),
secondary: Color(0xFF03DAC6),
onSecondary: Color(0xFF000000),
error: Color(0xFFB00020),
onError: Color(0xFFFFFFFF),
surface: Color(0xFFFFFFFF),
onSurface: Color(0xFF000000),
// ... ์ถ๊ฐ ์์
);
/// Dark ๋ชจ๋ ์์ ์คํด
static const ColorScheme dark = ColorScheme(
brightness: Brightness.dark,
primary: Color(0xFF90CAF9),
onPrimary: Color(0xFF000000),
// ... ์ถ๊ฐ ์์
);
}
3. Typography Definition#
import 'package:coui_flutter/coui_flutter.dart';
import 'package:flutter/material.dart' as material;
/// ์ฑ ํ์ดํฌ๊ทธ๋ํผ ์ ์
abstract final class AppTypography {
/// Pretendard ํฐํธ ๊ธฐ๋ฐ ํ์ดํฌ๊ทธ๋ํผ
static const Typography pretendard = Typography(
fontFamily: 'Pretendard',
displayLarge: material.TextStyle(
fontSize: 57,
fontWeight: material.FontWeight.w400,
letterSpacing: -0.25,
),
displayMedium: material.TextStyle(
fontSize: 45,
fontWeight: material.FontWeight.w400,
),
// ... ์ถ๊ฐ ์คํ์ผ
);
}
4. BuildContext Extension Methods#
import 'package:coui_flutter/coui_flutter.dart';
import 'package:flutter/material.dart';
/// Resources ํ
๋ง ํ์ฅ
extension ResourcesThemeContextExtension on BuildContext {
/// ComponentThemeData ์ ๊ทผ
ComponentThemeData? get componentTheme => Theme.of(this).componentTheme;
/// ColorScheme ์ ๊ทผ
ColorScheme get colorScheme => Theme.of(this).colorScheme;
/// Typography ์ ๊ทผ
Typography get typography => Theme.of(this).typography;
/// TextTheme ์ ๊ทผ
TextTheme get textTheme => Theme.of(this).textTheme;
}
5. Common Widget Pattern (TableBuilder)#
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
/// ๋ฒ์ฉ ํ
์ด๋ธ ๋น๋
///
/// ์ ๋ ฌ, ์ปฌ๋ผ ๋ฆฌ์ฌ์ด์ฆ, ๋ฆฌ์ค๋, ๊ฐ์์ฑ ์ ์ด ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค.
class TableBuilder<T, TSortField> extends HookWidget {
/// TableBuilder ์์ฑ์
const TableBuilder({
required this.items,
required this.columns,
this.isLoading = false,
this.sortState,
this.onSortChanged,
this.enableColumnResize = false,
this.enableColumnReorder = false,
this.visibilityController,
this.onRowTap,
this.rowHeight,
this.headerHeight,
super.key,
});
/// ํ
์ด๋ธ ๋ฐ์ดํฐ ํญ๋ชฉ
final List<T> items;
/// ์ปฌ๋ผ ์ ์
final List<TableColumnDef<T, TSortField>> columns;
/// ๋ก๋ฉ ์ํ
final bool isLoading;
/// ์ ๋ ฌ ์ํ
final SortState<TSortField>? sortState;
/// ์ ๋ ฌ ๋ณ๊ฒฝ ์ฝ๋ฐฑ
final ValueChanged<SortState<TSortField>>? onSortChanged;
/// ์ปฌ๋ผ ๋ฆฌ์ฌ์ด์ฆ ํ์ฑํ
final bool enableColumnResize;
/// ์ปฌ๋ผ ๋ฆฌ์ค๋ ํ์ฑํ
final bool enableColumnReorder;
/// ๊ฐ์์ฑ ์ปจํธ๋กค๋ฌ
final ColumnVisibilityController? visibilityController;
/// ํ ํญ ์ฝ๋ฐฑ
final ValueChanged<T>? onRowTap;
/// ํ ๋์ด
final double? rowHeight;
/// ํค๋ ๋์ด
final double? headerHeight;
@override
Widget build(BuildContext context) {
// ํ
์ด๋ธ ๊ตฌํ...
}
}
/// ํ
์ด๋ธ ์ปฌ๋ผ ์ ์
class TableColumnDef<T, TSortField> {
const TableColumnDef({
required this.id,
required this.title,
required this.cellBuilder,
this.width,
this.minWidth,
this.maxWidth,
this.sortField,
this.isVisible = true,
});
final String id;
final String title;
final Widget Function(T item) cellBuilder;
final double? width;
final double? minWidth;
final double? maxWidth;
final TSortField? sortField;
final bool isVisible;
}
6. Dialog Pattern#
import 'package:flutter/material.dart';
/// Show confirmation dialog
Future<bool?> showConfirmDialog(
BuildContext context, {
required String title,
required String message,
String? confirmText,
String? cancelText,
}) {
return showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text(title),
content: Text(message),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: Text(cancelText ?? '์ทจ์'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: Text(confirmText ?? 'ํ์ธ'),
),
],
),
);
}
CoUI Co Component Standard #
๊ธฐ์ค: coui v0.97.15 (latest). kobic vendored๋ v0.92.0์ด๋ผ gap/input ๋ฑ ์ผ๋ถ API๊ฐ ๋ค๋ฅผ ์ ์์.
CoUI exposes a single unified Co<X> widget per component. Style is selected via
the variant: / size: enums; per-instance chrome overrides flow through one
Core<X>Style object (buttonStyle / badgeStyle / inputStyle / iconStyle).
There are no Button.primary() / PrimaryBadge / Gap.s4() / FormTextField
/ HeroIcon APIs.
| Component | Widget | Variant enum | Removed |
|---|---|---|---|
| Button | CoButton(variant: ...) | CoreButtonVariant | Button.primary(), ButtonStyle.ghost() |
| Badge | CoBadge(variant: ...) | CoreBadgeVariant | PrimaryBadge, DestructiveBadge |
| Icon | CoIcon(CoLucideIcons.x) | โ | HeroIcon(HeroIcons.x) |
| Input | CoInput(type: ...) / CoTextField (labelled) | CoreInputVariant | FormTextField, InputFeature.* |
| Gap | CoGap.spaceN() / CoGap(gapStyle: CoreGapStyle(...)) | โ | Gap.s*, Spacing.s*, Insets.*, CoGap(size: CoreSpace.spaceN) (v0.92) |
Shared size enum CoreComponentSize: xs ยท sm ยท md ยท lg ยท xl.
Spacing tokens CoreSpace.spaceN โ the token name equals its pixel value.
Typography via context.textStyles.<size><weight> (e.g. lgSemibold).
ComponentTheme Extension (CoreTheme / CoreStyle) #
Project-wide component defaults are configured through the CoUI
CoreComponentTheme (passed to Theme/ThemeData as coreComponentTheme).
Each per-component slot (button, icon, gap, badge, โฆ) carries a
Core<X>Theme whose style is a Core<X>Style.
import 'package:coui_flutter/coui_flutter.dart';
/// ์ฑ ์ปดํฌ๋ํธ ํ
๋ง (CoUI CoreComponentTheme ๊ธฐ๋ฐ)
abstract final class AppComponentTheme {
/// ColorScheme๊ณผ Typography๋ก CoreComponentTheme ์์ฑ
static CoreComponentTheme from({
required ColorScheme colorScheme,
required Typography typography,
}) {
return CoreComponentTheme(
// ๋ฒํผ ๊ธฐ๋ณธ chrome (variant ๋ณ override ๋ variantStyles)
button: CoreButtonTheme(
style: CoreButtonStyle(
textStyle: typography.smSemibold,
),
),
// ์์ด์ฝ ๊ธฐ๋ณธ ํฌ๊ธฐ/์
icon: CoreIconTheme(
style: CoreIconStyle(
size: 24,
color: colorScheme.onSurface.toValue(),
),
),
// ๊ฐ๊ฒฉ ํ ํฐ ๊ธฐ๋ณธ๊ฐ (CoreGapTheme ์ style: CoreGapStyle(size:) ์ฌ์ฉ)
gap: const CoreGapTheme(style: CoreGapStyle(size: CoreSpace.space4)),
// ... ์ถ๊ฐ ์ฌ๋กฏ: badge / card / dialog / input ...
);
}
}
Slot field names (
button,icon,gap,badge, โฆ) andCore<X>Stylefields are authoritative inpackage/coui/packages/coui_core/lib/src/component/core_component_theme.dartand the per-component contracts (.../contracts/<component>/<component>_theme.dart). Verify against those before applying.
Reference Files#
package/resources/lib/src/core/theme/app_theme.dart
package/resources/lib/src/core/extensions/theme_context_extension.dart
package/resources/lib/src/widgets/table/table_builder.dart
package/resources/lib/src/widgets/dialog/confirm_dialog.dartChecklist#
- CoUI Package import Verify
- Light/Dark Both themes Definition
- Define ColorScheme as constants
- Apply Pretendard font for Typography
- Implement BuildContext extension methods
- Write KDoc comments on widgets
- Place super.key as last parameter
- Specify Type parameters when using Generic Types
- Custom icon: verify no existing
Assets.svg.*entry before adding a new SVG file - Custom icon: ran
melos run generate:assetsafter adding/removing an SVG underassets/svg/