WebberUI

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.jsonfiles[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 皆可關閉;項目點選後預設自動關閉。

FullscreenMenuItemhref 時渲染為 <a>,否則為 <button>,可搭配 onSelect 處理點選行為:

<FullscreenMenuItem onSelect={() => router.push("/men")} index="01">
  男子
</FullscreenMenuItem>;

傳入 openonOpenChange 可切換為受控模式(例如由外部的漢堡鈕控制):

const [open, setOpen] = React.useState(false);

<FullscreenMenu open={open} onOpenChange={setOpen}>
  {/* ... */}
</FullscreenMenu>;

Props

FullscreenMenu

Prop型別預設值說明
childrenReact.ReactNode內容,需包含 FullscreenMenuTriggerFullscreenMenuContent
openboolean受控模式的開啟狀態,不傳則由元件內部管理
onOpenChange(open: boolean) => void開啟狀態變更時的回呼(受控與非受控皆會觸發)

FullscreenMenuTrigger

Prop型別預設值說明
childrenReact.ReactNode觸發鈕內容
classNamestring追加到觸發鈕的 className

FullscreenMenuContent

Prop型別預設值說明
childrenReact.ReactNode覆蓋層內容,通常為若干 FullscreenMenuItem
align"start" | "center""start"選單項目的水平對齊方式
labelstring"主選單"覆蓋層的無障礙標籤(套用到 role="dialog"
classNamestring追加到覆蓋層容器的 className

FullscreenMenuItem

Prop型別預設值說明
childrenReact.ReactNode項目文字
hrefstring有值時渲染為 <a>;否則為 <button>
indexstring選填的序號標籤(如 "01"),顯示在項目前方
onSelect() => void點擊選取時的回呼
closeOnSelectbooleantrue選取後是否自動關閉選單
classNamestring追加到項目連結的 className

FullscreenMenuClose

Prop型別預設值說明
childrenReact.ReactNodelucide-react X 圖示自訂按鈕內容
classNamestring追加到關閉按鈕的 className

細節

  • 階梯式揭示:清單容器以 staggerChildren 對各項目錯開觸發時間,每個項目外層是 overflow-hidden 的靜態遮罩,內部連結才做 y 位移,因此文字像是從遮罩邊緣「升起」,形成 Nike 風格的乾淨裁切邊。退場時以 staggerDirection: -1 反向收合。
  • 由上而下的底幕:底幕以 scaleYtransformOrigin: 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 方案

On this page