Focus Recession Stage
頁面級焦點系統:任一區塊進入聚焦/編輯模式時,其餘版面整體向後退場——縮小、調暗、模糊成景深背景,Esc 或點外部彈簧回彈歸位。
載入預覽⋯
Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
<FocusRecessionStage />
安裝
npx shadcn@latest add https://webberui.com/r/focus-recession-stage.json或在 components.json 設定 registries 後,改用 @webberui/focus-recession-stage 安裝。
安裝依賴後,從 registry JSON(/r/focus-recession-stage.json 的 files[0].content)複製 focus-recession-stage.tsx 原始碼到你的 components/ui/ 目錄:
npm install motion clsx tailwind-mergeFocusTrigger 的轉場會讀取 --wb-duration-fast、--wb-ease-out CSS 變數,皆內建 fallback(200ms、cubic-bezier(0.22,1,0.36,1)),未定義也能正常運作。
使用
import {
FocusRecessionStage,
FocusRegion,
} from "@/components/ui/focus-recession-stage";
<FocusRecessionStage className="grid grid-cols-2 gap-4">
<FocusRegion id="inbox" label="聚焦 收件匣">
<InboxCard />
</FocusRegion>
<FocusRegion id="calendar" label="聚焦 行事曆">
<CalendarCard />
</FocusRegion>
</FocusRecessionStage>;區塊內含互動元素(輸入框、多個按鈕)時,改用 FocusTrigger 觸發,並關掉整塊觸發:
import { FocusRegion, FocusTrigger, useFocusRegion } from "@/components/ui/focus-recession-stage";
function NoteCard() {
const { focused } = useFocusRegion();
return (
<div>
{focused ? <textarea autoFocus /> : <p>筆記預覽…</p>}
<FocusTrigger>{focused ? "完成" : "編輯"}</FocusTrigger>
</div>
);
}
<FocusRegion id="note" activateOnPress={false}>
<NoteCard />
</FocusRegion>;Props
FocusRecessionStage
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
focusedId | string | null | — | 受控的聚焦區塊 id;傳 null 為無聚焦,不傳則由元件內部管理 |
defaultFocusedId | string | null | null | 非受控模式的初始聚焦 id |
onFocusedChange | (id: string | null) => void | — | 聚焦目標變更時觸發(含 Esc/點外部解除時) |
variant | 'depth' | 'flat' | 'spotlight' | 'depth' | 景深款式預設,可再以下方參數覆寫 |
recedeScale | number | 依 variant | 覆寫:退場區塊縮放比例 |
recedeOpacity | number | 依 variant | 覆寫:退場區塊透明度(0–1) |
recedeBlur | number | 依 variant | 覆寫:退場區塊模糊半徑(px) |
focusScale | number | 依 variant | 覆寫:聚焦區塊放大比例 |
scrimOpacity | number | 依 variant | 覆寫:遮罩不透明度(0–1) |
scrim | boolean | true | 聚焦時是否鋪一層暗色遮罩統一景深 |
dismissOnEsc | boolean | true | 按 Esc 是否解除聚焦 |
dismissOnOutsideClick | boolean | true | 點擊聚焦區塊以外處是否解除聚焦 |
children | React.ReactNode | — | 直接放入 FocusRegion 子元件 |
className | string | — | 附加到舞台容器的 class;可在此設定 grid/flex 版面 |
FocusRegion
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
id | string | —(必填) | 唯一識別 key:聚焦配對與點擊外部判定用 |
activateOnPress | boolean | true | 整塊即為觸發器(點擊/Enter/Space 聚焦);含互動內容或改用 FocusTrigger 時設為 false |
disabled | boolean | false | 停用此區塊:不可被聚焦、無按鈕語意 |
label | string | — | 整塊作為觸發器時的無障礙名稱(aria-label) |
children | React.ReactNode | — | 區塊內容;可透過 useFocusRegion() 讀取聚焦狀態 |
className | string | — | 附加到區塊外框的 class |
FocusTrigger
放在 FocusRegion(activateOnPress={false})內的原生 button,點擊在「聚焦/解除」間切換。承接所有原生 <button> 屬性(onClick 除外,由元件內部接管)。
useFocusRegion()
於 FocusRegion 子樹內呼叫,回傳 { id, focused, receded, focus, dismiss },可依 focused 條件顯示編輯 UI 或展開細節。
細節
- 景深退場:任一區塊聚焦時,聚焦區塊放大抬升到遮罩之上、維持全清晰;其餘區塊同時縮小、調暗、模糊為景深背景,並蓋上一層暗色遮罩統一整體氛圍
- 三款款式:
depth縮小+模糊+調暗(預設)、flat只調暗不位移(低干擾)、spotlight強聚光(更暗更模糊、聚焦區塊抬升更明顯);每個維度都可用個別 prop 微調覆寫 - 彈簧回彈:位移用 spring(微幅過衝),Esc 或點外部解除時整體以同一 spring 彈回歸位;透明度與模糊改用 tween,避免過衝造成模糊閃動
- 受控/非受控:可完全交給元件內部管理,或以
focusedId+onFocusedChange受控串接自家狀態 - 純 transform 效果:縮放、模糊、透明度皆不觸發重排(reflow),聚焦區塊以
z-index抬升覆蓋鄰塊,版面不位移 - 兩種觸發方式:整塊觸發(
activateOnPress,適合展示型卡片)或FocusTrigger(適合區塊內含輸入框、多個按鈕) - 解除聚焦:監聽於聚焦後才掛載、下一幀才生效,不會誤捕捉觸發聚焦的那次點擊;監聽器在解除與 unmount 時全數清理
可及性
- 整塊觸發時帶
role="button"、tabIndex={0}與aria-label,支援 Enter/Space 聚焦 - 退場中的區塊標記
aria-hidden並移出 Tab 焦點序(tabIndex={-1})、停用指標事件,成為單純的背景 - 以
data-focus-state="focused | receded | idle"對外暴露狀態,方便自訂樣式掛勾 - 使用者系統開啟「減少動態效果」時,聚焦與退場改為瞬間切換(無 spring/無漸變),景深與焦點功能完全保留