Drawer
從畫面邊緣滑入的抽屜面板,支援四向、遮罩點擊關閉,並可單軸拖曳滑走關閉。
載入預覽⋯
Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
<Drawer />
安裝
npx shadcn@latest add https://webberui.com/r/drawer.json或在 components.json 設定 registries 後,改用 @webberui/drawer 安裝。
安裝依賴後,從 registry JSON(/r/drawer.json 的 files[0].content)複製 drawer.tsx 原始碼到你的 components/ui/ 目錄:
npm install motion lucide-react clsx tailwind-merge使用
import {
Drawer,
DrawerTrigger,
DrawerContent,
DrawerClose,
} from "@/components/ui/drawer";
<Drawer>
<DrawerTrigger>開啟選單</DrawerTrigger>
<DrawerContent side="right" className="w-72">
<div className="flex items-center justify-between border-b border-neutral-200 px-5 py-4 dark:border-neutral-800">
<h2 className="text-base font-semibold">選單</h2>
<DrawerClose />
</div>
<nav className="p-3">{/* 選項 */}</nav>
</DrawerContent>
</Drawer>;DrawerContent 的 side 決定滑入方向("top" | "right" | "bottom" | "left",預設 "right")。面板從對應邊緣以 spring 滑入,點擊遮罩或按 Esc 即可關閉。
傳入 open 與 onOpenChange 可切換為受控模式:
const [open, setOpen] = React.useState(false);
<Drawer open={open} onOpenChange={setOpen}>
{/* ... */}
</Drawer>;Props
Drawer
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
children | React.ReactNode | — | 內容,需包含 DrawerTrigger 與 DrawerContent |
open | boolean | — | 受控模式的開啟狀態,不傳則由元件內部管理 |
onOpenChange | (open: boolean) => void | — | 開啟狀態變更時的回呼(受控與非受控皆會觸發) |
DrawerTrigger
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
children | React.ReactNode | — | 觸發鈕內容 |
className | string | — | 追加到觸發鈕的 className |
DrawerContent
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
side | "top" | "right" | "bottom" | "left" | "right" | 抽屜滑入的方向 |
className | string | — | 追加到面板容器的 className |
children | React.ReactNode | — | 面板內容 |
DrawerClose
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
children | React.ReactNode | lucide-react X 圖示 | 自訂按鈕內容 |
className | string | — | 追加到關閉按鈕的 className |
細節
- 四向滑入:
side對應面板貼齊的邊緣與初始離場位移。左右向為滿高側欄(w-80 max-w-[85vw]),上下向為滿寬橫幅(max-h-[85vh]),皆可用className覆寫尺寸。 - 拖曳關閉:面板以單軸
drag跟手,只在「關閉方向」放開約束、開啟方向以彈性阻尼回彈。放開時若關閉方向的位移超過 80px 或速度超過 500px/s 即關閉,AnimatePresence的exit從當前拖曳位置接續滑出;未達閾值則由dragSnapToOrigin自動彈回原位。 - Portal 到 body:面板與遮罩經
createPortal渲染到document.body,避免祖先的overflow裁切或transform建立 containing block 影響fixed定位。 - spring 滑入:入場使用 spring(
stiffness: 380, damping: 40),與拖曳彈回共用同一組 spring,手勢與程式動畫的落點手感一致。 - 受控模式:傳入
open即切換為受控,元件不再改動內部狀態;onOpenChange在受控與非受控下都會觸發,可用來監聽開關事件。 - 可調校點:切換色的過渡時長取用
var(--wb-duration-fast, 200ms),未定義時回退 200ms。
可及性
- 面板帶
role="dialog"與aria-modal="true";開啟時焦點移入面板(tabIndex={-1}),關閉後焦點歸還給觸發鈕 - 觸發鈕為原生
<button>,帶aria-haspopup="dialog"與aria-expanded狀態,開啟時以aria-controls連結面板 - 按 Esc 或點擊遮罩皆可關閉;開啟期間
body設為overflow: hidden鎖住背景捲動,關閉時恢復原值 - 使用者系統開啟「減少動態效果」時,滑入與拖曳退化為快速淡入淡出,不產生任何位移感
- 元件走輕量路線,未內建 focus trap;若面板內有多個互動元素且需要完整的鍵盤焦點循環,建議自行搭配 focus trap 方案