Skip to content

Commit 161ec74

Browse files
committed
fixup! ✨(frontend) make components accessible to screen readers
1 parent 6eb9036 commit 161ec74

File tree

4 files changed

+41
-25
lines changed

4 files changed

+41
-25
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Centralized selector constants for doc-tree hooks
2+
3+
export const SELECTORS = {
4+
MODAL:
5+
'[role="dialog"], .c__modal, [data-modal], .c__modal__overlay, .ReactModal_Content',
6+
MODAL_SCROLLER: '.c__modal__scroller',
7+
ACTIONS_CONTAINER: '.--docs--doc-tree-item-actions',
8+
DOC_SUB_PAGE_ITEM: '.--docs-sub-page-item',
9+
ROLE_MENU: '[role="menu"]',
10+
ROLE_MENUITEM_OR_BUTTON:
11+
'[role="menuitem"], button, [tabindex]:not([tabindex="-1"])',
12+
FOCUSABLE:
13+
'button, [role="button"], a[href], input, [tabindex]:not([tabindex="-1"])',
14+
DATA_TESTID_DOC_SUB_PAGE_ITEM: 'doc-sub-page-item-',
15+
DATA_TESTID_DOC_SUB_PAGE_ITEM_PREFIX: '[data-testid^="doc-sub-page-item-"]',
16+
} as const;

src/frontend/apps/impress/src/features/docs/doc-tree/hooks/useActionableMode.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { TreeDataItem } from '@gouvfr-lasuite/ui-kit';
22
import { useEffect, useRef } from 'react';
33
import type { NodeRendererProps } from 'react-arborist';
44

