feat: Add select component

This commit is contained in:
jhynsoo 2024-05-17 15:33:08 +09:00
parent f9474917af
commit ceed039a80

View 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>