Skip to content

Instantly share code, notes, and snippets.

@marr
Created June 12, 2025 12:49
Show Gist options
  • Save marr/5daecdde3ba633a5ddd08dc6012d17d2 to your computer and use it in GitHub Desktop.
Save marr/5daecdde3ba633a5ddd08dc6012d17d2 to your computer and use it in GitHub Desktop.
<script setup lang="ts">
import { ComboboxInput, ComboboxVirtualizer, useFilter, type AcceptableInputValue } from 'reka-ui';
import { Check, ChevronDown } from 'lucide-vue-next';
import type { HTMLAttributes } from 'vue';
const { contains } = useFilter({ sensitivity: 'base' });
const {
class: className = '',
items = [],
label = '',
} = defineProps<{
class?: HTMLAttributes['class'];
items?: CxlSelectableItemProps[];
label?: string;
readonly?: boolean;
}>();
const query = ref('');
const values = defineModel<AcceptableInputValue[]>({
default: () => [],
});
const filteredOptions = computed(() => {
const _options = items.filter((i) => !values.value.includes(i.value));
return query.value ? _options.filter((option) => contains(option.label, query.value)) : _options;
});
function getDisplayValue(value: AcceptableInputValue) {
return items.find((option) => option.value === value)?.label ?? String(value);
}
watch(
values,
() => {
query.value = '';
},
{ deep: true },
);
</script>
<template>
<Label v-if="label" class="tw-mb-2">{{ label }}</Label>
<Combobox v-model="values" multiple ignore-filter :disabled="readonly">
<ComboboxAnchor as-child>
<TagsInput
v-bind="$attrs"
v-model="values"
:class="className"
class="tw-min-h-10"
delimiter=""
:display-value="getDisplayValue"
:disabled="readonly"
>
<TagsInputItem v-for="item in values" :key="getDisplayValue(item)" class="tw-h-auto" :value="item">
<TagsInputItemText />
<TagsInputItemDelete v-if="!readonly" />
</TagsInputItem>
<template v-if="!readonly">
<ComboboxInput v-model="query" as-child>
<TagsInputInput class="tw-w-10" placeholder="Select..." @keydown.enter.prevent />
</ComboboxInput>
<ComboboxTrigger>
<ChevronDown class="tw-text-input" />
</ComboboxTrigger>
</template>
</TagsInput>
</ComboboxAnchor>
<ComboboxList class="tw-max-h-60 tw-overflow-auto">
<ComboboxEmpty>No Options</ComboboxEmpty>
<ComboboxVirtualizer
v-slot="{ option }"
:options="filteredOptions"
:estimate-size="36"
:text-content="(opt) => opt.label"
>
<ComboboxGroup class="tw-w-full">
<ComboboxItem :key="option.label" :value="option.value" :disabled="option.disabled">
<ComboboxItemIndicator>
<Check class="tw-ml-auto tw-h-4 tw-w-4" />
</ComboboxItemIndicator>
<span>
{{ option.label }}
</span>
</ComboboxItem>
</ComboboxGroup>
</ComboboxVirtualizer>
</ComboboxList>
</Combobox>
</template>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment