Path: blob/main/extensions/copilot/test/simulation/workbench/components/contextMenu.tsx
13399 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { makeStyles, MenuItem, MenuList, tokens } from '@fluentui/react-components';6import React, { createContext, MouseEvent, ReactNode, useContext, useEffect, useRef, useState } from 'react';78type MenuEntry = {9label: string;10onClick: () => void;11};1213type ContextMenuContextProps = {14showMenu: (event: MouseEvent, entries: MenuEntry[]) => void;15hideMenu: () => void;16menuEntries: MenuEntry[];17menuPosition: { x: number; y: number };18isVisible: boolean;19};2021const ContextMenuContext = createContext<ContextMenuContextProps | undefined>(undefined);2223export const ContextMenuProvider: React.FC<{ children: ReactNode }> = ({ children }) => {2425const [menuEntries, setMenuEntries] = useState<MenuEntry[]>([]);26const [menuPosition, setMenuPosition] = useState<{ x: number; y: number }>({ x: 0, y: 0 });27const [isVisible, setIsVisible] = useState(false);2829const showMenu = (event: MouseEvent, entries: MenuEntry[]) => {30event.preventDefault();31setMenuEntries(entries);32setMenuPosition({ x: event.pageX, y: event.pageY });33setIsVisible(true);34};3536const hideMenu = () => {37setIsVisible(false);38};3940return (41<ContextMenuContext.Provider value={{ showMenu, hideMenu, menuEntries, menuPosition, isVisible }}>42{children}43</ContextMenuContext.Provider>44);45};4647export const useContextMenu = () => {48const context = useContext(ContextMenuContext);49if (!context) {50throw new Error('useContextMenu must be used within a ContextMenuProvider');51}52return context;53};5455const useMenuListContainerStyles = makeStyles({56container: {57backgroundColor: tokens.colorNeutralBackground1,58minWidth: '128px',59minHeight: '48px',60maxWidth: '300px',61width: 'max-content',62boxShadow: `${tokens.shadow16}`,63paddingTop: '4px',64paddingBottom: '4px',65},66});6768export const ContextMenu: React.FC = () => {69const { menuEntries, menuPosition, isVisible, hideMenu } = useContextMenu();7071const handleEntryClick = (e: React.MouseEvent, entry: MenuEntry) => {72hideMenu();73entry.onClick();74};7576const menuListRef = useRef<HTMLDivElement>(null);7778useEffect(() => {79if (!menuListRef.current) {80return;81}82const handleClickOutside = (e: globalThis.MouseEvent) => {83if (menuListRef.current && e.target instanceof Node && !menuListRef.current.contains(e.target)) {84hideMenu();85}86};8788document.addEventListener('click', handleClickOutside);8990return () => {91document.removeEventListener('click', handleClickOutside);92};93});9495const styles = useMenuListContainerStyles();9697if (!isVisible) {98return null;99}100101return (102<div103className={styles.container}104style={{105position: 'absolute',106top: menuPosition.y,107left: menuPosition.x,108// border: '1px solid gray',109zIndex: 1000,110}}111>112<MenuList113ref={menuListRef}114onClick={hideMenu}115>116{menuEntries.map((entry, index) => (117<MenuItem118key={index}119onClick={(e) => handleEntryClick(e, entry)}120>121{entry.label}122</MenuItem>123))}124</MenuList>125</div>126);127};128129130