Skip to content

Commit d84f32c

Browse files
authored
Merge pull request #185 from icflorescu/next
Implement repositionOnRepeat, update dep versions, package version & changelog
2 parents 0f9b805 + fd95c96 commit d84f32c

10 files changed

+586
-516
lines changed

.github/workflows/publish-and-deploy.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
- name: Setup Node.js
3030
uses: actions/setup-node@v4
3131
with:
32-
node-version: '22.11'
32+
node-version: '23.9'
3333
cache: yarn
3434
- name: Restore cache
3535
uses: actions/cache@v4

CHANGELOG.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33
The following is a list of notable changes to the Mantine ContextMenu component.
44
Minor versions that are not listed in the changelog are minor bug fixes and small internal improvements or refactorings.
55

6-
## 7.15.3 (2025-01-08)
6+
## 7.17.1 (2025-03-06)
7+
8+
- Implement `repositionOnRepeat` option
9+
- Update dev dependencies to ensure compatibility with Mantine 7.17.1 & Next.js 15.2
10+
11+
## 7.15.3 (2025-01-09)
712

813
- Update dev dependencies to ensure compatibility with Mantine 7.15.3.
914
- Remove unnecessary menu overlay CSS background-color property

app/examples/basic-configuration/ProviderPropsExample.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { ContextMenuProvider } from '__PACKAGE__';
33
export function ProviderPropsExample({ children }: { children: React.ReactNode }) {
44
// example-start
55
return (
6-
<ContextMenuProvider zIndex={5000} shadow="md" borderRadius="md">
6+
<ContextMenuProvider zIndex={5000} shadow="md" borderRadius="md" repositionOnRepeat>
77
{children}
88
</ContextMenuProvider>
99
);

app/examples/basic-configuration/page.tsx

+9
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,15 @@ export default async function BasicConfigurationExamplePage() {
3939
<Code>borderRadius: MantineSize</Code> → defaults to <Code>xs</Code> (see{' '}
4040
<ExternalLink to={`${MANTINE_LINK}/core/paper/`}>Mantine Paper</ExternalLink> docs)
4141
</li>
42+
<li>
43+
<Code>repositionOnRepeat: boolean</Code> → defaults to <Code>false</Code>
44+
<br />
45+
Whether to reposition the context menu when the triggering event repeats.
46+
<br />
47+
If set to true, the context menu will reposition itself to the position of the triggering event.
48+
<br />
49+
If unset or set to false, the context menu will hide automatically when the triggering event repeats.
50+
</li>
4251
</ul>
4352
<CodeBlock code={code['ProviderPropsExample.tsx']} />
4453
<Txt>

package.json

+19-19
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "mantine-contextmenu",
3-
"version": "7.15.3",
3+
"version": "7.17.1",
44
"description": "Craft your applications for productivity and meet your users’ expectations by enhancing your Mantine-based UIs with a desktop-grade, lightweight yet fully-featured, dark-theme aware context-menu component, built by the creator of Mantine DataTable",
55
"keywords": [
66
"ui",
@@ -72,36 +72,36 @@
7272
"format": "prettier --write ."
7373
},
7474
"devDependencies": {
75-
"@mantine/code-highlight": "^7.15.3",
76-
"@mantine/core": "^7.15.3",
77-
"@mantine/hooks": "^7.15.3",
78-
"@mantine/notifications": "^7.15.3",
79-
"@tabler/icons-react": "^3.28.1",
80-
"@types/lodash": "^4.17.14",
81-
"@types/node": "^22.10.5",
82-
"@types/react": "^19.0.3",
83-
"@types/react-dom": "^19.0.2",
84-
"@typescript-eslint/eslint-plugin": "^8.19.1",
85-
"@typescript-eslint/parser": "^8.19.1",
75+
"@mantine/code-highlight": "^7.17.1",
76+
"@mantine/core": "^7.17.1",
77+
"@mantine/hooks": "^7.17.1",
78+
"@mantine/notifications": "^7.17.1",
79+
"@tabler/icons-react": "^3.31.0",
80+
"@types/lodash": "^4.17.16",
81+
"@types/node": "^22.13.9",
82+
"@types/react": "^19.0.10",
83+
"@types/react-dom": "^19.0.4",
84+
"@typescript-eslint/eslint-plugin": "^8.26.0",
85+
"@typescript-eslint/parser": "^8.26.0",
8686
"clsx": "^2.1.1",
8787
"cssnano": "^7.0.6",
8888
"eslint": "^8",
89-
"eslint-config-next": "^15.1.4",
89+
"eslint-config-next": "^15.2.1",
9090
"eslint-config-prettier": "^9.1.0",
9191
"lodash": "^4.17.21",
92-
"next": "^15.1.4",
93-
"postcss": "^8.4.49",
92+
"next": "^15.2.1",
93+
"postcss": "^8.5.3",
9494
"postcss-cli": "^11.0.0",
9595
"postcss-import": "^16.1.0",
9696
"postcss-preset-mantine": "^1.17.0",
9797
"postcss-simple-vars": "^7.0.1",
98-
"prettier": "^3.4.2",
98+
"prettier": "^3.5.3",
9999
"react": "^19.0.0",
100100
"react-dom": "^19.0.0",
101101
"sharp": "^0.33.5",
102-
"swr": "^2.3.0",
103-
"tsup": "^8.3.5",
104-
"typescript": "^5.7.2"
102+
"swr": "^2.3.2",
103+
"tsup": "^8.4.0",
104+
"typescript": "^5.8.2"
105105
},
106106
"peerDependencies": {
107107
"@mantine/core": ">=7.12",

package/ContextMenuItem.tsx

+9-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Box, UnstyledButton, parseThemeColor, rgba } from '@mantine/core';
22
import { useMediaQuery, useTimeout } from '@mantine/hooks';
33
import clsx from 'clsx';
4-
import { useContext, useRef, useState, type MouseEventHandler } from 'react';
4+
import { type MouseEventHandler, useContext, useRef, useState } from 'react';
55
import { ContextMenu } from './ContextMenu';
66
import { ContextMenuSettingsCtx } from './ContextMenuProvider';
77
import type { ContextMenuContent, ContextMenuItemOptions, WithRequiredProperty } from './types';
@@ -21,7 +21,7 @@ export function ContextMenuItem({
2121
const ref = useRef<HTMLButtonElement>(null);
2222
const { submenuDelay } = useContext(ContextMenuSettingsCtx);
2323

24-
const hoverAvailable = useMediaQuery(`(hover: hover)`);
24+
const hoverAvailable = useMediaQuery('(hover: hover)');
2525

2626
const [submenuPosition, setSubmenuPosition] = useState<{ x: number; y: number } | null>(null);
2727

@@ -98,14 +98,16 @@ export function ContextMenuItem({
9898
</Box>
9999
)}
100100
<div className="mantine-contextmenu-item-button-title">{title}</div>
101-
{ iconRight ? (
101+
{iconRight ? (
102102
<Box fz={0} ml="xs" mt={-2}>
103103
{iconRight}
104104
</Box>
105-
) : items && (
106-
<Box fz={10} mt={-2} ml="xs">
107-
108-
</Box>
105+
) : (
106+
items && (
107+
<Box fz={10} mt={-2} ml="xs">
108+
109+
</Box>
110+
)
109111
)}
110112
</UnstyledButton>
111113
{submenuPosition && <ContextMenu content={items as ContextMenuContent} onHide={onHide} {...submenuPosition} />}

package/ContextMenuOverlay.tsx

+28-1
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,42 @@
1+
import { useContext } from 'react';
2+
import { ContextMenuSettingsCtx } from './ContextMenuProvider';
3+
14
export type ContextMenuOverlayProps = React.PropsWithChildren<{
25
zIndex: number | undefined;
36
onHide: () => void;
47
}>;
58

69
export function ContextMenuOverlay({ zIndex, children, onHide }: ContextMenuOverlayProps) {
10+
const { repositionOnRepeat } = useContext(ContextMenuSettingsCtx);
11+
712
const handleHide = (e: React.MouseEvent) => {
813
e.preventDefault();
914
onHide();
1015
};
16+
17+
const handleContextMenu = async (e: React.MouseEvent) => {
18+
e.preventDefault();
19+
if (repositionOnRepeat) {
20+
const { clientX, clientY } = e;
21+
try {
22+
document
23+
.elementsFromPoint(clientX, clientY)[1]
24+
.dispatchEvent(new MouseEvent('contextmenu', { bubbles: true, clientX, clientY }));
25+
} catch {
26+
// ignore error
27+
}
28+
} else {
29+
onHide();
30+
}
31+
};
32+
1133
return (
12-
<div className="mantine-contextmenu-overlay" style={{ zIndex }} onClick={handleHide} onContextMenu={handleHide}>
34+
<div
35+
className="mantine-contextmenu-overlay"
36+
style={{ zIndex }}
37+
onClick={handleHide}
38+
onContextMenu={handleContextMenu}
39+
>
1340
{children}
1441
</div>
1542
);

package/ContextMenuProvider.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const DEFAULT_SETTINGS: WithRequiredProperty<ContextMenuSettings, 'shadow' | 'bo
1414
shadow: 'sm',
1515
borderRadius: 'xs',
1616
submenuDelay: 500,
17+
repositionOnRepeat: false,
1718
};
1819

1920
export const ContextMenuSettingsCtx = createContext(DEFAULT_SETTINGS);
@@ -44,6 +45,7 @@ export function ContextMenuProvider({
4445
shadow = DEFAULT_SETTINGS.shadow,
4546
borderRadius = DEFAULT_SETTINGS.borderRadius,
4647
submenuDelay = DEFAULT_SETTINGS.submenuDelay,
48+
repositionOnRepeat = DEFAULT_SETTINGS.repositionOnRepeat,
4749
children,
4850
}: ContextMenuProviderProps) {
4951
const [data, setData] = useState<(ContextMenuInstanceOptions & ContextMenuOptions) | null>(null);
@@ -72,7 +74,7 @@ export function ContextMenuProvider({
7274
};
7375

7476
return (
75-
<ContextMenuSettingsCtx.Provider value={{ shadow, borderRadius, submenuDelay }}>
77+
<ContextMenuSettingsCtx.Provider value={{ shadow, borderRadius, submenuDelay, repositionOnRepeat }}>
7678
<ContextMenuCtx.Provider value={{ showContextMenu, hideContextMenu, isContextMenuVisible: !!data }}>
7779
{children}
7880
{data && <ContextMenuPortal onHide={hideContextMenu} {...data} />}

package/types.ts

+8
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@ export type ContextMenuSettings = {
6767
* @default 500
6868
*/
6969
submenuDelay?: number;
70+
71+
/**
72+
* Whether to reposition the context menu when the triggering event repeats. If set to true, the context menu will
73+
* reposition itself to the position of the triggering event. If set to false, the context menu will hide
74+
* automatically when the triggering event repeats.
75+
* @default false
76+
*/
77+
repositionOnRepeat?: boolean;
7078
};
7179

7280
/**

0 commit comments

Comments
 (0)