move calendar, dropdown-menu, select, dialog, number-field components to

the ui package
This commit is contained in:
Gregor Vostrak
2026-03-03 18:15:29 +01:00
parent bce6cb9395
commit c581ad8854
66 changed files with 106 additions and 191 deletions

View File

@@ -0,0 +1,15 @@
<script setup lang="ts">
import type { SelectRootEmits, SelectRootProps } from 'reka-ui';
import { SelectRoot, useForwardPropsEmits } from 'reka-ui';
const props = defineProps<SelectRootProps>();
const emits = defineEmits<SelectRootEmits>();
const forwarded = useForwardPropsEmits(props, emits);
</script>
<template>
<SelectRoot v-bind="forwarded">
<slot />
</SelectRoot>
</template>

View File

@@ -0,0 +1,61 @@
<script setup lang="ts">
import { cn } from '@/lib/utils';
import {
SelectContent,
type SelectContentEmits,
type SelectContentProps,
SelectPortal,
SelectViewport,
useForwardPropsEmits,
} from 'reka-ui';
import { computed, type HTMLAttributes } from 'vue';
import { SelectScrollDownButton, SelectScrollUpButton } from '.';
defineOptions({
inheritAttrs: false,
});
const props = withDefaults(
defineProps<SelectContentProps & { class?: HTMLAttributes['class'] }>(),
{
position: 'popper',
}
);
const emits = defineEmits<SelectContentEmits>();
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
const forwarded = useForwardPropsEmits(delegatedProps, emits);
</script>
<template>
<SelectPortal>
<SelectContent
v-bind="{ ...forwarded, ...$attrs }"
:class="
cn(
'relative z-50 max-h-96 min-w-32 overflow-hidden rounded-md border border-popover-border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
position === 'popper' &&
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
props.class
)
">
<SelectScrollUpButton />
<SelectViewport
:class="
cn(
'p-1',
position === 'popper' &&
'h-[--reka-select-trigger-height] w-full min-w-[--reka-select-trigger-width]'
)
">
<slot />
</SelectViewport>
<SelectScrollDownButton />
</SelectContent>
</SelectPortal>
</template>

View File

@@ -0,0 +1,19 @@
<script setup lang="ts">
import { cn } from '@/lib/utils';
import { SelectGroup, type SelectGroupProps } from 'reka-ui';
import { computed, type HTMLAttributes } from 'vue';
const props = defineProps<SelectGroupProps & { class?: HTMLAttributes['class'] }>();
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
</script>
<template>
<SelectGroup :class="cn('p-1 w-full', props.class)" v-bind="delegatedProps">
<slot />
</SelectGroup>
</template>

View File

@@ -0,0 +1,43 @@
<script setup lang="ts">
import { cn } from '@/lib/utils';
import { Check } from 'lucide-vue-next';
import {
SelectItem,
SelectItemIndicator,
type SelectItemProps,
SelectItemText,
useForwardProps,
} from 'reka-ui';
import { computed, type HTMLAttributes } from 'vue';
const props = defineProps<SelectItemProps & { class?: HTMLAttributes['class'] }>();
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
const forwardedProps = useForwardProps(delegatedProps);
</script>
<template>
<SelectItem
v-bind="forwardedProps"
:class="
cn(
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
props.class
)
">
<span class="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectItemIndicator>
<Check class="h-4 w-4" />
</SelectItemIndicator>
</span>
<SelectItemText>
<slot />
</SelectItemText>
</SelectItem>
</template>

View File

@@ -0,0 +1,11 @@
<script setup lang="ts">
import { SelectItemText, type SelectItemTextProps } from 'reka-ui';
const props = defineProps<SelectItemTextProps>();
</script>
<template>
<SelectItemText v-bind="props">
<slot />
</SelectItemText>
</template>

View File

@@ -0,0 +1,13 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue';
import { cn } from '@/lib/utils';
import { SelectLabel, type SelectLabelProps } from 'reka-ui';
const props = defineProps<SelectLabelProps & { class?: HTMLAttributes['class'] }>();
</script>
<template>
<SelectLabel :class="cn('px-2 py-1.5 text-sm font-semibold', props.class)">
<slot />
</SelectLabel>
</template>

View File

