119 lines
4.1 KiB
Vue
119 lines
4.1 KiB
Vue
<script setup lang="ts">
|
|
// types
|
|
|
|
type Props = {
|
|
variant?: "solid" | "outlined";
|
|
disabled?: boolean;
|
|
modelValue: number | string | undefined;
|
|
error?: boolean;
|
|
options?: string[];
|
|
placeholder?: string;
|
|
triggerRootClass?: string;
|
|
loading?: boolean;
|
|
};
|
|
|
|
type Emits = {
|
|
"update:modelValue": [value: number | string | undefined];
|
|
};
|
|
|
|
// props
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
variant: "solid",
|
|
disabled: false,
|
|
placeholder: "وارد نشده",
|
|
loading: false,
|
|
});
|
|
|
|
const { modelValue, variant, error, triggerRootClass } = toRefs(props);
|
|
|
|
// emit
|
|
|
|
const emit = defineEmits<Emits>();
|
|
|
|
// computed
|
|
|
|
const selectedValue = computed({
|
|
get: () => modelValue.value,
|
|
set: (value: string) => emit("update:modelValue", value),
|
|
});
|
|
|
|
const classes = computed(() => {
|
|
return [
|
|
"flex items-center text-black justify-between cursor-text transition-all border-[1.5px] gap-3 grow-0 typo-label-md px-4 py-2.5 lg:py-3.5 selection:bg-slate-100 rounded-md lg:rounded-100 flex-1 w-full outline-none",
|
|
{
|
|
"input-solid": variant.value === "solid",
|
|
"input-outlined": variant.value === "outlined",
|
|
"input-effects": !error.value,
|
|
[variant.value === "solid" ? "input-solid-error" : "input-outlined-error"]: error.value,
|
|
},
|
|
triggerRootClass.value,
|
|
];
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<SelectRoot
|
|
v-model="selectedValue"
|
|
dir="rtl"
|
|
:disabled="disabled || loading"
|
|
>
|
|
<SelectTrigger :class="classes">
|
|
<slot
|
|
v-if="!!$slots.trigger"
|
|
name="trigger"
|
|
/>
|
|
|
|
<SelectValue
|
|
v-else
|
|
:placeholder="placeholder"
|
|
v-bind="$attrs"
|
|
:class="selectedValue ? '!text-black' : 'text-slate-400 font-normal'"
|
|
class="font-iran-yekan-x text-xs lg:text-sm text-start placeholder-slate-400 placeholder:text-xs lg:placeholder:text-sm"
|
|
/>
|
|
<Icon
|
|
:name="loading ? 'ci:svg-spinners-3-dots-fade' : 'ci:bi-chevron-down'"
|
|
size="16"
|
|
/>
|
|
</SelectTrigger>
|
|
|
|
<SelectPortal>
|
|
<SelectContent
|
|
data-side="bottom"
|
|
class="min-w-[160px] w-full bg-slate-50 border-slate-200 rounded-lg border shadow-sm will-change-[opacity,transform] data-[side=top]:animate-slide-down-fade data-[side=right]:animate-slide-left-fade data-[side=bottom]:animate-slide-up-fade data-[side=left]:animate-slide-right-fade z-[9999]"
|
|
:side-offset="5"
|
|
>
|
|
<SelectViewport class="p-[5px]">
|
|
<slot
|
|
v-if="!!$slots.content"
|
|
name="content"
|
|
/>
|
|
|
|
<SelectGroup v-else>
|
|
<SelectItem
|
|
v-for="(option, index) in options"
|
|
:key="index"
|
|
class="text-xs leading-none w-full rounded-sm py-5 flex items-center justify-between h-[25px] pr-[12px] relative select-none data-[disabled]:pointer-events-none data-[highlighted]:outline-none data-[highlighted]:bg-slate-300 data-[highlighted]:text-black"
|
|
:value="option"
|
|
>
|
|
<SelectItemIndicator
|
|
class="absolute left-0 w-[25px] inline-flex items-center justify-center"
|
|
>
|
|
<Icon
|
|
name="ci:bi-check"
|
|
size="20"
|
|
/>
|
|
</SelectItemIndicator>
|
|
<SelectItemText class="text-end font-iran-yekan-x text-sm">
|
|
{{ option }}
|
|
</SelectItemText>
|
|
</SelectItem>
|
|
</SelectGroup>
|
|
</SelectViewport>
|
|
</SelectContent>
|
|
</SelectPortal>
|
|
</SelectRoot>
|
|
</template>
|
|
|
|
<style scoped></style>
|