@olundot/ui · Surfaces
SheetDrawer
Sheet primitive를 운영 화면에 맞게 조합한 고수준 drawer입니다. desktop은 오른쪽, mobile은 하단에서 열립니다.
- 반응형 방향
- desktop에서는 오른쪽 drawer, mobile에서는 bottom sheet 형태로 열립니다.
- width
- default는 480px, wide는 720px, full은 거의 전체 화면 폭의 drawer입니다.
- motion
- SheetContent와 같은 motion object 또는 false를 받아 desktop right, mobile bottom 전환을 제어합니다. duration은 number(ms) 또는 ms/s 문자열입니다.
- caption
- title 위 eyebrow 영역입니다. breadcrumb, 상태 badge, 과목 badge처럼 먼저 보여야 하는 메타데이터를 둡니다.
- expand action
- expanded와 onExpandedChange로 크게 보기 / 작게 보기 header action을 제어합니다.
- footer
- 저장, 적용, 선택 완료 같은 하단 action을 고정합니다.
기본 drawer
오른쪽 desktop, 하단 mobile코드 보기tsx
import { useState } from "react";
import { SheetDrawer, Badge, Button } from "@olundot/ui";
export function SheetDrawerDefault() {
const [open, setOpen] = useState(false);
return (
<>
<Button variant="outline" onClick={() => setOpen(true)}>
빠른 상세 열기
</Button>
<SheetDrawer
open={open}
onOpenChange={setOpen}
caption={
<>
<Badge tone="info">객관식</Badge>
<Badge tone="neutral">심혈관계</Badge>
</>
}
title="문항 상세"
description="현재 선택한 문항의 검토 정보를 확인합니다."
>
<div className="grid gap-[var(--space-4)]">
<div className="rounded-[var(--radius-md)] border border-[var(--border-subtle)] bg-[var(--bg-canvas)] p-[var(--space-4)]">
<strong>흉통 감별 문항</strong>
<Badge tone="warning">검토 필요</Badge>
<p>정답률 62%, 이의 신청 2건이 접수되었습니다.</p>
</div>
<dl>
<div><dt>최근 수정일</dt><dd>2026-06-03</dd></div>
<div><dt>담당자</dt><dd>김교수</dd></div>
</dl>
</div>
</SheetDrawer>
</>
);
}footer action
저장과 취소를 drawer 하단에 고정코드 보기tsx
import { useState } from "react";
import { SheetDrawer, Button, Checkbox } from "@olundot/ui";
export function SheetDrawerFooter() {
const [open, setOpen] = useState(false);
return (
<>
<Button variant="primary" onClick={() => setOpen(true)}>
출제 범위 선택
</Button>
<SheetDrawer
open={open}
onOpenChange={setOpen}
title="출제 범위"
description="이번 시험에 포함할 단원을 선택합니다."
footer={
<>
<Button variant="secondary" size="sm" onClick={() => setOpen(false)}>
취소
</Button>
<Button variant="primary" size="sm" onClick={() => setOpen(false)}>
선택 완료
</Button>
</>
}
>
<div className="grid gap-[var(--space-2)]">
<Checkbox label="심혈관계" defaultChecked />
<Checkbox label="호흡기계" />
<Checkbox label="소화기계" />
</div>
</SheetDrawer>
</>
);
}duration 비교
300ms / 400ms / 500ms코드 보기tsx
import { useState } from "react";
import { SheetDrawer, Button, Badge } from "@olundot/ui";
const durations = [300, 400, 500] as const;
export function SheetDrawerDurationComparison() {
const [duration, setDuration] = useState<(typeof durations)[number] | null>(null);
return (
<>
<div className="flex flex-wrap gap-[var(--space-2)]">
{durations.map((value) => (
<Button key={value} variant="outline" onClick={() => setDuration(value)}>
{value}ms 열기
</Button>
))}
</div>
<SheetDrawer
open={duration !== null}
onOpenChange={(open) => !open && setDuration(null)}
motion={{ duration: duration ?? 300, easing: "decel" }}
title={`${duration ?? 300}ms drawer`}
description="SheetDrawer도 SheetContent와 같은 시간값 duration을 사용합니다."
>
<div className="grid gap-[var(--space-4)]">
<Badge tone="info">{duration ?? 300}ms</Badge>
<p>desktop right, mobile bottom 전환에 같은 duration이 적용됩니다.</p>
</div>
</SheetDrawer>
</>
);
}motion 없음
motion=false opt-out코드 보기tsx
import { useState } from "react";
import { SheetDrawer, Button, Badge } from "@olundot/ui";
export function SheetDrawerWithoutMotion() {
const [open, setOpen] = useState(false);
return (
<>
<Button variant="outline" onClick={() => setOpen(true)}>
정적 drawer 열기
</Button>
<SheetDrawer
open={open}
onOpenChange={setOpen}
motion={false}
title="검토 메모"
description="motion 없이 즉시 열리는 drawer입니다."
>
<div className="grid gap-[var(--space-4)]">
<Badge tone="info">motion 꺼짐</Badge>
<p>전환 효과를 끄되 desktop right, mobile bottom 배치는 그대로 유지합니다.</p>
</div>
</SheetDrawer>
</>
);
}확장 action
expanded toggle과 full 폭코드 보기tsx
import { useState } from "react";
import { SheetDrawer, Button } from "@olundot/ui";
export function SheetDrawerExpand() {
const [open, setOpen] = useState(false);
const [expanded, setExpanded] = useState(false);
return (
<div style={{ display: "grid", gap: "12px" }}>
<Button variant="outline" onClick={() => setOpen(true)}>
분석 패널 열기
</Button>
<SheetDrawer
open={open}
onOpenChange={(nextOpen) => {
setOpen(nextOpen);
if (!nextOpen) setExpanded(false);
}}
title="성취도 분석"
description="영역별 정답률과 취약 단원을 확인합니다."
width="wide"
expanded={expanded}
onExpandedChange={setExpanded}
expandLabel="크게 보기"
collapseLabel="작게 보기"
>
<div className="grid gap-[var(--space-4)]">
{["순환기 76%", "호흡기 58%", "감염 64%"].map((item) => (
<div key={item} className="rounded-[var(--radius-md)] border border-[var(--border-subtle)] bg-[var(--bg-canvas)] p-[var(--space-4)]">
{item}
</div>
))}
</div>
</SheetDrawer>
</div>
);
}