크로스 플랫폼 | CoUI

크로스 플랫폼

Flutter와 Web 동시 개발 가이드

크로스 플랫폼#

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+ 컴포넌트가 양쪽 플랫폼에서 동일하게 제공됩니다:

카테고리FlutterWeb
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)11
Locale (Localizations)11

디자인 토큰#

CoUI는 coui_core에 정의된 디자인 토큰을 Web/Flutter 양쪽에서 공유합니다.

Border Radius 토큰#

시맨틱 토큰CoreRadiusScaleWeb (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 토큰#

시맨틱 토큰CoreFieldSizeWebFlutter
Input 높이 md = 40px h-10 kTextFieldHeight

애니메이션 토큰#

인터랙션 애니메이션은 CoreDuration을 단일 소스로 사용합니다.

토큰CoreDuration용도
xsCoreDuration.xs100msToggle slide, 빠른 전환
short CoreDuration.short 150ms Button hover, Input focus
normal CoreDuration.normal 200ms Card hover, Accordion, Dialog
mediumCoreDuration.medium300ms페이지 전환
// 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'
)

컴포넌트별 애니메이션 매핑#

컴포넌트인터랙션DurationWebFlutter
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를 사용합니다:

프리셋dampingstiffness용도
gentle0.8200부드러운 전환
standard0.7300기본
bouncy0.5400탄성 효과
snappy0.9500빠른 반응
stiff1.0600즉각 반응

통일된 컴포넌트 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'),
  ],
)

개발 팁#

  1. API 먼저 설계: coui_core에 계약을 정의하고 양쪽 구현
  2. Named properties 우선: child 대신 title, content, footer 등 명시적 파라미터
  3. String 입력 지원: 가능하면 String을 받아 내부에서 Text/Component.text로 변환
  4. CoreDuration 사용: 하드코딩 duration 대신 AnimationConstants/CoreDuration 참조
  5. 테스트 분리: Flutter widget test + Web unit test 별도 작성
  6. DCM 통일: 양쪽 패키지에 동일한 DCM 규칙 적용
  7. Barrel export: 양쪽 coui_flutter.dart / coui_web.dart 동기화 유지