應用殼層與面板

Slide Over Panel

非模態的窄版浮動面板,可拖曳磁吸至左右任一側,快甩即收合為邊緣把手,全程彈簧跟手。

把這個效果裝進你的專案

在專案資料夾執行指令,CLI 會加入原始碼與依賴套件,再依下方使用範例引用元件。

npx webberui@latest add slide-over-panel

還沒有專案,或安裝遇到問題?看快速開始 →

Slide Over Panel 是用 React、Tailwind CSS 與 Motion 做的非模態浮動側邊面板(floating side panel):可以拖曳到左側或右側並自動吸附,甩動即收合成邊緣把手,打開時不鎖捲動,也不擋住底下內容的操作。

Playground

即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。

300
編輯中的呼叫預設屬性
<SlideOverPanel/>

完整範例包含預覽的排版與資料。先安裝元件,再把範例檔貼入你的專案。

適用情境

  • 編輯器或設計工具裡的屬性、圖層面板,使用者需要一邊看面板一邊操作畫布。
  • Dashboard 的近期動態、通知或篩選條件側欄,可以隨時收到邊緣。
  • 地圖或資料視覺化頁面上的圖例與說明,讓使用者自己決定放左邊或右邊。
  • 文件或後台頁面的輔助資訊,例如 AI 助理對話、待辦清單。
  • 不適合:需要遮罩、強制使用者先處理完再繼續的流程,改用 Drawer。

安裝

npx webberui@latest add https://webberui.com/r/slide-over-panel.json

或在 components.json 設定 registries 後,改用 @webberui/slide-over-panel 安裝。

使用

面板以 absolute 定位,anchor="container"(預設)時會貼齊最近的定位祖先,請確保外層容器為 position: relative:

import { SlideOverPanel } from "@/components/ui/slide-over-panel";

<div className="relative h-[480px] overflow-hidden">
  {/* 你的主要內容 */}
  <SlideOverPanel title="近期動態">
    <p>面板內容…</p>
  </SlideOverPanel>
</div>

若要讓面板浮於整個視口,改用 anchor="viewport"(採 fixed 定位,不需定位祖先)。

範例

受控收合:從工具列按鈕切換

傳入 collapsed 與 onCollapsedChange 由外部管理狀態;使用者拖曳或甩動把手時,回呼也會觸發,狀態保持同步。

"use client";

import * as React from "react";
import { SlideOverPanel } from "@/components/ui/slide-over-panel";

export function EditorWithPanel() {
  const [collapsed, setCollapsed] = React.useState(false);

  return (
    <div className="relative h-[560px] overflow-hidden rounded-xl border">
      <div className="p-4">
        <button onClick={() => setCollapsed((c) => !c)} className="rounded-md border px-3 py-1.5 text-sm">
          {collapsed ? "顯示屬性面板" : "隱藏屬性面板"}
        </button>
      </div>
      <SlideOverPanel
        title="屬性"
        collapsed={collapsed}
        onCollapsedChange={setCollapsed}
      >
        <dl className="space-y-2 text-sm">
          <div className="flex justify-between"><dt>寬度</dt><dd>320 px</dd></div>
          <div className="flex justify-between"><dt>高度</dt><dd>180 px</dd></div>
        </dl>
      </SlideOverPanel>
    </div>
  );
}

記住使用者選的那一側

非受控模式下用 defaultSide 給初始值,再以 onSideChange 把使用者的選擇存起來,下次載入時帶回。

"use client";

import { SlideOverPanel } from "@/components/ui/slide-over-panel";

export function FiltersPanel({ initialSide }: { initialSide: "left" | "right" }) {
  return (
    <SlideOverPanel
      title="篩選條件"
      defaultSide={initialSide}
      onSideChange={(side) => localStorage.setItem("filters-panel-side", side)}
      panelWidth={260}
    >
      {/* 篩選表單 */}
    </SlideOverPanel>
  );
}

浮在整個視窗上、預設收合

anchor="viewport" 改用 fixed 定位,不需要定位祖先;defaultCollapsed 讓頁面載入時只露出把手。

<SlideOverPanel
  anchor="viewport"
  defaultCollapsed
  label="說明面板"
  title="使用說明"
>
  <p className="text-sm">拖曳把手可以把面板移到另一側。</p>
</SlideOverPanel>

Props

Prop型別預設值說明
childrenReact.ReactNode—面板內容
titleReact.ReactNode—面板抬頭標題,同時作為無障礙標籤來源
side"left" | "right"—受控貼齊側
defaultSide"left" | "right""right"非受控預設貼齊側
onSideChange(side) => void—貼齊側變更回呼
collapsedboolean—受控收合狀態
defaultCollapsedbooleanfalse非受控預設收合狀態
onCollapsedChange(collapsed) => void—收合狀態變更回呼
panelWidthnumber300面板本體寬度(px,不含把手)
anchor"container" | "viewport""container"定位基準
labelstring—無障礙標籤,未提供時回退到 title
classNamestring—追加到面板本體的 className

細節

  • 磁吸貼側:拖放後依面板中心落在容器左右哪一半,彈簧吸附到該側並展開。
  • 快甩收合:放開時水平速度超過門檻(600 px/s),即朝甩動方向收合為邊緣把手;反向拉出把手則重新展開。
  • 全程跟手:拖曳、回彈、磁吸、收合皆走同一組 spring,落點俐落不生硬。
  • 受控雙軸:side 與 collapsed 皆支援受控/非受控雙模式,可各自獨立控制。

可及性

  • 面板為 role="region",以 title 或 label 作為 aria-label。
  • 邊緣把手為原生 button:Enter / Space 切換收合,← / → 磁吸至左右側並展開,補足拖曳無法用鍵盤操作的缺口。
  • 把手以 aria-expanded 反映展開狀態;收合時面板本體帶 aria-hidden 並停用互動。
  • 面板為非模態,不鎖捲動、不攔截底層內容的互動(pointer-events 僅作用於面板本身)。
  • 使用者系統開啟「減少動態效果」時,停用拖曳並改以瞬間定位取代彈簧位移。

常見問題

可以在 Next.js App Router 使用嗎?

可以。原始碼已有 "use client",在 Server Component 頁面直接 import 即可。面板會在量測到容器寬度後才顯示並滑入,伺服器端輸出時是透明的。

為什麼面板沒有出現在預期的位置?

預設 anchor="container" 時面板以 absolute inset-0 貼齊最近的定位祖先,外層容器需要 position: relative 與明確的高度。要貼齊整個視窗請改用 anchor="viewport"。

打開面板時,底下的內容還能操作嗎?

可以。外層舞台設定為 pointer-events-none,只有面板本身接收滑鼠事件,也不會鎖住頁面捲動。

只能用滑鼠拖曳嗎?

不是。聚焦邊緣把手後,Enter 或 Space 切換收合,←、→ 吸附到左側或右側並展開。開啟減少動態效果時會停用拖曳,改為直接定位。

怎麼調整寬度和外觀?

panelWidth 設定面板本體寬度(px,預設 300,不含 32px 的把手);className 會加到面板本體上,可以覆寫背景、邊框或陰影。