feat(story): new select component with floating-vue (#151)

This commit is contained in:
RainbowBird
2025-04-30 17:08:56 +08:00
committed by GitHub
parent 0a72441039
commit 99bc4ca7a6
4 changed files with 172 additions and 43 deletions
@@ -0,0 +1,66 @@
<script setup lang="ts">
import { Select } from '@proj-airi/ui'
import { ref } from 'vue'
const singleValue = ref('option1')
const disabledValue = ref('option2')
const options = [
{ label: 'Option 1', value: 'option1' },
{ label: 'Option 2', value: 'option2' },
{ label: 'Option 3', value: 'option3' },
{ label: 'Option 4', value: 'option4' },
]
</script>
<template>
<Story
title="Select"
group="form"
:layout="{ type: 'grid', width: 600 }"
>
<template #controls>
<ThemeColorsHueControl />
</template>
<Variant
id="basic"
title="Basic Select"
>
<div>
<Select
v-model="singleValue"
:options="options"
placeholder="Choose an option..."
/>
</div>
</Variant>
<Variant
id="disabled"
title="Disabled Select"
>
<div>
<Select
v-model="disabledValue"
:options="options"
placeholder="Disabled select"
disabled
/>
</div>
</Variant>
<Variant
id="custom-width"
title="Custom Width"
>
<div class="w-[300px]">
<Select
v-model="singleValue"
:options="options"
placeholder="Choose an option..."
/>
</div>
</Variant>
</Story>
</template>
+1
View File
@@ -36,6 +36,7 @@
},
"dependencies": {
"@vueuse/core": "^13.1.0",
"floating-vue": "^5.2.2",
"reka-ui": "^2.2.0",
"vue": "^3.5.13"
},
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { onClickOutside } from '@vueuse/core'
import { computed, ref } from 'vue'
import { Dropdown as VDropdown } from 'floating-vue'
import { computed } from 'vue'
const props = defineProps<{
options: { label: string, value: string | number }[]
@@ -11,66 +11,92 @@ const props = defineProps<{
const modelValue = defineModel<string | number>({ required: true })
const isOpen = ref(false)
const selectedLabel = computed(() => {
const selected = props.options.find(opt => opt.value === modelValue.value)
return selected ? selected.label : props.placeholder
})
function toggleDropdown() {
if (!props.disabled) {
isOpen.value = !isOpen.value
}
}
function selectOption(value: string | number) {
modelValue.value = value
isOpen.value = false
}
// Close dropdown when clicking outside
const dropdownRef = ref<HTMLElement | null>(null)
onClickOutside(dropdownRef, () => {
isOpen.value = false
})
</script>
<template>
<div
ref="dropdownRef"
class="relative w-full font-sans"
<VDropdown
auto-size
auto-boundary-max-size
>
<button
type="button"
class="min-w-[160px] flex items-center justify-between gap-2 border rounded-lg bg-white p-2.5 text-xs text-neutral-700 shadow-sm outline-none transition-colors dark:border-neutral-800 dark:bg-neutral-900 hover:bg-neutral-50 dark:text-neutral-200 focus:shadow-[0_0_0_2px] focus:shadow-black dark:hover:bg-neutral-800"
:class="{ 'cursor-not-allowed bg-neutral-100 text-neutral-400 dark:bg-neutral-800 dark:text-neutral-600': props.disabled }"
@click="toggleDropdown"
>
<span>{{ selectedLabel }}</span>
<div i-solar:alt-arrow-down-bold-duotone class="h-3.5 w-3.5 text-neutral-500 dark:text-neutral-400" />
</button>
<div
v-if="isOpen"
class="absolute left-0 right-0 top-[calc(100%+4px)] z-10 min-w-[160px] border rounded-lg bg-white shadow-lg transition-all dark:border-neutral-800 dark:bg-neutral-900"
class="min-w-[160px] flex cursor-pointer items-center justify-between gap-2 border rounded-lg bg-white p-2.5 text-xs text-neutral-700 shadow-sm outline-none transition-colors disabled:cursor-not-allowed dark:border-neutral-800 dark:bg-neutral-900 disabled:bg-neutral-100 hover:bg-neutral-50 dark:text-neutral-200 disabled:text-neutral-400 focus:ring-2 focus:ring-black/10 dark:disabled:bg-neutral-800 dark:hover:bg-neutral-800 dark:disabled:text-neutral-600"
:class="{ 'pointer-events-none': props.disabled }"
>
<div
v-if="props.title"
class="border-b p-2 text-xs text-neutral-500 font-semibold dark:border-neutral-800"
>
{{ props.title }}
<div class="flex-1 truncate">
<slot :label="selectedLabel">
{{ selectedLabel }}
</slot>
</div>
<div class="max-h-[300px] overflow-y-auto p-1">
<div i-solar:alt-arrow-down-bold-duotone class="h-3.5 w-3.5 text-neutral-500 dark:text-neutral-400" />
</div>
<template #popper="{ hide }">
<div class="min-w-[160px] flex flex-col gap-0.5 border border-neutral-200 rounded-lg bg-white p-1 shadow-lg dark:border-neutral-800 dark:bg-neutral-900">
<div
v-for="option in props.options"
v-for="option of props.options"
v-bind="{ ...$attrs, class: null, style: null }"
:key="option.value"
class="flex cursor-pointer select-none items-center rounded p-2 text-xs text-neutral-700 transition-colors hover:bg-neutral-50 dark:text-neutral-200 dark:hover:bg-neutral-800"
:class="{ 'bg-neutral-100 dark:bg-neutral-700': modelValue === option.value }"
@click="selectOption(option.value)"
class="cursor-pointer rounded px-2 py-1.5 text-sm text-neutral-700 hover:bg-neutral-100 dark:text-neutral-200 dark:hover:bg-neutral-800"
:class="{
'bg-neutral-100 dark:bg-neutral-800': modelValue === option.value,
}"
@click="selectOption(option.value); hide()"
>
{{ option.label }}
</div>
</div>
</div>
</div>
</template>
</VDropdown>
</template>
<style>
.resize-observer[data-v-b329ee4c] {
position: absolute;
top: 0;
left: 0;
z-index: -1;
width: 100%;
height: 100%;
border: none;
background-color: transparent;
pointer-events: none;
display: block;
overflow: hidden;
opacity: 0;
}
.resize-observer[data-v-b329ee4c] object {
display: block;
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
overflow: hidden;
pointer-events: none;
z-index: -1;
}
.v-popper__popper {
z-index: 10000;
top: 0;
left: 0;
outline: none;
}
.v-popper__arrow-container {
display: none;
}
.v-popper__inner {
border: none !important;
}
</style>