Nuxt UI v3-alpha 已发布!

尝试一下
组件

SelectMenu

显示带有高级功能的选择菜单。

使用

SelectMenu 组件默认渲染一个 Select 组件,并且基于 ui.select 预设。如果不想覆盖默认插槽,可以使用大多数 Select 属性来配置显示,例如 colorvariantsizeplaceholdericondisabled 等。

您可以像 Select 组件一样使用 ui 属性来覆盖默认配置。该 uiMenu 属性可用于覆盖默认菜单配置。

Select 组件一样,您可以使用 options 属性来传递字符串或对象数组。

<script setup lang="ts">
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']

const selected = ref(people[0])
</script>

<template>
  <USelectMenu v-model="selected" :options="people" />
</template>

多选

您可以使用 multiple 属性来选择多个值。

<script setup lang="ts">
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']

const selected = ref([])
</script>

<template>
  <USelectMenu v-model="selected" :options="people" multiple placeholder="Select people" />
</template>

对象

您可以将对象数组传递给 options,并根据整个对象进行比较,或者使用 by 属性根据特定键进行比较。您可以通过 option-attribute 属性配置将用于显示标签的字段,默认值为 label。此外,可以使用点符号(例如,user.name)来访问嵌套的对象属性。

<script setup lang="ts">
import type { Avatar } from '#ui/types'

const people = [{
  id: 'benjamincanac',
  label: 'benjamincanac',
  href: 'https://github.com/benjamincanac',
  target: '_blank',
  avatar: { src: 'https://avatars.githubusercontent.com/u/739984?v=4' }
}, {
  id: 'Atinux',
  label: 'Atinux',
  href: 'https://github.com/Atinux',
  target: '_blank',
  avatar: { src: 'https://avatars.githubusercontent.com/u/904724?v=4' }
}, {
  id: 'smarroufin',
  label: 'smarroufin',
  href: 'https://github.com/smarroufin',
  target: '_blank',
  avatar: { src: 'https://avatars.githubusercontent.com/u/7547335?v=4' }
}, {
  id: 'nobody',
  label: 'Nobody',
  icon: 'i-heroicons-user-circle'
}]

const selected = ref(people[0])
</script>

<template>
  <USelectMenu v-model="selected" :options="people">
    <template #leading>
      <UIcon v-if="selected.icon" :name="(selected.icon as string)" class="w-5 h-5" />
      <UAvatar v-else-if="selected.avatar" v-bind="(selected.avatar as Avatar)" size="2xs" />
    </template>
  </USelectMenu>
</template>

如果您只想选择单个对象属性而不是整个对象作为值,可以设置 value-attribute 属性。此属性默认为 null。选项中 value-attribute 字段的值必须是唯一的。

<script setup lang="ts">
const people = [{
  id: 1,
  name: 'Wade Cooper'
}, {
  id: 2,
  name: 'Arlene Mccoy'
}, {
  id: 3,
  name: 'Devon Webb'
}, {
  id: 4,
  name: 'Tom Cook'
}]

const selected = ref(people[0].id)
</script>

<template>
  <USelectMenu
    v-model="selected"
    :options="people"
    placeholder="Select people"
    value-attribute="id"
    option-attribute="name"
  />
</template>

图标

使用 selected-icon 属性设置不同的图标,或者在 ui.selectMenu.default.selectedIcon 中全局更改它。默认值为 i-heroicons-check-20-solid

<template>
  <USelectMenu
    selected-icon="i-heroicons-hand-thumb-up-solid"
    class="w-full lg:w-48"
    placeholder="Select a person"
    :options="['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']"
  />
</template>
了解如何从 Select 组件自定义图标。

可搜索

使用 searchable 属性启用搜索。

使用 searchable-placeholder 属性设置不同的占位符,或通过 ui.selectMenu.default.searchablePlaceholder.label 配置全局设置。默认值为 Search...

这将使用 Headless UI Combobox 组件而不是 Listbox

<template>
  <USelectMenu
    searchable
    searchable-placeholder="Search a person..."
    class="w-full lg:w-48"
    placeholder="Select a person"
    :options="['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']"
  />
</template>

属性

使用带有属性名称数组的 search-attributes 属性在每个选项对象上进行搜索。可以使用 dot.notation 访问嵌套属性。当属性值为数组或对象时,这些值会被强制转换为字符串,以便可以在其中进行搜索。

