組合方塊 (自動完成)

組合方塊是應用程式可存取自動完成和命令面板的基礎,並全面支援鍵盤導覽。

首先,透過 npm 安裝 Headless UI

npm install @headlessui/react

組合方塊是由 組合方塊組合方塊。輸入組合方塊。按鈕組合方塊。選項組合方塊。選項組合方塊。標籤 元件組成。

搜尋時,組合方塊。輸入 將自動開啟/關閉 組合方塊。選項

您完全掌握如何過濾結果,不論是使用模糊搜尋函式庫在用戶端或透過向 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 會追蹤有關每個元件的許多資訊,例如目前已選取哪個方塊選項、彈出視窗是否已開啟或關閉,或是目前透過鍵盤將哪個選單中的哪個項目設為目前項目。

但由於元件是標準且完全沒有套用樣式,因此在您提供每一種狀態的樣式之前,您在使用者介面上無法看到此資訊。

每個元件會透過 呈現道具公開有關自己目前狀態的資訊,您可以使用這些道具來有條件套用不同樣式或呈現不同內容。

例如,Combobox.Option 元件會公開 active 狀態,透過滑鼠或鍵盤告訴你選項目前是否有焦點,以及 selected 狀態,告訴你這個選項是否與 Combobox 目前的 value 相符。

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 屬性選擇器 來針對它們。

例如,這是當組合方塊開啟且第二個選項同時 selectedactive 時,Combobox.Options 元件與一些子 Combobox.Option 元件渲染的內容

<!-- 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.InputdisplayValue 欄位設定 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> ) }

將物件繫結為值時,請務必確定你將其作為物件的 相同實體 使用,用做 Comboboxvalue 以及對應的 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.Input
displayValue={(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" />

如果您為 Combobox 提供 defaultValue 屬性而非提供 value,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 表單 一併使用時或在將其與使用 FormData 而非使用 React 狀態來收集其狀態的 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 render prop 傳遞參數,以取得這些資訊。

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.Optionvalue

預設情況下,你的 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> ) }

如果你希望自己處理這部分(可能是因為某種原因需要額外加上包覆元素),你可以將 static prop 加入 Combobox.Options 執行個體,讓它永遠顯示,並檢查 Combobox 提供的 open render prop,以自行控制顯示或隱藏哪個元素。

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> ) }

使用 disabled prop 停用 Combobox.Option。這樣會讓選項無法透過滑鼠和鍵盤選取,且在按下上/下箭頭時會跳過這個選項。

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> ) }

預設情況下,一旦你選取下拉式選單中的值,就無法將下拉式選單清除回空值——清除輸入並使用 tab 鍵離開時,值會回到先前選取的值。

如果你希望在你的下拉式選單中支援空值,請使用 nullable prop。

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 prop 時,清除輸入並離開元素會使用 null 呼叫你的 onChangedisplayValue 回呼函式。

