File size: 2,818 Bytes
5f40163 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | import React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "#/utils/utils";
import { dropdownMenuListGapClassName } from "#/utils/dropdown-classes";
const contextMenuVariants = cva(
"z-50 overflow-hidden text-[var(--oh-foreground)]",
{
variants: {
theme: {
default:
"absolute rounded-md border border-[var(--oh-border-subtle)] bg-tertiary px-1 py-1 shadow-lg",
naked: "relative",
/** In document-body portal; coordinates come from inline `style`. */
popover:
"relative rounded-md border border-[var(--oh-border-subtle)] bg-tertiary px-1 py-1 shadow-lg",
},
size: {
compact: "py-1 px-1",
default: "",
},
layout: {
vertical: cn("flex flex-col", dropdownMenuListGapClassName),
},
position: {
top: "bottom-full",
bottom: "top-full",
none: "",
},
spacing: {
default: "mt-2",
none: "",
},
alignment: {
left: "left-0",
right: "right-0",
none: "",
},
},
compoundVariants: [
{
theme: "naked",
className: "shadow-none",
},
],
defaultVariants: {
theme: "default",
size: "default",
layout: "vertical",
spacing: "default",
},
},
);
interface ContextMenuProps {
ref?: React.RefObject<HTMLUListElement | null>;
testId?: string;
children: React.ReactNode;
className?: React.HTMLAttributes<HTMLUListElement>["className"];
style?: React.CSSProperties;
onKeyDown?: React.KeyboardEventHandler<HTMLUListElement>;
theme?: VariantProps<typeof contextMenuVariants>["theme"];
size?: VariantProps<typeof contextMenuVariants>["size"];
layout?: VariantProps<typeof contextMenuVariants>["layout"];
position?: VariantProps<typeof contextMenuVariants>["position"];
spacing?: VariantProps<typeof contextMenuVariants>["spacing"];
alignment?: VariantProps<typeof contextMenuVariants>["alignment"];
}
export function ContextMenu({
testId,
children,
className,
style,
onKeyDown,
ref,
theme,
size,
layout,
position,
spacing,
alignment,
}: ContextMenuProps) {
return (
// Keyboard navigation is opt-in via `onKeyDown` (home automation kebab).
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- popup list hosts Escape/arrow handlers when provided
<ul
data-testid={testId}
data-position={position}
ref={ref}
style={style}
onKeyDown={onKeyDown}
className={cn(
contextMenuVariants({
theme,
size,
layout,
position,
spacing,
alignment,
}),
className,
)}
>
{children}
</ul>
);
}
|