aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/packages/ui-new/src/components/ui/dropdown-menu.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/ui-new/src/components/ui/dropdown-menu.tsx')
-rw-r--r--packages/ui-new/src/components/ui/dropdown-menu.tsx269
1 files changed, 269 insertions, 0 deletions
diff --git a/packages/ui-new/src/components/ui/dropdown-menu.tsx b/packages/ui-new/src/components/ui/dropdown-menu.tsx
new file mode 100644
index 0000000..ee97374
--- /dev/null
+++ b/packages/ui-new/src/components/ui/dropdown-menu.tsx
@@ -0,0 +1,269 @@
+import { Menu as MenuPrimitive } from "@base-ui/react/menu";
+import { CheckIcon, ChevronRightIcon } from "lucide-react";
+import type * as React from "react";
+import { cn } from "@/lib/utils";
+
+function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {
+ return <MenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
+}
+
+function DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {
+ return <MenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />;
+}
+
+function DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {
+ return <MenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />;
+}
+
+function DropdownMenuContent({
+ align = "start",
+ alignOffset = 0,
+ side = "bottom",
+ sideOffset = 4,
+ className,
+ ...props
+}: MenuPrimitive.Popup.Props &
+ Pick<
+ MenuPrimitive.Positioner.Props,
+ "align" | "alignOffset" | "side" | "sideOffset"
+ >) {
+ return (
+ <MenuPrimitive.Portal>
+ <MenuPrimitive.Positioner
+ className="isolate z-50 outline-none"
+ align={align}
+ alignOffset={alignOffset}
+ side={side}
+ sideOffset={sideOffset}
+ >
+ <MenuPrimitive.Popup
+ data-slot="dropdown-menu-content"
+ className={cn(
+ "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 ring-foreground/10 bg-popover text-popover-foreground min-w-32 rounded-none shadow-md ring-1 duration-100 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 z-50 max-h-(--available-height) w-(--anchor-width) origin-(--transform-origin) overflow-x-hidden overflow-y-auto outline-none data-closed:overflow-hidden",
+ className,
+ )}
+ {...props}
+ />
+ </MenuPrimitive.Positioner>
+ </MenuPrimitive.Portal>
+ );
+}
+
+function DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {
+ return <MenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />;
+}
+
+function DropdownMenuLabel({
+ className,
+ inset,
+ ...props
+}: MenuPrimitive.GroupLabel.Props & {
+ inset?: boolean;
+}) {
+ return (
+ <MenuPrimitive.GroupLabel
+ data-slot="dropdown-menu-label"
+ data-inset={inset}
+ className={cn(
+ "text-muted-foreground px-2 py-2 text-xs data-inset:pl-7",
+ className,
+ )}
+ {...props}
+ />
+ );
+}
+
+function DropdownMenuItem({
+ className,
+ inset,
+ variant = "default",
+ ...props
+}: MenuPrimitive.Item.Props & {
+ inset?: boolean;
+ variant?: "default" | "destructive";
+}) {
+ return (
+ <MenuPrimitive.Item
+ data-slot="dropdown-menu-item"
+ data-inset={inset}
+ data-variant={variant}
+ className={cn(
+ "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-none px-2 py-2 text-xs data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 group/dropdown-menu-item relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
+ className,
+ )}
+ {...props}
+ />
+ );
+}
+
+function DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {
+ return <MenuPrimitive.SubmenuRoot data-slot="dropdown-menu-sub" {...props} />;
+}
+
+function DropdownMenuSubTrigger({
+ className,
+ inset,
+ children,
+ ...props
+}: MenuPrimitive.SubmenuTrigger.Props & {
+ inset?: boolean;
+}) {
+ return (
+ <MenuPrimitive.SubmenuTrigger
+ data-slot="dropdown-menu-sub-trigger"
+ data-inset={inset}
+ className={cn(
+ "focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-none px-2 py-2 text-xs data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 data-popup-open:bg-accent data-popup-open:text-accent-foreground flex cursor-default items-center outline-hidden select-none [&_svg]:pointer-events-none [&_svg]:shrink-0",
+ className,
+ )}
+ {...props}
+ >
+ {children}
+ <ChevronRightIcon className="ml-auto" />
+ </MenuPrimitive.SubmenuTrigger>
+ );
+}
+
+function DropdownMenuSubContent({
+ align = "start",
+ alignOffset = -3,
+ side = "right",
+ sideOffset = 0,
+ className,
+ ...props
+}: React.ComponentProps<typeof DropdownMenuContent>) {
+ return (
+ <DropdownMenuContent
+ data-slot="dropdown-menu-sub-content"
+ className={cn(
+ "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 ring-foreground/10 bg-popover text-popover-foreground min-w-24 rounded-none shadow-lg ring-1 duration-100 w-auto",
+ className,
+ )}
+ align={align}
+ alignOffset={alignOffset}
+ side={side}
+ sideOffset={sideOffset}
+ {...props}
+ />
+ );
+}
+
+function DropdownMenuCheckboxItem({
+ className,
+ children,
+ checked,
+ inset,
+ ...props
+}: MenuPrimitive.CheckboxItem.Props & {
+ inset?: boolean;
+}) {
+ return (
+ <MenuPrimitive.CheckboxItem
+ data-slot="dropdown-menu-checkbox-item"
+ data-inset={inset}
+ className={cn(
+ "focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-2 rounded-none py-2 pr-8 pl-2 text-xs data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
+ className,
+ )}
+ checked={checked}
+ {...props}
+ >
+ <span
+ className="absolute right-2 flex items-center justify-center pointer-events-none"
+ data-slot="dropdown-menu-checkbox-item-indicator"
+ >
+ <MenuPrimitive.CheckboxItemIndicator>
+ <CheckIcon />
+ </MenuPrimitive.CheckboxItemIndicator>
+ </span>
+ {children}
+ </MenuPrimitive.CheckboxItem>
+ );
+}
+
+function DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {
+ return (
+ <MenuPrimitive.RadioGroup
+ data-slot="dropdown-menu-radio-group"
+ {...props}
+ />
+ );
+}
+
+function DropdownMenuRadioItem({
+ className,
+ children,
+ inset,
+ ...props
+}: MenuPrimitive.RadioItem.Props & {
+ inset?: boolean;
+}) {
+ return (
+ <MenuPrimitive.RadioItem
+ data-slot="dropdown-menu-radio-item"
+ data-inset={inset}
+ className={cn(
+ "focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-2 rounded-none py-2 pr-8 pl-2 text-xs data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
+ className,
+ )}
+ {...props}
+ >
+ <span
+ className="absolute right-2 flex items-center justify-center pointer-events-none"
+ data-slot="dropdown-menu-radio-item-indicator"
+ >
+ <MenuPrimitive.RadioItemIndicator>
+ <CheckIcon />
+ </MenuPrimitive.RadioItemIndicator>
+ </span>
+ {children}
+ </MenuPrimitive.RadioItem>
+ );
+}
+
+function DropdownMenuSeparator({
+ className,
+ ...props
+}: MenuPrimitive.Separator.Props) {
+ return (
+ <MenuPrimitive.Separator
+ data-slot="dropdown-menu-separator"
+ className={cn("bg-border -mx-1 h-px", className)}
+ {...props}
+ />
+ );
+}
+
+function DropdownMenuShortcut({
+ className,
+ ...props
+}: React.ComponentProps<"span">) {
+ return (
+ <span
+ data-slot="dropdown-menu-shortcut"
+ className={cn(
+ "text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground ml-auto text-xs tracking-widest",
+ className,
+ )}
+ {...props}
+ />
+ );
+}
+
+export {
+ DropdownMenu,
+ DropdownMenuPortal,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuLabel,
+ DropdownMenuItem,
+ DropdownMenuCheckboxItem,
+ DropdownMenuRadioGroup,
+ DropdownMenuRadioItem,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuSub,
+ DropdownMenuSubTrigger,
+ DropdownMenuSubContent,
+};