Async State Slot
非同步狀態槽:在 idle/載入/空狀態/錯誤/內容五態間以高度變形與共享元素過渡切換。
載入預覽⋯
Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
0.4
160
<AsyncStateSlot />
安裝
npx shadcn@latest add https://webberui.com/r/async-state-slot.json或在 components.json 設定 registries 後,改用 @webberui/async-state-slot 安裝。
安裝依賴後,從 registry JSON(/r/async-state-slot.json 的 files[0].content)複製 async-state-slot.tsx 原始碼到你的 components/ui/ 目錄:
npm install motion lucide-react clsx tailwind-merge使用
以單一 state prop 驅動整個插槽;狀態切換時外框高度會平滑補間,退場元素摺疊、進場元素滑入補位。content 狀態顯示 children,其餘四態未提供覆寫時會渲染內建佔位。
import { AsyncStateSlot } from "@/components/ui/async-state-slot";
function Inbox() {
const [state, setState] = React.useState<AsyncState>("loading");
React.useEffect(() => {
fetch("/api/items")
.then((r) => r.json())
.then((items) => setState(items.length ? "content" : "empty"))
.catch(() => setState("error"));
}, []);
return (
<AsyncStateSlot state={state} onRetry={() => setState("loading")}>
<ItemList />
</AsyncStateSlot>
);
}各狀態皆可用對應 prop 完全覆寫內建畫面:
<AsyncStateSlot
state={state}
loading={<MySkeleton />}
empty={<MyEmptyState />}
>
<ItemList />
</AsyncStateSlot>Props
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
state | "idle" | "loading" | "empty" | "error" | "content" | — | 目前狀態(受控) |
children | ReactNode | — | content 狀態的內容 |
idle | ReactNode | 內建 | idle 狀態覆寫 |
loading | ReactNode | 內建骨架 | loading 狀態覆寫 |
empty | ReactNode | 內建插圖 | empty 狀態覆寫 |
error | ReactNode | 內建插圖 | error 狀態覆寫 |
idleTitle | string | "待命中" | 內建待命標題 |
idleDescription | string | "等待觸發載入。" | 內建待命說明 |
emptyTitle | string | "沒有資料" | 內建空狀態標題 |
emptyDescription | string | "目前這裡還沒有任何內容。" | 內建空狀態說明 |
errorTitle | string | "載入失敗" | 內建錯誤標題 |
errorDescription | string | "發生了一些問題,請稍後再試。" | 內建錯誤說明 |
onRetry | () => void | — | 提供時於內建錯誤狀態顯示重試按鈕 |
retryLabel | string | "重試" | 重試按鈕文字 |
duration | number | 0.4 | 高度變形與淡入淡出時長(秒) |
transition | Transition | — | 覆寫高度變形轉場(優先於 duration) |
minHeight | number | string | — | 插槽最小高度,避免切換塌陷 |
srLabels | Partial<Record<AsyncState, string>> | 內建 | 覆寫各狀態朗讀文字 |
細節
- 高度變形:外框以 Motion 的
layout依進場元素的實際高度平滑補間,內層以layout="position"抵銷縮放,文字不會被拉伸。 - 共享元素過渡:
AnimatePresence採mode="popLayout",退場元素移出排版流、換入元素獨自決定新高度,兩者過場重疊,形成「空狀態摺疊退場、首筆資料滑入補位」的效果。 - 狀態受控:
state由外部單一來源驅動,元件不持有內部狀態機,方便對應任何非同步資料流。 - 未提供覆寫時,
idle/empty/error共用置中插圖版型,loading為頭像加內文列的骨架。
可及性
- 容器帶
aria-busy(loading時為true)與data-state,方便樣式與輔助科技辨識。 - 內含
role="status"且aria-live="polite"的隱藏區塊,狀態切換時朗讀當前狀態,文字可用srLabels覆寫。 - 重試按鈕具鍵盤焦點樣式(
focus-visible)。 - 使用者系統開啟「減少動態效果」時,停用高度與位移動畫,狀態直接切換。