@@ -0,0 +1,26 @@
<script setup lang="ts">
import { cn } from '@/lib/utils';
import { ChevronDown } from 'lucide-vue-next';
import { SelectScrollDownButton, type SelectScrollDownButtonProps, useForwardProps } from 'reka-ui';
import { computed, type HTMLAttributes } from 'vue';
const props = defineProps<SelectScrollDownButtonProps & { class?: HTMLAttributes['class'] }>();
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
const forwardedProps = useForwardProps(delegatedProps);
</script>
<template>
<SelectScrollDownButton
v-bind="forwardedProps"
:class="cn('flex cursor-default items-center justify-center py-1', props.class)">
<slot>
<ChevronDown />
</slot>
</SelectScrollDownButton>
</template>

View File

@@ -0,0 +1,26 @@
<script setup lang="ts">
import { cn } from '@/lib/utils';
import { ChevronUp } from 'lucide-vue-next';
import { SelectScrollUpButton, type SelectScrollUpButtonProps, useForwardProps } from 'reka-ui';
import { computed, type HTMLAttributes } from 'vue';
const props = defineProps<SelectScrollUpButtonProps & { class?: HTMLAttributes['class'] }>();
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
const forwardedProps = useForwardProps(delegatedProps);
</script>
<template>
<SelectScrollUpButton
v-bind="forwardedProps"
:class="cn('flex cursor-default items-center justify-center py-1', props.class)">
<slot>
<ChevronUp />
</slot>
</SelectScrollUpButton>
</template>

View File

@@ -0,0 +1,17 @@
<script setup lang="ts">
import { cn } from '@/lib/utils';
import { SelectSeparator, type SelectSeparatorProps } from 'reka-ui';
import { computed, type HTMLAttributes } from 'vue';
const props = defineProps<SelectSeparatorProps & { class?: HTMLAttributes['class'] }>();
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props;
return delegated;
});
</script>
<template>
<SelectSeparator v-bind="delegatedProps" :class="cn('-mx-1 my-1 h-px bg-muted', props.class)" />
</template>

View File

@@ -0,0 +1,78 @@
<script setup lang="ts">
import { cn } from '@/lib/utils';
import { ChevronDown } from 'lucide-vue-next';
import { SelectIcon, SelectTrigger, type SelectTriggerProps, useForwardProps } from 'reka-ui';
import { computed, type HTMLAttributes } from 'vue';
const props = withDefaults(
defineProps<
SelectTriggerProps & {
size?: 'default' | 'sm' | 'lg';
class?: HTMLAttributes['class'];
showChevron?: boolean;
variant?: 'default' | 'outline';
active?: boolean;
}
>(),
{
showChevron: true,
variant: 'default',
active: false,
size: 'default',
}
);
const delegatedProps = computed(() => {
const {
class: _,
showChevron: __,
variant: ___,
active: ____,
size: _____,
...delegated
} = props;
return delegated;
});
const forwardedProps = useForwardProps(delegatedProps);
const sizeClasses = computed(() => {
switch (props.size) {
case 'sm':
return 'h-8 px-3 text-xs';
case 'lg':
return 'h-10 px-4 text-sm';
default:
return 'h-9 px-3 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>
<template>
<SelectTrigger
v-bind="forwardedProps"
:class="
cn(
'flex items-center justify-between gap-3 whitespace-nowrap rounded-md 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 v-if="showChevron" as-child>
<ChevronDown class="w-4 h-4 text-icon-default shrink-0" />
</SelectIcon>
</SelectTrigger>
</template>

View File

@@ -0,0 +1,11 @@
<script setup lang="ts">
import { SelectValue, type SelectValueProps } from 'reka-ui';
const props = defineProps<SelectValueProps>();
</script>
<template>
<SelectValue v-bind="props">
<slot />
</SelectValue>
</template>

View File

@@ -0,0 +1,11 @@
export { default as Select } from './Select.vue';
export { default as SelectContent } from './SelectContent.vue';
export { default as SelectGroup } from './SelectGroup.vue';
export { default as SelectItem } from './SelectItem.vue';
export { default as SelectItemText } from './SelectItemText.vue';
export { default as SelectLabel } from './SelectLabel.vue';
export { default as SelectScrollDownButton } from './SelectScrollDownButton.vue';
export { default as SelectScrollUpButton } from './SelectScrollUpButton.vue';
export { default as SelectSeparator } from './SelectSeparator.vue';
export { default as SelectTrigger } from './SelectTrigger.vue';
export { default as SelectValue } from './SelectValue.vue';