feat: add avatar, context-menu, dropdown-menu and select components

This commit is contained in:
Ahmed 2023-09-06 14:54:22 +01:00
parent 0768fa5bdd
commit b0cacf92c3
45 changed files with 857 additions and 0 deletions

View File

@ -0,0 +1,22 @@
<script setup lang="ts">
import { AvatarRoot } from 'radix-vue'
import { avatarVariant } from '.'
import { cn } from '@/lib/utils'
interface Props {
size?: NonNullable<Parameters<typeof avatarVariant>[0]>['size']
shape?: NonNullable<Parameters<typeof avatarVariant>[0]>['shape']
class?: string
}
const props = withDefaults(defineProps<Props>(), {
size: 'sm',
shape: 'circle',
})
</script>
<template>
<AvatarRoot :class="cn(avatarVariant({ size, shape }), props.class)">
<slot />
</AvatarRoot>
</template>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { AvatarFallback, type AvatarFallbackProps } from 'radix-vue'
const props = defineProps<AvatarFallbackProps>()
</script>
<template>
<AvatarFallback v-bind="props">
<slot />
</AvatarFallback>
</template>

View File

@ -0,0 +1,9 @@
<script setup lang="ts">
import { AvatarImage, type AvatarImageProps } from 'radix-vue'
const props = defineProps<AvatarImageProps>()
</script>
<template>
<AvatarImage v-bind="props" class="h-full w-full aspect-square" />
</template>

View File

@ -0,0 +1,22 @@
import { cva } from 'class-variance-authority'
export { default as Avatar } from './Avatar.vue'
export { default as AvatarImage } from './AvatarImage.vue'
export { default as AvatarFallback } from './AvatarFallback.vue'
export const avatarVariant = cva(
'inline-flex items-center justify-center font-normal text-foregorund select-none shrink-0 bg-muted overflow-hidden',
{
variants: {
size: {
sm: 'h-10 w-10 text-xs',
base: 'h-16 w-16 text-2xl',
lg: 'h-32 w-32 text-5xl',
},
shape: {
circle: 'rounded-full',
square: 'rounded-md',
},
},
},
)

View File

@ -0,0 +1,14 @@
<script setup lang="ts">
import { ContextMenuRoot } from 'radix-vue'
import type { ContextMenuRootEmits, ContextMenuRootProps } from 'radix-vue'
import { useEmitAsProps } from '@/lib/utils'
const props = defineProps<ContextMenuRootProps>()
const emits = defineEmits<ContextMenuRootEmits>()
</script>
<template>
<ContextMenuRoot v-bind="{ ...props, ...useEmitAsProps(emits) }">
<slot />
</ContextMenuRoot>
</template>

View File

@ -0,0 +1,34 @@
<script setup lang="ts">
import {
ContextMenuCheckboxItem,
type ContextMenuCheckboxItemEmits,
type ContextMenuCheckboxItemProps,
ContextMenuItemIndicator,
} from 'radix-vue'
import RadixIconsCheck from '~icons/radix-icons/check'
import { cn, useEmitAsProps } from '@/lib/utils'
const props = defineProps<ContextMenuCheckboxItemProps & { class?: string }>()
const emits = defineEmits<ContextMenuCheckboxItemEmits>()
</script>
<template>
<ContextMenuCheckboxItem
v-bind="{ ...props, ...useEmitAsProps(emits) }"
:class="[
cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 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 left-2 flex h-3.5 w-3.5 items-center justify-center">
<ContextMenuItemIndicator
class="absolute left-1.5 inline-flex w-4 h-4 items-center justify-center"
>
<RadixIconsCheck class="h-4 w-4" />
</ContextMenuItemIndicator>
</span>
<slot />
</ContextMenuCheckboxItem>
</template>

View File

