下拉菜单(自动完成)
下拉菜单是您应用程序中可访问的自动完成和命令面板的基础,具有对键盘导航的强大支持。
要开始使用,请通过 npm 安装无头 UI。
请注意,此库仅支持 Vue 3。
npm install @headlessui/vue
下拉菜单是使用 Combobox
、ComboboxInput
、ComboboxButton
、ComboboxOptions
、ComboboxOption
和 ComboboxLabel
组件构建的。
ComboboxInput
将在搜索时自动打开/关闭 ComboboxOptions
。
您完全负责如何过滤结果,无论是使用模糊搜索库在客户端还是通过向 API 发出服务器端请求。在本示例中,我们将为了演示目的而使逻辑保持简单。
<template> <Combobox v-model="selectedPerson"> <ComboboxInput @change="query = $event.target.value" /> <ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person" :value="person" > {{ person }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' const people = [ 'Durward Reynolds', 'Kenton Towne', 'Therese Wunsch', 'Benedict Kessler', 'Katelyn Rohan', ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>
在前面的示例中,我们使用 string
值列表作为数据,但您也可以使用包含其他信息的 对象。唯一的限制是您必须为输入提供 displayValue
。这很重要,以便可以在 ComboboxInput
中渲染对象的基于字符串的版本。
<template> <Combobox v-model="selectedPerson"> <ComboboxInput @change="query = $event.target.value"
:displayValue="(person) => person.name"/> <ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" :disabled="person.unavailable" > {{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' 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 }, ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>
无头 UI 会跟踪每个组件的许多状态,例如当前选择的哪个下拉菜单选项,弹出窗口是打开还是关闭,或者通过键盘当前激活的哪个下拉菜单项。
但由于这些组件是无头的,并且在开箱即用时完全没有样式,因此您在提供每个状态所需的样式之前,无法在 UI 中看到这些信息。
每个组件都通过 插槽道具 公开有关其当前状态的信息,您可以使用这些信息有条件地应用不同的样式或渲染不同的内容。
例如,ComboboxOption
组件公开了 active
状态,它告诉您该项是否当前通过鼠标或键盘处于焦点状态。
<template> <Combobox v-model="selectedPerson"> <ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <ComboboxOptions> <!-- Use the `active` state to conditionally style the active option. --> <!-- Use the `selected` state to conditionally style the selected option. --> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" as="template"
v-slot="{ active, selected }"> <li :class="{'bg-blue-500 text-white': active,'bg-white text-black': !active,}" ><CheckIcon v-show="selected" />{{ person.name }} </li> </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' import { CheckIcon } from '@heroicons/vue/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' }, ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>
有关所有可用插槽道具的完整列表,请参阅 组件 API 文档。
每个组件还通过 data-headlessui-state
属性公开有关其当前状态的信息,您可以使用这些信息有条件地应用不同的样式。
当 插槽道具 API 中的任何状态为 true
时,它们将在该属性中列为以空格分隔的字符串,因此您可以使用 CSS 属性选择器 以 [attr~=value]
的形式对其进行定位。
例如,以下是 ComboboxOptions
组件以及一些子 ComboboxOption
组件在下拉菜单打开且第二个项目为 active
时渲染的内容
<!-- Rendered `ComboboxOptions` --> <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:*
)定位此属性
<template> <Combobox v-model="selectedPerson"> <ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person"
class="ui-active:bg-blue-500 ui-active:text-white ui-not-active:bg-white ui-not-active:text-black"><CheckIcon class="hidden ui-selected:block" />{{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' import { CheckIcon } from '@heroicons/vue/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' }, ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>
与仅允许您提供字符串作为值的原生 HTML 表单控件不同,无头 UI 还支持绑定复杂对象。
<template>
<Combobox v-model="selectedPerson"><ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id":value="person":disabled="person.unavailable" > {{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue'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 },]const selectedPerson = ref(people[1]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>
在将对象绑定为值时,重要的是要确保您将 Combobox
的 value
以及相应的 ComboboxOption
同时使用同一个实例,否则它们将无法相等并导致下拉菜单行为不正常。
为了更轻松地使用同一对象的不同实例,您可以使用 by
道具,通过特定字段比较对象,而不是比较对象标识
<template> <Combobox :modelValue="modelValue" @update:modelValue="value => emit('update:modelValue', value)"
by="id"> <ComboboxInput @change="query = $event.target.value" :displayValue="(department) => department.name" /> <ComboboxOptions> <ComboboxOption v-for="department in filteredDepartments" :key="department.id" :value="department" > {{ department.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' const props = defineProps({ modelValue: Object }) const emit = defineEmits(['update:modelValue']) 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' }, ] const query = ref('') const filteredDepartments = computed(() => query.value === '' ? departments : departments.filter((department) => { return department.name .toLowerCase() .includes(query.value.toLowerCase()) }) ) </script>
您还可以将自己的比较函数传递给 by
道具,如果您希望完全控制如何比较对象
<template> <Combobox :modelValue="modelValue" @update:modelValue="value => emit('update:modelValue', value)"
:by="compareDepartments"> <ComboboxInput @change="query = $event.target.value" :displayValue="(department) => department.name" /> <ComboboxOptions> <ComboboxOption v-for="department in departments" :key="department.id" :value="department" > {{ department.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' const props = defineProps({ modelValue: Object }) const emit = defineEmits(['update:modelValue'])function compareDepartments(a, b) {return a.name.toLowerCase() === b.name.toLowerCase()}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' }, ] const query = ref('') const filteredDepartments = computed(() => query.value === '' ? departments : departments.filter((department) => { return department.name .toLowerCase() .includes(query.value.toLowerCase()) }) ) </script>
下拉菜单组件允许您选择多个值。您可以通过提供值数组而不是单个值来启用此功能。
<template>
<Combobox v-model="selectedPeople" multiple><ul v-if="selectedPeople.length > 0"> <li v-for="person in selectedPeople" :key="person.id"> {{ person.name }} </li> </ul> <ComboboxInput /> <ComboboxOptions> <ComboboxOption v-for="person in people" :key="person.id" :value="person"> {{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' 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' }, ] const selectedPeople = ref([people[0], people[1]]) </script>
当您选择选项时,这将保持下拉菜单处于打开状态,选择选项将在其位置进行切换。
只要添加或删除选项,您的 v-model
绑定就会更新为包含所有选中选项的数组。
默认情况下,Combobox
将使用输入内容作为屏幕阅读器的标签。如果您想更精确地控制向辅助技术宣布的内容,请使用 ComboboxLabel
组件。
<template> <Combobox v-model="selectedPerson">
<ComboboxLabel>Assignee:</ComboboxLabel><ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" > {{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxLabel, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' 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' }, ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>
如果您将 name
道具添加到您的下拉菜单,则将呈现隐藏的 input
元素并与您的选中值保持同步。
<template> <form action="/projects/1/assignee" method="post">
<Combobox v-model="selectedPerson" name="assignee"><ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" > {{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> <button>Submit</button> </form> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' 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' }, ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>
这使您可以在原生 HTML <form>
中使用下拉菜单并进行传统的表单提交,就像您的下拉菜单是原生 HTML 表单控件一样。
诸如字符串之类的基本值将被渲染为包含该值的单个隐藏输入,但诸如对象之类的复杂值将使用方括号表示法对名称进行编码,从而编码为多个输入
<input type="hidden" name="assignee[id]" value="1" /> <input type="hidden" name="assignee[name]" value="Durward Reynolds" />
如果您为 Combobox
提供 defaultValue
道具而不是 value
,无头 UI 将为您在内部跟踪其状态,允许您将其用作 不受控组件。
<template> <form action="/projects/1/assignee" method="post">
<Combobox name="assignee" :defaultValue="people[0]"><ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" > {{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> <button>Submit</button> </form> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' 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' }, ] const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>
当使用 HTML 表单 或使用通过 FormData 收集其状态而不是使用 React 状态跟踪其状态的表单 API 时,这可以简化您的代码。
您提供的任何 @update:modelValue
道具仍然会在组件的值发生更改时被调用,以防您需要运行任何副作用,但您无需使用它来自己跟踪组件的状态。
您可以允许用户输入列表中不存在的自己的值,方法是根据 query
值包含一个动态 ComboboxOption
。
<template> <Combobox v-model="selectedPerson"> <ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <ComboboxOptions>
<ComboboxOption v-if="queryPerson" :value="queryPerson">Create "{{ query }}"</ComboboxOption><ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" > {{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' 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' }, ] const selectedPerson = ref(people[0]) const query = ref('')const queryPerson = computed(() => {return query.value === '' ? null : { id: null, name: query.value }})const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>
根据您正在构建的内容,有时将有关活动选项的附加信息渲染到 <ComboboxOptions>
之外可能很有意义。例如,在命令面板的上下文中预览活动选项。在这些情况下,您可以读取 activeOption
插槽道具参数以访问此信息。
<template>
<Combobox v-model="selectedPerson" v-slot="{ activeOption }"><ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" > {{ person.name }} </ComboboxOption> </ComboboxOptions><div v-if="activeOption">The current active user is: {{ activeOption.name }}</div></Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' 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' }, ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>
activeOption
将是当前活动 ComboboxOption
的 value
。
默认情况下,您的 ComboboxOptions
实例将根据 Combobox
组件本身内部跟踪的 open
状态自动显示/隐藏。
<template> <Combobox v-model="selectedPerson"> <ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <!-- By default, the `ComboboxOptions` will automatically show/hide when typing in the `ComboboxInput`, or when pressing the `ComboboxButton`. --> <ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" > {{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' 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' }, ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>
如果您希望自己处理此问题(可能是因为您需要为此添加额外的包装元素),您可以将 static
道具添加到 ComboboxOptions
实例中,告诉它始终渲染,并检查 Combobox
提供的 open
插槽道具,以控制自己显示/隐藏哪个元素。
<template>
<Combobox v-model="selectedPerson" v-slot="{ open }"><ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /><div v-show="open"><!-- Using the `static` prop, the `ComboboxOptions` are always rendered and the `open` state is ignored. --><ComboboxOptions static><ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" > {{ person.name }} </ComboboxOption> </ComboboxOptions> </div> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' 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' }, ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>
使用 disabled
道具禁用 ComboboxOption
。这将使其无法通过鼠标和键盘选择,并且在按向上/向下箭头时将跳过它。
<template> <Combobox v-model="selectedPerson"> <ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <ComboboxOptions> <!-- Disabled options will be skipped by keyboard navigation. --> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person"
:disabled="person.unavailable"> <span :class='{ "opacity-75": person.unavailable }'> {{ person.name }} </span> </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' const people = [ { id: 1, name: 'Durward Reynolds', unavailable: true }, { 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 }, ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>
默认情况下,在您在下拉菜单中选择值后,无法将下拉菜单清除回空值 — 当您清除输入并离开元素时,该值会返回到先前选中的值。
如果您希望在下拉菜单中支持空值,请使用 nullable
道具。
<template>
<Combobox v-model="selectedPerson" nullable><ComboboxInput @change="query = $event.target.value":displayValue="(person) => person?.name"/> <ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" > {{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' const people = [ { id: 1, name: 'Durward Reynolds', unavailable: true }, { 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 }, ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>
使用 nullable
道具时,清除输入并从元素中导航将更新您的 v-model
绑定并使用 null
调用您的 displayValue
回调。
在允许 多个值 时,此道具没有任何作用,因为选项会在打开和关闭时切换,如果未选择任何内容,则会导致空数组(而不是 null)。
要为下拉菜单的打开/关闭设置动画,您可以使用 Vue 的内置 <transition>
组件。您需要做的就是将您的 ComboboxOptions
实例包装在 <transition>
中,然后会自动应用过渡。
<template> <Combobox v-model="selectedPerson"> <ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <!-- Use Vue's built-in `transition` component to add transitions. -->
<transitionenter-active-class="transition duration-100 ease-out"enter-from-class="transform scale-95 opacity-0"enter-to-class="transform scale-100 opacity-100"leave-active-class="transition duration-75 ease-out"leave-from-class="transform scale-100 opacity-100"leave-to-class="transform scale-95 opacity-0"><ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" > {{ person.name }} </ComboboxOption> </ComboboxOptions></transition></Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' 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' }, ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>
如果您希望为下拉菜单的不同子级协调多个过渡,请查看 无头 UI 中包含的过渡组件。
默认情况下,Combobox
及其子组件都会渲染一个适合该组件的默认元素。
例如,ComboboxLabel
默认情况下会渲染一个 label
,ComboboxInput
会渲染一个 input
,ComboboxButton
会渲染一个 button
,ComboboxOptions
会渲染一个 ul
,ComboboxOption
会渲染一个 li
。相比之下,Combobox
不会渲染元素,而是直接渲染其子元素。
这很容易使用 as
属性更改,该属性存在于每个组件上。
<template> <!-- Render a `div` instead of nothing -->
<Combobox as="div" v-model="selectedPerson"><ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <!-- Render a `div` instead of a `ul` --><ComboboxOptions as="div"><!-- Render a `span` instead of a `li` --> <ComboboxOptionas="span"v-for="person in filteredPeople" :key="person.id" :value="person" > {{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' 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' }, ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>
要告诉一个元素直接渲染其子元素而没有包装元素,请使用 as="template"
。
<template> <Combobox v-model="selectedPerson"> <!-- Render children directly instead of an `input` --> <ComboboxInput
as="template"@change="query = $event.target.value" :displayValue="(person) => person.name" > <input /> </ComboboxInput> <ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" > {{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' 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' }, ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>
当 Combobox 切换到打开状态时,ComboboxInput
将保持焦点。
ComboboxButton
被默认的 Tab 顺序忽略,这意味着在 ComboboxInput
中按 Tab
将跳过 ComboboxButton
。
单击 ComboboxButton
会切换选项列表的打开和关闭状态。单击选项列表外部的任何位置都会关闭下拉框。
命令 | 描述 |
向下箭头, 或者 向上箭头当 | 打开下拉框并聚焦选定的项目 |
Enter, 空格, 向下箭头, 或者 向上箭头当 | 打开下拉框,聚焦输入框并选择选定的项目 |
Esc 当下拉框打开时 | 关闭下拉框并恢复输入框中的选定项目 |
向下箭头 或者 向上箭头当下拉框打开时 | 聚焦上一个/下一个未禁用的项目 |
Home 或者 PageUp 当下拉框打开时 | 聚焦第一个未禁用的项目 |
End 或者 PageDown 当下拉框打开时 | 聚焦最后一个未禁用的项目 |
Enter 当下拉框打开时 | 选择当前项目 |
Enter 当下拉框关闭且处于表单中时 | 提交表单 |
Tab 当下拉框打开时 | 选择当前活动的项目并关闭下拉框 |
A–Z 或者 a–z 当下拉框打开时 | 调用 |
属性 | 默认值 | 描述 |
as | template | String | Component
|
v-model | — | T 选定的值。 |
defaultValue | — | T 使用无控制组件时的默认值。 |
by | — | keyof T | ((a: T, z: T) => boolean) 使用它来比较对象特定字段,或传递自己的比较函数以完全控制对象的比较方式。 |
disabled | false | Boolean 使用它来禁用整个下拉框组件及其相关子元素。 |
name | — | String 在表单内使用此组件时使用的名称。 |
nullable | — | Boolean 是否可以清除下拉框。 |
multiple | false | Boolean 是否可以选择多个选项。 |
插槽属性 | 描述 |
value |
选定的值。 |
open |
下拉框是否打开。 |
disabled |
下拉框是否禁用。 |
activeIndex |
活动选项的索引,如果没有活动选项,则为 null。 |
activeOption |
活动选项,如果没有活动选项,则为 null。 |
属性 | 默认值 | 描述 |
as | input | String | Component
|
displayValue | — | (item: T) => string
|
渲染属性 | 描述 |
open |
下拉框是否打开。 |
disabled |
下拉框是否禁用。 |
属性 | 默认值 | 描述 |
as | button | String | Component
|
插槽属性 | 描述 |
value |
选定的值。 |
open |
下拉框是否打开。 |
disabled |
下拉框是否禁用。 |
属性 | 默认值 | 描述 |
as | label | String | Component
|
插槽属性 | 描述 |
open |
下拉框是否打开。 |
disabled |
下拉框是否禁用。 |
属性 | 默认值 | 描述 |
as | ul | String | Component
|
static | false | Boolean 元素是否应忽略内部管理的打开/关闭状态。 |
unmount | true | Boolean 元素是否应根据打开/关闭状态卸载或隐藏。 |
hold | false | boolean 活动选项是否应在鼠标离开活动选项时保持活动状态。 |
插槽属性 | 描述 |
open |
下拉框是否打开。 |
属性 | 默认值 | 描述 |
value | — | T 选项值。 |
as | li | String | Component
|
disabled | false | Boolean 选项是否应为键盘导航和 ARIA 目的禁用。 |
插槽属性 | 描述 |
active |
选项是否为活动/聚焦选项。 |
selected |
选项是否为选定选项。 |
disabled |
选项是否为键盘导航和 ARIA 目的禁用。 |