指令面板
規則
- 使用
cmdk 函式庫 — 內建可組合、可存取、快速的模糊搜尋
- 觸發方式:
Cmd+K (Mac) / Ctrl+K (Windows/Linux) — 註冊全域鍵盤監聽器
- 分組動作:導航(頁面)、指令(動作)、最近使用(最近選取)— 顯示群組標題
- 鍵盤導航:使用方向鍵移動、Enter 選取、Escape 關閉 — 全部由
cmdk 處理
- 模糊搜尋:
cmdk 自動過濾 — 根據分數排名比對並高亮顯示
- 最近項目:在
localStorage 儲存最近選取的 5-10 個項目 — 輸入框為空時顯示在最上方
- 動態動作:從應用程式不同部分註冊動作 — 具情境感知(不同頁面顯示不同動作)
- 載入狀態:非同步搜尋結果(例如 API 搜尋)— 在結果區域顯示載入指示器
模式
import { Command } from "cmdk";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
function CommandPalette() {
const [open, setOpen] = useState(false);
const router = useRouter();
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
setOpen((prev) => !prev);
}
};
document.addEventListener("keydown", handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
}, []);
return (
<Command.Dialog open={open} onOpenChange={setOpen} label="指令面板">
<Command.Input placeholder="搜尋或輸入指令..." />
<Command.List>
<Command.Empty>找不到結果。</Command.Empty>
<Command.Group heading="最近使用">
{recentItems.map((item) => (
<Command.Item key={item.id} onSelect={() => { router.push(item.href); setOpen(false); }}>
{item.label}
</Command.Item>
))}
</Command.Group>
<Command.Group heading="導航">
<Command.Item onSelect={() => { router.push("/dashboard"); setOpen(false); }}>
儀表板
</Command.Item>
<Command.Item onSelect={() => { router.push("/settings"); setOpen(false); }}>
設定
</Command.Item>
</Command.Group>
<Command.Group heading="指令">
<Command.Item onSelect={() => { toggleTheme(); setOpen(false); }}>
切換深色模式
</Command.Item>
<Command.Item onSelect={() => { logout(); setOpen(false); }}>
登出
</Command.Item>
</Command.Group>
</Command.List>
</Command.Dialog>
);
}
避免事項
- 自行建立模糊搜尋 —
cmdk 已內建處理過濾、評分與排名
- 忘記在
Cmd+K 上呼叫 e.preventDefault() — 否則瀏覽器預設的搜尋/網址列會開啟
- 遺漏 Escape 按鍵處理 —
cmdk 透過 onOpenChange 處理,但請確保焦點回到先前的元素
- 僅使用靜態動作 — 應根據頁面上下文動態註冊動作(例如「編輯這篇文章」僅在文章頁面顯示)
- 沒有空狀態 — 當搜尋沒有符合結果時,務必顯示「找不到結果」