@ -0,0 +1,29 @@
<script setup lang="ts">
import {
ContextMenuContent,
type ContextMenuContentEmits,
type ContextMenuContentProps,
ContextMenuPortal,
} from 'radix-vue'
import { cn, useEmitAsProps } from '@/lib/utils'
const props = defineProps<ContextMenuContentProps & { class?: string }>()
const emits = defineEmits<ContextMenuContentEmits>()
</script>
<template>
<ContextMenuPortal>
<ContextMenuContent
:align-offset="props.alignOffset"
:class="[
cn(
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-80 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',
props.class,
),
]"
v-bind="{ ...props, ...useEmitAsProps(emits) }"
>
<slot />
</ContextMenuContent>
</ContextMenuPortal>
</template>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { ContextMenuGroup, type ContextMenuGroupProps } from 'radix-vue'
const props = defineProps<ContextMenuGroupProps>()
</script>
<template>
<ContextMenuGroup v-bind="props">
<slot />
</ContextMenuGroup>
</template>

View File

@ -0,0 +1,26 @@
<script setup lang="ts">
import {
ContextMenuItem,
type ContextMenuItemEmits,
type ContextMenuItemProps,
} from 'radix-vue'
import { cn, useEmitAsProps } from '@/lib/utils'
const props = defineProps<ContextMenuItemProps & { class?: string; inset?: boolean }>()
const emits = defineEmits<ContextMenuItemEmits>()
</script>
<template>
<ContextMenuItem
v-bind="{ ...props, ...useEmitAsProps(emits) }"
:class="[
cn(
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
inset && 'pl-8',
props.class,
),
]"
>
<slot />
</ContextMenuItem>
</template>

View File

@ -0,0 +1,18 @@
<script setup lang="ts">
import { ContextMenuLabel, type ContextMenuLabelProps } from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<ContextMenuLabelProps & { class?: string; inset?: boolean }>()
</script>
<template>
<ContextMenuLabel
v-bind="props"
:class="
cn('px-2 py-1.5 text-sm font-semibold text-foreground',
inset && 'pl-8', props.class ?? '',
)"
>
<slot />
</ContextMenuLabel>
</template>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { ContextMenuPortal, type ContextMenuPortalProps } from 'radix-vue'
const props = defineProps<ContextMenuPortalProps>()
</script>
<template>
<ContextMenuPortal v-bind="props">
<slot />
</ContextMenuPortal>
</template>

View File

@ -0,0 +1,20 @@
<script setup lang="ts">
import {
ContextMenuRadioGroup,
type ContextMenuRadioGroupEmits,
type ContextMenuRadioGroupProps,
} from 'radix-vue'
const props = defineProps<ContextMenuRadioGroupProps>()
const emits = defineEmits<ContextMenuRadioGroupEmits>()
</script>
<template>
<ContextMenuRadioGroup
v-bind="props"
@update:model-value="emits('update:modelValue', $event)"
>
<slot />
</ContextMenuRadioGroup>
</template>

View File

@ -0,0 +1,32 @@
<script setup lang="ts">
import {
ContextMenuItemIndicator,
ContextMenuRadioItem,
type ContextMenuRadioItemEmits,
type ContextMenuRadioItemProps,
} from 'radix-vue'
import RadixIconsDotFilled from '~icons/radix-icons/dot-filled'
import { cn, useEmitAsProps } from '@/lib/utils'
const props = defineProps<ContextMenuRadioItemProps & { class?: string }>()
const emits = defineEmits<ContextMenuRadioItemEmits>()
</script>
<template>
<ContextMenuRadioItem
v-bind="{ ...props, ...useEmitAsProps(emits) }"
:class="[
cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 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 left-2 flex h-3.5 w-3.5 items-center justify-center">
<ContextMenuItemIndicator>
<RadixIconsDotFilled class="h-4 w-4 fill-current" />
</ContextMenuItemIndicator>
</span>
<slot />
</ContextMenuRadioItem>
</template>

View File

@ -0,0 +1,13 @@
<script setup lang="ts">
import {
ContextMenuSeparator,
type ContextMenuSeparatorProps,
} from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<ContextMenuSeparatorProps>()
</script>
<template>
<ContextMenuSeparator v-bind="props" :class="cn('-mx-1 my-1 h-px bg-border', $attrs.class ?? '')" />
</template>

View File

@ -0,0 +1,9 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
</script>
<template>
<span :class="cn('ml-auto text-xs tracking-widest text-muted-foreground', $attrs.class ?? '')">
<slot />
</span>
</template>

View File

