Dropdown menu
Preview
<script setup lang="ts">
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuGroup,
DropdownMenuSubTrigger,
DropdownMenuSub,
DropdownMenuSeparator,
DropdownMenuGroupLabel,
} from "~/components/ui/dropdown-menu";
import { Button } from "~/components/ui/button";
import {
User2,
CreditCard,
Settings,
Keyboard,
Settings2,
Plus,
UserPlus,
} from "lucide-vue-next";
import { Mail, MessageSquare } from "lucide-vue-next";
import { PlusCircle } from "lucide-vue-next";
</script>
<template>
<DropdownMenu :close-on-select="true">
<DropdownMenuTrigger>
<Button variant="outline">Menu</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuGroup id="account">
<DropdownMenuGroupLabel for="account"> Account </DropdownMenuGroupLabel>
<DropdownMenuItem id="profile">
<User2 class="mr-2 h-4 w-4" />
Profile
</DropdownMenuItem>
<DropdownMenuItem id="billing">
<CreditCard class="mr-2 h-4 w-4" />
Billing
</DropdownMenuItem>
<DropdownMenuItem id="settings">
<Settings class="mr-2 h-4 w-4" />
Settings
</DropdownMenuItem>
<DropdownMenuItem id="shortcuts">
<Keyboard class="mr-2 h-4 w-4" />
Keyboard shortcuts
</DropdownMenuItem>
</DropdownMenuGroup>
<!-- <DropdownMenuSeparator /> -->
<DropdownMenuGroup id="team">
<DropdownMenuGroupLabel for="team"> Team </DropdownMenuGroupLabel>
<DropdownMenuItem id="team-settings">
<Settings2 class="mr-2 h-4 w-4" />
Team settings
</DropdownMenuItem>
<DropdownMenu>
<DropdownMenuSubTrigger>
<UserPlus class="mr-2 h-4 w-4" />
Invite users
</DropdownMenuSubTrigger>
<DropdownMenuContent>
<DropdownMenuItem id="invite-email">
<Mail class="mr-2 h-4 w-4" />
Email
</DropdownMenuItem>
<DropdownMenuItem id="invite-chat">
<MessageSquare class="mr-2 h-4 w-4" />
Chat
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem id="invite-more">
<PlusCircle class="mr-2 h-4 w-4" />
More
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</template>
Installation
Copy and paste this into your project
// ~/components/ui/dropdown-menu.tsx
import {
Menu,
MenuContent,
MenuItem,
MenuItemGroup,
MenuItemGroupLabel,
MenuPositioner,
MenuProps,
MenuSeparator,
MenuTrigger,
MenuTriggerItem,
} from "@ark-ui/vue";
import { ExtendProps, cn } from "~/lib/utils";
import { Teleport, defineComponent, onMounted, ref, h } from "vue";
import { ChevronRight } from "lucide-vue-next";
const Chevron = defineComponent({
setup() {
return () =>
h(ChevronRight, {
class: "w-4 h-4 ml-auto",
});
},
});
const DropdownMenu = defineComponent({
props: {} as ExtendProps<MenuProps>,
setup(_, { slots, attrs }) {
const key = ref("ssr");
onMounted(() => {
key.value = "csr";
});
return () => (
<Menu key={key.value} {...attrs}>
{slots.default?.()}
</Menu>
);
},
});
const DropdownMenuTrigger = MenuTrigger;
const DropdownMenuGroup = MenuItemGroup;
const DropdownMenuSub = Menu;
const DropdownMenuContent = defineComponent({
setup(_, { slots, attrs }) {
return () => (
<Teleport to="body">
<MenuPositioner>
<MenuContent
class={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg focus:outline-none",
attrs.class ?? ""
)}
>
{slots.default?.()}
</MenuContent>
</MenuPositioner>
</Teleport>
);
},
});
const DropdownMenuItem = defineComponent({
props: {
id: {
type: String,
required: true,
},
inset: {
type: Boolean,
default: false,
},
},
setup(props, { slots, attrs, emit }) {
return () => (
<MenuItem
id={props.id}
{...attrs}
class={cn(
"w-full relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors data-[focus]:bg-accent data-[focus]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
props.inset && "pl-8",
attrs.class ?? ""
)}
>
{slots.default?.()}
</MenuItem>
);
},
});
const DropdownMenuSubTrigger = defineComponent({
props: {
inset: {
type: Boolean,
default: false,
},
},
setup(props, { slots, attrs }) {
return () => (
<MenuTriggerItem
class={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[focus]:bg-accent data-[state=open]:bg-accent",
props.inset && "pl-8",
attrs.class ?? ""
)}
>
{slots.default?.()}
<Chevron />
</MenuTriggerItem>
);
},
});
const DropdownMenuSeparator = defineComponent({
setup(_, { attrs }) {
return () => (
<MenuSeparator
class={cn("-mx-1 my-1 h-px bg-muted", attrs.class ?? "")}
/>
);
},
});
const DropdownMenuGroupLabel = defineComponent({
props: {
for: {
type: String,
required: true,
},
},
setup(props, { attrs, slots }) {
return () => (
<MenuItemGroupLabel
htmlFor={props.for}
class={cn(
"block px-2 py-1.5 text-xs font-medium text-muted-foreground",
attrs.class ?? ""
)}
>
{slots.default?.()}
</MenuItemGroupLabel>
);
},
});
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuGroup,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSeparator,
DropdownMenuGroupLabel,
};
Usage
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSubTrigger,
DropdownMenuSub,
} from "~/components/ui/dropdown-menu";
Note: All dropdown menu items must have an id
attribute. This is used to identify item is clicked through the @select
event.
<DropdownMenu @select="(...)">
<DropdownMenuTrigger>Open</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem id="profile">Profile</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<UserPlus class="mr-2 h-4 w-4" />
Invite users
</DropdownMenuSubTrigger>
<DropdownMenuContent>
<DropdownMenuItem id="email">
Email
</DropdownMenuItem>
<DropdownMenuItem id="chat">
Chat
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem id="more">
More
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenuSub>
</DropdownMenuContent>
</DropdownMenu>