@asup/context-menu
v2.2.3
Published
REACT Typescript Context menu component
Readme
@asup/context-menu
A small, highly-configurable React + TypeScript context menu component and helpers.
Key points:
- Works with React 19 (package is built and tested against React 19; React is a peer dependency).
- TypeScript types included.
- Lightweight and focused on accessibility and nested sub-menus.
Storybook
Run Storybook to see interactive component examples and documentation.
# install dependencies
npm install
# run Storybook
npm run storybook
# run tests
npm run test
# build library bundle
npm run buildInstallation
Install from npm:
npm install @asup/context-menuNote: React and ReactDOM are peer dependencies — install a compatible React version in your application.
Usage
ContextMenuHandler
import { ContextMenuHandler, IMenuItem } from "@asup/context-menu";
const menuItems: IMenuItem[] = [
{ label: "Item 1", action: () => console.log("Item 1") },
{
label: "Item 2",
action: () => console.log("Item 2"),
group: [{ label: "Subitem 2.1", action: () => console.log("Subitem 2.1") }],
},
{ label: "Item 3 (disabled)", disabled: true },
];
<ContextMenuHandler menuItems={menuItems}>
<div>Right click here to open the menu</div>
</ContextMenuHandler>;AutoHeight
Use AutoHeight to wrap content that may expand/contract — it will manage layout height for smoother transitions.
import { AutoHeight } from "@asup/context-menu";
<AutoHeight>
<div style={{ padding: 12 }}>
This content can change size; AutoHeight will help the layout adjust smoothly.
</div>
</AutoHeight>;ClickForMenu
ClickForMenu attaches a click-based menu to any element (useful for toolbar buttons or inline actions). Give each instance a stable id so the trigger element can be referenced and cleaned up correctly.
import { ClickForMenu, IMenuItem } from "@asup/context-menu";
const clickItems: IMenuItem[] = [
{ label: "Edit", action: () => console.log("Edit") },
{ label: "Delete", action: () => console.log("Delete") },
];
<ClickForMenu
id="actions-menu"
menuItems={clickItems}
>
<button type="button">Actions</button>
</ClickForMenu>;ContextWindow
import { ContextWindow } from "@asup/context-menu";
<ContextWindow
id="window-1"
title="Window 1"
visible={true}
onClose={() => {}}
>
Window content
</ContextWindow>;useMouseMove (Hook)
useMouseMove is exported for draggable interactions. The API uses onMouseDown / onMouseMove / onMouseUp names, and the hook now wires both mouse and pointer document listeners during an active drag.
Key behavior:
- Registers both
mousemove+pointermovewhile active. - Registers both
mouseup+pointerupwhile active. - Restores
userSelecton the same element that started the drag, even if the release happens on a different target. - Restores
userSelectduring unmount cleanup if the interaction ends unexpectedly.
import { useRef, useState } from "react";
import { useMouseMove } from "@asup/context-menu";
export function DraggablePanel(): React.ReactElement {
const panelRef = useRef<HTMLDivElement | null>(null);
const [x, setX] = useState(0);
const [y, setY] = useState(0);
const { onMouseDown } = useMouseMove({
onMouseMove: (e) => {
setX((prev) => prev + e.movementX);
setY((prev) => prev + e.movementY);
},
onMouseUp: () => {
// Optional: snap/validate final position.
},
});
return (
<div
ref={panelRef}
style={{
transform: `translate(${x}px, ${y}px)`,
width: 220,
padding: 12,
border: "1px solid #999",
borderRadius: 8,
background: "white",
}}
>
<div
onMouseDown={onMouseDown}
onPointerDown={(e) =>
onMouseDown(e as unknown as React.MouseEvent<HTMLElement | SVGElement>)
}
style={{ cursor: "move", fontWeight: 600 }}
>
Drag Handle
</div>
<div style={{ marginTop: 8 }}>Panel body</div>
</div>
);
}See the Storybook for interactive examples and more options.
Development
Useful scripts (from package.json):
npm run prepare— run Husky (prepares Git hooks).npm run storybook— start Storybook to view component examples.npm run build-storybook— build a static Storybook site.npm run test— run Jest and collect coverage (jest --collectCoverage=true).npm run test-watch— run Jest in watch mode with coverage (jest --watch --collectCoverage=true --maxWorkers=4).npm run eslint— run ESLint oversrc(pattern:src/**/*.{js,jsx,ts,tsx}).npm run build— build the library bundle with Parcel. This script clears the Parcel cache before building (parcel build src/main.ts).
Contributing
Contributions and PRs are welcome. Please follow the repository conventions (linting, types, and tests).
- Fork the repo and create a feature branch.
- Run
npm install. - Run and update tests:
npm run test. - Submit a PR and describe your changes.
License
MIT — see the LICENCE file for details.
