feat: Add select component
This commit is contained in:
parent
f9474917af
commit
ceed039a80
156
src/components/common/Select.vue
Normal file
156
src/components/common/Select.vue
Normal file
@ -0,0 +1,156 @@
|
||||
<!-- eslint-disable vue/multi-word-component-names -->
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import FilledButton from './FilledButton.vue';
|
||||
|
||||
const { placeholder, options, optionName } = defineProps({
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '----',
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
optionName: {
|
||||
type: String,
|
||||
default: 'value',
|
||||
},
|
||||
});
|
||||
const body = document.querySelector('body');
|
||||
const buttonRef = ref(null);
|
||||
const isOpen = ref(false);
|
||||
const menuStyle = ref('');
|
||||
const selected = ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
const rect = buttonRef.value.$el.getBoundingClientRect();
|
||||
|
||||
menuStyle.value = `transform: translate(${rect.left}px, ${rect.bottom + 4}px)`;
|
||||
});
|
||||
|
||||
const selectedName = computed(() => selected.value?.[optionName] ?? selected.value);
|
||||
|
||||
function handleOpen() {
|
||||
console.log('open');
|
||||
isOpen.value = true;
|
||||
body.style.pointerEvents = 'none';
|
||||
body.style.overflow = 'hidden';
|
||||
document.addEventListener('pointerdown', handleClose, { once: true });
|
||||
}
|
||||
|
||||
function closeMenu() {
|
||||
isOpen.value = false;
|
||||
body.removeAttribute('style');
|
||||
}
|
||||
|
||||
function handleClose({ target }) {
|
||||
if (target && target?.tagName !== 'HTML') {
|
||||
return;
|
||||
}
|
||||
closeMenu();
|
||||
}
|
||||
|
||||
function handleSelect(option) {
|
||||
selected.value = option;
|
||||
closeMenu();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FilledButton
|
||||
:class="'select' + (isOpen ? ' active' : '')"
|
||||
ref="buttonRef"
|
||||
@mousedown="handleOpen"
|
||||
>
|
||||
<span>{{ selectedName ?? placeholder }}</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden=""
|
||||
>
|
||||
<path d="m6 9 6 6 6-6"></path>
|
||||
</svg>
|
||||
</FilledButton>
|
||||
<Teleport v-if="isOpen" to="body">
|
||||
<div class="menu-wrapper" :style="menuStyle">
|
||||
<ul class="menu">
|
||||
<li
|
||||
v-for="option in options"
|
||||
:key="option.value"
|
||||
@click.stop="handleSelect(option)"
|
||||
:class="'menu-item' + (selected === option ? 'selected' : '')"
|
||||
>
|
||||
{{ option[optionName] ?? option }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@keyframes show {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, -8px, 0) scale3d(0.95, 0.95, 0.95);
|
||||
}
|
||||
}
|
||||
|
||||
.select {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.active {
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.select svg {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.menu-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
min-width: max-content;
|
||||
pointer-events: auto;
|
||||
will-change: transform;
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
.menu {
|
||||
background-color: var(--color-background);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--color-border);
|
||||
animation: show 0.25s;
|
||||
padding: 4px;
|
||||
max-height: 320px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
padding: 6px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.25s;
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
background-color: var(--color-background-soft);
|
||||
}
|
||||
|
||||
.menu-item.selected {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue
Block a user