コマンドパレットCommandPaletteExperimental

ナビゲーションやアクション用のグローバルコマンドパレットです。

プレビュー

状態とバリエーション

標準表示

グローバルな移動先とアクションをまとめ、キーボードショートカットでも開けるようにします。

キーボードから開く場合は ⌘K

少数コマンド

小さなアプリではグループを絞り、空状態や検索文言だけを用途に合わせます。

キーボードから開く場合は ⌘K

コマンドが無いとき

権限がまだ無い、あるいは絞り込みで全部消えたときに出る面です。開いて何も無いのが分かるよう、emptyMessage は「無い理由」まで書きます。

キーボードから開く場合は ⌘K

プロパティ

表は横にスクロールできます
プロパティ初期値説明
openboolean-パレットの開閉状態。
onOpenChange(open: boolean) => void-開閉状態が変わった時に呼ばれるハンドラ。
groupsCommandPaletteGroup[][]見出しとコマンド項目の配列。
placeholderstring"Type a command or search..."検索入力のプレースホルダー。
emptyMessageReactNode"No results found."検索結果がない時に表示する文言。
dialogTitleReactNode"Command Menu"ダイアログのアクセシブルタイトル。
clearLabelstring"Clear search"検索クリアボタンの aria-label とツールチップ文言。
portalContainerHTMLElement | null-docs プレビューや擬似ブラウザ内にパレットを閉じ込めたい時のポータル先。

使い方

"use client"

import { Button, CommandPalette, Kbd } from "@gunjo/ui"
import {
  IconCalculator as Calculator,
  IconCalendar as Calendar,
  IconCreditCard as CreditCard,
  IconFileText as FileText,
  IconMoodSmile as Smile,
  IconSearch as Search,
  IconSettings as Settings,
  IconUserCircle as UserRound,
} from "@tabler/icons-react"
import { useEffect, useMemo, useState } from "react"

export function CommandPaletteExample() {
  const [open, setOpen] = useState(false)

  useEffect(() => {
    const down = (event: KeyboardEvent) => {
      if (event.key === "k" && (event.metaKey || event.ctrlKey)) {
        event.preventDefault()
        setOpen((current) => !current)
      }
    }
    document.addEventListener("keydown", down)
    return () => document.removeEventListener("keydown", down)
  }, [])

  const groups = useMemo(
    () => [
      {
        heading: "移動",
        items: [
          {
            id: "search",
            label: "ドキュメントを検索",
            icon: <Search />,
            shortcut: "⌘K",
            action: () => setOpen(false),
          },
          {
            id: "files",
            label: "最近のファイル",
            icon: <FileText />,
            action: () => setOpen(false),
          },
        ],
      },
      {
        heading: "ツール",
        items: [
          {
            id: "calendar",
            label: "カレンダー",
            icon: <Calendar />,
            action: () => setOpen(false),
          },
          { id: "emoji", label: "絵文字を検索", icon: <Smile />, action: () => setOpen(false) },
          {
            id: "calculator",
            label: "計算機",
            icon: <Calculator />,
            action: () => setOpen(false),
          },
        ],
      },
      {
        heading: "設定",
        items: [
          {
            id: "profile",
            label: "プロフィール",
            icon: <UserRound />,
            shortcut: "⌘P",
            action: () => setOpen(false),
          },
          {
            id: "billing",
            label: "請求",
            icon: <CreditCard />,
            shortcut: "⌘B",
            action: () => setOpen(false),
          },
          {
            id: "settings",
            label: "環境設定",
            icon: <Settings />,
            shortcut: "⌘S",
            action: () => setOpen(false),
          },
        ],
      },
    ],
    []
  )

  return (
    <div className="flex flex-col items-center gap-3">
      <p className="text-sm text-muted-foreground">
        キーボードから開く場合は <Kbd>⌘K</Kbd>
      </p>
      <Button type="button" variant="outline" onClick={() => setOpen(true)}>
        コマンドパレットを開く
      </Button>
      <CommandPalette
        open={open}
        onOpenChange={setOpen}
        dialogTitle="コマンドパレット"
        placeholder="コマンドまたはページを検索..."
        emptyMessage="一致するコマンドがありません。"
        clearLabel="検索をクリア"
        groups={groups}
      />
    </div>
  )
}

設計の判断

  • 並びは呼ぶ側のデータで決める。groups に見出しと項目を渡すと、群ごとに見出しが付き、群と群のあいだに区切りが入ります。資料が言う「最近使ったもの」「移動」「操作」の並べ分けは、この形でそのまま書けます。
  • 打った文字の当たり先を広げる。項目ごとの keywords に、表示名だけでなくショートカットの文字も入れています。入力欄は開いた時点で焦点が入り、消すボタンには clearLabel で名前を付けます。見つからないときの文言は LocaleProvider から取るので、日本語の画面では日本語で出ます。
  • 画面の高さを超えない。中身の高さは画面の高さから余白を引いた値で頭打ちにしてあります。項目が増えても、パレットが画面の外にはみ出して下が押せなくなることがありません。上下キー・Enter・Esc と焦点の閉じ込めは土台の CommandDialog が持ちます。

使用コンポーネント

いつ・なぜ使うか(UIXHERO)

「いつ・なぜ使うか」の判断は、姉妹サイト UIXHERO の記事で解説しています。