Files
fabric_trade_admin/src/components/ui/AdminSelect.vue
T

228 lines
5.0 KiB
Vue
Raw Normal View History

<script setup lang="ts">
import { computed, ref } from 'vue'
import { onClickOutside } from '@vueuse/core'
import AdminIcon from '@/components/icons/AdminIcon.vue'
export type AdminSelectOption = {
value: string
label: string
}
const props = withDefaults(
defineProps<{
modelValue: string
options: AdminSelectOption[]
placeholder?: string
ariaLabel?: string
block?: boolean
}>(),
{
placeholder: 'انتخاب کنید',
ariaLabel: 'انتخاب',
block: false,
},
)
const emit = defineEmits<{
'update:modelValue': [value: string]
}>()
const open = ref(false)
const rootRef = ref<HTMLElement | null>(null)
onClickOutside(rootRef, () => {
open.value = false
})
const selectedLabel = computed(
() => props.options.find((option) => option.value === props.modelValue)?.label ?? props.placeholder,
)
function toggle() {
open.value = !open.value
}
function pick(value: string) {
emit('update:modelValue', value)
open.value = false
}
</script>
<template>
<div
ref="rootRef"
class="admin-select"
:class="{ 'admin-select--open': open, 'admin-select--block': block }"
>
<button
type="button"
class="admin-select__trigger"
:aria-label="ariaLabel"
:aria-expanded="open"
aria-haspopup="listbox"
@click="toggle"
>
<span class="admin-select__value">{{ selectedLabel }}</span>
<span class="admin-select__chevron" aria-hidden="true">
<AdminIcon name="chevron-down" :size="16" />
</span>
</button>
<ul
v-if="open"
class="admin-select__menu"
role="listbox"
:aria-label="ariaLabel"
>
<li v-for="option in options" :key="option.value" role="presentation">
<button
type="button"
class="admin-select__option"
:class="{ 'admin-select__option--active': option.value === modelValue }"
role="option"
:aria-selected="option.value === modelValue"
@click="pick(option.value)"
>
<span>{{ option.label }}</span>
<AdminIcon
v-if="option.value === modelValue"
name="check"
:size="16"
class="admin-select__check"
/>
</button>
</li>
</ul>
</div>
</template>
<style scoped>
.admin-select {
position: relative;
min-width: 200px;
max-width: 100%;
}
.admin-select--block {
width: 100%;
min-width: 0;
}
.admin-select__trigger {
width: 100%;
min-height: var(--control-height, 40px);
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 0 12px 0 10px;
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
background: var(--color-surface);
color: var(--color-text);
font-family: inherit;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition:
border-color var(--transition-fast),
box-shadow var(--transition-fast),
background var(--transition-fast);
}
.admin-select__trigger:hover {
border-color: var(--color-primary);
background: var(--color-elevated, var(--color-surface));
}
.admin-select--open .admin-select__trigger,
.admin-select__trigger:focus-visible {
outline: none;
border-color: var(--color-primary);
box-shadow: var(--focus-ring);
}
.admin-select__value {
flex: 1;
min-width: 0;
text-align: start;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.admin-select__chevron {
display: inline-flex;
flex-shrink: 0;
color: var(--color-text-muted);
transition: transform var(--transition-fast);
}
.admin-select--open .admin-select__chevron {
transform: rotate(180deg);
color: var(--color-primary);
}
.admin-select__menu {
position: absolute;
inset-inline-start: 0;
top: calc(100% + 6px);
z-index: 40;
width: max(100%, 220px);
margin: 0;
padding: 8px;
list-style: none;
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
box-shadow: var(--shadow-card, 0 12px 28px rgba(15, 23, 42, 0.12));
display: flex;
flex-direction: column;
gap: 4px;
}
.admin-select__option {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 10px 12px;
border: 1px solid transparent;
border-radius: 12px;
background: transparent;
color: var(--color-text);
font-family: inherit;
font-size: 0.875rem;
font-weight: 500;
text-align: start;
cursor: pointer;
transition:
background var(--transition-fast),
border-color var(--transition-fast),
color var(--transition-fast);
}
.admin-select__option:hover {
background: var(--color-bg);
border-color: var(--color-border);
}
.admin-select__option--active {
background: var(--color-primary-soft);
border-color: transparent;
color: var(--color-primary);
font-weight: 600;
}
.admin-select__option--active:hover {
background: var(--color-primary-soft);
border-color: transparent;
}
.admin-select__check {
flex-shrink: 0;
color: var(--color-primary);
}
</style>