combobox
A combobox with custom item rendering
A combobox with custom item rendering
Загрузка превью…
src/combobox/c-combobox-16.vue
<!--
Unsplash-URL заменены локальным 1x1 data-URI (см. комментарий в
`c-combobox-10.vue`). `ComboboxTrigger`-as-`Button` воспроизведён тем же
приёмом (`cn(buttonVariants(...), ...)`), что и в `c-combobox-10.vue`.
-->
<script setup lang="ts">
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { buttonVariants } from "@/components/ui/button"
import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList, ComboboxTrigger, ComboboxValue } from "@/components/ui/combobox"
import { Field } from "@/components/ui/field"
import { Item, ItemContent, ItemDescription, ItemTitle } from "@/components/ui/item"
import { cn } from "@/lib/utils"
const PIXEL =
"data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAUwAOw=="
const users = [
{ id: "1", name: "Alex Johnson", email: "alex@example.com", initials: "AJ" },
{ id: "2", name: "Sarah Chen", email: "sarah@example.com", initials: "SC" },
{ id: "3", name: "Michael Rodriguez", email: "michael@example.com", initials: "MR" },
{ id: "4", name: "Emma Wilson", email: "emma@example.com", initials: "EW" },
{ id: "5", name: "David Kim", email: "david@example.com", initials: "DK" },
{ id: "6", name: "Aron Thompson", email: "lisa@example.com", initials: "LT" },
{ id: "7", name: "James Brown", email: "james@example.com", initials: "JB" },
{ id: "8", name: "Maria Garcia", email: "maria@example.com", initials: "MG" },
{ id: "9", name: "Nick Johnson", email: "nick@example.com", initials: "NJ" },
{ id: "10", name: "Liam Thompson", email: "liam@example.com", initials: "LT" },
]
const members = users.map((user, index) => ({
...user,
avatar: PIXEL,
position: ["Software Engineer", "Product Manager", "UX Designer", "Technical Lead", "CTO"][
index % 5
],
}))
</script>
<template>
<Field class="max-w-xs">
<Combobox :default-value="members[0]">
<ComboboxTrigger
:class="cn(buttonVariants({ variant: 'outline' }), 'w-full justify-between font-normal')"
>
<ComboboxValue v-slot="{ value }">
<span v-if="value" class="flex items-center gap-2">
<Avatar class="size-5">
<AvatarImage :src="(value as (typeof members)[number]).avatar" :alt="(value as (typeof members)[number]).name" />
<AvatarFallback>{{ (value as (typeof members)[number]).initials }}</AvatarFallback>
</Avatar>
<span>{{ (value as (typeof members)[number]).name }}</span>
</span>
<span v-else class="text-muted-foreground">Select a member</span>
</ComboboxValue>
</ComboboxTrigger>
<ComboboxContent class="max-w-(--anchor-width) min-w-(--anchor-width)">
<ComboboxInput :show-trigger="false" placeholder="Search members..." />
<ComboboxEmpty>No members found.</ComboboxEmpty>
<ComboboxList>
<ComboboxItem v-for="member in members" :key="member.id" :value="member">
<Item size="xs" class="p-0">
<Avatar class="size-6">
<AvatarImage :src="member.avatar" :alt="member.name" />
<AvatarFallback>{{ member.initials }}</AvatarFallback>
</Avatar>
<ItemContent>
<ItemTitle class="whitespace-nowrap">{{ member.name }}</ItemTitle>
<ItemDescription>{{ member.position }}</ItemDescription>
</ItemContent>
</Item>
</ComboboxItem>
</ComboboxList>
</ComboboxContent>
</Combobox>
</Field>
</template>
<style scoped>
/* reka-ui's ComboboxRoot renders a real DOM wrapper div (via internal
ListboxRoot Primitive) around its slot content, unlike Base UI's
headless ComboboxPrimitive.Root, which renders nothing. When the only
child is a ComboboxTrigger-as-button (no sibling ComboboxInput), this
unstyled div participates in an inline formatting context and can add
a stray 1-3px of height depending on the theme's font metrics --
invisible but breaks pixel parity with the React reference, which has
no such wrapper. display:contents removes the anonymous box without
touching layout/functionality (the div stays in the DOM, still used
internally by reka-ui as the popper anchor ref). */
:deep(div[dir]) {
display: contents;
}
</style>