組合方塊 (自動完成)
組合方塊是應用程式可存取自動完成和命令面板的基礎,並全面支援鍵盤導覽。
首先,透過 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 屬性選擇器 來針對它們。
例如,這是當組合方塊開啟且第二個選項同時 selected
和 active
時,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.Input
的 displayValue
欄位設定 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" />
如果您為 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.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> ) }
如果你希望自己處理這部分(可能是因為某種原因需要額外加上包覆元素),你可以將 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
呼叫你的 onChange
和 displayValue
回呼函式。
允許 選取多個值 時此 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} />
<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 元件沒有實體,因此它們也與 React 生態系統中的其他動畫程式庫組成得很好,例如 Framer Motion 和 React Spring。
預設下,Combobox
和其子元件會各呈現對該元件有意義的預設元素。
例如,Combobox.Label
預設會呈現 label
,Combobox.Input
會呈現 input
,Combobox.Button
會呈現 button
,Combobox.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.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
會忽視預設的 tab 順序,這表示在 Combobox.Input
中按下 Tab
會略過 Combobox.Button
。
點擊 Combobox.Button
會將選項清單切換為開啟與關閉狀態。點擊選項清單外的任何位置都會關閉組合方塊。
命令 | 說明 |
向下箭頭,或向上箭頭 | 開啟組合方塊並聚焦所選項目 |
Enter,Space,向下箭頭,或向上箭頭 | 開啟組合方塊,聚焦輸入內容並選取選取的項目 |
Esc在組合方塊開啟時 | 關閉組合方塊並還原輸入欄位中的選取項目 |
向下箭頭或向上箭頭組合方塊開啟時 | 聚焦上一個/下一個未停用的項目 |
Home或PageUp在組合方塊開啟時 | 聚焦第一個非停用項目 |
End或PageDown在組合方塊開啟時 | 聚焦最後一個非停用項目 |
Enter在組合方塊開啟時 | 選取目前項目 |
當組合方塊關閉並且在表單中按Enter | 提交表單 |
當組合方塊開啟時按Tab | 選取目前已啟用的項目並關閉組合方塊 |
當組合方塊開啟時按A–Z 或 a–z | 呼叫 |
屬性 | 預設值 | 說明 |
as | 片段 | 字串 | 元件
|
disabled | false | 布林 使用此屬性可以停用整個組合方塊元件與相關子元件。 |
value | — | T 已選取的值。 |
defaultValue | — | T 使用做為非受控元件時的預設值。 |
by | — | 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) => 字串 你的 |
呈現屬性 | 說明 |
open |
組合方塊是否開啟。 |
disabled |
組合方塊是否已停用。 |
屬性 | 預設值 | 說明 |
as | 按鈕 | 字串 | 元件 元素或組件 |
呈現屬性 | 說明 |
value |
已選取的值。 |
open |
組合方塊是否開啟。 |
disabled |
組合方塊是否已停用。 |
屬性 | 預設值 | 說明 |
as | 標籤 | 字串 | 元件 元素或組件 |
呈現屬性 | 說明 |
open |
組合方塊是否開啟。 |
disabled |
組合方塊是否已停用。 |
屬性 | 預設值 | 說明 |
as | ul | 字串 | 元件 元素或組件 |
靜態 | false | 布林 元素是否應該忽略內部管理的開啟/關閉狀態。 注意: |
取消掛載 | true | 布林 元素是否應該基於開啟/關閉狀態而取消掛載或隱藏。 注意: |
保留 | false | 布林值 即使滑鼠指標離開目前選項,活動選項是否應該保持活動。 |
呈現屬性 | 說明 |
open |
組合方塊是否開啟。 |
屬性 | 預設值 | 說明 |
value | — | T 選項值。 |
as | li | 字串 | 元件 元素或組件 |
disabled | false | 布林 選項是否應該禁用語音導覽及 ARIA 目的。 |
呈現屬性 | 說明 |
活動中 |
選項是否為活動中/焦點選項。 |
已選取 |
選項是否為已選取的選項。 |
disabled |
選項是否禁用語音導覽及 ARIA 目的。 |