diff options
| author | 2026-02-24 22:41:36 +0800 | |
|---|---|---|
| committer | 2026-02-24 22:41:36 +0800 | |
| commit | b275a3668b140d9ce4663de646519d2dbd4297e7 (patch) | |
| tree | 13799773d7d6ef5ac566bbfc4e28ed81798b965b /packages/ui-new/src/components/ui/dropdown-menu.tsx | |
| parent | 888f57b6f2ef3b81ba61f4009799f046739ba4dd (diff) | |
| download | DropOut-b275a3668b140d9ce4663de646519d2dbd4297e7.tar.gz DropOut-b275a3668b140d9ce4663de646519d2dbd4297e7.zip | |
refactor: rewrite login and settings pages
Diffstat (limited to 'packages/ui-new/src/components/ui/dropdown-menu.tsx')
| -rw-r--r-- | packages/ui-new/src/components/ui/dropdown-menu.tsx | 269 |
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, +}; |