크로스 플랫폼#
CoUI는 Flutter와 Jaspr Web에서 동일한 컴포넌트 세트를 제공합니다.
아키텍처#
coui_core (공유 계약)
├── coui_flutter (Flutter 구현)
└── coui_web (Jaspr Web 구현)
공유 계약 (coui_core)#
플랫폼 간 공통 인터페이스를 정의합니다:
// 예: CoreButtonContract
abstract class CoreButtonContract {
bool get disabled;
void Function()? get onPressed;
}
API 비교#
생성자 패턴#
// Named parameters, Widget tree
Button(
variant: ButtonVariant.primary,
size: ButtonSize.md,
onPressed: () {},
child: Text('Click'),
)
// Style modifiers, Component tree
Button(
style: [Button.primary, Button.md],
onClick: () {},
child: text('Click'),
)
테마 적용#
// ComponentThemeData + ThemeExtension
ComponentTheme(
data: ComponentThemeData(
button: ButtonTheme(borderRadius: ...),
),
child: widget,
)
// CSS + CoUI themes
// data-theme attribute로 전역 테마 전환
컴포넌트 매핑#
전체 140+ 컴포넌트가 양쪽 플랫폼에서 동일하게 제공됩니다:
| 카테고리 | Flutter | Web |
|---|---|---|
| Form (Input, Select, etc.) | 20+ | 20+ |
| Display (Avatar, Badge, etc.) | 25+ | 25+ |
| Navigation (Menu, Tabs, etc.) | 10+ | 10+ |
| Overlay (Dialog, Toast, etc.) | 10+ | 10+ |
| Layout (Accordion, Grid, etc.) | 15+ | 15+ |
| Control (Button, FAB, etc.) | 10+ | 10+ |
| Chart (Tracker) | 1 | 1 |
| Locale (Localizations) | 1 | 1 |
디자인 토큰#
CoUI는 coui_core에 정의된 디자인 토큰을 Web/Flutter 양쪽에서 공유합니다.
Border Radius 토큰#
| 시맨틱 토큰 | CoreRadiusScale | Web (Tailwind) | Flutter |
|---|---|---|---|
| field (input) | extraSmall = 4px |
rounded-sm |
ThemeData.fromCore() |
| selector (button) | small = 8px |
rounded-md |
ThemeData.fromCore() |
| box (card) | large = 16px |
rounded-lg |
ThemeData.fromCore() |
| badge | full = 9999px |
rounded-full |
ThemeData.fromCore() |
Size 토큰#
| 시맨틱 토큰 | CoreFieldSize | Web | Flutter |
|---|---|---|---|
| Input 높이 | md = 40px |
h-10 |
kTextFieldHeight |
애니메이션 토큰#
인터랙션 애니메이션은 CoreDuration을 단일 소스로 사용합니다.
| 토큰 | CoreDuration | 값 | 용도 |
|---|---|---|---|
| xs | CoreDuration.xs | 100ms | Toggle slide, 빠른 전환 |
| short | CoreDuration.short |
150ms | Button hover, Input focus |
| normal | CoreDuration.normal |
200ms | Card hover, Accordion, Dialog |
| medium | CoreDuration.medium | 300ms | 페이지 전환 |
// AnimationConstants가 CoreDuration을 래핑
const kDefaultDuration = AnimationConstants.short; // 150ms
const kToggleDuration = AnimationConstants.xs; // 100ms
// 컴포넌트에서 사용
AnimatedContainer(
duration: AnimationConstants.normal, // 200ms
curve: Curves.easeInOut,
child: card,
)
// Tailwind duration 클래스로 매핑
Button(style: [Button.primary],
// base: 'transition-colors duration-150'
)
Card.hoverable(
// base: 'transition-shadow duration-200'
)
컴포넌트별 애니메이션 매핑#
| 컴포넌트 | 인터랙션 | Duration | Web | Flutter |
|---|---|---|---|---|
| Button | hover 색상 | short (150ms) | transition-colors duration-150 |
WidgetState 기반 |
| Card | hover shadow | normal (200ms) | transition-shadow duration-200 |
Card(hoverable: true) |
| Toggle | thumb slide | xs (100ms) | transition-transform duration-100 |
kToggleDuration |
| Accordion | expand/collapse | normal (200ms) | transition-all duration-200 |
SizeTransition |
| Input | focus ring | short (150ms) | transition-colors duration-150 |
AnimatedContainer |
Spring 프리셋#
Flutter에서 물리 기반 애니메이션이 필요할 때 CoreSpringConfig를 사용합니다:
| 프리셋 | damping | stiffness | 용도 |
|---|---|---|---|
| gentle | 0.8 | 200 | 부드러운 전환 |
| standard | 0.7 | 300 | 기본 |
| bouncy | 0.5 | 400 | 탄성 효과 |
| snappy | 0.9 | 500 | 빠른 반응 |
| stiff | 1.0 | 600 | 즉각 반응 |
통일된 컴포넌트 API#
Web과 Flutter는 동일한 named properties API를 사용합니다.
Layout#
Card(
title: Text('Card Title'),
description: Text('Description'),
footer: Button(
variant: CoreButtonVariant.primary,
onPressed: () {},
child: Text('Action'),
),
)
Card(
title: Component.text('Card Title'),
description: Component.text('Description'),
footer: Button(
variant: CoreButtonVariant.primary,
onPressed: () {},
child: Component.text('Action'),
),
)
Form#
Select<String>(
options: [
SelectOption('apple', 'Apple'),
SelectOption('banana', 'Banana'),
],
placeholder: Text('Select a fruit'),
onChanged: (v) => print(v),
)
Select(
options: [
SelectOption('apple', 'Apple'),
SelectOption('banana', 'Banana'),
],
placeholder: 'Select a fruit',
onChanged: (v) => print(v),
)
Display#
Timeline(
items: [
TimelineItem(title: 'Order placed', timestamp: '10:00 AM'),
TimelineItem(title: 'Shipped', timestamp: '2:00 PM'),
],
)
Timeline(
items: [
TimelineItem(title: 'Order placed', timestamp: '10:00 AM'),
TimelineItem(title: 'Shipped', timestamp: '2:00 PM'),
],
)
개발 팁#
- API 먼저 설계: coui_core에 계약을 정의하고 양쪽 구현
-
Named properties 우선:
child대신title,content,footer등 명시적 파라미터 - String 입력 지원: 가능하면 String을 받아 내부에서 Text/Component.text로 변환
- CoreDuration 사용: 하드코딩 duration 대신 AnimationConstants/CoreDuration 참조
- 테스트 분리: Flutter widget test + Web unit test 별도 작성
- DCM 통일: 양쪽 패키지에 동일한 DCM 규칙 적용
-
Barrel export: 양쪽
coui_flutter.dart/coui_web.dart동기화 유지