Przeglądaj źródła

Merge pull request #13130 from overleaf/td-history-add-label-autofocus

History migration: Autofocus input in add label dialog

GitOrigin-RevId: b51187e735414cd4b027413dfe57a0ba332c7b1a
Tim Down 3 lat temu
rodzic
commit
80a5f4594e

+ 16 - 3
services/web/frontend/js/features/history/components/change-list/add-label-modal.tsx

@@ -1,5 +1,5 @@
 import { useTranslation } from 'react-i18next'
-import { useState } from 'react'
+import { useEffect, useState } from 'react'
 import { Modal, FormGroup, FormControl } from 'react-bootstrap'
 import ModalError from './modal-error'
 import AccessibleModal from '../../../../shared/components/accessible-modal'
@@ -9,6 +9,7 @@ import useAddOrRemoveLabels from '../../hooks/use-add-or-remove-labels'
 import { useHistoryContext } from '../../context/history-context'
 import { addLabel } from '../../services/api'
 import { Label } from '../../services/types/label'
+import { useRefWithAutoFocus } from '../../../../shared/hooks/use-ref-with-auto-focus'
 
 type AddLabelModalProps = {
   show: boolean
@@ -32,6 +33,17 @@ function AddLabelModal({ show, setShow, version }: AddLabelModalProps) {
   const { signal } = useAbortController()
   const { addUpdateLabel } = useAddOrRemoveLabels()
 
+  const { autoFocusedRef, resetAutoFocus } =
+    useRefWithAutoFocus<HTMLInputElement>()
+
+  // Reset the autofocus when `show` changes so that autofocus still happens if
+  // the dialog is shown, hidden and then shown again
+  useEffect(() => {
+    if (show) {
+      resetAutoFocus()
+    }
+  }, [resetAutoFocus, show])
+
   const handleModalExited = () => {
     setComment('')
 
@@ -71,7 +83,9 @@ function AddLabelModal({ show, setShow, version }: AddLabelModalProps) {
         <Modal.Body>
           {isError && <ModalError error={responseError} />}
           <FormGroup>
-            <FormControl
+            <input
+              ref={autoFocusedRef}
+              className="form-control"
               type="text"
               placeholder={t('history_new_label_name')}
               required
@@ -79,7 +93,6 @@ function AddLabelModal({ show, setShow, version }: AddLabelModalProps) {
               onChange={(
                 e: React.ChangeEvent<HTMLInputElement & FormControl>
               ) => setComment(e.target.value)}
-              autoFocus // eslint-disable-line jsx-a11y/no-autofocus
             />
           </FormGroup>
         </Modal.Body>

+ 23 - 4
services/web/frontend/js/shared/hooks/use-ref-with-auto-focus.ts

@@ -1,17 +1,36 @@
-import { useRef, useEffect } from 'react'
+import { useRef, useEffect, useCallback, useState } from 'react'
 
 export function useRefWithAutoFocus<T extends HTMLElement = HTMLElement>() {
   const autoFocusedRef = useRef<T>(null)
+  const [hasFocused, setHasFocused] = useState(false)
+  const resetAutoFocus = useCallback(() => setHasFocused(false), [])
 
+  // Run on every render but use hasFocused to ensure that the autofocus only
+  // happens once
   useEffect(() => {
+    if (hasFocused) {
+      return
+    }
+
+    let request: number | null = null
     if (autoFocusedRef.current) {
-      window.requestAnimationFrame(() => {
+      request = window.requestAnimationFrame(() => {
         if (autoFocusedRef.current) {
           autoFocusedRef.current.focus()
+          setHasFocused(true)
+          request = null
         }
       })
     }
-  }, [])
 
-  return { autoFocusedRef }
+    // Cancel a pending autofocus prior to autofocus actually happening on
+    // render, and on unmount
+    return () => {
+      if (request !== null) {
+        window.cancelAnimationFrame(request)
+      }
+    }
+  })
+
+  return { autoFocusedRef, resetAutoFocus }
 }