@ -0,0 +1,17 @@
<script setup lang="ts">
import {
ContextMenuSub,
type ContextMenuSubEmits,
type ContextMenuSubProps,
} from 'radix-vue'
const props = defineProps<ContextMenuSubProps>()
const emits = defineEmits<ContextMenuSubEmits>()
</script>
<template>
<ContextMenuSub v-bind="props" @update:open="emits('update:open', $event)">
<slot />
</ContextMenuSub>
</template>

View File

@ -0,0 +1,25 @@
<script setup lang="ts">
import {
ContextMenuSubContent,
type DropdownMenuSubContentEmits,
type DropdownMenuSubContentProps,
} from 'radix-vue'
import { cn, useEmitAsProps } from '@/lib/utils'
const props = defineProps<DropdownMenuSubContentProps & { class?: string }>()
const emits = defineEmits<DropdownMenuSubContentEmits>()
</script>
<template>
<ContextMenuSubContent
v-bind="{ ...props, ...useEmitAsProps(emits) }"
:class="
cn(
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 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',
props.class,
)
"
>
<slot />
</ContextMenuSubContent>
</template>

View File

@ -0,0 +1,26 @@
<script setup lang="ts">
import {
ContextMenuSubTrigger,
type ContextMenuSubTriggerProps,
} from 'radix-vue'
import { ChevronRight } from 'lucide-vue-next'
import { cn } from '@/lib/utils'
const props = defineProps<ContextMenuSubTriggerProps & { class?: string; inset?: boolean }>()
</script>
<template>
<ContextMenuSubTrigger
v-bind="props"
:class="[
cn(
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground',
inset && 'pl-8',
props.class,
),
]"
>
<slot />
<ChevronRight class="ml-auto h-4 w-4" />
</ContextMenuSubTrigger>
</template>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { ContextMenuTrigger, type ContextMenuTriggerProps } from 'radix-vue'
const props = defineProps<ContextMenuTriggerProps>()
</script>
<template>
<ContextMenuTrigger v-bind="props">
<slot />
</ContextMenuTrigger>
</template>

View File

@ -0,0 +1,14 @@
export { default as ContextMenu } from './ContextMenu.vue'
export { default as ContextMenuTrigger } from './ContextMenuTrigger.vue'
export { default as ContextMenuContent } from './ContextMenuContent.vue'
export { default as ContextMenuGroup } from './ContextMenuGroup.vue'
export { default as ContextMenuRadioGroup } from './ContextMenuRadioGroup.vue'
export { default as ContextMenuItem } from './ContextMenuItem.vue'
export { default as ContextMenuCheckboxItem } from './ContextMenuCheckboxItem.vue'
export { default as ContextMenuRadioItem } from './ContextMenuRadioItem.vue'
export { default as ContextMenuShortcut } from './ContextMenuShortcut.vue'
export { default as ContextMenuSeparator } from './ContextMenuSeparator.vue'
export { default as ContextMenuLabel } from './ContextMenuLabel.vue'
export { default as ContextMenuSub } from './ContextMenuSub.vue'
export { default as ContextMenuSubTrigger } from './ContextMenuSubTrigger.vue'
export { default as ContextMenuSubContent } from './ContextMenuSubContent.vue'

View File

@ -0,0 +1,13 @@
<script setup lang="ts">
import { DropdownMenuRoot, type DropdownMenuRootEmits, type DropdownMenuRootProps } from 'radix-vue'
import { useEmitAsProps } from '@/lib/utils'
const props = defineProps<DropdownMenuRootProps>()
const emits = defineEmits<DropdownMenuRootEmits>()
</script>
<template>
<DropdownMenuRoot v-bind="{ ...props, ...useEmitAsProps(emits) }">
<slot />
</DropdownMenuRoot>
</template>

View File

