Frontend Lab
← 回到所有模式
PB02🪟 Overlay · 覆蓋層側欄

Drawer

抽屜

難度 ★★★★★標籤 drawer · 側滑 · mobile3 min read

模式簡述

Drawer:從邊緣滑入的面板。手機端比 modal 更友善,因為內容可垂直捲動而不阻塞整頁。

關閉|預設關閉

Claude Code Prompt

💬 Claude Code Prompt
參考:PB02 drawer
組件:Drawer.tsx

Drawer:從邊緣滑入的面板。手機端比 modal 更友善,因為內容可垂直捲動而不阻塞整頁。

限制:
- 寬度沒設上限
- 沒做手勢 swipe 關閉
- 動畫太長

完整程式碼

Drawer.tsx
"use client";
import { useEffect, useState } from "react";
import { X } from "lucide-react";
import { cn } from "@/lib/utils";

type State = "closed" | "right" | "bottom";

export function Drawer({ state = "right" }: { state?: State }) {
const [open, setOpen] = useState(state !== "closed");
useEffect(() => setOpen(state !== "closed"), [state]);
const bottom = state === "bottom";

return (
  <div className="relative h-[300px] w-full max-w-md overflow-hidden rounded-lg border border-border bg-background/30">
    <div className="grid h-full place-items-center">
      <button
        onClick={() => setOpen(true)}
        className="rounded-md border border-border bg-background/50 px-3 py-1.5 font-mono text-xs hover:border-lab-accent"
      >
        開啟 drawer
      </button>
    </div>
    {open && (
      <>
        <div
          onClick={() => setOpen(false)}
          className="absolute inset-0 bg-black/50 backdrop-blur-sm animate-in fade-in duration-200"
        />
        <aside
          className={cn(
            "absolute border-border bg-background p-4 shadow-xl",
            bottom
              ? "inset-x-0 bottom-0 rounded-t-2xl border-t animate-in slide-in-from-bottom duration-300"
              : "inset-y-0 right-0 w-3/4 border-l animate-in slide-in-from-right duration-300",
          )}
        >
          <div className="flex items-center justify-between">
            <h3 className="text-sm font-medium">{bottom ? "底部 sheet" : "右側 drawer"}</h3>
            <button onClick={() => setOpen(false)} className="text-muted-foreground hover:text-foreground">
              <X className="h-4 w-4" />
            </button>
          </div>
          <ul className="mt-4 space-y-2 text-xs text-muted-foreground">
            <li className="rounded-md bg-background/40 px-2 py-1.5">選項一</li>
            <li className="rounded-md bg-background/40 px-2 py-1.5">選項二</li>
            <li className="rounded-md bg-background/40 px-2 py-1.5">選項三</li>
          </ul>
        </aside>
      </>
    )}
  </div>
);
}

何時用

  • ✅ 手機端的長表單或長清單
  • ✅ 需要快速切換的次要面板(filter、cart)
  • ✅ 從某個 entry icon 展開的詳細資訊

何時別用

  • ❌ 桌面端首選方案(桌面有空間直接 inline)
  • ❌ 需要使用者強烈關注的關鍵決策(用 modal)
  • ❌ 內容可省略只是一兩個欄位(用 popover)

我踩過的坑

  1. 寬度沒設上限:手機 OK,平板看起來像被撐爆。記得 max-w-md 或 sm:max-w-lg。

  2. 沒做手勢 swipe 關閉:手機使用者會試著從邊緣往外滑。用 framer-motion 的 drag 加上 dragConstraints。

  3. 動畫太長:手機端 300ms+ 會有點黏,建議 200ms 內。

相關模式

  • PB01
  • PC03

搜尋

按 ⌘K 隨時開啟