@olundot/ui · Surfaces
Sheet
화면 가장자리에서 열리는 보조 패널을 primitive 단위로 조합하는 Sheet 컴포넌트 묶음입니다.
- exports
- Sheet · SheetTrigger · SheetClose · SheetContent · SheetHeader · SheetFooter · SheetTitle · SheetDescription
- side
- right, left, top, bottom 위치를 SheetContent에서 선택합니다.
- mobileSide
- 좁은 화면에서 다른 방향으로 전환해야 할 때 사용합니다.
- motion
- type과 easing은 preset, duration은 number(ms) 또는 ms/s 문자열로 지정하며 motion={false}로 끌 수 있습니다.
- showCloseButton
- 기본 닫기 버튼 표시 여부입니다. 명시적 footer 닫기를 둘 때 false로 둡니다.
오른쪽 패널
Sheet primitives와 명시적 닫기코드 보기tsx
import {
Sheet, SheetTrigger, SheetContent, SheetHeader,
SheetTitle, SheetDescription, SheetFooter, SheetClose,
Badge, Button,
} from "@olundot/ui";
export function SheetRight() {
return (
<Sheet>
<SheetTrigger asChild>
<Button variant="outline">상세 패널 열기</Button>
</SheetTrigger>
<SheetContent side="right" showCloseButton={false}>
<SheetHeader>
<SheetTitle>학생 상세</SheetTitle>
<SheetDescription>응시 상태와 최근 활동을 확인합니다.</SheetDescription>
</SheetHeader>
<div className="grid gap-[var(--space-4)] p-[var(--space-6)]">
<div className="rounded-[var(--radius-md)] border border-[var(--border-subtle)] bg-[var(--bg-canvas)] p-[var(--space-4)]">
<strong>김지수</strong>
<p>의학과 4학년 · A반</p>
<Badge tone="success">응시 가능</Badge>
</div>
<dl>
<div><dt>최근 제출</dt><dd>내과학 모의고사 2회</dd></div>
<div><dt>평균 점수</dt><dd>88점</dd></div>
</dl>
</div>
<SheetFooter>
<SheetClose asChild>
<Button variant="secondary">닫기</Button>
</SheetClose>
</SheetFooter>
</SheetContent>
</Sheet>
);
}하단 패널
mobileSide 없이 bottom 배치코드 보기tsx
import {
Sheet, SheetTrigger, SheetContent, SheetHeader,
SheetTitle, SheetDescription, SheetFooter, SheetClose,
Button, Checkbox, Input,
} from "@olundot/ui";
export function SheetBottom() {
return (
<Sheet>
<SheetTrigger asChild>
<Button variant="outline">필터 열기</Button>
</SheetTrigger>
<SheetContent side="bottom" showCloseButton={false}>
<SheetHeader>
<SheetTitle>필터</SheetTitle>
<SheetDescription>목록에 적용할 조건을 선택합니다.</SheetDescription>
</SheetHeader>
<div className="grid gap-[var(--space-4)] p-[var(--space-6)]">
<Input surface="canvas" label="검색어" name="query" placeholder="학생명 또는 문항 코드" />
<Checkbox label="미채점 항목만 보기" defaultChecked />
<Checkbox label="오류 보고 항목 포함" />
</div>
<SheetFooter>
<SheetClose asChild>
<Button variant="primary">적용</Button>
</SheetClose>
</SheetFooter>
</SheetContent>
</Sheet>
);
}duration 비교
300ms / 400ms / 500ms코드 보기tsx
import { useState } from "react";
import {
Sheet, SheetContent, SheetHeader, SheetTitle,
SheetDescription, SheetFooter, SheetClose, Button,
} from "@olundot/ui";
const durations = [300, 400, 500] as const;
export function SheetDurationComparison() {
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>
<Sheet open={duration !== null} onOpenChange={(open) => !open && setDuration(null)}>
<SheetContent side="right" motion={{ duration: duration ?? 300, easing: "decel" }} showCloseButton={false}>
<SheetHeader>
<SheetTitle>{duration ?? 300}ms motion</SheetTitle>
<SheetDescription>duration prop은 number(ms), "400ms", "0.4s" 형식을 받습니다.</SheetDescription>
</SheetHeader>
<div className="grid gap-[var(--space-2)] p-[var(--space-6)] text-body-sm text-text-secondary">
<p>패널과 overlay가 같은 시간값으로 열리고 닫힙니다.</p>
</div>
<SheetFooter>
<SheetClose asChild>
<Button variant="secondary">닫기</Button>
</SheetClose>
</SheetFooter>
</SheetContent>
</Sheet>
</>
);
}motion 없음
motion=false opt-out코드 보기tsx
import {
Sheet, SheetTrigger, SheetContent, SheetHeader,
SheetTitle, SheetDescription, SheetFooter, SheetClose,
Button,
} from "@olundot/ui";
export function SheetWithoutMotion() {
return (
<Sheet>
<SheetTrigger asChild>
<Button variant="outline">정적 패널 열기</Button>
</SheetTrigger>
<SheetContent side="right" motion={false} showCloseButton={false}>
<SheetHeader>
<SheetTitle>검토 메모</SheetTitle>
<SheetDescription>motion을 끄고 즉시 열리는 보조 패널입니다.</SheetDescription>
</SheetHeader>
<div className="grid gap-[var(--space-2)] p-[var(--space-6)]">
<p>민감한 화면 녹화나 저사양 환경에서는 motion을 끌 수 있습니다.</p>
</div>
<SheetFooter>
<SheetClose asChild>
<Button variant="secondary">닫기</Button>
</SheetClose>
</SheetFooter>
</SheetContent>
</Sheet>
);
}제어 상태
open과 onOpenChange코드 보기tsx
import { useState } from "react";
import {
Sheet, SheetContent, SheetHeader, SheetTitle,
SheetDescription, SheetFooter, SheetClose, Button,
} from "@olundot/ui";
export function SheetControlled() {
const [open, setOpen] = useState(false);
return (
<>
<Button variant="primary" onClick={() => setOpen(true)}>
문항 목록 열기
</Button>
<Sheet open={open} onOpenChange={setOpen}>
<SheetContent side="left" showCloseButton={false}>
<SheetHeader>
<SheetTitle>문항 목록</SheetTitle>
<SheetDescription>검토할 문항으로 빠르게 이동합니다.</SheetDescription>
</SheetHeader>
<div className="grid gap-[var(--space-2)] p-[var(--space-6)]">
{["1. 흉통 감별", "2. 심전도 판독", "3. 응급 처치"].map((item) => (
<button type="button" key={item}>{item}</button>
))}
</div>
<SheetFooter>
<SheetClose asChild>
<Button variant="secondary">닫기</Button>
</SheetClose>
</SheetFooter>
</SheetContent>
</Sheet>
</>
);
}