@@ -85,6 +92,43 @@ export function Device(props: DeviceProps) {
key={key}
selected={selectedFile == location.name}
onClick={() => handleSelect(location.name)}
+ onContextMenu={(e) => {
+ e.preventDefault();
+ e.stopPropagation();
+
+ menu.showMenu(
+
,
+ { x: e.clientX, y: e.clientY },
+ e.target as HTMLElement
+ );
+ }}
fileName={location.name}
folder
/>
diff --git a/packages/interface/src/components/file/FileItem.tsx b/packages/interface/src/components/file/FileItem.tsx
index 73851165b..cb4003832 100644
--- a/packages/interface/src/components/file/FileItem.tsx
+++ b/packages/interface/src/components/file/FileItem.tsx
@@ -1,5 +1,5 @@
import clsx from 'clsx';
-import React from 'react';
+import React, { MouseEventHandler } from 'react';
import icons from '../../assets/icons';
import { ReactComponent as Folder } from '../../assets/svg/folder.svg';
@@ -11,7 +11,8 @@ interface Props extends DefaultProps {
format?: string;
folder?: boolean;
selected?: boolean;
- onClick?: () => void;
+ onClick?: MouseEventHandler
;
+ onContextMenu?: MouseEventHandler;
}
export default function FileItem(props: Props) {
@@ -26,7 +27,12 @@ export default function FileItem(props: Props) {
// );
// };
return (
-
+
;
+type Position = {
+ x: number;
+ y: number;
+};
+
+export interface MenuContextData {
+ currentMenu?: {
+ clickPosition: Position;
+ clickedElement: HTMLElement;
+ menuElement: MenuElement;
+ };
+}
+
+export interface MenuContextActions {
+ showMenu: (menu: MenuElement, clickPosition: Position, clickedElement: HTMLElement) => void;
+ dismiss: () => void;
+}
+
+export const MenuContext = React.createContext
({
+ showMenu() {},
+ dismiss() {}
+});
+
+export const useMenu = () => React.useContext(MenuContext);
+
+export const MenuOverlay: React.FC<{ children: React.ReactNode }> = (props) => {
+ const { children } = props;
+
+ const [menuState, setMenuState] = React.useState({});
+
+ const overlay = React.useRef(null);
+
+ const showMenu: MenuContextActions['showMenu'] = React.useCallback(
+ (menu, clickPosition, clickedElement) => {
+ setMenuState({
+ currentMenu: {
+ menuElement: menu,
+ clickPosition,
+ clickedElement
+ }
+ });
+ },
+ [setMenuState]
+ );
+
+ const dismiss: MenuContextActions['dismiss'] = React.useCallback(() => {
+ setMenuState({});
+ }, [setMenuState]);
+
+ useLayoutEffect(() => {
+ if (menuState.currentMenu) overlay.current?.focus();
+ else overlay.current?.blur();
+ }, [menuState]);
+
+ return (
+
+ {children}
+ {
+ if (e.key === 'Escape') {
+ e.stopPropagation();
+
+ setMenuState({});
+ }
+ }}
+ onClick={() => {
+ setMenuState({});
+ }}
+ onContextMenu={(e) => {
+ e.preventDefault();
+ }}
+ >
+ {menuState.currentMenu && React.isValidElement(menuState.currentMenu?.menuElement) && (
+
+ {React.cloneElement(menuState.currentMenu!.menuElement, {
+ className: 'absolute',
+ style: {
+ left: menuState.currentMenu?.clickPosition.x + 3,
+ top: menuState.currentMenu?.clickPosition.y + 3
+ }
+ })}
+
+ )}
+
+
+ );
+};
diff --git a/packages/ui/src/ContextMenu.tsx b/packages/ui/src/ContextMenu.tsx
index bf24c71dc..b3b78be36 100644
--- a/packages/ui/src/ContextMenu.tsx
+++ b/packages/ui/src/ContextMenu.tsx
@@ -15,15 +15,20 @@ export interface ContextMenuProps {
heading?: string;
items: ContextMenuItem[];
}[];
+ className?: string;
}
export const ContextMenu: React.FC = (props) => {
- const { sections = [] } = props;
+ const { sections = [], className, ...rest } = props;
return (
{sections.map((sec, i) => (
<>
diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts
index 1992f2cee..8f7f97726 100644
--- a/packages/ui/src/index.ts
+++ b/packages/ui/src/index.ts
@@ -1,2 +1,3 @@
export * from './Button';
export * from './Dropdown';
+export * from './ContextMenu';