feat: add Dropdown Menu component

This commit is contained in:
rev4324 2023-08-28 23:40:34 +02:00
parent 71bff69c95
commit 2cf2dca134
3 changed files with 369 additions and 0 deletions

View File

@ -4,6 +4,7 @@ import PopoverDemo from '@/registry/default/examples/PopoverDemo.vue'
import DialogDemo from '@/registry/default/examples/DialogDemo.vue'
import AlertDialogDemo from '@/registry/default/examples/AlertDialogDemo.vue'
import SelectDemo from '@/registry/default/examples/SelectDemo.vue'
import DropdownMenuDemo from '@/registry/default/examples/DropdownMenuDemo.vue'
</script>
<template>
@ -17,5 +18,7 @@ import SelectDemo from '@/registry/default/examples/SelectDemo.vue'
<AlertDialogDemo />
<SelectDemo />
<DropdownMenuDemo />
</div>
</template>

View File

@ -0,0 +1,110 @@
<script setup lang="ts">
import { Button } from "@/registry/default/ui/button";
import { DropdownMenuContent, DropdownMenuGroup, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger,
DropdownMenuItem, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubTrigger, DropdownMenuPortal, DropdownMenuSubContent, DropdownMenu
} from "@/registry/default/ui/dropdown-menu";
import {
Cloud,
CreditCard,
Github,
Keyboard,
LifeBuoy,
LogOut,
Mail,
MessageSquare,
Plus,
PlusCircle,
Settings,
User,
UserPlus,
Users,
} from "lucide-vue-next"
</script>
<template>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">Open</Button>
</DropdownMenuTrigger>
<DropdownMenuContent class="w-56">
<DropdownMenuLabel>My Account</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<User class="mr-2 h-4 w-4" />
<span>Profile</span>
<DropdownMenuShortcut>P</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
<CreditCard class="mr-2 h-4 w-4" />
<span>Billing</span>
<DropdownMenuShortcut>B</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
<Settings class="mr-2 h-4 w-4" />
<span>Settings</span>
<DropdownMenuShortcut>S</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
<Keyboard class="mr-2 h-4 w-4" />
<span>Keyboard shortcuts</span>
<DropdownMenuShortcut>K</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<Users class="mr-2 h-4 w-4" />
<span>Team</span>
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<UserPlus class="mr-2 h-4 w-4" />
<span>Invite users</span>
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem>
<Mail class="mr-2 h-4 w-4" />
<span>Email</span>
</DropdownMenuItem>
<DropdownMenuItem>
<MessageSquare class="mr-2 h-4 w-4" />
<span>Message</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<PlusCircle class="mr-2 h-4 w-4" />
<span>More...</span>
</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
<DropdownMenuItem>
<Plus class="mr-2 h-4 w-4" />
<span>New Team</span>
<DropdownMenuShortcut>+T</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Github class="mr-2 h-4 w-4" />
<span>GitHub</span>
</DropdownMenuItem>
<DropdownMenuItem>
<LifeBuoy class="mr-2 h-4 w-4" />
<span>Support</span>
</DropdownMenuItem>
<DropdownMenuItem disabled>
<Cloud class="mr-2 h-4 w-4" />
<span>API</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<LogOut class="mr-2 h-4 w-4" />
<span>Log out</span>
<DropdownMenuShortcut>Q</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</template>

View File

