[WEB-4730] dev: propel context menu component (#7745)

* dev: context menu component added

* dev: context menu story added

* chore: propel config updated
This commit is contained in:
Anmol Singh Bhatia 2025-09-10 00:15:55 +05:30 committed by GitHub
parent 1c8ac3d247
commit 7e03264758
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 172 additions and 0 deletions

View file

@ -25,6 +25,7 @@
"./collapsible": "./dist/collapsible/index.js",
"./combobox": "./dist/combobox/index.js",
"./command": "./dist/command/index.js",
"./context-menu": "./dist/context-menu/index.js",
"./dialog": "./dist/dialog/index.js",
"./emoji-icon-picker": "./dist/emoji-icon-picker/index.js",
"./icons": "./dist/icons/index.js",

View file

@ -0,0 +1,35 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { ContextMenu } from "./context-menu";
const meta: Meta<typeof ContextMenu> = {
title: "Components/ContextMenu",
component: ContextMenu,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
};
export default meta;
type Story = StoryObj<typeof ContextMenu>;
export const Default: Story = {
render: () => (
<ContextMenu>
<ContextMenu.Trigger>
<div className="flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed border-custom-border-300 text-sm">
Right click here
</div>
</ContextMenu.Trigger>
<ContextMenu.Portal>
<ContextMenu.Content>
<ContextMenu.Item>Back</ContextMenu.Item>
<ContextMenu.Item>Forward</ContextMenu.Item>
<ContextMenu.Item>Reload</ContextMenu.Item>
<ContextMenu.Separator />
<ContextMenu.Item>More Tools</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu.Portal>
</ContextMenu>
),
};

View file

@ -0,0 +1,128 @@
import * as React from "react";
import { ContextMenu as ContextMenuPrimitive } from "@base-ui-components/react/context-menu";
import { cn } from "../utils";
export interface ContextMenuProps extends React.ComponentProps<typeof ContextMenuPrimitive.Root> {
children: React.ReactNode;
}
export interface ContextMenuTriggerProps extends React.ComponentProps<typeof ContextMenuPrimitive.Trigger> {
children: React.ReactNode;
className?: string;
}
export interface ContextMenuContentProps extends React.ComponentProps<typeof ContextMenuPrimitive.Positioner> {
children: React.ReactNode;
className?: string;
side?: "top" | "right" | "bottom" | "left";
sideOffset?: number;
}
export interface ContextMenuItemProps extends React.ComponentProps<typeof ContextMenuPrimitive.Item> {
children: React.ReactNode;
className?: string;
disabled?: boolean;
}
const ContextMenuRoot = React.forwardRef<React.ElementRef<typeof ContextMenuPrimitive.Root>, ContextMenuProps>(
({ children, ...props }, _ref) => <ContextMenuPrimitive.Root {...props}>{children}</ContextMenuPrimitive.Root>
);
const ContextMenuTrigger = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Trigger>,
ContextMenuTriggerProps
>(({ className, children, ...props }, ref) => (
<ContextMenuPrimitive.Trigger ref={ref} className={cn("outline-none", className)} {...props}>
{children}
</ContextMenuPrimitive.Trigger>
));
const ContextMenuPortal = ContextMenuPrimitive.Portal;
const ContextMenuContent = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Positioner>,
ContextMenuContentProps
>(({ className, children, side = "bottom", sideOffset = 4, ...props }, ref) => (
<ContextMenuPrimitive.Positioner ref={ref} side={side} sideOffset={sideOffset} {...props}>
<ContextMenuPrimitive.Popup
className={cn(
"z-50 min-w-32 overflow-hidden rounded-md border border-custom-border-200 bg-custom-background-100 p-1 shadow-md",
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
"data-[state=closed]:zoom-out-95 data-[state=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",
className
)}
>
{children}
</ContextMenuPrimitive.Popup>
</ContextMenuPrimitive.Positioner>
));
const ContextMenuItem = React.forwardRef<React.ElementRef<typeof ContextMenuPrimitive.Item>, ContextMenuItemProps>(
({ className, disabled, children, ...props }, ref) => (
<ContextMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none",
"focus:bg-custom-background-90 focus:text-custom-text-100",
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
disabled={disabled}
{...props}
>
{children}
</ContextMenuPrimitive.Item>
)
);
const ContextMenuSeparator = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Separator>,
React.ComponentProps<typeof ContextMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-custom-border-200", className)}
{...props}
/>
));
const ContextMenuSubmenu = ContextMenuPrimitive.SubmenuRoot;
const ContextMenuSubmenuTrigger = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.SubmenuTrigger>,
React.ComponentProps<typeof ContextMenuPrimitive.SubmenuTrigger>
>(({ className, children, ...props }, ref) => (
<ContextMenuPrimitive.SubmenuTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:outline-none",
"focus:bg-custom-background-90 data-[state=open]:bg-custom-background-90",
className
)}
{...props}
>
{children}
</ContextMenuPrimitive.SubmenuTrigger>
));
ContextMenuRoot.displayName = "ContextMenu";
ContextMenuTrigger.displayName = "ContextMenuTrigger";
ContextMenuContent.displayName = "ContextMenuContent";
ContextMenuItem.displayName = "ContextMenuItem";
ContextMenuSeparator.displayName = "ContextMenuSeparator";
ContextMenuSubmenuTrigger.displayName = "ContextMenuSubmenuTrigger";
// compound components
const ContextMenu = Object.assign(ContextMenuRoot, {
Trigger: ContextMenuTrigger,
Portal: ContextMenuPortal,
Content: ContextMenuContent,
Item: ContextMenuItem,
Separator: ContextMenuSeparator,
Submenu: ContextMenuSubmenu,
SubmenuTrigger: ContextMenuSubmenuTrigger,
});
export { ContextMenu };

View file

@ -0,0 +1,7 @@
export { ContextMenu } from "./context-menu";
export type {
ContextMenuProps,
ContextMenuTriggerProps,
ContextMenuContentProps,
ContextMenuItemProps,
} from "./context-menu";

View file

@ -11,6 +11,7 @@ export default defineConfig({
"src/collapsible/index.ts",
"src/combobox/index.ts",
"src/command/index.ts",
"src/context-menu/index.ts",
"src/dialog/index.ts",
"src/emoji-icon-picker/index.ts",
"src/icons/index.ts",