<script setup lang="ts">
const options = [
  { id: 1, name: 'Wade Cooper', colors: ['red', 'yellow'] },
  { id: 2, name: 'Arlene Mccoy', colors: ['blue', 'yellow'] },
  { id: 3, name: 'Devon Webb', colors: ['green', 'blue'] },
  { id: 4, name: 'Tom Cook', colors: ['blue', 'red'] },
  { id: 5, name: 'Tanya Fox', colors: ['green', 'red'] },
  { id: 5, name: 'Hellen Schmidt', colors: ['green', 'yellow'] }
]

const selected = ref(options[1])
</script>

<template>
  <USelectMenu
    v-model="selected"
    :options="options"
    placeholder="Select a person"
    searchable
    searchable-placeholder="Search by name or color"
    option-attribute="name"
    by="id"
    :search-attributes="['name', 'colors']"
  >
    <template #option="{ option: person }">
      <span v-for="color in person.colors" :key="color.id" class="h-2 w-2 rounded-full" :class="`bg-${color}-500 dark:bg-${color}-400`" />
      <span class="truncate">{{ person.name }}</span>
    </template>
  </USelectMenu>
</template>

关闭时清除

默认情况下,搜索查询将在菜单关闭后保留。要关闭时清除它,请设置 clear-search-on-close 属性。

您也可以通过 ui.selectMenu.default.clearSearchOnClose 配置全局设置。默认值为 false

<template>
  <USelectMenu
    clear-search-on-close
    class="w-full lg:w-48"
    placeholder="Select a person"
    searchable
    searchable-placeholder="Search a person..."
    :options="['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']"
  />
</template>

控制查询

使用 v-model:query 来控制搜索查询。

<script setup lang="ts">
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']

const selected = ref()
const query = ref('Wade')
</script>

<template>
  <USelectMenu
    v-model="selected"
    v-model:query="query"
    :options="people"
    placeholder="Select a person"
    searchable
  />
</template>

将一个函数传递给 searchable 属性,以自定义搜索行为并根据您的需求过滤选项。该函数将接收查询作为其第一个参数,并应返回一个数组。

使用 debounce 属性调整函数的延迟。

使用 searchableLazy 属性来控制数据请求的及时性。

<script setup lang="ts">
const loading = ref(false)
const selected = ref([])

async function search(q: string) {
  loading.value = true

  const users: any[] = await $fetch('https://jsonplaceholder.typicode.com/users', { params: { q } })

  loading.value = false

  return users
}
</script>

<template>
  <USelectMenu
    v-model="selected"
    :loading="loading"
    :searchable="search"
    placeholder="Search for a user..."
    option-attribute="name"
    multiple
    trailing
    by="id"
  />
</template>

可创建

使用 creatable 属性启用在搜索未返回任何结果时创建新选项(仅适用于 searchable)。

尝试搜索以下示例中不存在的内容。

<script setup lang="ts">
const options = ref([
  { id: 1, name: 'bug', color: 'd73a4a' },
  { id: 2, name: 'documentation', color: '0075ca' },
  { id: 3, name: 'duplicate', color: 'cfd3d7' },
  { id: 4, name: 'enhancement', color: 'a2eeef' },
  { id: 5, name: 'good first issue', color: '7057ff' },
  { id: 6, name: 'help wanted', color: '008672' },
  { id: 7, name: 'invalid', color: 'e4e669' },
  { id: 8, name: 'question', color: 'd876e3' },
  { id: 9, name: 'wontfix', color: 'ffffff' }
])

const selected = ref([])

const labels = computed({
  get: () => selected.value,
  set: async (labels) => {
    const promises = labels.map(async (label) => {
      if (label.id) {
        return label
      }

      // In a real app, you would make an API call to create the label
      const response = {
        id: options.value.length + 1,
        name: label.name,
        color: generateColorFromString(label.name)
      }

      options.value.push(response)

      return response
    })

    selected.value = await Promise.all(promises)
  }
})

function hashCode(str) {
  let hash = 0
  for (let i = 0; i < str.length; i++) {
    hash = str.charCodeAt(i) + ((hash << 5) - hash)
  }
  return hash
}

function intToRGB(i) {
  const c = (i & 0x00FFFFFF)
    .toString(16)
    .toUpperCase()

  return '00000'.substring(0, 6 - c.length) + c
}

function generateColorFromString(str) {
  return intToRGB(hashCode(str))
}
</script>

