diff --git a/CHANGELOG.md b/CHANGELOG.md index a08acaa2d7..74f4057912 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to - 🐛(backend) fix trashbin list - ♿(frontend) improve accessibility: - ♿(frontend) remove empty alt on logo due to Axe a11y error #1516 + - ♿(frontend) add focus trap and enter key support to remove doc modal #1531 ## [3.8.2] - 2025-10-17 diff --git a/src/frontend/apps/impress/src/components/dropdown-menu/DropdownMenu.tsx b/src/frontend/apps/impress/src/components/dropdown-menu/DropdownMenu.tsx index f0af724dd7..c731080728 100644 --- a/src/frontend/apps/impress/src/components/dropdown-menu/DropdownMenu.tsx +++ b/src/frontend/apps/impress/src/components/dropdown-menu/DropdownMenu.tsx @@ -1,6 +1,7 @@ import { HorizontalSeparator } from '@gouvfr-lasuite/ui-kit'; import { Fragment, + KeyboardEvent, PropsWithChildren, ReactNode, useCallback, @@ -93,6 +94,18 @@ export const DropdownMenu = ({ } }, [isOpen, options]); + const handleKeyDown = useCallback( + (event: KeyboardEvent, option: DropdownMenuOption) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + event.stopPropagation(); + onOpenChange?.(false); + void option.callback?.(); + } + }, + [onOpenChange], + ); + if (disabled) { return children; } @@ -173,6 +186,7 @@ export const DropdownMenu = ({ onOpenChange?.(false); void option.callback?.(); }} + onKeyDown={(event) => handleKeyDown(event, option)} key={option.label} $align="center" $justify="space-between" diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/components/ModalRemoveDoc.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/components/ModalRemoveDoc.tsx index a31fb07c9a..ec7c6b6899 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/components/ModalRemoveDoc.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/components/ModalRemoveDoc.tsx @@ -6,17 +6,21 @@ import { useToastProvider, } from '@openfun/cunningham-react'; import { useRouter } from 'next/router'; +import { useEffect } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import { Box, ButtonCloseModal, Text, TextErrors } from '@/components'; import { useConfig } from '@/core'; import { KEY_LIST_DOC_TRASHBIN } from '@/docs/docs-grid'; +import { useKeyboardAction } from '@/hooks'; import { KEY_LIST_DOC } from '../api/useDocs'; import { useRemoveDoc } from '../api/useRemoveDoc'; import { useDocUtils } from '../hooks'; import { Doc } from '../types'; +const CANCEL_BUTTON_ID = 'modal-remove-doc-cancel-button'; + interface ModalRemoveDocProps { doc: Doc; onClose: () => void; @@ -57,20 +61,42 @@ export const ModalRemoveDoc = ({ }, }); + useEffect(() => { + const cancelButton = document.getElementById(CANCEL_BUTTON_ID); + if (cancelButton instanceof HTMLButtonElement) { + cancelButton.focus(); + } + }, []); + + const handleClose = () => { + onClose(); + }; + + const handleDelete = () => { + removeDoc({ + docId: doc.id, + }); + }; + + const handleCloseKeyDown = useKeyboardAction(handleClose); + const handleDeleteKeyDown = useKeyboardAction(handleDelete); + return ( onClose()} + onClose={handleClose} aria-describedby="modal-remove-doc-title" rightActions={ <> @@ -78,11 +104,8 @@ export const ModalRemoveDoc = ({ aria-label={t('Delete document')} color="danger" fullWidth - onClick={() => - removeDoc({ - docId: doc.id, - }) - } + onClick={handleDelete} + onKeyDown={handleDeleteKeyDown} > {t('Delete')} @@ -108,7 +131,8 @@ export const ModalRemoveDoc = ({ onClose()} + onClick={handleClose} + onKeyDown={handleCloseKeyDown} /> } diff --git a/src/frontend/apps/impress/src/hooks/index.ts b/src/frontend/apps/impress/src/hooks/index.ts new file mode 100644 index 0000000000..90c70edb73 --- /dev/null +++ b/src/frontend/apps/impress/src/hooks/index.ts @@ -0,0 +1 @@ +export * from './useKeyboardAction'; diff --git a/src/frontend/apps/impress/src/hooks/useKeyboardAction.ts b/src/frontend/apps/impress/src/hooks/useKeyboardAction.ts new file mode 100644 index 0000000000..c0aba68f51 --- /dev/null +++ b/src/frontend/apps/impress/src/hooks/useKeyboardAction.ts @@ -0,0 +1,22 @@ +import { KeyboardEvent, useCallback } from 'react'; + +/** + * Hook to handle keyboard actions (Enter and Space) on interactive elements. + * Prevents default behavior and stops propagation to avoid conflicts. + * + * @param callback - The function to execute when Enter or Space is pressed + * @returns A keyboard event handler function + + */ +export const useKeyboardAction = (callback: () => void) => { + return useCallback( + (event: KeyboardEvent) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + event.stopPropagation(); + callback(); + } + }, + [callback], + ); +};