fixup! ✨(frontend) make components accessible to screen readers
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
// Centralized selector constants for doc-tree hooks
|
||||
|
||||
export const SELECTORS = {
|
||||
MODAL:
|
||||
'[role="dialog"], .c__modal, [data-modal], .c__modal__overlay, .ReactModal_Content',
|
||||
MODAL_SCROLLER: '.c__modal__scroller',
|
||||
ACTIONS_CONTAINER: '.--docs--doc-tree-item-actions',
|
||||
DOC_SUB_PAGE_ITEM: '.--docs-sub-page-item',
|
||||
ROLE_MENU: '[role="menu"]',
|
||||
ROLE_MENUITEM_OR_BUTTON:
|
||||
'[role="menuitem"], button, [tabindex]:not([tabindex="-1"])',
|
||||
FOCUSABLE:
|
||||
'button, [role="button"], a[href], input, [tabindex]:not([tabindex="-1"])',
|
||||
DATA_TESTID_DOC_SUB_PAGE_ITEM: 'doc-sub-page-item-',
|
||||
DATA_TESTID_DOC_SUB_PAGE_ITEM_PREFIX: '[data-testid^="doc-sub-page-item-"]',
|
||||
} as const;
|
||||
@@ -2,6 +2,8 @@ import { TreeDataItem } from '@gouvfr-lasuite/ui-kit';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import type { NodeRendererProps } from 'react-arborist';
|
||||
|
||||
import { SELECTORS } from '../dom-selectors';
|
||||
|
||||
type FocusableNode<T> = NodeRendererProps<TreeDataItem<T>>['node'] & {
|
||||
isFocused?: boolean;
|
||||
focus?: () => void;
|
||||
@@ -19,17 +21,13 @@ export const useActionableMode = <T>(
|
||||
const actionsRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const modalOpen = document.querySelector(
|
||||
'[role="dialog"], .c__modal, [data-modal], .c__modal__overlay, .ReactModal_Content',
|
||||
);
|
||||
const modalOpen = document.querySelector(SELECTORS.MODAL);
|
||||
if (!node?.isFocused || modalOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
const toActions = (e: KeyboardEvent) => {
|
||||
const modalOpen = document.querySelector(
|
||||
'[role="dialog"], .c__modal, [data-modal], .c__modal__overlay, .ReactModal_Content',
|
||||
);
|
||||
const modalOpen = document.querySelector(SELECTORS.MODAL);
|
||||
if (modalOpen) {
|
||||
return;
|
||||
}
|
||||
@@ -46,7 +44,7 @@ export const useActionableMode = <T>(
|
||||
e.preventDefault();
|
||||
|
||||
const focusables = actionsRef.current?.querySelectorAll<HTMLElement>(
|
||||
'button, [role="button"], a[href], input, [tabindex]:not([tabindex="-1"])',
|
||||
SELECTORS.FOCUSABLE,
|
||||
);
|
||||
|
||||
const first = focusables?.[0];
|
||||
@@ -67,9 +65,7 @@ export const useActionableMode = <T>(
|
||||
return;
|
||||
}
|
||||
|
||||
const modal = document.querySelector(
|
||||
'[role="dialog"], .c__modal, [data-modal], .c__modal__overlay, .ReactModal_Content',
|
||||
);
|
||||
const modal = document.querySelector(SELECTORS.MODAL);
|
||||
if (modal) {
|
||||
return;
|
||||
}
|
||||
@@ -84,7 +80,7 @@ export const useActionableMode = <T>(
|
||||
e.stopPropagation();
|
||||
|
||||
const focusables = actionsRef.current?.querySelectorAll<HTMLElement>(
|
||||
'button, [role="button"], a[href], input, [tabindex]:not([tabindex="-1"])',
|
||||
SELECTORS.FOCUSABLE,
|
||||
);
|
||||
|
||||
if (!focusables || focusables.length === 0) {
|
||||
|
||||
+15
-13
@@ -1,5 +1,7 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { SELECTORS } from '../dom-selectors';
|
||||
|
||||
interface UseDropdownFocusManagementProps {
|
||||
isOpen: boolean;
|
||||
docId: string;
|
||||
@@ -20,12 +22,12 @@ export const useDropdownFocusManagement = ({
|
||||
const timer = setTimeout(() => {
|
||||
// Try to find menu in actions container first
|
||||
const menuElement = actionsRef?.current
|
||||
?.closest('.--docs--doc-tree-item-actions')
|
||||
?.querySelector('[role="menu"]');
|
||||
?.closest(SELECTORS.ACTIONS_CONTAINER)
|
||||
?.querySelector(SELECTORS.ROLE_MENU);
|
||||
|
||||
if (menuElement) {
|
||||
const firstMenuItem = menuElement.querySelector<HTMLElement>(
|
||||
'[role="menuitem"], button, [tabindex]:not([tabindex="-1"])',
|
||||
SELECTORS.ROLE_MENUITEM_OR_BUTTON,
|
||||
);
|
||||
if (firstMenuItem) {
|
||||
firstMenuItem.focus();
|
||||
@@ -34,11 +36,11 @@ export const useDropdownFocusManagement = ({
|
||||
}
|
||||
|
||||
// Fallback: find any menu in document
|
||||
const allMenus = document.querySelectorAll('[role="menu"]');
|
||||
const allMenus = document.querySelectorAll(SELECTORS.ROLE_MENU);
|
||||
const lastMenu = allMenus[allMenus.length - 1];
|
||||
if (lastMenu) {
|
||||
const firstMenuItem = lastMenu.querySelector<HTMLElement>(
|
||||
'[role="menuitem"], button, [tabindex]:not([tabindex="-1"])',
|
||||
SELECTORS.ROLE_MENUITEM_OR_BUTTON,
|
||||
);
|
||||
if (firstMenuItem) {
|
||||
firstMenuItem.focus();
|
||||
@@ -56,30 +58,30 @@ export const useDropdownFocusManagement = ({
|
||||
}
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
const modal = document.querySelector(
|
||||
'[role="dialog"], .c__modal, [data-modal], .c__modal__overlay, .ReactModal_Content',
|
||||
);
|
||||
const modal = document.querySelector(SELECTORS.MODAL);
|
||||
if (modal) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only handle focus return if no modal is open
|
||||
let subPageItem = actionsRef?.current?.closest('.--docs-sub-page-item');
|
||||
let subPageItem = actionsRef?.current?.closest(
|
||||
SELECTORS.DOC_SUB_PAGE_ITEM,
|
||||
);
|
||||
|
||||
// If not found, try to find by data-testid
|
||||
if (!subPageItem) {
|
||||
const testIdElement = document.querySelector(
|
||||
`[data-testid="doc-sub-page-item-${docId}"]`,
|
||||
`[data-testid="${SELECTORS.DATA_TESTID_DOC_SUB_PAGE_ITEM}${docId}"]`,
|
||||
);
|
||||
subPageItem =
|
||||
testIdElement?.closest('.--docs-sub-page-item') ||
|
||||
testIdElement?.parentElement?.closest('.--docs-sub-page-item');
|
||||
testIdElement?.closest(SELECTORS.DOC_SUB_PAGE_ITEM) ||
|
||||
testIdElement?.parentElement?.closest(SELECTORS.DOC_SUB_PAGE_ITEM);
|
||||
}
|
||||
|
||||
// Focus the sub-document if found
|
||||
if (subPageItem) {
|
||||
const focusableElement = subPageItem.querySelector<HTMLElement>(
|
||||
'[data-testid^="doc-sub-page-item-"]',
|
||||
SELECTORS.DATA_TESTID_DOC_SUB_PAGE_ITEM_PREFIX,
|
||||
);
|
||||
|
||||
if (focusableElement) {
|
||||
|
||||
+3
-1
@@ -1,5 +1,7 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { SELECTORS } from '../dom-selectors';
|
||||
|
||||
export const useKeyboardActivation = (
|
||||
keys: string[],
|
||||
enabled: boolean,
|
||||
@@ -12,7 +14,7 @@ export const useKeyboardActivation = (
|
||||
}
|
||||
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
const modal = document.querySelector('.c__modal__scroller');
|
||||
const modal = document.querySelector(SELECTORS.MODAL_SCROLLER);
|
||||
|
||||
if (modal) {
|
||||
e.stopPropagation();
|
||||
|
||||
Reference in New Issue
Block a user