@ -0,0 +1,30 @@
<script setup lang="ts">
import {
DropdownMenuCheckboxItem,
type DropdownMenuCheckboxItemEmits,
type DropdownMenuCheckboxItemProps,
DropdownMenuItemIndicator,
} from 'radix-vue'
import RadixIconsCheck from '~icons/radix-icons/check'
import { cn, useEmitAsProps } from '@/lib/utils'
const props = defineProps<DropdownMenuCheckboxItemProps & { class?: string }>()
const emits = defineEmits<DropdownMenuCheckboxItemEmits>()
</script>
<template>
<DropdownMenuCheckboxItem
v-bind="{ ...props, ...useEmitAsProps(emits) }"
:class=" cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
props.class,
)"
>
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuItemIndicator>
<RadixIconsCheck class="w-4 h-4" />
</DropdownMenuItemIndicator>
</span>
<slot />
</DropdownMenuCheckboxItem>
</template>

View File

@ -0,0 +1,36 @@
<script setup lang="ts">
import {
DropdownMenuContent,
type DropdownMenuContentProps,
DropdownMenuPortal,
} from 'radix-vue'
import { cn } from '@/lib/utils'
const props = withDefaults(
defineProps<DropdownMenuContentProps & { class?: string }>(),
{
sideOffset: 4,
},
)
</script>
<template>
<DropdownMenuPortal>
<DropdownMenuContent
:loop="props.loop"
:as-child="props.asChild"
:side-offset="props.sideOffset"
:side="props.side"
:align="props.align"
:align-offset="props.alignOffset"
:class="[
cn(
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 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',
props.class,
),
]"
>
<slot />
</DropdownMenuContent>
</DropdownMenuPortal>
</template>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { DropdownMenuGroup, type DropdownMenuGroupProps } from 'radix-vue'
const props = defineProps<DropdownMenuGroupProps>()
</script>
<template>
<DropdownMenuGroup v-bind="props">
<slot />
</DropdownMenuGroup>
</template>

View File

@ -0,0 +1,21 @@
<script setup lang="ts">
import { DropdownMenuItem, type DropdownMenuItemProps } from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<DropdownMenuItemProps & { inset?: boolean; class?: string }>()
</script>
<template>
<DropdownMenuItem
v-bind="props"
:class="[
cn(
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
inset && 'pl-8',
props.class,
),
]"
>
<slot />
</DropdownMenuItem>
</template>

View File

@ -0,0 +1,20 @@
<script setup lang="ts">
import { DropdownMenuLabel, type DropdownMenuLabelProps } from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<DropdownMenuLabelProps & {
inset?: boolean
class?: string
}>()
</script>
<template>
<DropdownMenuLabel
v-bind="props"
:class="
cn('px-2 py-1.5 text-sm font-semibold',
inset && 'pl-8', props.class)"
>
<slot />
</DropdownMenuLabel>
</template>

View File

@ -0,0 +1,20 @@
<script setup lang="ts">
import {
DropdownMenuRadioGroup,
type DropdownMenuRadioGroupEmits,
type DropdownMenuRadioGroupProps,
} from 'radix-vue'
const props = defineProps<DropdownMenuRadioGroupProps>()
const emits = defineEmits<DropdownMenuRadioGroupEmits>()
</script>
<template>
<DropdownMenuRadioGroup
v-bind="props"
@update:model-value="emits('update:modelValue', $event)"
>
<slot />
</DropdownMenuRadioGroup>
</template>

View File

@ -0,0 +1,32 @@
<script setup lang="ts">
import {
DropdownMenuItemIndicator,
DropdownMenuRadioItem,
type DropdownMenuRadioItemEmits,
type DropdownMenuRadioItemProps,
} from 'radix-vue'
import RadixIconsDotFilled from '~icons/radix-icons/dot-filled'
import { cn, useEmitAsProps } from '@/lib/utils'
const props = defineProps<DropdownMenuRadioItemProps & { class?: string }>()
const emits = defineEmits<DropdownMenuRadioItemEmits>()
</script>
<template>
<DropdownMenuRadioItem
v-bind="{ ...props, ...useEmitAsProps(emits) }"
:class="cn(
'flex relative items-center rounded-md transition-colors data-[disabled]:opacity-50 data-[disabled]:pointer-events-none data-[highlighted]:bg-outline-hover pl-7 py-1.5 text-sm outline-none select-none cursor-default',
props.class,
)"
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuItemIndicator>
<RadixIconsDotFilled class="h-4 w-4 fill-current" />
</DropdownMenuItemIndicator>
</span>
<slot />
</DropdownMenuRadioItem>
</template>

