/shared:resources — 화면 공통부품·디자인 담당#
| 항목 | 내용 |
|---|---|
| 실행 명령 | /shared:resources |
| 별칭 | /ui:theme, /widget:create |
| 모델 | sonnet |
| 사용 도구 | Read, Edit, Write, Glob, Grep |
| 연계 스킬 | flutter-ui |
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의 최신 표준 방식(
Button(variant: ...)등, v0.127부터Co접두어 없음)에 맞는지 확인하고 적용합니다. - 마무리 점검 — 밝은/어두운 테마가 모두 정의됐는지, 주석이 달렸는지 등 체크리스트로 빠진 부분을 확인합니다.
⚙️ 상세 옵션·실행 명세 (개발자 / 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 Icon(LucideIcons.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/