LogoSkills

resources-agent

UI 컴포넌트, 테마, 확장 메서드 생성 전문가입니다. CoUI 커스터마이징과 공통 위젯 구현에 사용합니다.

/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

안에서 무슨 일이 벌어지나요#

요청한 부품 종류에 맞춰 정해진 패턴대로 코드를 만들고, 마지막에 빠진 게 없는지 점검합니다.

  1. 테마 만들기 — 밝은/어두운 모드 색상표를 상수로 정의하고, Pretendard 글꼴을 입힌 뒤, 둘을 묶어 앱 전체 테마(AppTheme)로 완성합니다.
  2. 공통 위젯 만들기 — 표·팝업창 같은 재사용 부품을 정해진 구조로 만듭니다. (예: 정렬·컬럼 크기조절·순서변경이 되는 범용 표)
  3. 편의 단축 코드 만들기 — 화면 코드에서 context.colorScheme처럼 짧게 테마·색·글꼴에 접근하도록 확장 메서드를 추가합니다.
  4. CoUI 표준 확인 — 버튼·뱃지·아이콘 같은 부품은 CoUI의 최신 표준 방식(Button(variant: ...) 등, v0.127부터 Co 접두어 없음)에 맞는지 확인하고 적용합니다.
  5. 마무리 점검 — 밝은/어두운 테마가 모두 정의됐는지, 주석이 달렸는지 등 체크리스트로 빠진 부분을 확인합니다.

⚙️ 상세 옵션·실행 명세 (개발자 / 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:resources Activated when command is invoked
  • Invoked during UI component, theme, and extension method work

Parameters#

ParameterRequiredDescription
component_typetheme, widget, extension
component_nameComponent name (PascalCase)
categoryWidget 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:

  1. Export the icon from Figma as SVG
  2. Add it to package/resources/assets/svg/ (check assets.gen.dart first — don't duplicate an existing entry)
  3. Regenerate: melos run generate:assets (re-runs flutter_gen_runner)
  4. 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.

ComponentWidgetVariant enumRemoved
ButtonCoButton(variant: ...)CoreButtonVariantButton.primary(), ButtonStyle.ghost()
BadgeCoBadge(variant: ...)CoreBadgeVariantPrimaryBadge, DestructiveBadge
IconCoIcon(CoLucideIcons.x)HeroIcon(HeroIcons.x)
InputCoInput(type: ...) / CoTextField (labelled)CoreInputVariantFormTextField, InputFeature.*
GapCoGap.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, …) and Core<X>Style fields are authoritative in package/coui/packages/coui_core/lib/src/component/core_component_theme.dart and 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.dart

Checklist#

  • 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:assets after adding/removing an SVG under assets/svg/