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>

Установка

npx shadcn-vue@latest add https://revueui.rootapi.dev/r/c-combobox-16.json

Зависимости реестра

Источник: порт из ReUI (Keenthemes, MIT)