<template>
  <USelectMenu
    v-model="labels"
    by="id"
    name="labels"
    :options="options"
    option-attribute="name"
    multiple
    searchable
    creatable
  >
    <template #label>
      <template v-if="labels.length">
        <span class="flex items-center -space-x-1">
          <span v-for="label of labels" :key="label.id" class="flex-shrink-0 w-2 h-2 mt-px rounded-full" :style="{ background: `#${label.color}` }" />
        </span>
        <span>{{ labels.length }} label{{ labels.length > 1 ? 's' : '' }}</span>
      </template>
      <template v-else>
        <span class="text-gray-500 dark:text-gray-400 truncate">Select labels</span>
      </template>
    </template>

    <template #option="{ option }">
      <span
        class="flex-shrink-0 w-2 h-2 mt-px rounded-full"
        :style="{ background: `#${option.color}` }"
      />
      <span class="truncate">{{ option.name }}</span>
    </template>

    <template #option-create="{ option }">
      <span class="flex-shrink-0">New label:</span>
      <span
        class="flex-shrink-0 w-2 h-2 mt-px rounded-full -mx-1"
        :style="{ background: `#${generateColorFromString(option.name)}` }"
      />
      <span class="block truncate">{{ option.name }}</span>
    </template>
  </USelectMenu>
</template>

但是,如果您希望在搜索查询时创建选项(除了完全匹配之外),可以将 show-create-option-when 属性设置为 'always'

您也可以通过 ui.selectMenu.default.showCreateOptionWhen 配置全局设置。默认值为 empty

尝试搜索以下示例中存在的内容,但不是完全匹配。

<script setup lang="ts">
const options = ref([
  { id: 1, name: 'bug' },
  { id: 2, name: 'documentation' },
  { id: 3, name: 'duplicate' },
  { id: 4, name: 'enhancement' },
  { id: 5, name: 'good first issue' },
  { id: 6, name: 'help wanted' },
  { id: 7, name: 'invalid' },
  { id: 8, name: 'question' },
  { id: 9, name: 'wontfix' }
])

const selected = ref([])

const labels = computed({
  get: () => selected.value,
  set: async (labels) => {
    const promises = labels.map(async (label) => {
      if (label.id) {
        return label
      }

      // In a real app, you would make an API call to create the label
      const response = {
        id: options.value.length + 1,
        name: label.name
      }

      options.value.push(response)

      return response
    })

    selected.value = await Promise.all(promises)
  }
})
</script>

<template>
  <USelectMenu
    v-model="labels"
    by="id"
    name="labels"
    :options="options"
    option-attribute="name"
    multiple
    searchable
    creatable
    show-create-option-when="always"
    placeholder="Select labels"
  />
</template>

将一个函数传递给 show-create-option-when 属性来控制是否显示创建选项。此函数接受两个参数:查询(作为第一个参数)和当前结果数组(作为第二个参数)。它应返回 true 以显示创建选项。

以下示例显示了如何在查询至少为三个字符长且与任何当前结果(不区分大小写)不完全匹配时使创建选项可见。

<script setup lang="ts">
const options = ref([
  { id: 1, name: 'bug' },
  { id: 2, name: 'documentation' },
  { id: 3, name: 'duplicate' },
  { id: 4, name: 'enhancement' },
  { id: 5, name: 'good first issue' },
  { id: 6, name: 'help wanted' },
  { id: 7, name: 'invalid' },
  { id: 8, name: 'question' },
  { id: 9, name: 'wontfix' }
])

const selected = ref([])

const labels = computed({
  get: () => selected.value,
  set: async (labels) => {
    const promises = labels.map(async (label) => {
      if (label.id) {
        return label
      }

      // In a real app, you would make an API call to create the label
      const response = {
        id: options.value.length + 1,
        name: label.name
      }

      options.value.push(response)

      return response
    })

    selected.value = await Promise.all(promises)
  }
})

const showCreateOption = (query, results) => {
  const lowercaseQuery = String.prototype.toLowerCase.apply(query || '')
  return lowercaseQuery.length >= 3 && !results.find((option) => {
    return String.prototype.toLowerCase.apply(option['name'] || '') === lowercaseQuery
  })
}
</script>

<template>
  <USelectMenu
    v-model="labels"
    by="id"
    name="labels"
    :options="options"
    option-attribute="name"
    multiple
    searchable
    creatable
    :show-create-option-when="showCreateOption"
    placeholder="Select labels"
  />
</template>

Popper

使用 popper 属性自定义 popper 实例。

箭头

