mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-13 18:52:14 +01:00
migrate select/multiselect components to Radix Vue primitives
This commit is contained in:
@@ -12,6 +12,10 @@ function getKeyFromItem(item: Client) {
|
||||
function getNameForItem(item: Client) {
|
||||
return item.name;
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
submit: [];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -19,7 +23,9 @@ function getNameForItem(item: Client) {
|
||||
search-placeholder="Search for a Client..."
|
||||
:items="clients"
|
||||
:get-key-from-item="getKeyFromItem"
|
||||
:get-name-for-item="getNameForItem">
|
||||
:get-name-for-item="getNameForItem"
|
||||
no-item-label="No Client"
|
||||
@submit="emit('submit')">
|
||||
<template #trigger>
|
||||
<slot name="trigger"></slot>
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import SelectDropdown from '@/packages/ui/src/Input/SelectDropdown.vue';
|
||||
import Badge from '@/packages/ui/src/Badge.vue';
|
||||
import { ChevronDownIcon } from '@heroicons/vue/20/solid';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/Components/ui/select';
|
||||
import type { BillableKey } from '@/types/projects';
|
||||
|
||||
const model = defineModel<BillableKey>({
|
||||
@@ -21,38 +25,26 @@ const options: Option[] = [
|
||||
},
|
||||
];
|
||||
|
||||
function getKeyFromItem(item: Option) {
|
||||
return item.key;
|
||||
}
|
||||
|
||||
function getNameFromItem(item: Option) {
|
||||
return item.name;
|
||||
}
|
||||
|
||||
function getNameForKey(key: BillableKey | undefined) {
|
||||
const item = options.find((item) => getKeyFromItem(item) === key);
|
||||
const item = options.find((item) => item.key === key);
|
||||
if (item) {
|
||||
return getNameFromItem(item);
|
||||
return item.name;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SelectDropdown
|
||||
v-model="model"
|
||||
:get-key-from-item="getKeyFromItem"
|
||||
:get-name-for-item="getNameFromItem"
|
||||
:items="options">
|
||||
<template #trigger>
|
||||
<Badge size="xlarge" class="bg-input-background cursor-pointer">
|
||||
<span>
|
||||
{{ getNameForKey(model) }}
|
||||
</span>
|
||||
<ChevronDownIcon class="text-text-secondary w-5"></ChevronDownIcon>
|
||||
</Badge>
|
||||
</template>
|
||||
</SelectDropdown>
|
||||
<Select v-model="model">
|
||||
<SelectTrigger>
|
||||
<SelectValue>{{ getNameForKey(model) }}</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="option in options" :key="option.key" :value="option.key">
|
||||
{{ option.name }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, nextTick, ref, watch } from 'vue';
|
||||
import { useMembersQuery } from '@/utils/useMembersQuery';
|
||||
import { UserIcon, ChevronDownIcon } from '@heroicons/vue/24/solid';
|
||||
import { useFocus } from '@vueuse/core';
|
||||
import { UserIcon } from '@heroicons/vue/24/solid';
|
||||
import { ChevronDown } from 'lucide-vue-next';
|
||||
import type { ProjectMember } from '@/packages/api/src';
|
||||
import { Badge, SelectDropdown } from '@/packages/ui/src';
|
||||
import type { Member } from '@/packages/api/src';
|
||||
import {
|
||||
ComboboxAnchor,
|
||||
ComboboxContent,
|
||||
ComboboxInput,
|
||||
ComboboxItem,
|
||||
ComboboxRoot,
|
||||
ComboboxViewport,
|
||||
} from 'radix-vue';
|
||||
import Dropdown from '@/packages/ui/src/Input/Dropdown.vue';
|
||||
import { Button } from '@/Components/ui/button';
|
||||
|
||||
const { members } = useMembersQuery();
|
||||
|
||||
@@ -24,16 +33,24 @@ const props = withDefaults(
|
||||
}
|
||||
);
|
||||
|
||||
const searchInput = ref<HTMLInputElement | null>(null);
|
||||
|
||||
const open = ref(false);
|
||||
const searchValue = ref('');
|
||||
const searchInput = ref<HTMLElement | null>(null);
|
||||
|
||||
useFocus(searchInput, { initialValue: true });
|
||||
watch(open, (isOpen) => {
|
||||
if (isOpen) {
|
||||
searchValue.value = '';
|
||||
nextTick(() => {
|
||||
// @ts-expect-error We need to access the actual HTML Element to focus
|
||||
searchInput.value?.$el?.focus();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const filteredMembers = computed<Member[]>(() => {
|
||||
return members.value.filter((member) => {
|
||||
return (
|
||||
member.name.toLowerCase().includes(searchValue.value?.toLowerCase()?.trim() || '') &&
|
||||
member.name.toLowerCase().includes(searchValue.value.toLowerCase().trim() || '') &&
|
||||
!props.hiddenMembers.some((hiddenMember) => hiddenMember.member_id === member.id) &&
|
||||
member.is_placeholder === false
|
||||
);
|
||||
@@ -44,29 +61,65 @@ const currentValue = computed(() => {
|
||||
if (model.value) {
|
||||
return members.value.find((member) => member.id === model.value)?.name;
|
||||
}
|
||||
return searchValue.value;
|
||||
return '';
|
||||
});
|
||||
|
||||
function selectMember(member: Member) {
|
||||
model.value = member.id;
|
||||
open.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SelectDropdown
|
||||
v-model="model"
|
||||
:items="filteredMembers"
|
||||
:get-key-from-item="(member) => member.id"
|
||||
:get-name-for-item="(member) => member.name">
|
||||
<Dropdown v-model="open" align="start" :close-on-content-click="false">
|
||||
<template #trigger>
|
||||
<Badge
|
||||
tag="button"
|
||||
class="flex w-full text-base text-left space-x-3 px-3 text-text-secondary bg-input-background font-normal cursor py-1.5">
|
||||
<UserIcon class="relative z-10 w-4 text-text-secondary"></UserIcon>
|
||||
<div v-if="currentValue" class="flex-1 truncate">
|
||||
{{ currentValue }}
|
||||
<Button
|
||||
:disabled="disabled"
|
||||
type="button"
|
||||
variant="input"
|
||||
size="input"
|
||||
class="w-full justify-between text-start font-normal">
|
||||
<div class="flex items-center gap-3 truncate">
|
||||
<UserIcon class="w-4 text-text-secondary shrink-0" />
|
||||
<span v-if="currentValue" class="truncate text-text-primary">{{
|
||||
currentValue
|
||||
}}</span>
|
||||
<span v-else class="text-muted-foreground">Select a member...</span>
|
||||
</div>
|
||||
<div v-else class="flex-1">Select a member...</div>
|
||||
<ChevronDownIcon class="w-4 text-text-secondary"></ChevronDownIcon>
|
||||
</Badge>
|
||||
<ChevronDown class="w-4 h-4 text-icon-default shrink-0" />
|
||||
</Button>
|
||||
</template>
|
||||
</SelectDropdown>
|
||||
<template #content>
|
||||
<ComboboxRoot
|
||||
v-model:search-term="searchValue"
|
||||
:open="open"
|
||||
class="relative"
|
||||
:filter-function="(val: string[]) => val">
|
||||
<ComboboxAnchor>
|
||||
<ComboboxInput
|
||||
ref="searchInput"
|
||||
class="bg-card-background border-0 placeholder-text-tertiary text-sm text-text-primary py-2.5 focus:ring-0 border-b border-card-background-separator focus:border-card-background-separator w-full"
|
||||
placeholder="Search for a member..." />
|
||||
</ComboboxAnchor>
|
||||
<ComboboxContent
|
||||
:dismiss-able="false"
|
||||
position="inline"
|
||||
class="w-60 max-h-60 overflow-y-auto">
|
||||
<ComboboxViewport>
|
||||
<ComboboxItem
|
||||
v-for="member in filteredMembers"
|
||||
:key="member.id"
|
||||
:value="member.id"
|
||||
class="flex items-center gap-3 px-3 py-2.5 text-sm text-text-primary data-[highlighted]:bg-card-background-active cursor-default"
|
||||
@select.prevent="selectMember(member)">
|
||||
<UserIcon class="w-4 text-text-secondary shrink-0" />
|
||||
<span class="truncate">{{ member.name }}</span>
|
||||
</ComboboxItem>
|
||||
</ComboboxViewport>
|
||||
</ComboboxContent>
|
||||
</ComboboxRoot>
|
||||
</template>
|
||||
</Dropdown>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -7,9 +7,10 @@ import PrimaryButton from '@/packages/ui/src/Buttons/PrimaryButton.vue';
|
||||
import MemberCombobox from '@/Components/Common/Member/MemberCombobox.vue';
|
||||
import { UserIcon, ArrowRightIcon } from '@heroicons/vue/24/solid';
|
||||
import { Badge } from '@/packages/ui/src';
|
||||
import { useMutation } from '@tanstack/vue-query';
|
||||
import { useMutation, useQueryClient } from '@tanstack/vue-query';
|
||||
import { getCurrentOrganizationId } from '@/utils/useUser';
|
||||
import { useNotificationsStore } from '@/utils/notification';
|
||||
const queryClient = useQueryClient();
|
||||
const { handleApiRequestNotifications, addNotification } = useNotificationsStore();
|
||||
|
||||
const show = defineModel('show', { default: false });
|
||||
@@ -50,6 +51,7 @@ async function submit() {
|
||||
'Members successfully merged!',
|
||||
'There was an error merging the members.',
|
||||
() => {
|
||||
queryClient.invalidateQueries({ queryKey: ['members'] });
|
||||
show.value = false;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -12,6 +12,10 @@ function getKeyFromItem(item: Member) {
|
||||
function getNameForItem(item: Member) {
|
||||
return item.name;
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
submit: [];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -19,7 +23,8 @@ function getNameForItem(item: Member) {
|
||||
search-placeholder="Search for a Member..."
|
||||
:items="members"
|
||||
:get-key-from-item="getKeyFromItem"
|
||||
:get-name-for-item="getNameForItem">
|
||||
:get-name-for-item="getNameForItem"
|
||||
@submit="emit('submit')">
|
||||
<template #trigger>
|
||||
<slot name="trigger"></slot>
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import SelectDropdown from '@/packages/ui/src/Input/SelectDropdown.vue';
|
||||
import Badge from '@/packages/ui/src/Badge.vue';
|
||||
import { ChevronDownIcon } from '@heroicons/vue/20/solid';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/Components/ui/select';
|
||||
import type { Role } from '@/types/jetstream';
|
||||
import { usePage } from '@inertiajs/vue3';
|
||||
|
||||
@@ -13,38 +17,26 @@ const page = usePage<{
|
||||
availableRoles: Role[];
|
||||
}>();
|
||||
|
||||
function getKeyFromItem(item: Role) {
|
||||
return item.key;
|
||||
}
|
||||
|
||||
function getNameFromItem(item: Role) {
|
||||
return item.name;
|
||||
}
|
||||
|
||||
function getNameForKey(key: string | undefined) {
|
||||
const item = page.props.availableRoles.find((item) => getKeyFromItem(item) === key);
|
||||
const item = page.props.availableRoles.find((item) => item.key === key);
|
||||
if (item) {
|
||||
return getNameFromItem(item);
|
||||
return item.name;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SelectDropdown
|
||||
v-model="model"
|
||||
:get-key-from-item="getKeyFromItem"
|
||||
:get-name-for-item="getNameFromItem"
|
||||
:items="page.props.availableRoles">
|
||||
<template #trigger>
|
||||
<Badge size="xlarge" class="bg-input-background cursor-pointer">
|
||||
<span>
|
||||
{{ getNameForKey(model) }}
|
||||
</span>
|
||||
<ChevronDownIcon class="text-text-secondary w-5"></ChevronDownIcon>
|
||||
</Badge>
|
||||
</template>
|
||||
</SelectDropdown>
|
||||
<Select v-model="model">
|
||||
<SelectTrigger>
|
||||
<SelectValue>{{ getNameForKey(model) }}</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="role in page.props.availableRoles" :key="role.key" :value="role.key">
|
||||
{{ role.name }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -12,6 +12,10 @@ function getKeyFromItem(item: Project) {
|
||||
function getNameForItem(item: Project) {
|
||||
return item.name;
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
submit: [];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -19,7 +23,9 @@ function getNameForItem(item: Project) {
|
||||
search-placeholder="Search for a Project..."
|
||||
:items="projects"
|
||||
:get-key-from-item="getKeyFromItem"
|
||||
:get-name-for-item="getNameForItem">
|
||||
:get-name-for-item="getNameForItem"
|
||||
no-item-label="No Project"
|
||||
@submit="emit('submit')">
|
||||
<template #trigger>
|
||||
<slot name="trigger"></slot>
|
||||
</template>
|
||||
|
||||
151
resources/js/Components/Common/Reporting/ReportingFilterBar.vue
Normal file
151
resources/js/Components/Common/Reporting/ReportingFilterBar.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<script setup lang="ts">
|
||||
import { CheckCircleIcon, TagIcon, UserGroupIcon } from '@heroicons/vue/20/solid';
|
||||
import { FolderIcon } from '@heroicons/vue/16/solid';
|
||||
import BillableIcon from '@/packages/ui/src/Icons/BillableIcon.vue';
|
||||
import ReportingRoundingControls from '@/Components/Common/Reporting/ReportingRoundingControls.vue';
|
||||
import TaskMultiselectDropdown from '@/Components/Common/Task/TaskMultiselectDropdown.vue';
|
||||
import ClientMultiselectDropdown from '@/Components/Common/Client/ClientMultiselectDropdown.vue';
|
||||
import MemberMultiselectDropdown from '@/Components/Common/Member/MemberMultiselectDropdown.vue';
|
||||
import ReportingFilterBadge from '@/Components/Common/Reporting/ReportingFilterBadge.vue';
|
||||
import ProjectMultiselectDropdown from '@/Components/Common/Project/ProjectMultiselectDropdown.vue';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/Components/ui/select';
|
||||
import MainContainer from '@/packages/ui/src/MainContainer.vue';
|
||||
import DateRangePicker from '@/packages/ui/src/Input/DateRangePicker.vue';
|
||||
import TagDropdown from '@/packages/ui/src/Tag/TagDropdown.vue';
|
||||
import { useTagsQuery } from '@/utils/useTagsQuery';
|
||||
import { useTagsStore } from '@/utils/useTags';
|
||||
|
||||
type TimeEntryRoundingType = 'up' | 'down' | 'nearest';
|
||||
|
||||
const selectedMembers = defineModel<string[]>('selectedMembers', { required: true });
|
||||
const selectedProjects = defineModel<string[]>('selectedProjects', { required: true });
|
||||
const selectedTasks = defineModel<string[]>('selectedTasks', { required: true });
|
||||
const selectedClients = defineModel<string[]>('selectedClients', { required: true });
|
||||
const selectedTags = defineModel<string[]>('selectedTags', { required: true });
|
||||
const billable = defineModel<'true' | 'false' | null>('billable', { required: true });
|
||||
const roundingEnabled = defineModel<boolean>('roundingEnabled', { required: true });
|
||||
const roundingType = defineModel<TimeEntryRoundingType>('roundingType', { required: true });
|
||||
const roundingMinutes = defineModel<number>('roundingMinutes', { required: true });
|
||||
const startDate = defineModel<string>('startDate', { required: true });
|
||||
const endDate = defineModel<string>('endDate', { required: true });
|
||||
|
||||
const emit = defineEmits<{
|
||||
submit: [];
|
||||
}>();
|
||||
|
||||
const { tags } = useTagsQuery();
|
||||
|
||||
async function createTag(name: string) {
|
||||
return await useTagsStore().createTag(name);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="py-2.5 w-full border-b border-default-background-separator">
|
||||
<MainContainer class="sm:flex space-y-4 sm:space-y-0 justify-between">
|
||||
<div class="flex flex-wrap items-center space-y-2 sm:space-y-0 space-x-3">
|
||||
<div class="text-sm font-medium">Filters</div>
|
||||
<MemberMultiselectDropdown
|
||||
v-model="selectedMembers"
|
||||
@submit="emit('submit')">
|
||||
<template #trigger>
|
||||
<ReportingFilterBadge
|
||||
:count="selectedMembers.length"
|
||||
:active="selectedMembers.length > 0"
|
||||
title="Members"
|
||||
:icon="UserGroupIcon" />
|
||||
</template>
|
||||
</MemberMultiselectDropdown>
|
||||
<ProjectMultiselectDropdown
|
||||
v-model="selectedProjects"
|
||||
@submit="emit('submit')">
|
||||
<template #trigger>
|
||||
<ReportingFilterBadge
|
||||
:count="selectedProjects.length"
|
||||
:active="selectedProjects.length > 0"
|
||||
title="Projects"
|
||||
:icon="FolderIcon" />
|
||||
</template>
|
||||
</ProjectMultiselectDropdown>
|
||||
<TaskMultiselectDropdown
|
||||
v-model="selectedTasks"
|
||||
@submit="emit('submit')">
|
||||
<template #trigger>
|
||||
<ReportingFilterBadge
|
||||
:count="selectedTasks.length"
|
||||
:active="selectedTasks.length > 0"
|
||||
title="Tasks"
|
||||
:icon="CheckCircleIcon" />
|
||||
</template>
|
||||
</TaskMultiselectDropdown>
|
||||
<ClientMultiselectDropdown
|
||||
v-model="selectedClients"
|
||||
@submit="emit('submit')">
|
||||
<template #trigger>
|
||||
<ReportingFilterBadge
|
||||
:count="selectedClients.length"
|
||||
:active="selectedClients.length > 0"
|
||||
title="Clients"
|
||||
:icon="FolderIcon" />
|
||||
</template>
|
||||
</ClientMultiselectDropdown>
|
||||
<TagDropdown
|
||||
v-model="selectedTags"
|
||||
:create-tag
|
||||
:tags="tags"
|
||||
@submit="emit('submit')">
|
||||
<template #trigger>
|
||||
<ReportingFilterBadge
|
||||
:count="selectedTags.length"
|
||||
:active="selectedTags.length > 0"
|
||||
title="Tags"
|
||||
:icon="TagIcon" />
|
||||
</template>
|
||||
</TagDropdown>
|
||||
|
||||
<Select v-model="billable" @update:model-value="emit('submit')">
|
||||
<SelectTrigger
|
||||
size="small"
|
||||
variant="outline"
|
||||
:active="billable !== null"
|
||||
:show-chevron="false">
|
||||
<SelectValue class="flex items-center gap-2">
|
||||
<BillableIcon
|
||||
class="h-4"
|
||||
:class="
|
||||
billable !== null
|
||||
? 'dark:text-accent-300/80 text-accent-400/80'
|
||||
: 'text-text-quaternary'
|
||||
" />
|
||||
<span class="text-text-secondary">{{
|
||||
billable === 'false' ? 'Non Billable' : 'Billable'
|
||||
}}</span>
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem :value="null">Both</SelectItem>
|
||||
<SelectItem value="true">Billable</SelectItem>
|
||||
<SelectItem value="false">Non Billable</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<ReportingRoundingControls
|
||||
v-model:enabled="roundingEnabled"
|
||||
v-model:type="roundingType"
|
||||
v-model:minutes="roundingMinutes"
|
||||
@change="emit('submit')" />
|
||||
</div>
|
||||
<div>
|
||||
<DateRangePicker
|
||||
v-model:start="startDate"
|
||||
v-model:end="endDate"
|
||||
@submit="emit('submit')" />
|
||||
</div>
|
||||
</MainContainer>
|
||||
</div>
|
||||
</template>
|
||||
@@ -12,6 +12,10 @@ function getKeyFromItem(item: Task) {
|
||||
function getNameForItem(item: Task) {
|
||||
return item.name;
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
submit: [];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -19,7 +23,9 @@ function getNameForItem(item: Task) {
|
||||
search-placeholder="Search for a Task..."
|
||||
:items="tasks"
|
||||
:get-key-from-item="getKeyFromItem"
|
||||
:get-name-for-item="getNameForItem">
|
||||
:get-name-for-item="getNameForItem"
|
||||
no-item-label="No Task"
|
||||
@submit="emit('submit')">
|
||||
<template #trigger>
|
||||
<slot name="trigger"></slot>
|
||||
</template>
|
||||
|
||||
@@ -4,19 +4,43 @@ import { ChevronDown } from 'lucide-vue-next';
|
||||
import { SelectIcon, SelectTrigger, type SelectTriggerProps, useForwardProps } from 'reka-ui';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
|
||||
const props = defineProps<
|
||||
SelectTriggerProps & { size?: 'small'; class?: HTMLAttributes['class'] }
|
||||
>();
|
||||
const props = withDefaults(
|
||||
defineProps<
|
||||
SelectTriggerProps & {
|
||||
size?: 'small';
|
||||
class?: HTMLAttributes['class'];
|
||||
showChevron?: boolean;
|
||||
variant?: 'default' | 'outline';
|
||||
active?: boolean;
|
||||
}
|
||||
>(),
|
||||
{
|
||||
showChevron: true,
|
||||
variant: 'default',
|
||||
active: false,
|
||||
}
|
||||
);
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
const { class: _, showChevron: __, variant: ___, active: ____, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps);
|
||||
|
||||
const sizeClasses = computed(() => {
|
||||
return props.size === 'small' ? 'h-[34px] text-sm' : 'h-[42px]';
|
||||
return props.size === 'small' ? 'h-8 text-xs' : 'h-[42px] w-full text-sm';
|
||||
});
|
||||
|
||||
const variantClasses = computed(() => {
|
||||
if (props.variant === 'outline') {
|
||||
if (props.active) {
|
||||
return 'border border-accent-300/50 bg-accent-50 hover:bg-accent-100 dark:border-accent-300/50 dark:bg-accent-300/5 dark:hover:bg-accent-300/10';
|
||||
}
|
||||
return 'border shadow-xs hover:text-text-primary bg-card-background dark:bg-transparent border-input dark:border-input hover:bg-white/5';
|
||||
}
|
||||
return 'border border-input-border bg-input-background shadow-sm';
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -25,14 +49,15 @@ const sizeClasses = computed(() => {
|
||||
v-bind="forwardedProps"
|
||||
:class="
|
||||
cn(
|
||||
'flex w-full items-center justify-between whitespace-nowrap rounded-md border border-input-border bg-input-background px-3 py-2.5 shadow-sm data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:truncate text-start',
|
||||
'flex items-center justify-between gap-3 whitespace-nowrap rounded-md px-3 py-2.5 data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:truncate text-start font-medium transition-colors',
|
||||
sizeClasses,
|
||||
variantClasses,
|
||||
props.class
|
||||
)
|
||||
">
|
||||
<slot />
|
||||
<SelectIcon as-child>
|
||||
<ChevronDown class="w-4 h-4 opacity-50 shrink-0" />
|
||||
<SelectIcon v-if="showChevron" as-child>
|
||||
<ChevronDown class="w-4 h-4 text-icon-default shrink-0" />
|
||||
</SelectIcon>
|
||||
</SelectTrigger>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user