333 lines · 11.6 KB · TypeScript
RawDownload
"use client"; 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"; const MenuCreateHandle = MenuPrimitive.createHandle; const Menu = MenuPrimitive.Root; const MenuPortal = MenuPrimitive.Portal; function MenuTrigger({ className, children, ...props }: MenuPrimitive.Trigger.Props) {  return (    <MenuPrimitive.Trigger className={className} data-slot="menu-trigger" {...props}>      {children}    </MenuPrimitive.Trigger>  );} function MenuPopup({  children,  className,  sideOffset = 4,  align = "center",  alignOffset,  side = "bottom",  anchor,  ...props}: MenuPrimitive.Popup.Props & {  align?: MenuPrimitive.Positioner.Props["align"];  sideOffset?: MenuPrimitive.Positioner.Props["sideOffset"];  alignOffset?: MenuPrimitive.Positioner.Props["alignOffset"];  side?: MenuPrimitive.Positioner.Props["side"];  anchor?: MenuPrimitive.Positioner.Props["anchor"];}) {  const hasExplicitWidthClass =    typeof className === "string" &&    className.split(/\s+/).some((classToken) => {      const utility = classToken.split(":").at(-1) ?? classToken;      return /^(?:min-|max-)?w-/.test(utility);    });   return (    <MenuPrimitive.Portal>      <MenuPrimitive.Positioner        align={align}        alignOffset={alignOffset}        anchor={anchor}        className="z-[130]"        data-slot="menu-positioner"        side={side}        sideOffset={sideOffset}      >        <MenuPrimitive.Popup          className={cn(            "dropdown-glass relative flex origin-(--transform-origin) rounded-lg shadow-[0_16px_40px_-18px_rgb(0_0_0/55%)] outline-none focus:outline-none dark:shadow-[0_18px_44px_-18px_rgb(0_0_0/80%)]",            !hasExplicitWidthClass && "min-w-32",            className,          )}          data-slot="menu-popup"          {...props}        >          <div className="max-h-(--available-height) w-full overflow-y-auto p-1">{children}</div>        </MenuPrimitive.Popup>      </MenuPrimitive.Positioner>    </MenuPrimitive.Portal>  );} function MenuGroup(props: MenuPrimitive.Group.Props) {  return <MenuPrimitive.Group data-slot="menu-group" {...props} />;} function MenuItem({  className,  inset,  variant = "default",  ...props}: MenuPrimitive.Item.Props & {  inset?: boolean;  variant?: "default" | "destructive";}) {  return (    <MenuPrimitive.Item      className={cn(        "[&>svg]:-mx-0.5 flex min-h-8 cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1 text-base text-foreground outline-none data-disabled:pointer-events-none data-disabled:cursor-not-allowed data-highlighted:bg-accent data-inset:ps-8 data-[variant=destructive]:text-destructive-foreground data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&>svg:not([class*='opacity-'])]:opacity-80 [&>svg:not([class*='size-'])]:size-4.5 sm:[&>svg:not([class*='size-'])]:size-4 [&>svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:[&>svg:not([class*='text-'])]:text-current [&>svg]:pointer-events-none [&>svg]:shrink-0",        className,      )}      data-inset={inset}      data-slot="menu-item"      data-variant={variant}      {...props}    />  );} function MenuCheckboxItem({  className,  children,  checked,  variant = "default",  ...props}: MenuPrimitive.CheckboxItem.Props & {  variant?: "default" | "switch";}) {  return (    <MenuPrimitive.CheckboxItem      checked={checked}      className={cn(        "grid min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-pointer items-center gap-2 rounded-sm py-1 ps-2 text-base text-foreground outline-none data-disabled:pointer-events-none data-disabled:cursor-not-allowed data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",        variant === "switch" ? "grid-cols-[1fr_auto] gap-4 pe-1.5" : "grid-cols-[1rem_1fr] pe-4",        className,      )}      data-slot="menu-checkbox-item"      {...props}    >      {variant === "switch" ? (        <>          <span className="col-start-1">{children}</span>          <MenuPrimitive.CheckboxItemIndicator            className="inset-shadow-[0_1px_--theme(--color-black/4%)] inline-flex h-[calc(var(--thumb-size)+2px)] w-[calc(var(--thumb-size)*2-2px)] shrink-0 items-center rounded-full p-px outline-none transition-[background-color,box-shadow] duration-200 [--thumb-size:--spacing(4)] focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-checked:bg-primary data-unchecked:bg-input data-disabled:opacity-64 sm:[--thumb-size:--spacing(3)]"            keepMounted          >            <span className="pointer-events-none block aspect-square h-full in-[[data-slot=menu-checkbox-item][data-checked]]:origin-[var(--thumb-size)_50%] origin-left in-[[data-slot=menu-checkbox-item][data-checked]]:translate-x-[calc(var(--thumb-size)-4px)] in-[[data-slot=menu-checkbox-item]:active]:not-data-disabled:scale-x-110 in-[[data-slot=menu-checkbox-item]:active]:rounded-[var(--thumb-size)/calc(var(--thumb-size)*1.10)] rounded-(--thumb-size) bg-background shadow-sm/5 will-change-transform [transition:translate_.15s,border-radius_.15s,scale_.1s_.1s,transform-origin_.15s]" />          </MenuPrimitive.CheckboxItemIndicator>        </>      ) : (        <>          <MenuPrimitive.CheckboxItemIndicator className="col-start-1">            <svg              fill="none"              height="24"              stroke="currentColor"              strokeLinecap="round"              strokeLinejoin="round"              strokeWidth="2"              viewBox="0 0 24 24"              width="24"              xmlns="http://www.w3.org/2000/svg"            >              <path d="M5.252 12.7 10.2 18.63 18.748 5.37" />            </svg>          </MenuPrimitive.CheckboxItemIndicator>          <span className="col-start-2">{children}</span>        </>      )}    </MenuPrimitive.CheckboxItem>  );} function MenuRadioGroup(props: MenuPrimitive.RadioGroup.Props) {  return <MenuPrimitive.RadioGroup data-slot="menu-radio-group" {...props} />;} function MenuRadioItem({  className,  children,  hideIndicator: _hideIndicator = false,  ...props}: MenuPrimitive.RadioItem.Props & {  hideIndicator?: boolean;}) {  return (    <MenuPrimitive.RadioItem      className={cn(        "[&_svg]:-mx-0.5 flex min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-pointer items-center rounded-sm px-2 py-1 text-base text-foreground outline-none data-checked:bg-foreground/[0.08] data-disabled:pointer-events-none data-disabled:cursor-not-allowed data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",        className,      )}      data-slot="menu-radio-item"      {...props}    >      <span className="min-w-0 flex-1">{children}</span>    </MenuPrimitive.RadioItem>  );} function MenuRadioItemIndicator({  className,  children,  ...props}: MenuPrimitive.RadioItemIndicator.Props) {  return (    <MenuPrimitive.RadioItemIndicator      aria-hidden      className={cn("flex shrink-0", className)}      data-slot="menu-radio-item-indicator"      {...props}    >      {children ?? <CheckIcon className="size-3.5" />}    </MenuPrimitive.RadioItemIndicator>  );} function MenuGroupLabel({  className,  inset,  ...props}: MenuPrimitive.GroupLabel.Props & {  inset?: boolean;}) {  return (    <MenuPrimitive.GroupLabel      className={cn(        "px-2 py-1.5 font-medium text-muted-foreground text-xs data-inset:ps-9 sm:data-inset:ps-8",        className,      )}      data-inset={inset}      data-slot="menu-label"      {...props}    />  );} function MenuSeparator({ className, ...props }: MenuPrimitive.Separator.Props) {  return (    <MenuPrimitive.Separator      className={cn("mx-2 my-1 h-px bg-border", className)}      data-slot="menu-separator"      {...props}    />  );} function MenuShortcut({ className, ...props }: React.ComponentProps<"kbd">) {  return (    <kbd      className={cn(        "ms-auto font-medium font-sans text-secondary-label text-xs tracking-widest",        className,      )}      data-slot="menu-shortcut"      {...props}    />  );} function MenuSub(props: MenuPrimitive.SubmenuRoot.Props) {  return <MenuPrimitive.SubmenuRoot data-slot="menu-sub" {...props} />;} function MenuSubTrigger({  className,  inset,  children,  ...props}: MenuPrimitive.SubmenuTrigger.Props & {  inset?: boolean;}) {  return (    <MenuPrimitive.SubmenuTrigger      className={cn(        // Leading-icon treatment matches `MenuItem`: a sub-trigger sits in the        // same column as the items around it, so its icon has to align and dim        // with theirs. Scoped away from the last child because the chevron is        // also a direct svg — on a sub-trigger with no leading icon it is the        // only one, and these rules would take away its `ms-auto` alignment.        "[&>svg:not(:last-child)]:-mx-0.5 flex min-h-8 cursor-pointer items-center gap-2 rounded-sm px-2 py-1 text-base text-foreground outline-none data-disabled:cursor-not-allowed data-disabled:pointer-events-none data-highlighted:bg-accent data-popup-open:bg-accent data-inset:ps-8 data-highlighted:text-accent-foreground data-popup-open:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground [&>svg:not(:last-child):not([class*='opacity-'])]:opacity-80 [&_svg]:pointer-events-none [&>svg]:shrink-0",        className,      )}      data-inset={inset}      data-slot="menu-sub-trigger"      {...props}    >      {children}      <ChevronRightIcon className="-me-0.5 ms-auto opacity-80" />    </MenuPrimitive.SubmenuTrigger>  );} function MenuSubPopup({  className,  sideOffset = 0,  alignOffset,  align = "start",  ...props}: MenuPrimitive.Popup.Props & {  align?: MenuPrimitive.Positioner.Props["align"];  sideOffset?: MenuPrimitive.Positioner.Props["sideOffset"];  alignOffset?: MenuPrimitive.Positioner.Props["alignOffset"];}) {  const defaultAlignOffset = align !== "center" ? -5 : undefined;   return (    <MenuPopup      align={align}      alignOffset={alignOffset ?? defaultAlignOffset}      className={className}      data-slot="menu-sub-content"      side="inline-end"      sideOffset={sideOffset}      {...props}    />  );} export {  MenuCreateHandle,  MenuCreateHandle as DropdownMenuCreateHandle,  Menu,  Menu as DropdownMenu,  MenuPortal,  MenuPortal as DropdownMenuPortal,  MenuTrigger,  MenuTrigger as DropdownMenuTrigger,  MenuPopup,  MenuPopup as DropdownMenuContent,  MenuGroup,  MenuGroup as DropdownMenuGroup,  MenuItem,  MenuItem as DropdownMenuItem,  MenuCheckboxItem,  MenuCheckboxItem as DropdownMenuCheckboxItem,  MenuRadioGroup,  MenuRadioGroup as DropdownMenuRadioGroup,  MenuRadioItem,  MenuRadioItem as DropdownMenuRadioItem,  MenuRadioItemIndicator,  MenuGroupLabel,  MenuGroupLabel as DropdownMenuLabel,  MenuSeparator,  MenuSeparator as DropdownMenuSeparator,  MenuShortcut,  MenuShortcut as DropdownMenuShortcut,  MenuSub,  MenuSub as DropdownMenuSub,  MenuSubTrigger,  MenuSubTrigger as DropdownMenuSubTrigger,  MenuSubPopup,  MenuSubPopup as DropdownMenuSubContent,};