View File

@ -0,0 +1,12 @@
<script setup lang="ts">
import {
DropdownMenuSeparator,
type DropdownMenuSeparatorProps,
} from 'radix-vue'
const props = defineProps<DropdownMenuSeparatorProps>()
</script>
<template>
<DropdownMenuSeparator v-bind="props" class="-mx-1 my-1 h-px bg-muted" />
</template>

View File

@ -0,0 +1,9 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
</script>
<template>
<span :class="cn('ml-auto text-xs tracking-widest opacity-60', $attrs.class ?? '')">
<slot />
</span>
</template>

View File

@ -0,0 +1,17 @@
<script setup lang="ts">
import {
DropdownMenuSub,
type DropdownMenuSubEmits,
type DropdownMenuSubProps,
} from 'radix-vue'
const props = defineProps<DropdownMenuSubProps>()
const emits = defineEmits<DropdownMenuSubEmits>()
</script>
<template>
<DropdownMenuSub v-bind="props" @update:open="emits('update:open', $event)">
<slot />
</DropdownMenuSub>
</template>

View File

@ -0,0 +1,20 @@
<script setup lang="ts">
import {
DropdownMenuSubContent,
type DropdownMenuSubContentEmits,
type DropdownMenuSubContentProps,
} from 'radix-vue'
import { cn, useEmitAsProps } from '@/lib/utils'
const props = defineProps<DropdownMenuSubContentProps>()
const emits = defineEmits<DropdownMenuSubContentEmits>()
</script>
<template>
<DropdownMenuSubContent
v-bind="{ ...props, ...useEmitAsProps(emits) }"
:class="cn('z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg 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', $attrs.class ?? '')"
>
<slot />
</DropdownMenuSubContent>
</template>

View File

@ -0,0 +1,25 @@
<script setup lang="ts">
import {
DropdownMenuSubTrigger,
type DropdownMenuSubTriggerProps,
} from 'radix-vue'
import { ChevronRight } from 'lucide-vue-next'
import { cn } from '@/lib/utils'
const props = defineProps<DropdownMenuSubTriggerProps & { class?: string }>()
</script>
<template>
<DropdownMenuSubTrigger
v-bind="props"
:class="[
cn(
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent',
props.class,
),
]"
>
<slot />
<ChevronRight class="ml-auto h-4 w-4" />
</DropdownMenuSubTrigger>
</template>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { DropdownMenuTrigger, type DropdownMenuTriggerProps } from 'radix-vue'
const props = defineProps<DropdownMenuTriggerProps>()
</script>
<template>
<DropdownMenuTrigger class="outline-none" v-bind="props">
<slot />
</DropdownMenuTrigger>
</template>

View File

@ -0,0 +1,16 @@
export { DropdownMenuPortal } from 'radix-vue'
export { default as DropdownMenu } from './DropdownMenu.vue'
export { default as DropdownMenuTrigger } from './DropdownMenuTrigger.vue'
export { default as DropdownMenuContent } from './DropdownMenuContent.vue'
export { default as DropdownMenuGroup } from './DropdownMenuGroup.vue'
export { default as DropdownMenuRadioGroup } from './DropdownMenuRadioGroup.vue'
export { default as DropdownMenuItem } from './DropdownMenuItem.vue'
export { default as DropdownMenuCheckboxItem } from './DropdownMenuCheckboxItem.vue'
export { default as DropdownMenuRadioItem } from './DropdownMenuRadioItem.vue'
export { default as DropdownMenuShortcut } from './DropdownMenuShortcut.vue'
export { default as DropdownMenuSeparator } from './DropdownMenuSeparator.vue'
export { default as DropdownMenuLabel } from './DropdownMenuLabel.vue'
export { default as DropdownMenuSub } from './DropdownMenuSub.vue'
export { default as DropdownMenuSubTrigger } from './DropdownMenuSubTrigger.vue'
export { default as DropdownMenuSubContent } from './DropdownMenuSubContent.vue'

View File

