Skip to content

Commit a26cef8

Browse files
committed
feat: Add context
1 parent f7b785d commit a26cef8

File tree

7 files changed

+36
-18
lines changed

7 files changed

+36
-18
lines changed

src/components/ContextMenu.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import cx from 'clsx';
44
import MenuItem from './MenuItem';
55
import Separator from './Separator';
66
import SubMenu from './SubMenu';
7+
78
import { cloneChildren, getCursorPosition, validateMenuPosition } from '../utils';
89
import { HIDE_ON_EVENTS } from '../constants';
10+
import ContextProvider from '../context';
911
import { Position } from '../types';
1012

1113
export interface ContextMenuProps extends HTMLAttributes<HTMLDivElement> {
@@ -58,16 +60,14 @@ const ContextMenu = ({
5860
};
5961

6062
const hide = () => {
61-
const position = { x: 0, y: 0 };
62-
63-
if (animateExit) setState((prev) => ({ ...prev, position, leaving: true }));
64-
else setState((prev) => ({ ...prev, position, active: false }));
63+
if (animateExit) setState((prev) => ({ ...prev, leaving: true }));
64+
else setState((prev) => ({ ...prev, active: false }));
6565
};
6666

6767
const handleAnimationEnd = () => {
6868
const { leaving, active } = state;
6969

70-
if (leaving && active) setState((prev) => ({ ...prev, active: false, leaving: false }));
70+
if (leaving && active) setState(() => ({ position: { x: 0, y: 0 }, active: false, leaving: false }));
7171
};
7272

7373
useEffect(() => {
@@ -109,7 +109,7 @@ const ContextMenu = ({
109109
onClick={(event) => event.stopPropagation()}
110110
tabIndex={-1}
111111
>
112-
{cloneChildren(children, { hide })}
112+
<ContextProvider hide={hide}>{cloneChildren(children)}</ContextProvider>
113113
</div>
114114
);
115115
};

src/components/MenuItem.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
1-
import { useState, useCallback, ButtonHTMLAttributes } from 'react';
1+
import { useState, useCallback, ButtonHTMLAttributes, useContext } from 'react';
22
import cx from 'clsx';
33

4-
export interface MenuItemProps extends ButtonHTMLAttributes<HTMLButtonElement> {}
4+
import { ContextMenuContext } from '../context';
55

6-
export interface MenuItemExternalProps extends ButtonHTMLAttributes<HTMLButtonElement> {
7-
hide: () => void;
8-
}
6+
export interface MenuItemProps extends ButtonHTMLAttributes<HTMLButtonElement> {}
97

108
interface MenuItemState {
119
clicked: boolean;
1210
eventRef: React.MouseEvent<HTMLButtonElement> | null;
1311
}
1412

1513
const MenuItem = ({ children, onClick, className, disabled = false, ...rest }: MenuItemProps) => {
14+
const context = useContext(ContextMenuContext);
1615
const [state, setState] = useState<MenuItemState>({ clicked: false, eventRef: null });
1716

1817
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
@@ -22,11 +21,10 @@ const MenuItem = ({ children, onClick, className, disabled = false, ...rest }: M
2221
};
2322

2423
const handleAnimationEnd = useCallback(() => {
25-
const { hide } = rest as MenuItemExternalProps;
2624
setState((prev) => ({ ...prev, clicked: false }));
2725

2826
if (state.clicked && state.eventRef) {
29-
hide();
27+
context?.hide();
3028
onClick!(state.eventRef);
3129
}
3230
// eslint-disable-next-line react-hooks/exhaustive-deps

src/components/SubMenu.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { useState, useEffect, useRef, HTMLAttributes } from 'react';
22
import cx from 'clsx';
33

4-
import { MenuItemExternalProps } from './MenuItem';
54
import { BOTTOM_CLASS, CLOSE_DELAY, RIGHT_CLASS } from '../constants';
65
import { cloneChildren } from '../utils';
76

@@ -85,7 +84,7 @@ const SubMenu = ({ label, children, className, disabled = false, ...rest }: SubM
8584
className="react-context-menu__submenu"
8685
>
8786
{/* rest is sent from the ContextMenu element */}
88-
{cloneChildren(children, rest as MenuItemExternalProps)}
87+
{cloneChildren(children)}
8988
</div>
9089
</div>
9190
);

src/context/context.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { createContext } from 'react';
2+
3+
export interface ContextMenuContextProps {
4+
hide: () => void;
5+
}
6+
7+
export const ContextMenuContext = createContext<ContextMenuContextProps | null>(null);

src/context/index.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { ReactNode } from 'react';
2+
3+
import { ContextMenuContext, ContextMenuContextProps } from './context';
4+
5+
export interface ContextProviderProps extends ContextMenuContextProps {
6+
children: ReactNode;
7+
}
8+
9+
const ContextProvider = ({ hide, children }: ContextProviderProps) => {
10+
return <ContextMenuContext.Provider value={{ hide }}>{children}</ContextMenuContext.Provider>;
11+
};
12+
13+
export default ContextProvider;
14+
export { ContextMenuContext };

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ export {
1010
SubMenu,
1111
type SubMenuProps,
1212
} from './components';
13+
export { default as ContextProvider, ContextMenuContext } from './context';
1314
export type { Position } from './types';

src/utils.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { Children, cloneElement, ReactElement, ReactNode } from 'react';
22

3-
import { MenuItemExternalProps } from './components/MenuItem';
43
import { Position } from './types';
54

65
export const getCursorPosition = (e: MouseEvent): Position => {
@@ -26,8 +25,8 @@ export const validateMenuPosition = (position: Position, element: HTMLDivElement
2625
return { x, y };
2726
};
2827

29-
export const cloneChildren = (children: ReactNode, props?: MenuItemExternalProps) => {
28+
export const cloneChildren = (children: ReactNode) => {
3029
const filteredItems = Children.toArray(children).filter(Boolean);
3130

32-
return filteredItems.map((item) => cloneElement(item as ReactElement<any>, props));
31+
return filteredItems.map((item) => cloneElement(item as ReactElement<any>));
3332
};

0 commit comments

Comments
 (0)