コンボボックス (オートコンプリート)
コンボボックスは、キーボードナビゲーションの堅牢なサポートを備えた、アプリのアクセシブルなオートコンプリートおよびコマンドパレットの基礎となります。
まず、npm を使用して Headless UI をインストールします
npm install @headlessui/react
コンボボックスは、Combobox
、Combobox.Input
、Combobox.Button
、Combobox.Options
、Combobox.Option
および Combobox.Label
コンポーネントを使用して構築されます。
Combobox.Input
は、検索時に自動的に Combobox.Options
を開閉します。
あいまい検索ライブラリをクライアント側で使用するか、API へのサーバー側リクエストを行うかなど、結果をどのようにフィルタリングするかは完全にユーザーが管理します。この例では、デモ目的でロジックを単純に保ちます。
import { useState } from 'react' import { Combobox } from '@headlessui/react' const people = [ 'Durward Reynolds', 'Kenton Towne', 'Therese Wunsch', 'Benedict Kessler', 'Katelyn Rohan', ] function MyCombobox() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.toLowerCase().includes(query.toLowerCase()) }) return ( <Combobox value={selectedPerson} onChange={setSelectedPerson}> <Combobox.Input onChange={(event) => setQuery(event.target.value)} /> <Combobox.Options> {filteredPeople.map((person) => ( <Combobox.Option key={person} value={person}> {person} </Combobox.Option> ))} </Combobox.Options> </Combobox> ) }
Headless UI は、どのリストボックスオプションが現在選択されているか、ポップオーバーが開いているか閉じているか、メニュー内のどの項目がキーボードで現在アクティブであるかなど、各コンポーネントに関する多くの状態を追跡します。
ただし、コンポーネントはヘッドレスで、すぐに使用できる状態では完全にスタイル設定されていないため、各状態に必要なスタイルを自分で提供するまで、UI でこの情報を確認することはできません。
各コンポーネントは、現在の状態に関する情報を、条件付きで異なるスタイルを適用したり、異なるコンテンツをレンダリングするために使用できるレンダープロップを介して公開します。
たとえば、Combobox.Option
コンポーネントは、オプションがマウスまたはキーボードで現在フォーカスされているかどうかを示す active
状態と、そのオプションが Combobox
の現在の value
と一致するかどうかを示す selected
状態を公開します。
import { useState, Fragment } from 'react' import { Combobox } from '@headlessui/react' import { CheckIcon } from '@heroicons/react/20/solid' const people = [ { id: 1, name: 'Durward Reynolds' }, { id: 2, name: 'Kenton Towne' }, { id: 3, name: 'Therese Wunsch' }, { id: 4, name: 'Benedict Kessler' }, { id: 5, name: 'Katelyn Rohan' }, ] function MyCombobox() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return ( <Combobox value={selectedPerson} onChange={setSelectedPerson}> <Combobox.Input onChange={(event) => setQuery(event.target.value)} displayValue={(person) => person.name} /> <Combobox.Options> {filteredPeople.map((person) => ( /* Use the `active` state to conditionally style the active option. */ /* Use the `selected` state to conditionally style the selected option. */ <Combobox.Option key={person.id} value={person} as={Fragment}>
{({ active, selected }) => (<li className={`${active ? 'bg-blue-500 text-white' : 'bg-white text-black'}`} >{selected && <CheckIcon />}{person.name} </li> )} </Combobox.Option> ))} </Combobox.Options> </Combobox> ) }
各コンポーネントの完全なレンダープロップ API については、コンポーネント API ドキュメントを参照してください。
各コンポーネントは、現在の状態に関する情報を、条件付きで異なるスタイルを適用するために使用できる data-headlessui-state
属性を介して公開します。
レンダープロップ APIのいずれかの状態が true
の場合、それらはスペースで区切られた文字列としてこの属性にリストされるため、[attr~=value]
の形式でCSS属性セレクターを使用してそれらをターゲットにすることができます。
たとえば、コンボボックスが開いていて、2 番目のオプションが selected
と active
の両方である場合、子 Combobox.Option
コンポーネントを含む Combobox.Options
コンポーネントがレンダリングする内容は次のとおりです。
<!-- Rendered `Combobox.Options` --> <ul data-headlessui-state="open"> <li data-headlessui-state="">Wade Cooper</li> <li data-headlessui-state="active selected">Arlene Mccoy</li> <li data-headlessui-state="">Devon Webb</li> </ul>
Tailwind CSSを使用している場合は、@headlessui/tailwindcssプラグインを使用して、ui-open:*
や ui-active:*
などの修飾子でこの属性をターゲットにできます。
import { useState, Fragment } from 'react' import { Combobox } from '@headlessui/react' import { CheckIcon } from '@heroicons/react/20/solid' const people = [ { id: 1, name: 'Durward Reynolds' }, { id: 2, name: 'Kenton Towne' }, { id: 3, name: 'Therese Wunsch' }, { id: 4, name: 'Benedict Kessler' }, { id: 5, name: 'Katelyn Rohan' }, ] function MyCombobox() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return ( <Combobox value={selectedPerson} onChange={setSelectedPerson}> <Combobox.Input onChange={(event) => setQuery(event.target.value)} displayValue={(person) => person.name} /> <Combobox.Options> {filteredPeople.map((person) => ( <Combobox.Option key={person.id} value={person}
className="ui-active:bg-blue-500 ui-active:text-white ui-not-active:bg-white ui-not-active:text-black"><CheckIcon className="hidden ui-selected:block" />{person.name} </Combobox.Option> ))} </Combobox.Options> </Combobox> ) }
文字列のみを値として提供できるネイティブ HTML フォームコントロールとは異なり、Headless UI は複雑なオブジェクトのバインドもサポートしています。
オブジェクトをバインドするときは、選択したオプションの文字列表現が入力にレンダリングされるように、Combobox.Input
に displayValue
を設定してください
import { useState } from 'react' import { Combobox } from '@headlessui/react'
const people = [{ id: 1, name: 'Durward Reynolds', unavailable: false },{ id: 2, name: 'Kenton Towne', unavailable: false },{ id: 3, name: 'Therese Wunsch', unavailable: false },{ id: 4, name: 'Benedict Kessler', unavailable: true },{ id: 5, name: 'Katelyn Rohan', unavailable: false },]function MyCombobox() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return (<Combobox value={selectedPerson} onChange={setSelectedPerson}><Combobox.Input onChange={(event) => setQuery(event.target.value)}displayValue={(person) => person.name}/> <Combobox.Options> {filteredPeople.map((person) => ( <Combobox.Option key={person.id}value={person}disabled={person.unavailable} > {person.name} </Combobox.Option> ))} </Combobox.Options> </Combobox> ) }
オブジェクトを値としてバインドする場合は、オブジェクトの同じインスタンスを Combobox
の value
と対応する Combobox.Option
の両方として使用するようにしてください。そうしないと、等しくなることができず、コンボボックスが正しく動作しなくなります。
同じオブジェクトの異なるインスタンスを簡単に操作できるようにするために、by
プロパティを使用して、オブジェクトの同一性を比較する代わりに特定のフィールドでオブジェクトを比較できます
import { useState } from 'react' import { Combobox } from '@headlessui/react' const departments = [ { id: 1, name: 'Marketing', contact: 'Durward Reynolds' }, { id: 2, name: 'HR', contact: 'Kenton Towne' }, { id: 3, name: 'Sales', contact: 'Therese Wunsch' }, { id: 4, name: 'Finance', contact: 'Benedict Kessler' }, { id: 5, name: 'Customer service', contact: 'Katelyn Rohan' }, ]
function DepartmentPicker({ selectedDepartment, onChange }) {const [query, setQuery] = useState('') const filteredDepartments = query === '' ? departments : departments.filter((department) => { return department.name.toLowerCase().includes(query.toLowerCase()) }) return (<Combobox value={selectedDepartment} by="id" onChange={onChange}><Combobox.Input onChange={(event) => setQuery(event.target.value)} displayValue={(department) => department.name} /> <Combobox.Options> {filteredDepartments.map((department) => ( <Combobox.Option key={department.id} value={department}> {department.name} </Combobox.Option> ))} </Combobox.Options> </Combobox> ) }
オブジェクトの比較方法を完全に制御したい場合は、独自の比較関数を by
プロパティに渡すこともできます
import { useState } from 'react' import { Combobox } from '@headlessui/react' const departments = [ { id: 1, name: 'Marketing', contact: 'Durward Reynolds' }, { id: 2, name: 'HR', contact: 'Kenton Towne' }, { id: 3, name: 'Sales', contact: 'Therese Wunsch' }, { id: 4, name: 'Finance', contact: 'Benedict Kessler' }, { id: 5, name: 'Customer service', contact: 'Katelyn Rohan' }, ]
function compareDepartments(a, b) {return a.name.toLowerCase() === b.name.toLowerCase()}function DepartmentPicker({ selectedDepartment, onChange }) { const [query, setQuery] = useState('') const filteredDepartments = query === '' ? departments : departments.filter((department) => { return department.name.toLowerCase().includes(query.toLowerCase()) }) return ( <Combobox value={selectedDepartment}by={compareDepartments}onChange={onChange} > <Combobox.Input onChange={(event) => setQuery(event.target.value)} displayValue={(department) => department.name} /> <Combobox.Options> {filteredDepartments.map((department) => ( <Combobox.Option key={department.id} value={department}> {department.name} </Combobox.Option> ))} </Combobox.Options> </Combobox> ) }
コンボボックスで複数の値を選択できるようにするには、multiple
プロパティを使用し、単一のオプションではなく配列を value
に渡します。
import { useState } from 'react' import { Combobox } from '@headlessui/react' const people = [ { id: 1, name: 'Durward Reynolds' }, { id: 2, name: 'Kenton Towne' }, { id: 3, name: 'Therese Wunsch' }, { id: 4, name: 'Benedict Kessler' }, { id: 5, name: 'Katelyn Rohan' }, ] function MyCombobox() {
const [selectedPeople, setSelectedPeople] = useState([people[0], people[1]])return ( <Combobox value={selectedPeople} onChange={setSelectedPeople} multiple> {selectedPeople.length > 0 && ( <ul> {selectedPeople.map((person) => ( <li key={person.id}>{person.name}</li> ))} </ul> )}<Combobox.Input /><Combobox.Options> {people.map((person) => ( <Combobox.Option key={person.id} value={person}> {person.name} </Combobox.Option> ))} </Combobox.Options> </Combobox> ) }
displayValue
プロパティは、selectedPeople
が既に入力の上にあるリストに表示されているため、省略されています。Combobox.Input
に項目を表示したい場合は、displayValue
は配列を受け取ります。
import { useState } from 'react' import { Combobox } from '@headlessui/react' const people = [ { id: 1, name: 'Durward Reynolds' }, { id: 2, name: 'Kenton Towne' }, { id: 3, name: 'Therese Wunsch' }, { id: 4, name: 'Benedict Kessler' }, { id: 5, name: 'Katelyn Rohan' }, ] function MyCombobox() {
const [selectedPeople, setSelectedPeople] = useState([people[0], people[1]])return ( <Combobox value={selectedPeople} onChange={setSelectedPeople} multiple> <Combobox.InputdisplayValue={(people) =>people.map((person) => person.name).join(', ')}/> <Combobox.Options> {people.map((person) => ( <Combobox.Option key={person.id} value={person}> {person.name} </Combobox.Option> ))} </Combobox.Options> </Combobox> ) }
これにより、オプションを選択するときにコンボボックスが開いたままになり、オプションを選択するとその場で切り替えられます。
オプションが追加または削除されるたびに、選択したすべてのオプションを含む配列を使用して onChange
ハンドラーが呼び出されます。
デフォルトでは、Combobox
はスクリーンリーダーのラベルとして入力内容を使用します。支援技術にアナウンスされる内容をより詳細に制御したい場合は、Combobox.Label
コンポーネントを使用します。
import { useState } from 'react' import { Combobox } from '@headlessui/react' const people = [ { id: 1, name: 'Durward Reynolds' }, { id: 2, name: 'Kenton Towne' }, { id: 3, name: 'Therese Wunsch' }, { id: 4, name: 'Benedict Kessler' }, { id: 5, name: 'Katelyn Rohan' }, ] function MyCombobox() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return ( <Combobox value={selectedPerson} onChange={setSelectedPerson}>
<Combobox.Label>Assignee:</Combobox.Label><Combobox.Input onChange={(event) => setQuery(event.target.value)} displayValue={(person) => person.name} /> <Combobox.Options> {filteredPeople.map((person) => ( <Combobox.Option key={person.id} value={person}> {person.name} </Combobox.Option> ))} </Combobox.Options> </Combobox> ) }
コンボボックスに name
プロパティを追加すると、非表示の input
要素がレンダリングされ、選択した値と同期されます。
import { useState } from 'react' import { Combobox } from '@headlessui/react' const people = [ { id: 1, name: 'Durward Reynolds' }, { id: 2, name: 'Kenton Towne' }, { id: 3, name: 'Therese Wunsch' }, { id: 4, name: 'Benedict Kessler' }, { id: 5, name: 'Katelyn Rohan' }, ] function Example() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return ( <form action="/projects/1/assignee" method="post"> <Combobox value={selectedPerson} onChange={setSelectedPerson}
name="assignee"> <Combobox.Input onChange={(event) => setQuery(event.target.value)} displayValue={(person) => person.name} /> <Combobox.Options> {filteredPeople.map((person) => ( <Combobox.Option key={person.id} value={person}> {person.name} </Combobox.Option> ))} </Combobox.Options> </Combobox> <button>Submit</button> </form> ) }
これにより、ネイティブ HTML <form>
内でコンボボックスを使用したり、コンボボックスがネイティブ HTML フォームコントロールであるかのように従来のフォーム送信を行うことができます。
文字列のような基本値は、その値を含む単一の非表示入力としてレンダリングされますが、オブジェクトのような複雑な値は、名前の角括弧表記を使用して複数の入力にエンコードされます
<input type="hidden" name="assignee[id]" value="1" /> <input type="hidden" name="assignee[name]" value="Durward Reynolds" />
value
の代わりに defaultValue
プロパティを Combobox
に提供すると、Headless UI は状態を内部で追跡するため、非制御コンポーネントとして使用できます。
import { useState } from 'react' import { Combobox } from '@headlessui/react' const people = [ { id: 1, name: 'Durward Reynolds' }, { id: 2, name: 'Kenton Towne' }, { id: 3, name: 'Therese Wunsch' }, { id: 4, name: 'Benedict Kessler' }, { id: 5, name: 'Katelyn Rohan' }, ] function Example() { const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return ( <form action="/projects/1/assignee" method="post">
<Combobox name="assignee" defaultValue={people[0]}><Combobox.Input onChange={(event) => setQuery(event.target.value)} displayValue={(person) => person.name} /> <Combobox.Options> {filteredPeople.map((person) => ( <Combobox.Option key={person.id} value={person}> {person.name} </Combobox.Option> ))} </Combobox.Options> </Combobox> <button>Submit</button> </form> ) }
これにより、コンボボックスをHTMLフォームで使用する場合や、Reactの状態を使用して追跡する代わりにFormDataを使用して状態を収集するフォームAPIを使用する場合にコードを簡略化できます。
コンポーネントの値が変更されたときに副作用を実行する必要がある場合に備えて、提供する onChange
プロパティは引き続き呼び出されますが、コンポーネントの状態を自分で追跡するためにそれを使用する必要はありません。
query
値に基づいて動的な Combobox.Option
を含めることで、リストに存在しない独自の値をユーザーが入力できるようにすることができます。
import { useState } from 'react' import { Combobox } from '@headlessui/react' const people = [ { id: 1, name: 'Durward Reynolds' }, { id: 2, name: 'Kenton Towne' }, { id: 3, name: 'Therese Wunsch' }, { id: 4, name: 'Benedict Kessler' }, { id: 5, name: 'Katelyn Rohan' }, ] function Example() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return ( <Combobox value={selectedPerson} onChange={setSelectedPerson}> <Combobox.Input onChange={(event) => setQuery(event.target.value)} displayValue={(person) => person.name} /> <Combobox.Options>
{query.length > 0 && (<Combobox.Option value={{ id: null, name: query }}>Create "{query}"</Combobox.Option>)}{filteredPeople.map((person) => ( <Combobox.Option key={person.id} value={person}> {person.name} </Combobox.Option> ))} </Combobox.Options> </Combobox> ) }
構築しているものによっては、<Combobox.Options>
の外側にあるアクティブなオプションに関する追加情報をレンダリングすると便利な場合があります。たとえば、コマンドパレットのコンテキスト内にあるアクティブなオプションのプレビューなどです。このような状況では、activeOption
レンダープロップ引数を読み取って、この情報にアクセスできます。
import { useState } from 'react' import { Combobox } from '@headlessui/react' const people = [ { id: 1, name: 'Durward Reynolds' }, { id: 2, name: 'Kenton Towne' }, { id: 3, name: 'Therese Wunsch' }, { id: 4, name: 'Benedict Kessler' }, { id: 5, name: 'Katelyn Rohan' }, ] function MyCombobox() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return ( <Combobox value={selectedPerson} onChange={setSelectedPerson}>
{({ activeOption }) => (<> <Combobox.Input onChange={(event) => setQuery(event.target.value)} displayValue={(person) => person.name} /> <Combobox.Options> {filteredPeople.map((person) => ( <Combobox.Option key={person.id} value={person}> {person.name} </Combobox.Option> ))} </Combobox.Options>{activeOption && (<div>The current active user is: {activeOption.name}</div>)}</> )} </Combobox> ) }
activeOption
は、現在アクティブな Combobox.Option
の value
になります。
デフォルトでは、Combobox.Options
インスタンスは、Combobox
コンポーネント自体内で追跡される内部 open
状態に基づいて自動的に表示/非表示になります。
import { useState } from 'react' import { Combobox } from '@headlessui/react' const people = [ { id: 1, name: 'Durward Reynolds' }, { id: 2, name: 'Kenton Towne' }, { id: 3, name: 'Therese Wunsch' }, { id: 4, name: 'Benedict Kessler' }, { id: 5, name: 'Katelyn Rohan' }, ] function MyCombobox() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return ( <Combobox value={selectedPerson} onChange={setSelectedPerson}> <Combobox.Input onChange={(event) => setQuery(event.target.value)} displayValue={(person) => person.name} /> {/* By default, the `Combobox.Options` will automatically show/hide when typing in the `Combobox.Input`, or when pressing the `Combobox.Button`. */} <Combobox.Options> {filteredPeople.map((person) => ( <Combobox.Option key={person.id} value={person}> {person.name} </Combobox.Option> ))} </Combobox.Options> </Combobox> ) }
これを自分で処理したい場合(何らかの理由で追加のラッパー要素を追加する必要がある場合など)、Combobox.Options
インスタンスに static
プロパティを追加して常にレンダリングするように指示し、Combobox
によって提供される open
レンダープロップを調べて、表示/非表示にする要素を自分で制御できます。
import { useState } from 'react' import { Combobox } from '@headlessui/react' const people = [ { id: 1, name: 'Durward Reynolds' }, { id: 2, name: 'Kenton Towne' }, { id: 3, name: 'Therese Wunsch' }, { id: 4, name: 'Benedict Kessler' }, { id: 5, name: 'Katelyn Rohan' }, ] function MyCombobox() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return ( <Combobox value={selectedPerson} onChange={setSelectedPerson}>
{({ open }) => (<> <Combobox.Input onChange={(event) => setQuery(event.target.value)} displayValue={(person) => person.name} />{open && (<div> {/* Using `static`, `Combobox.Options` are always rendered and the `open` state is ignored. */}<Combobox.Options static>{filteredPeople.map((person) => ( <Combobox.Option key={person.id} value={person}> {person.name} </Combobox.Option> ))} </Combobox.Options> </div> )} </> )} </Combobox> ) }
Combobox.Option
を無効にするには、disabled
プロパティを使用します。これにより、マウスとキーボードで選択できなくなり、上下矢印を押したときにスキップされます。
import { useState } from 'react' import { Combobox } from '@headlessui/react' const people = [ { id: 1, name: 'Durward Reynolds', unavailable: false }, { id: 2, name: 'Kenton Towne', unavailable: false }, { id: 3, name: 'Therese Wunsch', unavailable: false }, { id: 4, name: 'Benedict Kessler', unavailable: true }, { id: 5, name: 'Katelyn Rohan', unavailable: false }, ] function MyCombobox() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return ( <Combobox value={selectedPerson} onChange={setSelectedPerson}> <Combobox.Input onChange={(event) => setQuery(event.target.value)} displayValue={(person) => person.name} /> <Combobox.Options> {filteredPeople.map((person) => ( /* Disabled options will be skipped by keyboard navigation. */ <Combobox.Option key={person.id} value={person}
disabled={person.unavailable}> <span className={person.unavailable ? 'opacity-75' : ''}> {person.name} </span> </Combobox.Option> ))} </Combobox.Options> </Combobox> ) }
デフォルトでは、コンボボックスで値を選択すると、コンボボックスを空の値に戻す方法はありません。入力をクリアしてタブで移動すると、値は以前に選択した値に戻ります。
コンボボックスで空の値をサポートする場合は、nullable
プロップを使用してください。
import { useState } from 'react' import { Combobox } from '@headlessui/react' const people = [ { id: 1, name: 'Durward Reynolds', unavailable: false }, { id: 2, name: 'Kenton Towne', unavailable: false }, { id: 3, name: 'Therese Wunsch', unavailable: false }, { id: 4, name: 'Benedict Kessler', unavailable: true }, { id: 5, name: 'Katelyn Rohan', unavailable: false }, ] function MyCombobox() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return (
<Combobox value={selectedPerson} onChange={setSelectedPerson} nullable><Combobox.Input onChange={(event) => setQuery(event.target.value)}displayValue={(person) => person?.name}/> <Combobox.Options> {filteredPeople.map((person) => ( <Combobox.Option key={person.id} value={person}> {person.name} </Combobox.Option> ))} </Combobox.Options> </Combobox> ) }
nullable
プロップを使用すると、入力をクリアして要素から移動すると、onChange
および displayValue
コールバックが null
で呼び出されます。
このプロップは、複数の値を許可する場合、オプションがオンとオフを切り替えるため、何も選択されていない場合は(nullではなく)空の配列になるため、何も影響しません。
コンボボックスパネルの開閉をアニメーション化するには、提供されている Transition
コンポーネントを使用します。Combobox.Options
を <Transition>
でラップするだけで、トランジションが自動的に適用されます。
import { useState } from 'react' import { Combobox, Transition } from '@headlessui/react' const people = [ { id: 1, name: 'Durward Reynolds' }, { id: 2, name: 'Kenton Towne' }, { id: 3, name: 'Therese Wunsch' }, { id: 4, name: 'Benedict Kessler' }, { id: 5, name: 'Katelyn Rohan' }, ] function MyCombobox() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return ( <Combobox value={selectedPerson} onChange={setSelectedPerson}> <Combobox.Input onChange={(event) => setQuery(event.target.value)} displayValue={(person) => person.name} />
<Transitionenter="transition duration-100 ease-out"enterFrom="transform scale-95 opacity-0"enterTo="transform scale-100 opacity-100"leave="transition duration-75 ease-out"leaveFrom="transform scale-100 opacity-100"leaveTo="transform scale-95 opacity-0"><Combobox.Options> {filteredPeople.map((person) => ( <Combobox.Option key={person.id} value={person}> {person.name} </Combobox.Option> ))} </Combobox.Options></Transition></Combobox> ) }
デフォルトでは、組み込みの Transition
コンポーネントは Combobox
コンポーネントと自動的に通信して、開閉状態を処理します。ただし、この動作をより詳細に制御する必要がある場合は、明示的に制御できます。
import { useState } from 'react' import { Combobox, Transition } from '@headlessui/react' const people = [ { id: 1, name: 'Durward Reynolds' }, { id: 2, name: 'Kenton Towne' }, { id: 3, name: 'Therese Wunsch' }, { id: 4, name: 'Benedict Kessler' }, { id: 5, name: 'Katelyn Rohan' }, ] function MyCombobox() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return ( <Combobox value={selectedPerson} onChange={setSelectedPerson}>
{({ open }) => (<><Combobox.Input onChange={(event) => setQuery(event.target.value)} displayValue={(person) => person.name} /> {/* Use the `Transition` + `open` render prop argument to add transitions. */} <Transitionshow={open}enter="transition duration-100 ease-out" enterFrom="transform scale-95 opacity-0" enterTo="transform scale-100 opacity-100" leave="transition duration-75 ease-out" leaveFrom="transform scale-100 opacity-100" leaveTo="transform scale-95 opacity-0" > {/* Don't forget to add `static` to your `Combobox.Options`! */}<Combobox.Options static>{filteredPeople.map((person) => ( <Combobox.Option key={person.id} value={person}> {person.name} </Combobox.Option> ))} </Combobox.Options> </Transition></>)}</Combobox> ) }
レンダリングレスであるため、Headless UI コンポーネントは、Framer Motion や React Spring などの React エコシステムの他のアニメーションライブラリともうまく構成できます。
デフォルトでは、Combobox
とそのサブコンポーネントはそれぞれ、そのコンポーネントに適したデフォルト要素をレンダリングします。
たとえば、Combobox.Label
はデフォルトで label
を、Combobox.Input
は input
を、Combobox.Button
は button
を、Combobox.Options
は ul
を、Combobox.Option
は li
をレンダリングします。対照的に、Combobox
は要素をレンダリングせず、代わりにその子を直接レンダリングします。
as
プロップを使用して、コンポーネントを別の要素または独自のカスタムコンポーネントとしてレンダリングします。カスタムコンポーネントが forward refs を使用して、Headless UI が正しく処理できるようにする必要があります。
import { forwardRef, useState } from 'react' import { Combobox } from '@headlessui/react' const people = [ { id: 1, name: 'Durward Reynolds' }, { id: 2, name: 'Kenton Towne' }, { id: 3, name: 'Therese Wunsch' }, { id: 4, name: 'Benedict Kessler' }, { id: 5, name: 'Katelyn Rohan' }, ]
let MyCustomButton = forwardRef(function (props, ref) {return <button className="..." ref={ref} {...props} />})function MyCombobox() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return (<Combobox as="div" value={selectedPerson} onChange={setSelectedPerson}><Combobox.Input onChange={(event) => setQuery(event.target.value)} displayValue={(person) => person.name} /> <Combobox.Buttonas={MyCustomButton}> Open</Combobox.Button><Combobox.Options as="div">{filteredPeople.map((person) => ( <Combobox.Option as="span" key={person.id} value={person}> {person.name} </Combobox.Option> ))} </Combobox.Options> </Combobox> ) }
要素にラッパー要素なしでその子を直接レンダリングするように指示するには、Fragment
を使用します。
import { useState, Fragment } from 'react' import { Combobox } from '@headlessui/react' const people = [ { id: 1, name: 'Durward Reynolds' }, { id: 2, name: 'Kenton Towne' }, { id: 3, name: 'Therese Wunsch' }, { id: 4, name: 'Benedict Kessler' }, { id: 5, name: 'Katelyn Rohan' }, ] function MyCombobox() { const [selectedPerson, setSelectedPerson] = useState(people[0]) const [query, setQuery] = useState('') const filteredPeople = query === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()) }) return ( <Combobox value={selectedPerson} onChange={setSelectedPerson}> {/* Render a `Fragment` instead of an `input` */} <Combobox.Input
as={Fragment}onChange={(event) => setQuery(event.target.value)} displayValue={(person) => person.name} > <input /> </Combobox.Input> <Combobox.Options> {filteredPeople.map((person) => ( <Combobox.Option key={person.id} value={person}> {person.name} </Combobox.Option> ))} </Combobox.Options> </Combobox> ) }
コンボボックスが開くと、Combobox.Input
はフォーカスされたままになります。
Combobox.Button
はデフォルトのタブフローでは無視されます。つまり、Combobox.Input
で Tab
を押すと、Combobox.Button
をスキップします。
Combobox.Button
をクリックすると、オプションリストが開閉します。オプションリストの外側をクリックすると、コンボボックスが閉じます。
コマンド | 説明 |
下矢印, または 上矢印 | コンボボックスを開き、選択した項目にフォーカスを移動します |
Enter, Space, 下矢印, または 上矢印 | コンボボックスを開き、入力にフォーカスを移動し、選択した項目を選択します |
Esc コンボボックスが開いている場合 | コンボボックスを閉じ、入力フィールドで選択した項目を復元します |
下矢印 または 上矢印コンボボックスが開いている場合 | 前の/次の無効でない項目にフォーカスを移動します |
Home または PageUp コンボボックスが開いている場合 | 最初の無効でない項目にフォーカスを移動します |
End または PageDown コンボボックスが開いている場合 | 最後の無効でない項目にフォーカスを移動します |
Enter コンボボックスが開いている場合 | 現在の項目を選択します |
Enter コンボボックスが閉じていて、フォーム内にある場合 | フォームを送信します |
Tab コンボボックスが開いている場合 | 現在アクティブな項目を選択し、コンボボックスを閉じます |
A–Z または a–z コンボボックスが開いている場合 | リストをフィルタリングできる |
プロパティ | デフォルト | 説明 |
as | Fragment | 文字列 | コンポーネント
|
disabled | false | ブール値 コンボボックスコンポーネント全体および関連する子を無効にするために使用します。 |
value | — | T 選択された値。 |
defaultValue | — | T 非制御コンポーネントとして使用する場合のデフォルト値。 |
by | — | keyof T | ((a: T, z: T) => boolean) 特定のフィールドでオブジェクトを比較するためにこれを使用するか、オブジェクトの比較方法を完全に制御するために独自の比較関数を渡します。 |
onChange | — | (value: T) => void 新しいオプションが選択されたときに呼び出す関数。 |
name | — | 文字列 このコンポーネントをフォーム内で使用する場合に使用される名前。 |
nullable | — | ブール値 コンボボックスをクリアできるかどうか。 |
multiple | false | ブール値 複数のオプションを選択できるかどうか。 |
レンダリングプロップ | 説明 |
value |
選択された値。 |
open |
コンボボックスが開いているかどうか。 |
disabled |
コンボボックスが無効になっているかどうか。 |
activeIndex |
アクティブなオプションのインデックスまたはアクティブなオプションがない場合は null。 |
activeOption |
アクティブなオプションまたはアクティブなオプションがない場合は null。 |
プロパティ | デフォルト | 説明 |
as | input | 文字列 | コンポーネント
|
displayValue | — | (item: T) => string
|
レンダリングプロップ | 説明 |
open |
コンボボックスが開いているかどうか。 |
disabled |
コンボボックスが無効になっているかどうか。 |
プロパティ | デフォルト | 説明 |
as | button | 文字列 | コンポーネント
|
レンダリングプロップ | 説明 |
value |
選択された値。 |
open |
コンボボックスが開いているかどうか。 |
disabled |
コンボボックスが無効になっているかどうか。 |
コンボボックスがスクリーンリーダーにアナウンスするテキストをより詳細に制御するために使用できるラベル。その id
属性は自動的に生成され、aria-labelledby
属性を介してルート Combobox
コンポーネントにリンクされます。
プロパティ | デフォルト | 説明 |
as | label | 文字列 | コンポーネント
|
レンダリングプロップ | 説明 |
open |
コンボボックスが開いているかどうか。 |
disabled |
コンボボックスが無効になっているかどうか。 |
プロパティ | デフォルト | 説明 |
as | ul | 文字列 | コンポーネント
|
static | false | ブール値 要素が内部で管理される開閉状態を無視するかどうか。 注意: |
unmount | true | ブール値 要素を開閉状態に基づいてアンマウントするか非表示にするかどうか。 注意: |
hold | false | boolean マウスがアクティブなオプションから離れても、アクティブなオプションをアクティブな状態に保つかどうか。 |
レンダリングプロップ | 説明 |
open |
コンボボックスが開いているかどうか。 |
プロパティ | デフォルト | 説明 |
value | — | T オプションの値。 |
as | li | 文字列 | コンポーネント
|
disabled | false | ブール値 キーボードナビゲーションおよび ARIA の目的で、オプションを無効にするかどうか。 |
レンダリングプロップ | 説明 |
active |
オプションがアクティブ/フォーカスされているオプションであるかどうか。 |
selected |
オプションが選択されたオプションであるかどうか。 |
disabled |
キーボードナビゲーションおよび ARIA の目的で、オプションを無効にするかどうか。 |