Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/test/simulation/workbench/components/contextMenu.tsx
13399 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { makeStyles, MenuItem, MenuList, tokens } from '@fluentui/react-components';
7
import React, { createContext, MouseEvent, ReactNode, useContext, useEffect, useRef, useState } from 'react';
8
9
type MenuEntry = {
10
label: string;
11
onClick: () => void;
12
};
13
14
type ContextMenuContextProps = {
15
showMenu: (event: MouseEvent, entries: MenuEntry[]) => void;
16
hideMenu: () => void;
17
menuEntries: MenuEntry[];
18
menuPosition: { x: number; y: number };
19
isVisible: boolean;
20
};
21
22
const ContextMenuContext = createContext<ContextMenuContextProps | undefined>(undefined);
23
24
export const ContextMenuProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
25
26
const [menuEntries, setMenuEntries] = useState<MenuEntry[]>([]);
27
const [menuPosition, setMenuPosition] = useState<{ x: number; y: number }>({ x: 0, y: 0 });
28
const [isVisible, setIsVisible] = useState(false);
29
30
const showMenu = (event: MouseEvent, entries: MenuEntry[]) => {
31
event.preventDefault();
32
setMenuEntries(entries);
33
setMenuPosition({ x: event.pageX, y: event.pageY });
34
setIsVisible(true);
35
};
36
37
const hideMenu = () => {
38
setIsVisible(false);
39
};
40
41
return (
42
<ContextMenuContext.Provider value={{ showMenu, hideMenu, menuEntries, menuPosition, isVisible }}>
43
{children}
44
</ContextMenuContext.Provider>
45
);
46
};
47
48
export const useContextMenu = () => {
49
const context = useContext(ContextMenuContext);
50
if (!context) {
51
throw new Error('useContextMenu must be used within a ContextMenuProvider');
52
}
53
return context;
54
};
55
56
const useMenuListContainerStyles = makeStyles({
57
container: {
58
backgroundColor: tokens.colorNeutralBackground1,
59
minWidth: '128px',
60
minHeight: '48px',
61
maxWidth: '300px',
62
width: 'max-content',
63
boxShadow: `${tokens.shadow16}`,
64
paddingTop: '4px',
65
paddingBottom: '4px',
66
},
67
});
68
69
export const ContextMenu: React.FC = () => {
70
const { menuEntries, menuPosition, isVisible, hideMenu } = useContextMenu();
71
72
const handleEntryClick = (e: React.MouseEvent, entry: MenuEntry) => {
73
hideMenu();
74
entry.onClick();
75
};
76
77
const menuListRef = useRef<HTMLDivElement>(null);
78
79
useEffect(() => {
80
if (!menuListRef.current) {
81
return;
82
}
83
const handleClickOutside = (e: globalThis.MouseEvent) => {
84
if (menuListRef.current && e.target instanceof Node && !menuListRef.current.contains(e.target)) {
85
hideMenu();
86
}
87
};
88
89
document.addEventListener('click', handleClickOutside);
90
91
return () => {
92
document.removeEventListener('click', handleClickOutside);
93
};
94
});
95
96
const styles = useMenuListContainerStyles();
97
98
if (!isVisible) {
99
return null;
100
}
101
102
return (
103
<div
104
className={styles.container}
105
style={{
106
position: 'absolute',
107
top: menuPosition.y,
108
left: menuPosition.x,
109
// border: '1px solid gray',
110
zIndex: 1000,
111
}}
112
>
113
<MenuList
114
ref={menuListRef}
115
onClick={hideMenu}
116
>
117
{menuEntries.map((entry, index) => (
118
<MenuItem
119
key={index}
120
onClick={(e) => handleEntryClick(e, entry)}
121
>
122
{entry.label}
123
</MenuItem>
124
))}
125
</MenuList>
126
</div>
127
);
128
};
129
130