Skip to content
Snippets Groups Projects
ComboBoxSimple.vue 2.35 KiB
Newer Older
<template>
  <ComboBox
    v-model="modelValue"
    v-bind="props"
    :choices="filteredChoices"
    :close-on-select="props.closeOnSelect ?? !Array.isArray(modelValue)"
    input-container-class="tw-flex tw-flex-wrap tw-p-2 tw-gap-2 tw-items-baseline tw-w-full form-control tw-h-auto tw-min-h-[46px]"
    input-class="tw-border-none tw-px-1 tw-w-[100px] focus:tw-shadow-none focus:tw-outline-none focus:tw-ring-0"
    @search="searchQuery = $event"
  >
    <template #default="{ choice, ...attributes }">
      <slot :choice="choice" v-bind="attributes">
        <li v-bind="attributes">
          {{ choice.name }}
        </li>
      </slot>
    </template>

    <template #selected="{ value, deselect, isOpen }">
      <slot name="selected" :value="value" :deselect="deselect" :is-open="isOpen">
        <template v-if="Array.isArray(value)">
          <Tag
            v-for="(item, index) in value"
            :key="index"
            :label="item.name"
            removable
            @remove="deselect(item)"
          />
        </template>
        <template v-else>
          <p v-if="value">{{ value.name }}</p>
        </template>
      </slot>
    </template>
  </ComboBox>
</template>

<script setup lang="ts" generic="T extends { id: ID, name: string }">
import { ID } from '@/api'
import Tag from './generic/Tag.vue'
import ComboBox from './ComboBox.vue'
import type { ComboBoxProps } from '@/components/ComboBox.vue'
import { computed, ref } from 'vue'
import { matchesSearch } from '@/util'

defineOptions({
  compatConfig: { MODE: 3 },
})
defineSlots<{
  default(
    props: Record<string, unknown> & {
      id: string
      choice: T
      index: number
      activeIndex: number
    },
  ): unknown
  selected(props: {
    value: null | T | T[]
    deselect: (choice: null | T) => void
    isOpen: boolean
  }): unknown
}>()

const modelValue = defineModel<null | T | T[]>({ required: true })
const props = defineProps<ComboBoxProps<T>>()

const searchQuery = ref('')
const selectedIds = computed(() => {
  if (Array.isArray(modelValue.value)) {
    return modelValue.value.map((item) => item.id)
  } else if (modelValue.value !== null) {
    return [modelValue.value.id]
  } else {
    return []
  }
})

const filteredChoices = computed(() =>
  props.choices.filter(
    ({ id, name }) => !selectedIds.value.includes(id) && matchesSearch(name, searchQuery.value),
  ),
)
</script>