Explorar o código

Merge pull request #19881 from overleaf/rd-filetree-context-menu

[web] Context menu should receive focus in the filetree on keyboard navigation

GitOrigin-RevId: aeec3713b32792bd5aef1756929a4fc893f30274
Rebeka Dekany %!s(int64=2) %!d(string=hai) anos
pai
achega
0e43a18bc6

+ 34 - 4
services/web/frontend/js/features/file-tree/components/file-tree-context-menu.tsx

@@ -1,4 +1,4 @@
-import React from 'react'
+import React, { useEffect, useRef } from 'react'
 import ReactDOM from 'react-dom'
 import ReactDOM from 'react-dom'
 import { Dropdown } from 'react-bootstrap'
 import { Dropdown } from 'react-bootstrap'
 import { useEditorContext } from '../../../shared/context/editor-context'
 import { useEditorContext } from '../../../shared/context/editor-context'
@@ -9,12 +9,33 @@ import FileTreeItemMenuItems from './file-tree-item/file-tree-item-menu-items'
 function FileTreeContextMenu() {
 function FileTreeContextMenu() {
   const { permissionsLevel } = useEditorContext()
   const { permissionsLevel } = useEditorContext()
   const { contextMenuCoords, setContextMenuCoords } = useFileTreeMainContext()
   const { contextMenuCoords, setContextMenuCoords } = useFileTreeMainContext()
+  const toggleButtonRef = useRef<HTMLButtonElement | null>(null)
 
 
-  if (permissionsLevel === 'readOnly' || !contextMenuCoords) return null
+  useEffect(() => {
+    if (contextMenuCoords) {
+      toggleButtonRef.current = document.querySelector(
+        '.entity-menu-toggle'
+      ) as HTMLButtonElement | null
+      focusContextMenu()
+    }
+  }, [contextMenuCoords])
+
+  if (!contextMenuCoords || permissionsLevel === 'readOnly') return null
+
+  // A11y - Move the focus to the context menu when it opens
+  function focusContextMenu() {
+    const contextMenu = document.querySelector(
+      '[aria-labelledby="dropdown-file-tree-context-menu"]'
+    ) as HTMLElement | null
+    contextMenu?.focus()
+  }
 
 
   function close() {
   function close() {
-    // reset context menu
     setContextMenuCoords(null)
     setContextMenuCoords(null)
+    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()
+    }
   }
   }
 
 
   function handleToggle(wantOpen: boolean) {
   function handleToggle(wantOpen: boolean) {
@@ -25,6 +46,14 @@ function FileTreeContextMenu() {
     handleToggle(false)
     handleToggle(false)
   }
   }
 
 
+  // A11y - Close the context menu when the user presses the Tab key
+  // Focus should move to the next element in the filetree
+  function handleKeyDown(event: React.KeyboardEvent<Dropdown>) {
+    if (event.key === 'Tab') {
+      close()
+    }
+  }
+
   return ReactDOM.createPortal(
   return ReactDOM.createPortal(
     <Dropdown
     <Dropdown
       onClick={handleClick}
       onClick={handleClick}
@@ -37,9 +66,10 @@ function FileTreeContextMenu() {
       }
       }
       className="context-menu"
       className="context-menu"
       style={contextMenuCoords}
       style={contextMenuCoords}
+      onKeyDown={handleKeyDown}
     >
     >
       <FakeDropDownToggle bsRole="toggle" />
       <FakeDropDownToggle bsRole="toggle" />
-      <Dropdown.Menu>
+      <Dropdown.Menu tabIndex={-1}>
         <FileTreeItemMenuItems />
         <FileTreeItemMenuItems />
       </Dropdown.Menu>
       </Dropdown.Menu>
     </Dropdown>,
     </Dropdown>,

+ 1 - 1
services/web/frontend/js/features/file-tree/components/file-tree-item/file-tree-item-menu-items.jsx

@@ -56,7 +56,7 @@ function FileTreeItemMenuItems() {
       ) : null}
       ) : null}
       {canCreate ? (
       {canCreate ? (
         <>
         <>
-          <MenuItem divider />
+          <li role="none" className="divider" />
           <MenuItem onClick={createWithAnalytics}>{t('new_file')}</MenuItem>
           <MenuItem onClick={createWithAnalytics}>{t('new_file')}</MenuItem>
           <MenuItem onClick={startCreatingFolder}>{t('new_folder')}</MenuItem>
           <MenuItem onClick={startCreatingFolder}>{t('new_folder')}</MenuItem>
           <MenuItem onClick={uploadWithAnalytics}>{t('upload')}</MenuItem>
           <MenuItem onClick={uploadWithAnalytics}>{t('upload')}</MenuItem>