@ -0,0 +1,256 @@
import {
DropdownMenuCheckboxItem as DropdownMenuCheckboxItemPrimitive, type DropdownMenuCheckboxItemProps,
DropdownMenuContent as DropdownMenuContentPrimitive, type DropdownMenuContentProps,
DropdownMenuGroup as DropdownMenuGroupPrimitive,
DropdownMenuItemIndicator as DropdownMenuItemIndicatorPrimitive,
DropdownMenuItem as DropdownMenuItemPrimitive, type DropdownMenuItemProps,
DropdownMenuLabel as DropdownMenuLabelPrimitive, type DropdownMenuLabelProps,
DropdownMenuPortal as DropdownMenuPortalPrimitive,
DropdownMenuRadioGroup as DropdownMenuRadioGroupPrimitive,
DropdownMenuRadioItem as DropdownMenuRadioItemPrimitive, type DropdownMenuRadioItemProps,
DropdownMenuRoot as DropdownMenuRootPrimitive,
DropdownMenuSeparator as DropdownMenuSeparatorPrimitive, type DropdownMenuSeparatorProps,
DropdownMenuSubContent as DropdownMenuSubContentPrimitive, type DropdownMenuSubContentProps,
DropdownMenuSub as DropdownMenuSubPrimitive,
DropdownMenuSubTrigger as DropdownMenuSubTriggerPrimitive, type DropdownMenuSubTriggerProps,
DropdownMenuTrigger as DropdownMenuTriggerPrimitive,
} from 'radix-vue'
import { defineComponent } from 'vue'
import { Check, ChevronRight, Circle } from 'lucide-vue-next'
import { cn, convertToComponent, useEmitAsProps } from '@/utils'
const ChevronRightIcon = convertToComponent(ChevronRight)
const CheckIcon = convertToComponent(Check)
const CircleIcon = convertToComponent(Circle)
const DropdownMenu = DropdownMenuRootPrimitive
const DropdownMenuTrigger = DropdownMenuTriggerPrimitive
const DropdownMenuGroup = DropdownMenuGroupPrimitive
const DropdownMenuPortal = DropdownMenuPortalPrimitive
const DropdownMenuSub = DropdownMenuSubPrimitive
const DropdownMenuRadioGroup = DropdownMenuRadioGroupPrimitive
const DropdownMenuSubTrigger = defineComponent<
DropdownMenuSubTriggerProps & {
inset?: boolean
}
>(
(props, { attrs, slots }) => {
return () => (
<DropdownMenuSubTriggerPrimitive
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.inset && 'pl-8',
attrs.class ?? '',
)}
{...props}
>
{slots.default?.()}
<ChevronRightIcon class="ml-auto w-4 h-4" />
</DropdownMenuSubTriggerPrimitive>
)
},
{
name: 'DropdownMenuSubTrigger',
},
)
const DropdownMenuSubContent = defineComponent<DropdownMenuSubContentProps>(
(props, { attrs, emit, slots }) => {
const emitsAsProps = useEmitAsProps(emit)
return () => (
<DropdownMenuSubContentPrimitive
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 ?? '',
)}
{...props}
{...emitsAsProps}
>
{slots.default?.()}
</DropdownMenuSubContentPrimitive>
)
},
{
name: 'DropdownMenuSubContent',
emits: DropdownMenuSubContentPrimitive.emits,
},
)
const DropdownMenuContent = defineComponent<DropdownMenuContentProps>(
(props, { attrs, emit, slots }) => {
const emitsAsProps = useEmitAsProps(emit)
return () => (
<DropdownMenuPortalPrimitive>
<DropdownMenuContentPrimitive
sideOffset={props.sideOffset ?? 4}
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',
attrs.class ?? '',
)}
{...props}
{...emitsAsProps}
>
{slots.default?.()}
</DropdownMenuContentPrimitive>
</DropdownMenuPortalPrimitive>
)
},
{
name: 'DropdownMenuContent',
emits: DropdownMenuContentPrimitive.emits,
},
)
const DropdownMenuItem = defineComponent<
DropdownMenuItemProps & {
inset?: boolean
}
>(
(props, { attrs, emit, slots }) => {
const emitsAsProps = useEmitAsProps(emit)
return () => (
<DropdownMenuItemPrimitive
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',
props.inset && 'pl-8',
attrs.class ?? '',
)}
{...props}
{...emitsAsProps}
>
{slots.default?.()}
</DropdownMenuItemPrimitive>
)
},
{
name: 'DropdownMenuItem',
emits: DropdownMenuItemPrimitive.emits,
},
)
const DropdownMenuCheckboxItem = defineComponent<DropdownMenuCheckboxItemProps>(
(props, { attrs, slots, emit }) => {
const emitsAsProps = useEmitAsProps(emit)
return () => (
<DropdownMenuCheckboxItemPrimitive
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',
attrs.class ?? '',
)}
checked={props.checked}
{...props}
{...emitsAsProps}
>
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuItemIndicatorPrimitive>
<CheckIcon class="h-4 w-4" />
</DropdownMenuItemIndicatorPrimitive>
</span>
{slots.default?.()}
</DropdownMenuCheckboxItemPrimitive>
)
},
{ name: 'DropdownMenuCheckboxItem', emits: DropdownMenuItemPrimitive.emits },
)
const DropdownMenuRadioItem = defineComponent<DropdownMenuRadioItemProps>(
(props, { attrs, slots, emit }) => {
const emitsAsProps = useEmitAsProps(emit)
return () => (
<DropdownMenuRadioItemPrimitive
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',
attrs.class ?? '',
)}
{...props}
{...emitsAsProps}
>
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuItemIndicatorPrimitive>
<CircleIcon class="h-4 w-4 fill-current" />
</DropdownMenuItemIndicatorPrimitive>
</span>
{slots.default?.()}
</DropdownMenuRadioItemPrimitive>
)
},
{
name: 'DropdownMenuRadioItem',
emits: DropdownMenuRadioItemPrimitive.emits,
},
)
const DropdownMenuLabel = defineComponent<
DropdownMenuLabelProps & {
inset?: boolean
}
>(
(props, { attrs, slots }) => {
return () => (
<DropdownMenuLabelPrimitive
class={cn(
'px-2 py-1.5 text-sm font-semibold',
props.inset && 'pl-8',
attrs.class ?? '',
)}
{...props}
>
{slots.default?.()}
</DropdownMenuLabelPrimitive>
)
},
{
name: 'DropdownMenuLabel',
},
)
const DropdownMenuSeparator = defineComponent<DropdownMenuSeparatorProps>(
(props, { attrs }) => {
return () => (
<DropdownMenuSeparatorPrimitive
class={cn('mx-1 my-1 h-px bg-muted', attrs.class)}
{...props}
/>
)
},
{
name: 'DropdownMenuSeparator',
},
)
const DropdownMenuShortcut = defineComponent(
(props, { attrs }) => {
return () => (
<span
class={cn(
'ml-auto text-xs tracking-widest opacity-60',
attrs.class ?? '',
)}
{...props}
/>
)
},
{
name: 'DropdownMenuShortcut',
},
)
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
}