<script setup lang="ts">
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']

const selected = ref(people[0])
</script>

<template>
  <USelectMenu v-model="selected" :options="people" :popper="{ arrow: true }" />
</template>

放置

<script setup lang="ts">
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']

const selected = ref(people[0])
</script>

<template>
  <USelectMenu v-model="selected" :options="people" :popper="{ placement: 'left-end' }" />
</template>

偏移量

<script setup lang="ts">
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']

const selected = ref(people[0])
</script>

<template>
  <USelectMenu v-model="selected" :options="people" :popper="{ offsetDistance: 0 }" />
</template>

插槽

label

您可以覆盖 #label 插槽并自行处理显示。

<script setup lang="ts">
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']

const selected = ref([])
</script>

<template>
  <USelectMenu v-model="selected" :options="people" multiple>
    <template #label>
      <span v-if="selected.length" class="truncate">{{ selected.join(', ') }}</span>
      <span v-else>Select people</span>
    </template>
  </USelectMenu>
</template>

default

您还可以完全覆盖 #default 插槽。

<script setup lang="ts">
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']

const selected = ref(people[3])
</script>

<template>
  <USelectMenu v-slot="{ open }" v-model="selected" :options="people">
    <UButton color="gray" class="flex-1 justify-between">
      {{ selected }}

      <UIcon name="i-heroicons-chevron-right-20-solid" class="w-5 h-5 transition-transform text-gray-400 dark:text-gray-500" :class="[open && 'transform rotate-90']" />
    </UButton>
  </USelectMenu>
</template>

option

使用 #option 插槽自定义选项内容。您将在插槽作用域中访问 optionactiveselected 属性。

<script setup lang="ts">
const people = [
  { name: 'Wade Cooper', online: true },
  { name: 'Arlene Mccoy', online: false },
  { name: 'Devon Webb', online: false },
  { name: 'Tom Cook', online: true },
  { name: 'Tanya Fox', online: false },
  { name: 'Hellen Schmidt', online: true },
  { name: 'Caroline Schultz', online: true },
  { name: 'Mason Heaney', online: false },
  { name: 'Claudie Smitham', online: true },
  { name: 'Emil Schaefer', online: false }
]

const selected = ref(people[3])
</script>

<template>
  <USelectMenu v-model="selected" :options="people" option-attribute="name">
    <template #label>
      <span :class="[selected.online ? 'bg-green-400' : 'bg-gray-200', 'inline-block h-2 w-2 flex-shrink-0 rounded-full']" aria-hidden="true" />
      <span class="truncate">{{ selected.name }}</span>
    </template>

    <template #option="{ option: person }">
      <span :class="[person.online ? 'bg-green-400' : 'bg-gray-200', 'inline-block h-2 w-2 flex-shrink-0 rounded-full']" aria-hidden="true" />
      <span class="truncate">{{ person.name }}</span>
    </template>
  </USelectMenu>
</template>

option-empty

使用 #option-empty 插槽自定义在 searchable 属性为 true 且没有选项时显示的内容。您将在插槽作用域中访问 query 属性。

您也可以通过 ui.selectMenu.default.optionEmpty.label 配置全局设置。令牌 {query} 将被 query 属性替换。默认值为 No results for "{query}".

<script setup lang="ts">
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']

const selected = ref(people[0])
</script>

<template>
  <USelectMenu v-model="selected" :options="people" searchable>
    <template #option-empty="{ query }">
      <q>{{ query }}</q> not found
    </template>
  </USelectMenu>
</template>

option-create

使用 #option-create 插槽自定义在 creatable 属性为 true 且没有选项时显示的内容。您将在插槽作用域中访问 query 属性。

Creatable 部分提供了一个示例。

empty

使用 #empty 插槽自定义在没有选项时显示的内容。

您也可以通过 ui.selectMenu.default.empty.label 配置全局设置。默认值为 No options.

<script setup lang="ts">
const people = []

const selected = ref()
</script>

<template>
  <USelectMenu v-model="selected" :options="people">
    <template #empty>
      No people
    </template>
  </USelectMenu>
</template>

属性

