style: compact dropdown and context menu styling

This commit is contained in:
Maze Winther 2026-04-07 01:55:21 +02:00
parent 30709d7134
commit 4f7d401a97
2 changed files with 299 additions and 299 deletions

View File

@ -36,7 +36,7 @@ const ContextMenuSub = ContextMenuPrimitive.Sub;
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup; const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
const contextMenuItemVariants = cva( const contextMenuItemVariants = cva(
"relative flex cursor-pointer select-none items-center gap-2.5 px-4 py-1.5 text-sm outline-hidden data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0", "relative flex cursor-pointer select-none items-center gap-2 rounded-sm px-3 py-1.5 text-sm text-foreground/85 outline-hidden data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:size-3.5 [&_svg]:shrink-0",
{ {
variants: { variants: {
variant: { variant: {
@ -94,7 +94,7 @@ const ContextMenuSubContent = React.forwardRef<
<ContextMenuPrimitive.SubContent <ContextMenuPrimitive.SubContent
ref={ref} ref={ref}
className={cn( className={cn(
"bg-popover text-popover-foreground z-50 min-w-48 overflow-hidden rounded-lg border shadow-xl py-2.5", "bg-popover text-popover-foreground z-50 min-w-48 overflow-hidden rounded-md border shadow-xl p-1",
className, className,
)} )}
{...props} {...props}
@ -112,7 +112,7 @@ const ContextMenuContent = React.forwardRef<
<ContextMenuPrimitive.Content <ContextMenuPrimitive.Content
ref={ref} ref={ref}
className={cn( className={cn(
"bg-popover text-popover-foreground z-50 min-w-48 overflow-hidden rounded-lg border shadow-xl py-1.5", "bg-popover text-popover-foreground z-50 min-w-48 overflow-hidden rounded-md border shadow-xl p-1",
className, className,
)} )}
{...props} {...props}
@ -155,7 +155,7 @@ const ContextMenuItem = React.forwardRef<
{...props} {...props}
> >
{icon && ( {icon && (
<span className="absolute left-3 flex size-3.5 items-center justify-center text-muted-foreground [&_svg]:size-4 [&_svg]:shrink-0"> <span className="absolute left-3 flex size-3.5 items-center justify-center text-muted-foreground [&_svg]:size-3.5 [&_svg]:shrink-0">
{icon} {icon}
</span> </span>
)} )}
@ -242,7 +242,7 @@ const ContextMenuLabel = React.forwardRef<
<ContextMenuPrimitive.Label <ContextMenuPrimitive.Label
ref={ref} ref={ref}
className={cn( className={cn(
"flex items-center gap-2.5 px-4 py-1.5 text-sm font-semibold text-foreground", "flex items-center gap-2 px-3 py-1.5 text-sm font-semibold text-foreground",
inset && "pl-8", inset && "pl-8",
className, className,
)} )}
@ -262,7 +262,7 @@ const ContextMenuSeparator = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<ContextMenuPrimitive.Separator <ContextMenuPrimitive.Separator
ref={ref} ref={ref}
className={cn("bg-border my-2 h-px", className)} className={cn("bg-border mx-1 my-1.5 h-px", className)}
{...props} {...props}
/> />
)); ));

View File

@ -1,293 +1,293 @@
"use client"; "use client";
import * as React from "react"; import * as React from "react";
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui"; import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
import { Check, ChevronRight, Circle } from "lucide-react"; import { Check, ChevronRight, Circle } from "lucide-react";
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/utils/ui"; import { cn } from "@/utils/ui";
import { useOverlayOpenChange } from "./use-overlay-open-change"; import { useOverlayOpenChange } from "./use-overlay-open-change";
function DropdownMenu({ function DropdownMenu({
open, open,
onOpenChange, onOpenChange,
...props ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) { }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
const handleOpenChange = useOverlayOpenChange({ const handleOpenChange = useOverlayOpenChange({
source: "dropdown-menu", source: "dropdown-menu",
open, open,
onOpenChange, onOpenChange,
}); });
return ( return (
<DropdownMenuPrimitive.Root <DropdownMenuPrimitive.Root
open={open} open={open}
onOpenChange={handleOpenChange} onOpenChange={handleOpenChange}
{...props} {...props}
/> />
); );
} }
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
const DropdownMenuGroup = DropdownMenuPrimitive.Group; const DropdownMenuGroup = DropdownMenuPrimitive.Group;
const DropdownMenuPortal = DropdownMenuPrimitive.Portal; const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
const DropdownMenuSub = DropdownMenuPrimitive.Sub; const DropdownMenuSub = DropdownMenuPrimitive.Sub;
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
const dropdownMenuItemVariants = cva( const dropdownMenuItemVariants = cva(
"relative flex cursor-pointer select-none items-center gap-2 rounded-md px-3 py-2 text-sm text-foreground outline-hidden data-[highlighted]:bg-popover-hover data-disabled:pointer-events-none data-disabled:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0", "relative flex cursor-pointer select-none items-center gap-2 rounded-sm px-2.5 py-1.5 text-sm text-foreground/85 outline-hidden data-[highlighted]:bg-popover-hover data-disabled:pointer-events-none data-disabled:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
{ {
variants: { variants: {
variant: { variant: {
default: "", default: "",
destructive: destructive:
"text-destructive data-[highlighted]:bg-destructive/5 data-[highlighted]:text-destructive", "text-destructive data-[highlighted]:bg-destructive/5 data-[highlighted]:text-destructive",
}, },
}, },
defaultVariants: { defaultVariants: {
variant: "default", variant: "default",
}, },
}, },
); );
const DropdownMenuSubTrigger = React.forwardRef< const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>, React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & { React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean; inset?: boolean;
variant?: VariantProps<typeof dropdownMenuItemVariants>["variant"]; variant?: VariantProps<typeof dropdownMenuItemVariants>["variant"];
} }
>(({ className, inset, children, variant = "default", ...props }, ref) => ( >(({ className, inset, children, variant = "default", ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger <DropdownMenuPrimitive.SubTrigger
ref={ref} ref={ref}
className={cn( className={cn(
dropdownMenuItemVariants({ variant }), dropdownMenuItemVariants({ variant }),
"data-[state=open]:bg-muted data-[state=open]:text-foreground", "data-[state=open]:bg-muted data-[state=open]:text-foreground",
inset && "pl-8", inset && "pl-8",
className, className,
)} )}
{...props} {...props}
> >
{children} {children}
<ChevronRight className="ml-auto" /> <ChevronRight className="ml-auto" />
</DropdownMenuPrimitive.SubTrigger> </DropdownMenuPrimitive.SubTrigger>
)); ));
DropdownMenuSubTrigger.displayName = DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName; DropdownMenuPrimitive.SubTrigger.displayName;
const DropdownMenuSubContent = React.forwardRef< const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>, React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent> React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent <DropdownMenuPrimitive.SubContent
ref={ref} ref={ref}
className={cn( className={cn(
"group/menu bg-popover text-popover-foreground z-50 min-w-32 overflow-hidden rounded-2xl border p-2 shadow-lg", "group/menu bg-popover text-popover-foreground z-50 min-w-32 overflow-hidden rounded-md border p-1 shadow-lg",
className, className,
)} )}
{...props} {...props}
/> />
)); ));
DropdownMenuSubContent.displayName = DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName; DropdownMenuPrimitive.SubContent.displayName;
const DropdownMenuContent = React.forwardRef< const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>, React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => ( >(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal> <DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content <DropdownMenuPrimitive.Content
ref={ref} ref={ref}
sideOffset={sideOffset} sideOffset={sideOffset}
onCloseAutoFocus={(e) => { onCloseAutoFocus={(e) => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
}} }}
className={cn( className={cn(
"group/menu bg-popover text-popover-foreground z-50 min-w-32 overflow-hidden rounded-lg border p-1.5 shadow-lg", "group/menu bg-popover text-popover-foreground z-50 min-w-32 overflow-hidden rounded-md border p-1 shadow-lg",
className, className,
)} )}
{...props} {...props}
/> />
</DropdownMenuPrimitive.Portal> </DropdownMenuPrimitive.Portal>
)); ));
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
const DropdownMenuItem = React.forwardRef< const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>, React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & { React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean; inset?: boolean;
icon?: React.ReactNode; icon?: React.ReactNode;
variant?: VariantProps<typeof dropdownMenuItemVariants>["variant"]; variant?: VariantProps<typeof dropdownMenuItemVariants>["variant"];
} }
>( >(
( (
{ {
className, className,
inset, inset,
icon, icon,
variant = "default", variant = "default",
children, children,
asChild, asChild,
...props ...props
}, },
ref, ref,
) => { ) => {
const iconSlot = ( const iconSlot = (
<span className="hidden size-4 shrink-0 items-center justify-center group-has-data-has-icon/menu:flex"> <span className="hidden size-4 shrink-0 items-center justify-center group-has-data-has-icon/menu:flex">
{icon} {icon}
</span> </span>
); );
const renderedChildren = const renderedChildren =
asChild && React.isValidElement(children) ? ( asChild && React.isValidElement(children) ? (
React.cloneElement( React.cloneElement(
children as React.ReactElement<{ children?: React.ReactNode }>, children as React.ReactElement<{ children?: React.ReactNode }>,
{}, {},
iconSlot, iconSlot,
(children as React.ReactElement<{ children?: React.ReactNode }>).props (children as React.ReactElement<{ children?: React.ReactNode }>).props
.children, .children,
) )
) : ( ) : (
<> <>
{iconSlot} {iconSlot}
{children} {children}
</> </>
); );
return ( return (
<DropdownMenuPrimitive.Item <DropdownMenuPrimitive.Item
ref={ref} ref={ref}
asChild={asChild} asChild={asChild}
data-has-icon={icon ? "" : undefined} data-has-icon={icon ? "" : undefined}
className={cn( className={cn(
dropdownMenuItemVariants({ variant }), dropdownMenuItemVariants({ variant }),
inset && "pl-8", inset && "pl-8",
className, className,
)} )}
{...props} {...props}
> >
{renderedChildren} {renderedChildren}
</DropdownMenuPrimitive.Item> </DropdownMenuPrimitive.Item>
); );
}, },
); );
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
const DropdownMenuCheckboxItem = React.forwardRef< const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>, React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem> & { React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem> & {
variant?: VariantProps<typeof dropdownMenuItemVariants>["variant"]; variant?: VariantProps<typeof dropdownMenuItemVariants>["variant"];
} }
>(({ className, children, checked, variant = "default", ...props }, ref) => ( >(({ className, children, checked, variant = "default", ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem <DropdownMenuPrimitive.CheckboxItem
ref={ref} ref={ref}
className={cn( className={cn(
dropdownMenuItemVariants({ variant }), dropdownMenuItemVariants({ variant }),
"pr-8 pl-2", "pr-8 pl-2",
className, className,
)} )}
checked={checked} checked={checked}
onSelect={(e) => { onSelect={(e) => {
e.preventDefault(); e.preventDefault();
}} }}
{...props} {...props}
> >
{children} {children}
<span className="absolute right-2 flex size-3.5 items-center justify-center"> <span className="absolute right-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator> <DropdownMenuPrimitive.ItemIndicator>
<Check className="size-4" /> <Check className="size-4" />
</DropdownMenuPrimitive.ItemIndicator> </DropdownMenuPrimitive.ItemIndicator>
</span> </span>
</DropdownMenuPrimitive.CheckboxItem> </DropdownMenuPrimitive.CheckboxItem>
)); ));
DropdownMenuCheckboxItem.displayName = DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName; DropdownMenuPrimitive.CheckboxItem.displayName;
const DropdownMenuRadioItem = React.forwardRef< const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>, React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem> & { React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem> & {
variant?: VariantProps<typeof dropdownMenuItemVariants>["variant"]; variant?: VariantProps<typeof dropdownMenuItemVariants>["variant"];
} }
>(({ className, children, variant = "default", ...props }, ref) => ( >(({ className, children, variant = "default", ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem <DropdownMenuPrimitive.RadioItem
ref={ref} ref={ref}
className={cn( className={cn(
dropdownMenuItemVariants({ variant }), dropdownMenuItemVariants({ variant }),
"pr-2 pl-8", "pr-2 pl-8",
className, className,
)} )}
{...props} {...props}
> >
<span className="absolute left-2 flex size-3.5 items-center justify-center"> <span className="absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator> <DropdownMenuPrimitive.ItemIndicator>
<Circle className="size-2 fill-current" /> <Circle className="size-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator> </DropdownMenuPrimitive.ItemIndicator>
</span> </span>
{children} {children}
</DropdownMenuPrimitive.RadioItem> </DropdownMenuPrimitive.RadioItem>
)); ));
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName; DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
const DropdownMenuLabel = React.forwardRef< const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>, React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & { React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean; inset?: boolean;
} }
>(({ className, inset, ...props }, ref) => ( >(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label <DropdownMenuPrimitive.Label
ref={ref} ref={ref}
className={cn( className={cn(
"px-3 pb-2 pt-1 text-[11px] font-bold uppercase tracking-wider text-muted-foreground", "px-2 pb-1 pt-0.5 text-[11px] font-bold uppercase tracking-wider text-muted-foreground",
inset && "pl-8", inset && "pl-8",
className, className,
)} )}
{...props} {...props}
/> />
)); ));
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
const DropdownMenuSeparator = React.forwardRef< const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>, React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator> React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator <DropdownMenuPrimitive.Separator
ref={ref} ref={ref}
className={cn("bg-border/60 mx-1 my-2 h-px", className)} className={cn("bg-border mx-1 my-1.5 h-px", className)}
{...props} {...props}
/> />
)); ));
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
const DropdownMenuShortcut = ({ const DropdownMenuShortcut = ({
className, className,
...props ...props
}: React.HTMLAttributes<HTMLSpanElement>) => { }: React.HTMLAttributes<HTMLSpanElement>) => {
return ( return (
<span <span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)} className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props} {...props}
/> />
); );
}; };
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"; DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
export { export {
DropdownMenu, DropdownMenu,
DropdownMenuTrigger, DropdownMenuTrigger,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuCheckboxItem, DropdownMenuCheckboxItem,
DropdownMenuRadioItem, DropdownMenuRadioItem,
DropdownMenuLabel, DropdownMenuLabel,
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuShortcut, DropdownMenuShortcut,
DropdownMenuGroup, DropdownMenuGroup,
DropdownMenuPortal, DropdownMenuPortal,
DropdownMenuSub, DropdownMenuSub,
DropdownMenuSubContent, DropdownMenuSubContent,
DropdownMenuSubTrigger, DropdownMenuSubTrigger,
DropdownMenuRadioGroup, DropdownMenuRadioGroup,
}; };