Drawer#
화면 측면에서 슬라이드하여 나타나는 모달 패널 컴포넌트입니다. 명령형 API (openCoDrawer) 로 호출하여 표시하고, 사용자가 닫으면 Future
가 결과와 함께 완료됩니다.
Live Preview#
class DrawerDefaultExample extends StatelessComponent {
const DrawerDefaultExample({super.key});
@override
Component build(BuildContext context) {
return CoButton(
variant: CoreButtonVariant.outline,
onPressed: () {
openCoDrawer<void>(
context: context,
side: CoreDrawerSide.left,
builder: (close) => CoDrawer(
title: 'Navigation',
content: div(
[
const Text('Dashboard'),
const Text('Settings'),
const Text('Profile'),
],
classes: 'flex flex-col gap-${CoreSpace.scale.space8}',
),
),
);
},
child: const Text('Open drawer'),
);
}
}
class DrawerDefaultExample extends StatelessWidget {
const DrawerDefaultExample({super.key});
@override
Widget build(BuildContext context) {
return CoButton(
variant: CoreButtonVariant.outline,
onPressed: () {
openCoDrawer<void>(
context: context,
side: CoreDrawerSide.left,
builder: (ctx) => CoDrawer(
title: 'Navigation',
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text('Dashboard'),
Text('Settings'),
Text('Profile'),
],
),
),
);
},
child: const Text('Open drawer'),
);
}
}
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 보조 콘텐츠(필터, 설정, 상세 정보)를 화면 옆에서 슬라이드로 보여줄 때
- 메인 콘텐츠를 가리지 않고 추가 작업 공간이 필요할 때
- 모바일 네비게이션 메뉴를 구현할 때
대신 다른 컴포넌트를 사용하세요:
Dialog: 사용자 확인이 필요한 짧은 메시지일 때Popover: 특정 요소에 붙는 작은 팝업일 때Tabs: 콘텐츠를 탭으로 전환하는 것이 더 적합할 때
기본 사용법 (Basic Usage)#
CoUI 는 Flutter 와 Web 에서 동일한 명령형 API 를 제공합니다. 아래 예제 코드는 양쪽 플랫폼에서 그대로 사용할 수 있습니다.
// 트리거 버튼 클릭 시 드로어 열기
CoButton(
variant: CoreButtonVariant.outline,
onPressed: () {
openCoDrawer<void>(
context: context,
side: CoreDrawerSide.left,
builder: (close) => CoDrawer(
title: 'Navigation',
content: Column( // Web 은 div(...)
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text('Dashboard'),
Text('Settings'),
Text('Profile'),
],
),
),
);
},
child: const Text('Open drawer'),
)
openCoDrawer<T> 는 Future<T?> 를 반환합니다 — 사용자가 패널 안에서 close(value)
를 호출하면 그 값으로, 배리어/드래그/Escape/× 버튼으로 닫으면 null 로 완료됩니다.
// 결과를 받아오는 패턴
final picked = await openCoDrawer<String>(
context: context,
side: CoreDrawerSide.right,
builder: (close) => CoDrawer(
title: 'Pick a colour',
content: ColorList(onPick: (c) => close(c.name)),
),
);
API#
openCoDrawer<T>(...)#
| 인자 | 타입 | 기본값 | 설명 |
|---|---|---|---|
context |
BuildContext |
필수 | 가장 가까운 CoUIWeb / CoUIApp 또는 CoOverlayHost ancestor 를 찾는 데 사용 |
builder |
WidgetBuilder (Flutter) / CoDrawer Function(close) (Web) |
필수 | 드로어 panel 을 빌드하는 함수. Web 빌더는 close([T?]) 콜백을 받아 panel 안에서 결과와 함께 닫을 수 있음 |
side |
CoreDrawerSide |
필수 | 슬라이드 인 방향 (left / right / top / bottom) |
CoDrawer#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
content |
Widget / Component |
필수 | 드로어 내부에 표시할 콘텐츠 |
side |
CoreDrawerSide |
left |
슬라이드 인 방향 (위젯 파라미터) |
title |
String? |
null |
드로어 상단 제목 텍스트 |
barrierDismissible |
bool |
true |
배리어(스크림) 클릭 시 드로어 닫기 |
draggable |
bool |
true |
가장자리를 50% 이상 드래그하면 닫힘 |
dismissOnEscape |
bool |
true |
Escape 키로 닫기 (Web 전용) |
drawerStyle |
CoreDrawerStyle? |
null |
panel chrome / barrier / animation / nested titleStyle 묶음 |
스타일 시스템 — drawerStyle (Epic #1302)#
CoDrawer 의 panel chrome / barrier / animation / 슬롯 미세 조정은 단일
drawerStyle (CoreDrawerStyle) 으로 흐릅니다. 시맨틱 enum (side) 과
동작 정책 (barrierDismissible, draggable, dismissOnEscape) 은 위젯
파라미터 그대로.
openCoDrawer(
context: context,
side: CoreDrawerSide.right,
builder: (ctx) => CoDrawer(
title: 'Settings',
content: ...,
drawerStyle: CoreDrawerStyle(
panelBackgroundColor: cs.surface,
panelBorderRadius: 16,
panelWidth: 360,
barrierColor: cs.scrim.withOpacity(.4),
openAnimationDuration: Duration(milliseconds: 350),
closeAnimationDuration: Duration(milliseconds: 200),
titleStyle: CoreTextStyle(
fontSize: 18,
fontWeight: CoreFontWeight.semiBold,
),
),
),
)
CoreDrawerStyle 필드
| 필드 | 타입 | 설명 |
|---|---|---|
panelBackgroundColor / panelBorderColor |
Color? / String? |
panel 배경 / 보더 색 |
panelBorderRadius | double? | panel 보더 반경 |
panelWidth | double? | left/right 드로어 너비 |
panelHeight | double? | top/bottom 드로어 높이 |
barrierColor |
Color? / String? |
scrim 색 |
openAnimationDuration |
Duration? |
열기 애니메이션 (기본 350ms) |
closeAnimationDuration | Duration? | 닫기 애니메이션 |
titleStyle | CoreTextStyle? | 제목 텍스트 |
Resolve chain
design system default
→ CoreDrawerTheme.style // 프로젝트 공통
→ 부모 컴포넌트 슬롯 오버라이드
→ widget.drawerStyle // 인스턴스별
closeCoDrawer<T>(context, [result]) (Flutter)#
명령형으로 가장 위 드로어를 닫습니다 (Navigator.pop 과 동일한 의미).
Web 은 builder 의 close([T?]) 콜백을 사용하세요.
변형 (Variants)#
위치 (Side)#
openCoDrawer(side: CoreDrawerSide.left, ...) // 좌측
openCoDrawer(side: CoreDrawerSide.right, ...) // 우측
openCoDrawer(side: CoreDrawerSide.top, ...) // 상단
openCoDrawer(side: CoreDrawerSide.bottom, ...) // 하단
좌/우 드로어는 기본 너비 256 px (CoreDrawerTokens.defaultWidth), 상/하 드로어는 기본 높이 192 px (CoreDrawerTokens.defaultHeight).
동작 스펙 (Behavior)#
열기/닫기#
CoDrawer 는 명령형(imperative) 컴포넌트 입니다 (shadcn_flutter / shadcn-ui 와 동일한 모델). 상태 boolean 으로 제어하지 않고,
openCoDrawer 를 호출해 표시하고 다음 중 하나로 닫힙니다:
- 배리어(스크림) 탭 —
barrierDismissible: true(default) - 드로어 가장자리 드래그 —
draggable: true(default), 50 % threshold - Escape 키 —
dismissOnEscape: true(default, Web) - × 버튼 클릭 —
title이 있을 때 헤더에 표시 - 빌더의
close([result])콜백 (Web) /closeCoDrawer(context, result)(Flutter)
애니메이션#
-
슬라이드: 350 ms
ease-out(열기) /ease-out-cubic(닫기) —CoreDrawerTokens.animationDurationMs - 배리어 페이드: 350 ms
ease-out(양쪽 동일) - 드래그 중: transition 일시 정지 (panel 이 손가락을 즉시 따라감)
레이아웃#
- 좌/우 드로어: 너비
CoreDrawerTokens.defaultWidth(256 px) × 화면 전체 높이 - 상/하 드로어: 화면 전체 너비 × 높이
CoreDrawerTokens.defaultHeight(192 px) CoreDrawerTheme.width/.height로 오버라이드 가능
사용 가이드라인 (Usage Guidelines)#
✅ Do#
드로어에 명확한 제목과 닫기 방법을 제공하세요.
openCoDrawer(
context: context,
side: CoreDrawerSide.right,
builder: (close) => CoDrawer(
title: '필터 설정',
content: FilterPanel(onApply: (f) => close(f)),
),
)
title 이 있으면 자동으로 헤더 + × 버튼이 그려집니다. 사용자가 어떤 패널인지 파악하고 쉽게 닫을 수 있습니다.
❌ Don't#
짧은 확인 메시지에 Drawer 를 사용하지 마세요.
openCoDrawer(
context: context,
side: CoreDrawerSide.bottom,
builder: (close) => CoDrawer(
content: Text('정말 삭제하시겠습니까?'),
),
)
간단한 확인은 CoDialog 가 더 적합합니다. CoDrawer 는 복잡한 콘텐츠/네비게이션용입니다.
✅ Do#
콘텐츠에 맞는 side 를 선택하세요.
- 네비게이션 메뉴 →
left - 보조 상세 / 필터 패널 →
right - 모바일 시트 / 액션 →
bottom - 알림 / 이벤트 시트 →
top
사용자 기대에 맞는 방향이어야 직관적입니다.
접근성 (Accessibility)#
키보드 인터랙션#
| 키 | 동작 |
|---|---|
Escape | 드로어 닫기 (dismissOnEscape: true 일 때) |
Tab | 드로어 내 요소 간 포커스 이동 |
Shift+Tab | 이전 요소로 포커스 이동 |
시맨틱#
-
Flutter:
Navigator.push로 별도 라우트 — 시스템 백 버튼 /PopScope가 자동 처리.Semantics(container: true, label: title) -
Web:
role="dialog"+aria-modal="true"+aria-label={title}
닫기 접근성#
- × 버튼 (
role="button"+aria-label="Close") 이 헤더에 표시되어 키보드/스크린 리더 접근 가능 - 배리어 탭 + 드래그 + Escape 키 — 다양한 dismiss 경로 제공
크로스 플랫폼 차이점 (Platform Differences)#
CoDrawer 는 Flutter / Web 에서 동일한 명령형 API (openCoDrawer<T>, CoDrawer
panel) 를 공유합니다. 아래는 플랫폼 내부 구현 차이만 나열합니다.
| 항목 | Flutter | Web |
|---|---|---|
| 오버레이 마운트 | Navigator.push(_CoDrawerRoute<T>) |
CoOverlayHost.dialog 레이어 portal |
| 애니메이션 | SlideTransition + Tween<Offset> 350 ms Curves.easeOut |
CSS transform: translateX 350 ms ease-out |
| 배리어 | PopupRoute.barrierColor |
<div class="absolute inset-0"> + click handler |
| 드래그 dismiss | GestureDetector + onPanUpdate/End |
mousedown/move/up listener |
| 닫기 API | closeCoDrawer(context, result) 또는 Navigator.pop(ctx, result) |
builder 의 close([result]) 콜백 |
| 시스템 back | PopScope 자동 | (없음) |
관련 컴포넌트 (Related Components)#
- Dialog: 모달 대화상자. 짧은 확인/입력에 적합 (Drawer 는 복잡한 콘텐츠용)
- Navigation: 네비게이션 컴포넌트. Drawer 안에 배치하여 모바일 메뉴 구현
- Popover: 요소에 붙는 팝업. Drawer 보다 작고 특정 컨텍스트에 연결
조합 예제#
// 모바일 필터 드로어 패턴
final filter = await openCoDrawer<FilterValue>(
context: context,
side: CoreDrawerSide.right,
builder: (close) => CoDrawer(
title: '필터',
content: FilterPanel(
onApply: (value) => close(value),
),
),
);
if (filter != null) applyFilter(filter);