(null)
+
+ // Handle autofocus
+ useEffect(() => {
+ if (props.autofocus && props.isOpen && dropdownMenuRef.current) {
+ const firstButton = dropdownMenuRef.current.querySelector('button')
+ if (firstButton) {
+ firstButton.focus()
+ }
+ }
+ }, [props.autofocus, props.isOpen])
+
+ const wrap = (name: string, cliOptions?: unknown) => () => {
+ if (name === 'onCliTutorMode' && cliOptions && props.doSetCliOptions) {
+ props.doSetCliOptions(cliOptions)
+ }
+ const handler = props[name as keyof ContextMenuProps]
+ if (typeof handler === 'function') {
+ handler()
+ }
+ props.handleClick()
+ }
+
+ const {
+ t, onRename, onRemove, onDownload, onInspect, onShare, onDownloadCar, onPublish,
+ translateX, translateY, className, isMfs, isUnknown, isCliTutorModeEnabled
+ } = props
+
+ return (
+
+
+
+ {onShare && (
+
+ )}
+
+
+
+ {onInspect && (
+
+ )}
+
+ {!isUnknown && onDownload && (
+
+ )}
+ {!isUnknown && onDownloadCar && (
+
+ )}
+ {!isUnknown && isMfs && onRename && (
+
+ )}
+ {!isUnknown && isMfs && onRemove && (
+
+ )}
+ {onPublish && (
+
+ )}
+
+
+
+ )
+})
+
+export default withTranslation('files', { withRef: true })(ContextMenu)
diff --git a/src/files/dropdown/Dropdown.js b/src/files/dropdown/Dropdown.js
deleted file mode 100644
index 6ebeb6aa9..000000000
--- a/src/files/dropdown/Dropdown.js
+++ /dev/null
@@ -1,41 +0,0 @@
-import React, { forwardRef } from 'react'
-import { Dropdown as Drop, DropdownMenu as Menu } from '@tableflip/react-dropdown'
-import StrokeCode from '../../icons/StrokeCode.js'
-
-export const Option = ({ children, onClick, className = '', isCliTutorModeEnabled, onCliTutorMode, ...props }) => (
- isCliTutorModeEnabled
- ?
-
-
-
- :
-)
-
-export const DropdownMenu = forwardRef((props, ref) => {
- const { children, arrowMarginRight, width = 200, translateX = 0, translateY = 0, ...rest } = props
-
- return (
-
- )
-})
-
-export const Dropdown = Drop
diff --git a/src/files/dropdown/dropdown.tsx b/src/files/dropdown/dropdown.tsx
new file mode 100644
index 000000000..c1ff5434c
--- /dev/null
+++ b/src/files/dropdown/dropdown.tsx
@@ -0,0 +1,103 @@
+import React, { forwardRef, useEffect } from 'react'
+import { Dropdown as Drop, DropdownMenu as Menu } from '@tableflip/react-dropdown'
+import StrokeCode from '../../icons/StrokeCode.js'
+
+interface OptionProps {
+ children: React.ReactNode
+ onClick?: () => void
+ className?: string
+ isCliTutorModeEnabled?: boolean
+ onCliTutorMode?: (value: boolean) => void
+}
+
+interface DropdownMenuProps {
+ children: React.ReactNode
+ arrowMarginRight?: string
+ width?: number
+ translateX?: number
+ translateY?: number
+ top?: number
+ left?: string
+ open?: boolean
+ onDismiss?: () => void
+}
+
+interface MenuProps {
+ boxShadow: string
+ open: boolean
+ className: string
+ background: string
+ width: number
+ left: string
+ onDismiss: () => void
+ translateX?: number
+ translateY?: number
+ top?: number
+ arrowHeight?: number
+ arrowMarginLeft: string
+ arrowMarginRight: string
+ arrowAlign: 'left' | 'right'
+ alignRight: boolean
+ children: React.ReactNode
+}
+
+export const Option: React.FC = ({ children, onClick, className = '', isCliTutorModeEnabled, onCliTutorMode, ...props }) => {
+ return isCliTutorModeEnabled
+ ?
+
+
+
+ :
+}
+
+export const DropdownMenu = forwardRef((props, ref) => {
+ const { children, arrowMarginRight, width = 200, translateX = 0, translateY = 0, ...rest } = props
+
+ const disableContextMenu = (e: MouseEvent) => {
+ e.preventDefault()
+ }
+
+ const keyDownHandler = (e: KeyboardEvent) => {
+ if (e.key === 'Escape' && props.open) {
+ props?.onDismiss?.()
+ }
+ }
+
+ useEffect(() => {
+ document.addEventListener('keydown', keyDownHandler)
+ document.addEventListener('contextmenu', disableContextMenu)
+ return () => {
+ document.removeEventListener('contextmenu', disableContextMenu)
+ document.removeEventListener('keydown', keyDownHandler)
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [props.open])
+
+ return (
+
+ )
+})
+
+export const Dropdown = Drop
diff --git a/src/files/file-input/FileInput.js b/src/files/file-input/FileInput.js
index 5f654740a..e6f97a4d9 100644
--- a/src/files/file-input/FileInput.js
+++ b/src/files/file-input/FileInput.js
@@ -10,7 +10,7 @@ import NewFolderIcon from '../../icons/StrokeNewFolder.js'
import DecentralizationIcon from '../../icons/StrokeDecentralization.js'
import DataIcon from '../../icons/StrokeData.js'
// Components
-import { Dropdown, DropdownMenu, Option } from '../dropdown/Dropdown.js'
+import { Dropdown, DropdownMenu, Option } from '../dropdown/dropdown.tsx'
import Button from '../../components/button/button.tsx'
import { cliCmdKeys } from '../../bundles/files/consts.js'
diff --git a/src/files/files-grid/files-grid.tsx b/src/files/files-grid/files-grid.tsx
index f276bcd2f..a8c918c58 100644
--- a/src/files/files-grid/files-grid.tsx
+++ b/src/files/files-grid/files-grid.tsx
@@ -3,7 +3,7 @@ import { Trans, withTranslation } from 'react-i18next'
import { useDrop } from 'react-dnd'
import { NativeTypes } from 'react-dnd-html5-backend'
import { ExtendedFile, FileStream, normalizeFiles } from '../../lib/files.js'
-import GridFile from './grid-file.jsx'
+import GridFile from './grid-file'
// @ts-expect-error - redux-bundler-react is not typed
import { connect } from 'redux-bundler-react'
import './files-grid.css'
diff --git a/src/files/files-grid/grid-file.tsx b/src/files/files-grid/grid-file.tsx
index 3a1f96c03..4f294b448 100644
--- a/src/files/files-grid/grid-file.tsx
+++ b/src/files/files-grid/grid-file.tsx
@@ -4,13 +4,13 @@ import { useDrag, useDrop, type DropTargetMonitor } from 'react-dnd'
import { FileStream, humanSize, normalizeFiles } from '../../lib/files.js'
import { CID } from 'multiformats/cid'
import { isBinary } from 'istextorbinary'
-import FileIcon from '../file-icon/FileIcon.js'
+import FileIcon from '../file-icon/FileIcon'
// @ts-expect-error - redux-bundler-react is not typed
import { connect } from 'redux-bundler-react'
-import FileThumbnail from '../file-preview/file-thumbnail.js'
-import PinIcon from '../pin-icon/PinIcon.js'
-import GlyphDots from '../../icons/GlyphDots.js'
-import Checkbox from '../../components/checkbox/Checkbox.js'
+import FileThumbnail from '../file-preview/file-thumbnail'
+import PinIcon from '../pin-icon/PinIcon'
+import GlyphDots from '../../icons/GlyphDots'
+import Checkbox from '../../components/checkbox/Checkbox'
import { NativeTypes } from 'react-dnd-html5-backend'
import { join, basename } from 'path'
import './grid-file.css'
diff --git a/tsconfig.json b/tsconfig.json
index ac9429f20..76fe1b4a8 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -80,6 +80,7 @@
"src/env.js",
"src/lib/hofs/**/*.js",
"src/lib/guards.js",
+ "src/files/context-menu/ContextMenu.stories.tsx",
"src/files/file-preview/file-thumbnail.tsx",
"src/files/type-from-ext/index.js",
"src/files/files-grid/files-grid.tsx",