🐈⬛
shadcn/uiで検索ボックスを作る
はじめに
shadcn/uiを使って次のような検索ボックスを作ったので、メモとして残します
使用技術
前提とする使用技術は以下の通り
shadcn/uiのcomponentとしてはCommandを使用します
上記のCommandのexampleは、基本的に候補となるデータがクライアント側に全て揃っている前提なので、入力文字列に応じてサーバーから候補データを取得するとなると、少し工夫が必要になります
実装
全体像は以下のようになります
"use client"
import { Command, CommandEmpty, CommandInput, CommandItem, CommandList } from "@/components/ui/command"
import { FC, useCallback, useEffect, useRef, useState } from "react"
...
const Page: FC = () => {
const inputRef = useRef<HTMLInputElement>(null); //focus周りの挙動を制御するために使用
const [open, setOpen] = useState(false);
const [selected, setSelected] = useState<string>()
const [searchResults, setSearchResults] = useState<string[]>([])
const [inputText, setInputText] = useState('')
const handleKeyDown = useCallback((e: React.KeyboardEvent<HTMLDivElement>) => {
const input = inputRef.current
if (input) {
if (e.key === "Escape") {
input.blur();
}
}
}, [])
useEffect(() => {
// APIから検索結果を取得
setSearchResults(mockSearch(inputText))
}, [inputText])
return (
<div className='flex items-center size-full justify-center p-10'>
<Command shouldFilter={false} onKeyDown={handleKeyDown} value={selected} className='overflow-visible w-[512px]'>
<CommandInput value={inputText} ref={inputRef} placeholder='猫の種類を検索' onValueChange={(text) => {
setInputText(text)
// 再編集時には選択済み項目をクリア
if (selected) {
setSelected(undefined)
}
}}
onBlur={() => setOpen(false)}
onFocus={() => {
setOpen(true)
if (selected) {
inputRef.current?.select() // フォーカス時に選択済み項目がある場合、全選択する
}
}}
/>
<div className="relative mt-2">
{!selected && open && (
<CommandList className="absolute left-0 top-0 w-full rounded bg-background shadow-md">
<CommandEmpty className="text-muted-foreground px-4 py-2">ヒットなし</CommandEmpty>
{searchResults?.map(v => (
<CommandItem
className="flex items-center gap-2"
onSelect={() => {
setSelected(v)
setInputText(v)
}}
value={v} key={v}>
{v}
</CommandItem>
))}
</CommandList>
)}
</div>
</Command>
</div>
)
}
export default Page
ポイント
-
CommandList
(選択候補)は検索ボックスにフォーカスが当たったタイミングで表示させたいので、表示・非表示の状態をこちらで制御できるようにしています(open, setOpen
)。この制御がないと、input
からフォーカスが外れた後も選択候補が表示され続けてしまいます - リストから候補が選択された(もしくはESCキーが押下された)場合、
CommandList
を閉じるようにしています - 入力文字列に対する一致処理はAPI側で実施する予定なので、
Command
側では不要なフィルタリングは行わないようにshouldFilter={false}
を指定しています
その他
API側で検索を行うという意図においては、次のIssueも参考になるかもしれません
Discussion