64 lines
1.9 KiB
Vue
64 lines
1.9 KiB
Vue
<script setup lang="ts">
|
|
import { CheckIcon } from '@lucide/vue'
|
|
import type { HTMLAttributes } from 'vue'
|
|
import { computed, inject, onMounted, ref } from 'vue'
|
|
import { cn } from '@/lib/utils'
|
|
import { SelectContextKey } from './context'
|
|
import SelectItemText from './SelectItemText.vue'
|
|
|
|
const props = defineProps<{
|
|
value: string
|
|
disabled?: boolean
|
|
class?: HTMLAttributes['class']
|
|
}>()
|
|
|
|
const context = inject(SelectContextKey)!
|
|
const itemRef = ref<HTMLElement | null>(null)
|
|
|
|
const isSelected = computed(() => context.modelValue.value === props.value)
|
|
|
|
function select() {
|
|
if (props.disabled) return
|
|
context.modelValue.value = props.value
|
|
context.open.value = false
|
|
}
|
|
|
|
function onPointerMove() {
|
|
if (props.disabled) return
|
|
itemRef.value?.focus()
|
|
}
|
|
|
|
onMounted(() => {
|
|
context.registerLabel(props.value, itemRef.value?.textContent?.trim() ?? '')
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
ref="itemRef"
|
|
data-slot="select-item"
|
|
role="option"
|
|
:data-value="value"
|
|
:aria-selected="isSelected"
|
|
:data-disabled="disabled ? '' : undefined"
|
|
tabindex="-1"
|
|
:class="
|
|
cn(
|
|
'focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm [&_svg:not([class*=size-])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2 relative flex w-full cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 transition-colors',
|
|
props.class,
|
|
)
|
|
"
|
|
@click="select"
|
|
@pointermove="onPointerMove"
|
|
>
|
|
<span class="pointer-events-none absolute right-2 flex size-4 items-center justify-center">
|
|
<slot v-if="isSelected" name="indicator-icon">
|
|
<CheckIcon class="pointer-events-none" />
|
|
</slot>
|
|
</span>
|
|
|
|
<SelectItemText>
|
|
<slot />
|
|
</SelectItemText>
|
|
</div>
|
|
</template>
|