name
string
null
size
SelectSize
null
"md""2xs""xs""sm""lg""xl"
color
string
config.default.color
icon
string
null
ui
{ form?: string; placeholder?: string; default?: DeepPartial<{ size: string; color: string; variant: string; loadingIcon: string; trailingIcon: string; }, any>; wrapper?: string; base?: string; ... 9 more ...; icon?: DeepPartial<...>; } & { ...; } & { ...; }
{}
id
string
null
modelValue
string | number | boolean | object | any[]
""
options
string[] | { [key: string]: any; disabled?: boolean; }[]
[]
variant
SelectVariant
config.default.variant
"outline""none"
placeholder
string
null
loadingIcon
string
config.default.loadingIcon
leadingIcon
string
null
trailingIcon
string
config.default.trailingIcon
by
string
undefined
popper
PopperOptions
{}
query
string
null
selectedIcon
string
configMenu.default.selectedIcon
optionAttribute
string
"label"
valueAttribute
string
null
searchAttributes
unknown[]
null
debounce
number
200
uiMenu
{ select?: string; input?: string; required?: string; label?: string; option?: DeepPartial<{ create: string; base: string; rounded: string; padding: string; size: string; color: string; container: string; active: string; ... 7 more ...; chip: { ...; }; }, any>; ... 14 more ...; empty?: string; } & { ...; } & { ...; }
{}
selectClass
string
null
可搜索
boolean | ((query: string) => any[] | Promise<any[]>)
false
searchablePlaceholder
string
configMenu.default.searchablePlaceholder.label
showCreateOptionWhen
"empty" | "always" | ((query: string, results: any[]) => boolean)
configMenu.default.showCreateOptionWhen
required
boolean
false
disabled
boolean
false
leading
boolean
false
trailing
boolean
false
loading
boolean
false
padded
boolean
true
multiple
boolean
false
searchableLazy
boolean
false
clearSearchOnClose
boolean
configMenu.default.clearSearchOnClose
creatable
boolean
false

配置