允許 選取多個值 時此 prop 不會做任何事,原因是選項會切換開關,假如沒有選取任何東西,結果就會得到一個空陣列(而非 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} />
<Transition
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"
>
<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. */} <Transition
show={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 元件沒有實體,因此它們也與 React 生態系統中的其他動畫程式庫組成得很好,例如 Framer MotionReact Spring

預設下,Combobox 和其子元件會各呈現對該元件有意義的預設元素。

例如,Combobox.Label 預設會呈現 labelCombobox.Input 會呈現 inputCombobox.Button 會呈現 buttonCombobox.Options 會呈現 ul,而 Combobox.Option 會呈現 li。對照來看,Combobox 不會呈現元素,而是直接呈現其子元素。

使用 as 屬性可以把元件呈現為不同的元素或你自己的自訂元件,請確認自訂元件有 轉送 ref,這樣 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.Button
as={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 會忽視預設的 tab 順序,這表示在 Combobox.Input 中按下 Tab 會略過 Combobox.Button

點擊 Combobox.Button 會將選項清單切換為開啟與關閉狀態。點擊選項清單外的任何位置都會關閉組合方塊。

命令說明

向下箭頭向上箭頭Combobox.Input 聚焦時

開啟組合方塊並聚焦所選項目

EnterSpace向下箭頭向上箭頭Combobox.Button 聚焦時

開啟組合方塊,聚焦輸入內容並選取選取的項目

Esc在組合方塊開啟時

關閉組合方塊並還原輸入欄位中的選取項目

向下箭頭向上箭頭組合方塊開啟時

聚焦上一個/下一個未停用的項目

HomePageUp在組合方塊開啟時

聚焦第一個非停用項目

EndPageDown在組合方塊開啟時

聚焦最後一個非停用項目

Enter在組合方塊開啟時

選取目前項目

當組合方塊關閉並且在表單中按Enter

提交表單

當組合方塊開啟時按Tab

選取目前已啟用的項目並關閉組合方塊

當組合方塊開啟時按A–Za–z

呼叫onChange,讓您可以篩選清單

所有相關 ARIA 屬性都會自動管理。

主要的組合方塊元件。

屬性預設值說明
as片段
字串 | 元件

Combobox 應以其形式呈現的元素或元件。

disabledfalse
布林

使用此屬性可以停用整個組合方塊元件與相關子元件。

value
T

已選取的值。

defaultValue
T

使用做為非受控元件時的預設值。

by
T 的關鍵字 | ((a: T, z: T) => boolean)

使用此屬性可按特定欄位來比較物件,或傳遞您自己的比較函數,以完全控制物件的比較方式。

onChange
(value: T) => void

當選取新選項時呼叫的函數。

name
字串

在表單中使用此元件時使用的名稱。

nullable
布林

是否可以清除組合方塊。

multiplefalse
布林

是否允許多選。

呈現屬性說明
value

T

已選取的值。

open

布林

組合方塊是否開啟。

disabled

布林

組合方塊是否已停用。

activeIndex

數字 | null

若沒有任何已啟用的選項,則為選項的索引或 null。

activeOption

T | null

已啟用的選項或沒有任何已啟用的選項則為 null。

組合方塊的輸入。

屬性預設值說明
asinput
字串 | 元件

Combobox.Input 應以其形式呈現的元素或元件。

displayValue
(item: T) => 字串

你的value的字串表示。

呈現屬性說明
open

布林

組合方塊是否開啟。

disabled

布林

組合方塊是否已停用。

組合方塊的按鈕。

屬性預設值說明
as按鈕
字串 | 元件

元素或組件組合方塊.Button應呈現為什麼。

呈現屬性說明
value

T

已選取的值。

open

布林

組合方塊是否開啟。

disabled

布林

組合方塊是否已停用。

一個標籤,可用於更進一步控制組合方塊將會向螢幕朗讀機公布的文字。其id屬性將會自動產生,並透過aria-labelledby屬性連結到根組合方塊組件。

屬性預設值說明
as標籤
字串 | 元件

元素或組件組合方塊.Label應呈現為什麼。

呈現屬性說明
open

布林

組合方塊是否開啟。

disabled

布林

組合方塊是否已停用。

直接包裝在你的自訂組合方塊中的選單列表的組件。

屬性預設值說明
asul
字串 | 元件

元素或組件組合方塊.Options應呈現為什麼。

靜態false
布林

元素是否應該忽略內部管理的開啟/關閉狀態。

注意:靜態取消掛載不能同時使用。如果你嘗試這樣做,你會得到一個 TypeScript 錯誤。

取消掛載true
布林

元素是否應該基於開啟/關閉狀態而取消掛載或隱藏。

注意:靜態取消掛載不能同時使用。如果你嘗試這樣做,你會得到一個 TypeScript 錯誤。

保留false
布林值

即使滑鼠指標離開目前選項,活動選項是否應該保持活動。

呈現屬性說明
open

布林

組合方塊是否開啟。

用於包裝組合方塊中的每個項目。

屬性預設值說明
value
T

選項值。

asli
字串 | 元件

元素或組件組合方塊.選項應呈現為什麼。

disabledfalse
布林

選項是否應該禁用語音導覽及 ARIA 目的。

呈現屬性說明
活動中

布林

選項是否為活動中/焦點選項。

已選取

布林

選項是否為已選取的選項。

disabled

布林

選項是否禁用語音導覽及 ARIA 目的。

如果你有興趣使用headless UI及Tailwind CSS製作預先設計的組件範例,請查看Tailwind UI -由我們建立的一系列設計精美的精心製作組件。

這是支持像此類開源專案相關工作的絕佳方式,並使我們得以改善專案並持續維護。