應用殼層與面板

Anchored Settings Shell

錨點設定頁框架:左側 scroll-spy 導覽、右側兩欄設定區塊,指示器平滑跟隨捲動,未儲存變更時儲存列以彈簧滑入。

把這個效果裝進你的專案

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

npx webberui@latest add anchored-settings-shell

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

Playground

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

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

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

安裝

npx webberui@latest add https://webberui.com/r/anchored-settings-shell.json

或在 components.json 設定 registries 後,改用 @webberui/anchored-settings-shell 安裝。

使用

import {
  AnchoredSettingsShell,
  SettingsSection,
  SettingsRow,
} from "@/components/ui/anchored-settings-shell";
import { Bell, User } from "lucide-react";

export function Example() {
  const [dirty, setDirty] = React.useState(false);

  return (
    <AnchoredSettingsShell
      dirty={dirty}
      onSave={() => setDirty(false)}
      onDiscard={() => setDirty(false)}
    >
      <SettingsSection id="profile" label="個人資料" icon={User} description="公開身分與聯絡方式">
        <SettingsRow label="顯示名稱" htmlFor="name">
          <input id="name" onChange={() => setDirty(true)} />
        </SettingsRow>
      </SettingsSection>

      <SettingsSection id="notifications" label="通知" icon={Bell}>
        <SettingsRow label="產品更新">{/* ...控制項 */}</SettingsRow>
      </SettingsSection>
    </AnchoredSettingsShell>
  );
}

導覽項目由 SettingsSection 子元件的 id、label、icon 自動推導,順序與內容一致。

非受控模式

省略 dirty prop 時改用內部狀態,於表單欄位的 onChange 呼叫 useSettingsDirty() 回傳的函式即可標記未儲存變更;onSave/onDiscard 後會自動重置。

import { useSettingsDirty } from "@/components/ui/anchored-settings-shell";

function NameField() {
  const markDirty = useSettingsDirty();
  return <input onChange={markDirty} />;
}

捲動容器

若設定頁位於巢狀 overflow-y-auto 容器內,將該容器的 ref 傳入 container,scroll-spy 便會以該容器為捲動根。省略時以視窗為捲動容器。

Props

AnchoredSettingsShell

Prop型別預設值說明
childrenReactNode—放入 SettingsSection 子元件
dirtyboolean—受控:是否有未儲存變更;省略則用內部非受控狀態
onSave() => void | Promise<void>—按下儲存時觸發,Promise pending 期間顯示載入狀態
onDiscard() => void—按下捨棄時觸發
containerRefObject<HTMLElement | null>—巢狀捲動容器的 ref,作為 scroll-spy 根
dirtyMessagestring"你有尚未儲存的變更"儲存列提示文字
saveLabelstring"儲存變更"儲存按鈕文字
discardLabelstring"捨棄"捨棄按鈕文字
navLabelstring"設定區塊"錨點導覽的無障礙標籤

SettingsSection

Prop型別預設值說明
idstring—區塊錨點 id,導覽項連結至此
labelReactNode—區塊標題,同時作為導覽項文字
iconLucideIcon—導覽項圖示
descriptionReactNode—標題下方說明文字
childrenReactNode—區塊內容(表單欄位等)

SettingsRow

Prop型別預設值說明
labelReactNode—欄位標籤
hintReactNode—標籤下方輔助說明
htmlForstring—綁定控制項 id 的 label for
childrenReactNode—右側控制項

細節

  • 導覽指示器由 useSpring 驅動 top/height,切換區塊時在導覽項之間連續滑動。
  • scroll-spy 以 IntersectionObserver 取樣各區塊可見比例,選可見度最高者為目前焦點,同時支援視窗與巢狀容器兩種捲動根。
  • 未儲存變更時底部儲存列以彈簧滑入,sticky bottom 定位使其始終貼齊捲動視區底緣。

可及性

  • 導覽為 <nav> + 錨點連結,目前區塊標記 aria-current="location";點擊平滑捲動至對應區塊。
  • 有未儲存變更時支援 ⌘/Ctrl+S 快捷鍵觸發儲存。
  • 儲存中會停用按鈕並顯示轉圈載入圖示,避免重複送出。
  • 開啟「減少動態效果」時,指示器與儲存列改為即時定位、捲動改為 auto,不影響版面與功能。
  • 每個區塊以 aria-labelledby 對應其標題,切換開關使用 role="switch" 與 aria-checked。