2024-08-19 05:15:08 -04:00
|
|
|
import React, { useEffect, useRef } from 'react'
|
2020-11-26 09:22:30 -05:00
|
|
|
import ReactDOM from 'react-dom'
|
2024-09-25 09:46:02 -04:00
|
|
|
import { Dropdown as BS3Dropdown } from 'react-bootstrap'
|
|
|
|
import {
|
|
|
|
Dropdown,
|
|
|
|
DropdownMenu,
|
|
|
|
} from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
2024-08-22 11:00:07 -04:00
|
|
|
import { useFileTreeData } from '@/shared/context/file-tree-data-context'
|
2021-03-18 05:52:36 -04:00
|
|
|
import { useFileTreeMainContext } from '../contexts/file-tree-main'
|
2020-11-26 09:22:30 -05:00
|
|
|
|
|
|
|
import FileTreeItemMenuItems from './file-tree-item/file-tree-item-menu-items'
|
2024-09-25 09:46:02 -04:00
|
|
|
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
2020-11-26 09:22:30 -05:00
|
|
|
|
|
|
|
function FileTreeContextMenu() {
|
2024-08-22 11:00:07 -04:00
|
|
|
const { fileTreeReadOnly } = useFileTreeData()
|
2022-01-10 10:46:46 -05:00
|
|
|
const { contextMenuCoords, setContextMenuCoords } = useFileTreeMainContext()
|
2024-08-19 05:15:08 -04:00
|
|
|
const toggleButtonRef = useRef<HTMLButtonElement | null>(null)
|
2020-11-26 09:22:30 -05:00
|
|
|
|
2024-08-19 05:15:08 -04:00
|
|
|
useEffect(() => {
|
|
|
|
if (contextMenuCoords) {
|
|
|
|
toggleButtonRef.current = document.querySelector(
|
|
|
|
'.entity-menu-toggle'
|
|
|
|
) as HTMLButtonElement | null
|
|
|
|
focusContextMenu()
|
|
|
|
}
|
|
|
|
}, [contextMenuCoords])
|
|
|
|
|
2024-08-22 11:00:07 -04:00
|
|
|
if (!contextMenuCoords || fileTreeReadOnly) return null
|
2024-08-19 05:15:08 -04:00
|
|
|
|
|
|
|
// A11y - Move the focus to the context menu when it opens
|
|
|
|
function focusContextMenu() {
|
2024-09-25 09:46:02 -04:00
|
|
|
const BS3contextMenu = document.querySelector(
|
2024-08-19 05:15:08 -04:00
|
|
|
'[aria-labelledby="dropdown-file-tree-context-menu"]'
|
|
|
|
) as HTMLElement | null
|
2024-09-25 09:46:02 -04:00
|
|
|
BS3contextMenu?.focus()
|
2024-08-19 05:15:08 -04:00
|
|
|
}
|
2020-11-26 09:22:30 -05:00
|
|
|
|
|
|
|
function close() {
|
|
|
|
setContextMenuCoords(null)
|
2024-08-19 05:15:08 -04:00
|
|
|
if (toggleButtonRef.current) {
|
|
|
|
// A11y - Move the focus back to the toggle button when the context menu closes by pressing the Esc key
|
|
|
|
toggleButtonRef.current.focus()
|
|
|
|
}
|
2020-11-26 09:22:30 -05:00
|
|
|
}
|
|
|
|
|
2024-01-26 04:23:48 -05:00
|
|
|
function handleToggle(wantOpen: boolean) {
|
2020-11-26 09:22:30 -05:00
|
|
|
if (!wantOpen) close()
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleClick() {
|
|
|
|
handleToggle(false)
|
|
|
|
}
|
|
|
|
|
2024-08-19 05:15:08 -04:00
|
|
|
// A11y - Close the context menu when the user presses the Tab key
|
|
|
|
// Focus should move to the next element in the filetree
|
2024-09-25 09:46:02 -04:00
|
|
|
function handleKeyDown(event: React.KeyboardEvent<BS3Dropdown | Element>) {
|
2024-08-19 05:15:08 -04:00
|
|
|
if (event.key === 'Tab') {
|
|
|
|
close()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-26 09:22:30 -05:00
|
|
|
return ReactDOM.createPortal(
|
2024-09-25 09:46:02 -04:00
|
|
|
<BootstrapVersionSwitcher
|
|
|
|
bs3={
|
|
|
|
<BS3Dropdown
|
|
|
|
onClick={handleClick}
|
|
|
|
open
|
|
|
|
id="dropdown-file-tree-context-menu"
|
|
|
|
onToggle={handleToggle}
|
|
|
|
dropup={
|
|
|
|
document.body.offsetHeight / contextMenuCoords.top < 2 &&
|
|
|
|
document.body.offsetHeight - contextMenuCoords.top < 250
|
|
|
|
}
|
|
|
|
className="context-menu"
|
|
|
|
style={contextMenuCoords}
|
|
|
|
onKeyDown={handleKeyDown}
|
|
|
|
>
|
|
|
|
<FakeDropDownToggle bsRole="toggle" />
|
|
|
|
<BS3Dropdown.Menu tabIndex={-1}>
|
|
|
|
<FileTreeItemMenuItems />
|
|
|
|
</BS3Dropdown.Menu>
|
|
|
|
</BS3Dropdown>
|
2024-07-23 07:01:25 -04:00
|
|
|
}
|
2024-09-25 09:46:02 -04:00
|
|
|
bs5={
|
|
|
|
<div style={contextMenuCoords} className="context-menu">
|
|
|
|
<Dropdown
|
|
|
|
show
|
|
|
|
drop={
|
|
|
|
document.body.offsetHeight / contextMenuCoords.top < 2 &&
|
|
|
|
document.body.offsetHeight - contextMenuCoords.top < 250
|
|
|
|
? 'up'
|
|
|
|
: 'down'
|
|
|
|
}
|
|
|
|
focusFirstItemOnShow // A11y - Focus the first item in the context menu when it opens since the menu is rendered at the root level
|
|
|
|
onKeyDown={handleKeyDown}
|
|
|
|
onToggle={handleToggle}
|
|
|
|
>
|
|
|
|
<DropdownMenu
|
|
|
|
className="dropdown-menu-sm-width"
|
|
|
|
id="dropdown-file-tree-context-menu"
|
|
|
|
>
|
|
|
|
<FileTreeItemMenuItems />
|
|
|
|
</DropdownMenu>
|
|
|
|
</Dropdown>
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
/>,
|
2024-01-26 04:23:48 -05:00
|
|
|
document.body
|
2020-11-26 09:22:30 -05:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// fake component required as Dropdowns require a Toggle, even tho we don't want
|
|
|
|
// one for the context menu
|
2024-01-26 04:23:48 -05:00
|
|
|
const FakeDropDownToggle = React.forwardRef<undefined, { bsRole: string }>(
|
|
|
|
({ bsRole }, ref) => {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
)
|
2020-11-26 09:22:30 -05:00
|
|
|
|
2021-05-13 06:21:35 -04:00
|
|
|
FakeDropDownToggle.displayName = 'FakeDropDownToggle'
|
|
|
|
|
2020-11-26 09:22:30 -05:00
|
|
|
export default FileTreeContextMenu
|