Files
Verso/services/web/frontend/js/features/file-tree/components/file-tree-context-menu.tsx
T
Rebeka DekanyandCopybot d751b88e6b Bootstrap files and folders cleanup (#27692)
* Remove icons folder

* Create folders for badge, button, and dropdown components

* Remove Bootstrap 5 from test

* Rename `getBootstrap5Breakpoint` to `getBootstrapBreakpoint`

* Cleanup and update BS 5 comments

* Move components to the shared folder

* Rename `tooltips-bs5` to `tooltip`

* Remove `-bs5` suffix

* Fix path

* Delete BS3 version file

* Rename `_form_marketing-bootstrap-5` to `_form_marketing`

* Delete BS3 version file

* Rename `_contact_general_modal-marketing-bootstrap-5` to `_contact_general_modal-marketing`

* Delete BS3 version file

* Rename `_contact_modal-marketing-bootstrap-5` to `_contact_modal-marketing`

* Delete BS3 version file

* Rename `thin-footer-bootstrap-5` to `thin-footer`

* Delete BS3 version file

* Rename `language-picker-bootstrap-5` to `language-picker`

* Rename `fat-footer-react-bootstrap-5` to `fat-footer-react`

* Delete BS3 version file

* Rename `navbar-marketing-bootstrap-5` to `navbar-marketing`

* Rename `navbar-marketing-react-bootstrap-5` to `navbar-marketing-react`

* Delete BS3 version file

* Rename `layout-website-redesign-cms-bootstrap-5` to `layout-website-redesign-cms`

* Source format

* Fix path

GitOrigin-RevId: cf0f5db7c84cf545c69213dcc271d9ff17fe5db7
2025-08-11 08:06:16 +00:00

105 lines
2.9 KiB
TypeScript

import React, { useCallback, useEffect, useRef } from 'react'
import ReactDOM from 'react-dom'
import {
Dropdown,
DropdownMenu,
} from '@/shared/components/dropdown/dropdown-menu'
import { useFileTreeData } from '@/shared/context/file-tree-data-context'
import { useFileTreeMainContext } from '../contexts/file-tree-main'
import FileTreeItemMenuItems from './file-tree-item/file-tree-item-menu-items'
function FileTreeContextMenu() {
const { fileTreeReadOnly } = useFileTreeData()
const { contextMenuCoords, setContextMenuCoords } = useFileTreeMainContext()
const toggleButtonRef = useRef<HTMLButtonElement | null>(null)
const keyboardInputRef = useRef(false)
useEffect(() => {
if (contextMenuCoords) {
toggleButtonRef.current = document.querySelector(
'.entity-menu-toggle'
) as HTMLButtonElement | null
}
}, [contextMenuCoords])
useEffect(() => {
if (contextMenuCoords && keyboardInputRef.current) {
const firstDropdownMenuItem = document.querySelector(
'#dropdown-file-tree-context-menu .dropdown-item:not([disabled])'
) as HTMLButtonElement | null
if (firstDropdownMenuItem) {
firstDropdownMenuItem.focus()
}
}
}, [contextMenuCoords])
function close() {
if (!contextMenuCoords) return
setContextMenuCoords(null)
if (toggleButtonRef.current) {
// A11y - Focus moves back to the trigger button when the context menu is dismissed
toggleButtonRef.current.focus()
}
}
function handleToggle(wantOpen: boolean) {
if (!wantOpen) close()
}
function handleClose(event: React.KeyboardEvent<Element>) {
if (event.key === 'Tab' || event.key === 'Escape') {
event.preventDefault()
close()
}
}
const handleKeyDown = useCallback(() => {
keyboardInputRef.current = true
}, [])
const handleMouseDown = useCallback(() => {
keyboardInputRef.current = false
}, [])
useEffect(() => {
document.addEventListener('keydown', handleKeyDown)
document.addEventListener('mousedown', handleMouseDown)
return () => {
document.removeEventListener('keydown', handleKeyDown)
document.removeEventListener('mousedown', handleMouseDown)
}
}, [handleKeyDown, handleMouseDown])
if (!contextMenuCoords || fileTreeReadOnly) return null
return ReactDOM.createPortal(
<div style={contextMenuCoords} className="context-menu">
<Dropdown
show
drop={
document.body.offsetHeight / contextMenuCoords.top < 2 &&
document.body.offsetHeight - contextMenuCoords.top < 250
? 'up'
: 'down'
}
onKeyDown={handleClose}
onToggle={handleToggle}
>
<DropdownMenu
className="dropdown-menu-sm-width"
id="dropdown-file-tree-context-menu"
>
<FileTreeItemMenuItems />
</DropdownMenu>
</Dropdown>
</div>,
document.body
)
}
export default FileTreeContextMenu