122 lines
2.8 KiB
Vue
122 lines
2.8 KiB
Vue
<template>
|
|
<div ref="triggerRef">
|
|
<button
|
|
type="button"
|
|
class="nych-select ms-trigger"
|
|
@click="toggleOpen"
|
|
>
|
|
<span
|
|
class="nych-select-label"
|
|
:data-p="modelValue.length === 0 ? 'placeholder' : undefined"
|
|
>
|
|
{{ modelValue.length === 0 ? placeholder : modelValue.join(', ') }}
|
|
</span>
|
|
<span class="nych-select-dropdown">
|
|
<svg
|
|
class="nych-select-dropdownIcon ms-chevron"
|
|
:class="{ 'ms-chevron-open': isOpen }"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
>
|
|
<polyline points="6 9 12 15 18 9" />
|
|
</svg>
|
|
</span>
|
|
</button>
|
|
|
|
<Teleport to="body">
|
|
<template v-if="isOpen">
|
|
<div class="ms-backdrop" @click="close" />
|
|
<div class="nych-select-overlay ms-panel" :style="overlayStyle">
|
|
<ul class="nych-select-list">
|
|
<li
|
|
v-for="opt in options"
|
|
:key="opt"
|
|
class="nych-select-option"
|
|
:data-p-selected="modelValue.includes(opt) ? 'true' : undefined"
|
|
@click="toggleOption(opt)"
|
|
>
|
|
{{ opt }}
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</template>
|
|
</Teleport>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, onUnmounted } from 'vue'
|
|
|
|
const props = withDefaults(defineProps<{
|
|
modelValue: string[]
|
|
options: string[]
|
|
placeholder?: string
|
|
}>(), { placeholder: 'Select…' })
|
|
|
|
const emit = defineEmits<{ 'update:modelValue': [string[]] }>()
|
|
|
|
const triggerRef = ref<HTMLElement>()
|
|
const isOpen = ref(false)
|
|
const overlayStyle = ref<Record<string, string>>({})
|
|
|
|
function open() {
|
|
const rect = triggerRef.value!.getBoundingClientRect()
|
|
overlayStyle.value = {
|
|
position: 'fixed',
|
|
top: `${rect.bottom + 4}px`,
|
|
left: `${rect.left}px`,
|
|
width: `${rect.width}px`,
|
|
}
|
|
isOpen.value = true
|
|
window.addEventListener('scroll', close, { capture: true, once: true })
|
|
}
|
|
|
|
function close() {
|
|
isOpen.value = false
|
|
}
|
|
|
|
function toggleOpen() {
|
|
isOpen.value ? close() : open()
|
|
}
|
|
|
|
function toggleOption(opt: string) {
|
|
const cur = props.modelValue
|
|
emit('update:modelValue', cur.includes(opt) ? cur.filter(x => x !== opt) : [...cur, opt])
|
|
}
|
|
|
|
onUnmounted(close)
|
|
</script>
|
|
|
|
<style>
|
|
/* Must be global — teleported content renders outside this component's scoped tree */
|
|
.ms-trigger {
|
|
display: flex;
|
|
width: 100%;
|
|
min-width: unset;
|
|
text-align: left;
|
|
font-family: var(--font-sans);
|
|
font-size: 14px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.ms-backdrop {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 9998;
|
|
}
|
|
|
|
.ms-panel {
|
|
z-index: 9999;
|
|
}
|
|
|
|
.ms-chevron {
|
|
transition: transform 0.15s ease;
|
|
}
|
|
.ms-chevron-open {
|
|
transform: rotate(180deg);
|
|
}
|
|
</style>
|