@ -0,0 +1,14 @@
<script setup lang="ts">
import type { SelectRootEmits, SelectRootProps } from 'radix-vue'
import { SelectRoot } from 'radix-vue'
import { useEmitAsProps } from '@/lib/utils'
const props = defineProps<SelectRootProps>()
const emits = defineEmits<SelectRootEmits>()
</script>
<template>
<SelectRoot v-bind="{ ...props, ...useEmitAsProps(emits) }">
<slot />
</SelectRoot>
</template>

View File

@ -0,0 +1,44 @@
<script setup lang="ts">
import {
SelectContent,
type SelectContentEmits,
type SelectContentProps,
SelectPortal,
SelectViewport,
} from 'radix-vue'
import { cn, useEmitAsProps } from '@/lib/utils'
const props = withDefaults(
defineProps<SelectContentProps & { class?: string }>(), {
position: 'popper',
sideOffset: 4,
},
)
const emits = defineEmits<SelectContentEmits>()
const emitsAsProps = useEmitAsProps(emits)
</script>
<template>
<SelectPortal>
<SelectContent
v-bind="{ ...props, ...emitsAsProps, ...$attrs }"
:class="
cn(
'relative z-50 min-w-[10rem] overflow-hidden rounded-md bg-background border border-border text-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,
)
"
>
<SelectViewport
:class="
cn('p-1',
position === 'popper'
&& 'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]')"
>
<slot />
</SelectViewport>
</SelectContent>
</SelectPortal>
</template>

View File

@ -0,0 +1,12 @@
<script setup lang="ts">
import { SelectGroup, type SelectGroupProps } from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<SelectGroupProps & { class?: string }>()
</script>
<template>
<SelectGroup :class="cn('p-1 w-full', props.class)" v-bind="props">
<slot />
</SelectGroup>
</template>

View File

@ -0,0 +1,34 @@
<script setup lang="ts">
import {
SelectItem,
SelectItemIndicator,
type SelectItemProps,
SelectItemText,
} from 'radix-vue'
import { cn } from '@/lib/utils'
import RadixIconsCheck from '~icons/radix-icons/check'
const props = defineProps<SelectItemProps & { class?: string }>()
</script>
<template>
<SelectItem
v-bind="props"
:class="
cn(
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 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 left-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectItemIndicator>
<RadixIconsCheck 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 'radix-vue'
const props = defineProps<SelectItemTextProps>()
</script>
<template>
<SelectItemText v-bind="props">
<slot />
</SelectItemText>
</template>

View File

@ -0,0 +1,12 @@
<script setup lang="ts">
import { SelectLabel, type SelectLabelProps } from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<SelectLabelProps & { class?: string }>()
</script>
<template>
<SelectLabel :class="cn('py-1.5 pl-8 pr-2 text-sm font-semibold', props.class)">
<slot />
</SelectLabel>
</template>

View File

@ -0,0 +1,10 @@
<script setup lang="ts">
import { SelectSeparator, type SelectSeparatorProps } from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<SelectSeparatorProps & { class?: string }>()
</script>
<template>
<SelectSeparator :class="cn('-mx-1 my-1 h-px bg-muted', props.class)" />
</template>

View File

@ -0,0 +1,33 @@
<script setup lang="ts">
import { SelectIcon, SelectTrigger, type SelectTriggerProps } from 'radix-vue'
import { ChevronDown } from 'lucide-vue-next'
import { cn } from '@/lib/utils'
const props = withDefaults(
defineProps<SelectTriggerProps & { class?: string; invalid?: boolean }>(),
{
class: '',
invalid: false,
},
)
</script>
<template>
<SelectTrigger
v-bind="props"
:class="[
cn(
'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
props.class,
),
props.invalid
? '!ring-destructive ring-2 placeholder:!text-destructive'
: '',
]"
>
<slot />
<SelectIcon as-child>
<ChevronDown class="w-4 h-4 opacity-50" />
</SelectIcon>
</SelectTrigger>
</template>

View File

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

View File

@ -0,0 +1,9 @@
export { default as Select } from './Select.vue'
export { default as SelectValue } from './SelectValue.vue'
export { default as SelectTrigger } from './SelectTrigger.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 SelectSeparator } from './SelectSeparator.vue'