使用 ui 属性覆盖选择配置,使用 uiMenu 属性覆盖菜单配置。
{
  wrapper: 'relative',
  base: 'relative block w-full disabled:cursor-not-allowed disabled:opacity-75 focus:outline-none border-0',
  form: 'form-select',
  rounded: 'rounded-md',
  placeholder: 'text-gray-400 dark:text-gray-500',
  file: {
    base: 'file:mr-1.5 file:font-medium file:text-gray-500 dark:file:text-gray-400 file:bg-transparent file:border-0 file:p-0 file:outline-none'
  },
  size: {
    '2xs': 'text-xs',
    xs: 'text-xs',
    sm: 'text-sm',
    md: 'text-sm',
    lg: 'text-sm',
    xl: 'text-base'
  },
  gap: {
    '2xs': 'gap-x-1',
    xs: 'gap-x-1.5',
    sm: 'gap-x-1.5',
    md: 'gap-x-2',
    lg: 'gap-x-2.5',
    xl: 'gap-x-2.5'
  },
  padding: {
    '2xs': 'px-2 py-1',
    xs: 'px-2.5 py-1.5',
    sm: 'px-2.5 py-1.5',
    md: 'px-3 py-2',
    lg: 'px-3.5 py-2.5',
    xl: 'px-3.5 py-2.5'
  },
  leading: {
    padding: {
      '2xs': 'ps-7',
      xs: 'ps-8',
      sm: 'ps-9',
      md: 'ps-10',
      lg: 'ps-11',
      xl: 'ps-12'
    }
  },
  trailing: {
    padding: {
      '2xs': 'pe-7',
      xs: 'pe-8',
      sm: 'pe-9',
      md: 'pe-10',
      lg: 'pe-11',
      xl: 'pe-12'
    }
  },
  color: {
    white: {
      outline: 'shadow-sm bg-white dark:bg-gray-900 text-gray-900 dark:text-white ring-1 ring-inset ring-gray-300 dark:ring-gray-700 focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400'
    },
    gray: {
      outline: 'shadow-sm bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-white ring-1 ring-inset ring-gray-300 dark:ring-gray-700 focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400'
    }
  },
  variant: {
    outline: 'shadow-sm bg-transparent text-gray-900 dark:text-white ring-1 ring-inset ring-{color}-500 dark:ring-{color}-400 focus:ring-2 focus:ring-{color}-500 dark:focus:ring-{color}-400',
    none: 'bg-transparent focus:ring-0 focus:shadow-none'
  },
  icon: {
    base: 'flex-shrink-0 text-gray-400 dark:text-gray-500',
    color: 'text-{color}-500 dark:text-{color}-400',
    loading: 'animate-spin',
    size: {
      '2xs': 'h-4 w-4',
      xs: 'h-4 w-4',
      sm: 'h-5 w-5',
      md: 'h-5 w-5',
      lg: 'h-5 w-5',
      xl: 'h-6 w-6'
    },
    leading: {
      wrapper: 'absolute inset-y-0 start-0 flex items-center',
      pointer: 'pointer-events-none',
      padding: {
        '2xs': 'px-2',
        xs: 'px-2.5',
        sm: 'px-2.5',
        md: 'px-3',
        lg: 'px-3.5',
        xl: 'px-3.5'
      }
    },
    trailing: {
      wrapper: 'absolute inset-y-0 end-0 flex items-center',
      pointer: 'pointer-events-none',
      padding: {
        '2xs': 'px-2',
        xs: 'px-2.5',
        sm: 'px-2.5',
        md: 'px-3',
        lg: 'px-3.5',
        xl: 'px-3.5'
      }
    }
  },
  default: {
    size: 'sm',
    color: 'white',
    variant: 'outline',
    loadingIcon: 'i-heroicons-arrow-path-20-solid',
    trailingIcon: 'i-heroicons-chevron-down-20-solid'
  }
}
{
  container: 'z-20 group',
  trigger: 'flex items-center w-full',
  width: 'w-full',
  height: 'max-h-60',
  base: 'relative focus:outline-none overflow-y-auto scroll-py-1',
  background: 'bg-white dark:bg-gray-800',
  shadow: 'shadow-lg',
  rounded: 'rounded-md',
  padding: 'p-1',
  ring: 'ring-1 ring-gray-200 dark:ring-gray-700',
  empty: 'text-sm text-gray-400 dark:text-gray-500 px-2 py-1.5',
  option: {
    base: 'cursor-default select-none relative flex items-center justify-between gap-1',
    rounded: 'rounded-md',
    padding: 'px-1.5 py-1.5',
    size: 'text-sm',
    color: 'text-gray-900 dark:text-white',
    container: 'flex items-center gap-1.5 min-w-0',
    active: 'bg-gray-100 dark:bg-gray-900',
    inactive: '',
    selected: 'pe-7',
    disabled: 'cursor-not-allowed opacity-50',
    empty: 'text-sm text-gray-400 dark:text-gray-500 px-2 py-1.5',
    icon: {
      base: 'flex-shrink-0 h-5 w-5',
      active: 'text-gray-900 dark:text-white',
      inactive: 'text-gray-400 dark:text-gray-500'
    },
    selectedIcon: {
      wrapper: 'absolute inset-y-0 end-0 flex items-center',
      padding: 'pe-2',
      base: 'h-5 w-5 text-gray-900 dark:text-white flex-shrink-0'
    },
    avatar: {
      base: 'flex-shrink-0',
      size: '2xs'
    },
    chip: {
      base: 'flex-shrink-0 w-2 h-2 mx-1 rounded-full'
    },
    create: 'block truncate'
  },
  transition: {
    leaveActiveClass: 'transition ease-in duration-100',
    leaveFromClass: 'opacity-100',
    leaveToClass: 'opacity-0'
  },
  popper: {
    placement: 'bottom-end'
  },
  default: {
    selectedIcon: 'i-heroicons-check-20-solid',
    clearSearchOnClose: false,
    showCreateOptionWhen: 'empty',
    searchablePlaceholder: {
      label: 'Search...'
    },
    empty: {
      label: 'No options.'
    },
    optionEmpty: {
      label: 'No results for "{query}".'
    }
  },
  arrow: {
    base: 'invisible before:visible before:block before:rotate-45 before:z-[-1] before:w-2 before:h-2',
    ring: 'before:ring-1 before:ring-gray-200 dark:before:ring-gray-700',
    rounded: 'before:rounded-sm',
    background: 'before:bg-white dark:before:bg-gray-700',
    shadow: 'before:shadow',
    placement: "group-data-[popper-placement*='right']:-left-1 group-data-[popper-placement*='left']:-right-1 group-data-[popper-placement*='top']:-bottom-1 group-data-[popper-placement*='bottom']:-top-1"
  },
  select: 'inline-flex items-center text-left cursor-default',
  input: 'block w-[calc(100%+0.5rem)] focus:ring-transparent text-sm px-3 py-1.5 text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-800 border-0 border-b border-gray-200 dark:border-gray-700 sticky -top-1 -mt-1 mb-1 -mx-1 z-10 placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none',
  required: 'absolute inset-0 w-px opacity-0 cursor-default',
  label: 'block truncate'
}