/* * (c) Copyright 2026 Palantir Technologies Inc. All rights reserved. */ import type { Meta, StoryObj } from "@storybook/react-vite"; import { storybookLayoutDecorator } from "@storybook-common"; import { expect, screen, waitFor } from "storybook/test"; import { Menu } from "../menu/menu"; import { MenuDivider } from "../menu/menuDivider"; import { MenuItem } from "../menu/menuItem"; import { ContextMenu, type ContextMenuChildrenProps } from "./contextMenu"; const SAMPLE_MENU = ( ); const TARGET_STYLE: React.CSSProperties = { alignItems: "center", background: "rgba(138, 155, 168, 0.15)", border: "1px dashed rgba(138, 155, 168, 0.5)", borderRadius: 3, display: "flex", height: 120, justifyContent: "center", padding: 20, textAlign: "center", width: 240, }; const meta: Meta = { title: "Core/Context Menu/ContextMenu", component: ContextMenu, decorators: [storybookLayoutDecorator], args: { content: SAMPLE_MENU, disabled: false, }, argTypes: { content: { control: false, table: { disable: true } }, disabled: { control: "boolean" }, tagName: { control: "text" }, onContextMenu: { action: "context-menu-opened" }, onClose: { table: { disable: true } }, popoverProps: { table: { disable: true } }, children: { table: { disable: true } }, }, } satisfies Meta; export default meta; type Story = StoryObj; /** * A basic context menu wraps its children in a `
` and opens on right-click. */ export const Default: Story = { args: { children:
Right-click anywhere in this box
, }, }; /** * The `content` prop accepts a render function. The function receives the current * `isOpen` state, `targetOffset`, and `mouseEvent`, which can be used to render * content that reacts to the click location. */ export const RenderFunctionContent: Story = { name: "Content Render Function", args: { children:
Right-click to see click coordinates
, content: ({ targetOffset }) => ( {targetOffset === undefined ? undefined : ( <> )} ), }, }; /** * Provide `children` as a render function to take full control of the wrapper element. * This is useful when the default `
` wrapper breaks layout. You must wire up the * `className`, `onContextMenu`, `ref`, and embed the `popover` element yourself. */ export const AdvancedChildren: Story = { name: "Advanced Children Render", args: { children: (props: ContextMenuChildrenProps) => (
{props.popover} Render-function child — background changes when menu opens
), }, }; /** * Use the `tagName` prop to customize the HTML tag of the generated wrapper element. */ export const CustomTagName: Story = { name: "Custom Tag Name", args: { tagName: "section", children:
{"Wrapped in a
element"}
, }, }; /** * Override the popover placement via `popoverProps.placement`. The default is * `right-start`, but you may need a different placement near viewport edges. */ export const CustomPlacement: Story = { name: "Custom Placement", args: { children:
Right-click — menu opens above-left
, popoverProps: { placement: "top-end" }, }, }; /** * When `disabled` is true, right-clicking the target falls back to the browser's * native context menu instead of opening the Blueprint menu. */ export const Disabled: Story = { args: { disabled: true, children:
Disabled — browser context menu shows instead
, }, }; /** * Interactive playground — toggle `disabled` and adjust other props via the controls panel. */ export const Playground: Story = { args: { children:
Right-click me!
, }, }; /** * Demonstrates the menu opening on a right-click (contextmenu) event. */ export const RightClickOpen: Story = { name: "Right Click Open", args: { children:
Right-click to open menu
, popoverProps: { transitionDuration: 0 }, }, play: async ({ canvas, userEvent, step }) => { await step("Right-click target to open menu", async () => { const target = canvas.getByText("Right-click to open menu"); const rect = target.getBoundingClientRect(); const coords = { clientX: rect.left + rect.width / 2, clientY: rect.top + rect.height / 2 }; await userEvent.pointer({ keys: "[MouseRight]", target, coords }); await waitFor(() => expect(screen.getByText("Select all")).toBeVisible()); }); }, }; /** * When `disabled` is true, right-clicking does not open the Blueprint context menu. */ export const DisabledNoOpen: Story = { name: "Disabled No Open", args: { disabled: true, children:
Disabled target
, }, play: async ({ canvas, userEvent, step }) => { await step("Right-click disabled target — menu does not open", async () => { const target = canvas.getByText("Disabled target"); const rect = target.getBoundingClientRect(); const coords = { clientX: rect.left + rect.width / 2, clientY: rect.top + rect.height / 2 }; await userEvent.pointer({ keys: "[MouseRight]", target, coords }); await expect(screen.queryByText("Select all")).not.toBeInTheDocument(); }); }, };