5+
import { SELECTORS } from '../css-selectors';
6+
57
type FocusableNode<T> = NodeRendererProps<TreeDataItem<T>>['node'] & {
68
isFocused?: boolean;
79
focus?: () => void;
@@ -19,17 +21,13 @@ export const useActionableMode = <T>(
1921
const actionsRef = useRef<HTMLDivElement>(null);
2022

2123
useEffect(() => {
22-
const modalOpen = document.querySelector(
23-
'[role="dialog"], .c__modal, [data-modal], .c__modal__overlay, .ReactModal_Content',
24-
);
24+
const modalOpen = document.querySelector(SELECTORS.MODAL);
2525
if (!node?.isFocused || modalOpen) {
2626
return;
2727
}
2828

2929
const toActions = (e: KeyboardEvent) => {
30-
const modalOpen = document.querySelector(
31-
'[role="dialog"], .c__modal, [data-modal], .c__modal__overlay, .ReactModal_Content',
32-
);
30+
const modalOpen = document.querySelector(SELECTORS.MODAL);
3331
if (modalOpen) {
3432
return;
3533
}
@@ -46,7 +44,7 @@ export const useActionableMode = <T>(
4644
e.preventDefault();
4745

4846
const focusables = actionsRef.current?.querySelectorAll<HTMLElement>(
49-
'button, [role="button"], a[href], input, [tabindex]:not([tabindex="-1"])',
47+
SELECTORS.FOCUSABLE,
5048
);
5149

5250
const first = focusables?.[0];
@@ -67,9 +65,7 @@ export const useActionableMode = <T>(
6765
return;
6866
}
6967

70-
const modal = document.querySelector(
71-
'[role="dialog"], .c__modal, [data-modal], .c__modal__overlay, .ReactModal_Content',
72-
);
68+
const modal = document.querySelector(SELECTORS.MODAL);
7369
if (modal) {
7470
return;
7571
}
@@ -84,7 +80,7 @@ export const useActionableMode = <T>(
8480
e.stopPropagation();
8581

8682
const focusables = actionsRef.current?.querySelectorAll<HTMLElement>(
87-
'button, [role="button"], a[href], input, [tabindex]:not([tabindex="-1"])',
83+
SELECTORS.FOCUSABLE,
8884
);
8985

9086
if (!focusables || focusables.length === 0) {

src/frontend/apps/impress/src/features/docs/doc-tree/hooks/useDropdownFocusManagement.ts

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { useEffect } from 'react';
22

3+
import { SELECTORS } from '../css-selectors';
4+
35
interface UseDropdownFocusManagementProps {
46
isOpen: boolean;
57
docId: string;
@@ -20,12 +22,12 @@ export const useDropdownFocusManagement = ({
2022
const timer = setTimeout(() => {
2123
// Try to find menu in actions container first
2224
const menuElement = actionsRef?.current
23-
?.closest('.--docs--doc-tree-item-actions')
24-
?.querySelector('[role="menu"]');
25+
?.closest(SELECTORS.ACTIONS_CONTAINER)
26+
?.querySelector(SELECTORS.ROLE_MENU);
2527

2628
if (menuElement) {
2729
const firstMenuItem = menuElement.querySelector<HTMLElement>(
28-
'[role="menuitem"], button, [tabindex]:not([tabindex="-1"])',
30+
SELECTORS.ROLE_MENUITEM_OR_BUTTON,
2931
);
3032
if (firstMenuItem) {
3133
firstMenuItem.focus();
@@ -34,11 +36,11 @@ export const useDropdownFocusManagement = ({
3436
}
3537

3638
// Fallback: find any menu in document
37-
const allMenus = document.querySelectorAll('[role="menu"]');
39+
const allMenus = document.querySelectorAll(SELECTORS.ROLE_MENU);
3840
const lastMenu = allMenus[allMenus.length - 1];
3941
if (lastMenu) {
4042
const firstMenuItem = lastMenu.querySelector<HTMLElement>(
41-
'[role="menuitem"], button, [tabindex]:not([tabindex="-1"])',
43+
SELECTORS.ROLE_MENUITEM_OR_BUTTON,
4244
);
4345
if (firstMenuItem) {
4446
firstMenuItem.focus();
@@ -56,30 +58,30 @@ export const useDropdownFocusManagement = ({
5658
}
5759

5860
const timer = setTimeout(() => {
59-
const modal = document.querySelector(
60-
'[role="dialog"], .c__modal, [data-modal], .c__modal__overlay, .ReactModal_Content',
61-
);
61+
const modal = document.querySelector(SELECTORS.MODAL);
6262
if (modal) {
6363
return;
6464
}
6565

6666
// Only handle focus return if no modal is open
67-
let subPageItem = actionsRef?.current?.closest('.--docs-sub-page-item');
67+
let subPageItem = actionsRef?.current?.closest(
68+
SELECTORS.DOC_SUB_PAGE_ITEM,
69+
);
6870

6971
// If not found, try to find by data-testid
7072
if (!subPageItem) {
7173
const testIdElement = document.querySelector(
72-
`[data-testid="doc-sub-page-item-${docId}"]`,
74+
`[data-testid="${SELECTORS.DATA_TESTID_DOC_SUB_PAGE_ITEM}${docId}"]`,
7375
);
7476
subPageItem =
75-
testIdElement?.closest('.--docs-sub-page-item') ||
76-
testIdElement?.parentElement?.closest('.--docs-sub-page-item');
77+
testIdElement?.closest(SELECTORS.DOC_SUB_PAGE_ITEM) ||
78+
testIdElement?.parentElement?.closest(SELECTORS.DOC_SUB_PAGE_ITEM);
7779
}
7880

7981
// Focus the sub-document if found
8082
if (subPageItem) {
8183
const focusableElement = subPageItem.querySelector<HTMLElement>(
82-
'[data-testid^="doc-sub-page-item-"]',
84+
SELECTORS.DATA_TESTID_DOC_SUB_PAGE_ITEM_PREFIX,
8385
);
8486

8587
if (focusableElement) {

src/frontend/apps/impress/src/features/docs/doc-tree/hooks/useKeyboardActivation.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { useEffect } from 'react';
22

3+
import { SELECTORS } from '../css-selectors';
4+
35
export const useKeyboardActivation = (
46
keys: string[],
57
enabled: boolean,
@@ -12,7 +14,7 @@ export const useKeyboardActivation = (
1214
}
1315

1416
const onKeyDown = (e: KeyboardEvent) => {
15-
const modal = document.querySelector('.c__modal__scroller');
17+
const modal = document.querySelector(SELECTORS.MODAL_SCROLLER);
1618

1719
if (modal) {
1820
e.stopPropagation();

0 commit comments

Comments
 (0)