Fullscreen Menu
Nike 風格的全螢幕覆蓋選單,底幕由上而下揭幕、選單項目以階梯式錯開自遮罩下方向上滑入。
載入預覽⋯
Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
<FullscreenMenu />
安裝
npx shadcn@latest add https://webberui.com/r/fullscreen-menu.json或在 components.json 設定 registries 後,改用 @webberui/fullscreen-menu 安裝。
安裝依賴後,從 registry JSON(/r/fullscreen-menu.json 的 files[0].content)複製 fullscreen-menu.tsx 原始碼到你的 components/ui/ 目錄:
npm install motion lucide-react clsx tailwind-merge使用
import {
FullscreenMenu,
FullscreenMenuTrigger,
FullscreenMenuContent,
FullscreenMenuItem,
} from "@/components/ui/fullscreen-menu";
<FullscreenMenu>
<FullscreenMenuTrigger>開啟選單</FullscreenMenuTrigger>
<FullscreenMenuContent>
<FullscreenMenuItem href="#men" index="01">
男子
</FullscreenMenuItem>
<FullscreenMenuItem href="#women" index="02">
女子
</FullscreenMenuItem>
<FullscreenMenuItem href="#sport" index="03">
運動系列
</FullscreenMenuItem>
</FullscreenMenuContent>
</FullscreenMenu>;點擊觸發鈕後,底幕由畫面頂端向下揭幕,接著各 FullscreenMenuItem 依序自遮罩下方向上滑入。點擊底幕、按右上角關閉鈕或 Esc 皆可關閉;項目點選後預設自動關閉。
FullscreenMenuItem 有 href 時渲染為 <a>,否則為 <button>,可搭配 onSelect 處理點選行為:
<FullscreenMenuItem onSelect={() => router.push("/men")} index="01">
男子
</FullscreenMenuItem>;傳入 open 與 onOpenChange 可切換為受控模式(例如由外部的漢堡鈕控制):
const [open, setOpen] = React.useState(false);
<FullscreenMenu open={open} onOpenChange={setOpen}>
{/* ... */}
</FullscreenMenu>;Props
FullscreenMenu
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
children | React.ReactNode | — | 內容,需包含 FullscreenMenuTrigger 與 FullscreenMenuContent |
open | boolean | — | 受控模式的開啟狀態,不傳則由元件內部管理 |
onOpenChange | (open: boolean) => void | — | 開啟狀態變更時的回呼(受控與非受控皆會觸發) |
FullscreenMenuTrigger
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
children | React.ReactNode | — | 觸發鈕內容 |
className | string | — | 追加到觸發鈕的 className |
FullscreenMenuContent
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
children | React.ReactNode | — | 覆蓋層內容,通常為若干 FullscreenMenuItem |
align | "start" | "center" | "start" | 選單項目的水平對齊方式 |
label | string | "主選單" | 覆蓋層的無障礙標籤(套用到 role="dialog") |
className | string | — | 追加到覆蓋層容器的 className |
FullscreenMenuItem
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
children | React.ReactNode | — | 項目文字 |
href | string | — | 有值時渲染為 <a>;否則為 <button> |
index | string | — | 選填的序號標籤(如 "01"),顯示在項目前方 |
onSelect | () => void | — | 點擊選取時的回呼 |
closeOnSelect | boolean | true | 選取後是否自動關閉選單 |
className | string | — | 追加到項目連結的 className |
FullscreenMenuClose
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
children | React.ReactNode | lucide-react X 圖示 | 自訂按鈕內容 |
className | string | — | 追加到關閉按鈕的 className |
細節
- 階梯式揭示:清單容器以
staggerChildren對各項目錯開觸發時間,每個項目外層是overflow-hidden的靜態遮罩,內部連結才做y位移,因此文字像是從遮罩邊緣「升起」,形成 Nike 風格的乾淨裁切邊。退場時以staggerDirection: -1反向收合。 - 由上而下的底幕:底幕以
scaleY(transformOrigin: top)由畫面頂端向下揭幕,退場時延遲一小段再收回,讓項目先退場、底幕最後才闔上。 - Portal 到 body:覆蓋層與底幕經
createPortal渲染到document.body,避免祖先的overflow裁切或transform建立 containing block 影響fixed定位。 - pointer-events 分層:對話層預設
pointer-events-none,讓覆蓋層以外的點擊落到底幕(點擊即關閉),只有連結、關閉鈕等互動元素自身恢復pointer-events。 - 受控模式:傳入
open即切換為受控,元件不再改動內部狀態;onOpenChange在受控與非受控下都會觸發,可用來與外部導覽狀態同步。 - 可調校點:hover 與圖示的過渡時長取用
var(--wb-duration-fast, 200ms),緩動取用var(--wb-ease-out, cubic-bezier(0.22,1,0.36,1)),未定義時回退預設值。
可及性
- 覆蓋層帶
role="dialog"、aria-modal="true"與aria-label;開啟時焦點移入覆蓋層(tabIndex={-1}),關閉後焦點歸還給觸發鈕 - 觸發鈕為原生
<button>,帶aria-haspopup="dialog"與aria-expanded狀態,開啟時以aria-controls連結覆蓋層 - 選單以
<nav>與<ul>/<li>語意結構承載,項目依href渲染為<a>或<button>,皆可鍵盤聚焦與操作 - 按 Esc 或點擊底幕皆可關閉;開啟期間
body設為overflow: hidden鎖住背景捲動,關閉時恢復原值 - 使用者系統開啟「減少動態效果」時,底幕與項目退化為快速淡入淡出,不產生任何位移感
- 元件走輕量路線,未內建 focus trap;若覆蓋層內互動元素較多且需要完整的鍵盤焦點循環,建議自行搭配 focus trap 方案