Skip to content

Commit 5f35f48

Browse files
committed
adds f2 shortcut using a fakenode since it's outside the treeview structure
Signed-off-by: Cyril <[email protected]>
1 parent b7b9208 commit 5f35f48

File tree

4 files changed

+50
-5
lines changed

4 files changed

+50
-5
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ and this project adheres to
66

77
## [Unreleased]
88

9+
### Fixed
10+
11+
- ♿(frontend) improve accessibility:
12+
- ♿️Improve keyboard accessibility for the document tree #1681
13+
914
## [4.0.0] - 2025-12-01
1015

1116
### Added

src/frontend/apps/impress/src/features/docs/doc-tree/components/DocSubPageItem.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,6 @@ export const DocSubPageItem = (props: TreeViewNodeProps<Doc>) => {
204204
$css={css`
205205
text-align: left;
206206
min-width: 0;
207-
order: 1;
208207
`}
209208
>
210209
<Box

src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTree.tsx

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
useTreeContext,
77
} from '@gouvfr-lasuite/ui-kit';
88
import { useRouter } from 'next/navigation';
9-
import { useCallback, useEffect, useState } from 'react';
9+
import { useCallback, useEffect, useRef, useState } from 'react';
1010
import { useTranslation } from 'react-i18next';
1111
import { css } from 'styled-components';
1212

@@ -16,6 +16,8 @@ import { Doc, SimpleDocItem } from '@/docs/doc-management';
1616

1717
import { KEY_DOC_TREE, useDocTree } from '../api/useDocTree';
1818
import { useMoveDoc } from '../api/useMove';
19+
import { useActionableMode } from '../hooks/useActionableMode';
20+
import type { ActionableNodeLike } from '../hooks/useActionableMode';
1921
import { findIndexInTree } from '../utils';
2022

2123
import { DocSubPageItem } from './DocSubPageItem';
@@ -32,12 +34,29 @@ export const DocTree = ({ currentDoc }: DocTreeProps) => {
3234
const treeContext = useTreeContext<Doc | null>();
3335
const router = useRouter();
3436
const [rootActionsOpen, setRootActionsOpen] = useState(false);
37+
const [rootItemFocused, setRootItemFocused] = useState(false);
3538
const rootIsSelected =
3639
!!treeContext?.root?.id &&
3740
treeContext?.treeData.selectedNode?.id === treeContext.root.id;
41+
const rootItemRef = useRef<HTMLDivElement>(null);
3842

3943
const { t } = useTranslation();
4044

45+
/**
46+
* Fake node to reuse useActionableMode hook for the root item.
47+
* This allows F2 keyboard navigation and Escape handling on the root item
48+
* without duplicating the logic from DocSubPageItem.
49+
*/
50+
const fakeRootNode: ActionableNodeLike = {
51+
isFocused: rootItemFocused,
52+
focus: () => {
53+
rootItemRef.current?.focus();
54+
},
55+
};
56+
57+
const { actionsRef: rootActionsRef, onKeyDownCapture: onRootToolbarKeys } =
58+
useActionableMode(fakeRootNode, rootActionsOpen);
59+
4160
const [initialOpenState, setInitialOpenState] = useState<OpenMap | undefined>(
4261
undefined,
4362
);
@@ -85,12 +104,23 @@ export const DocTree = ({ currentDoc }: DocTreeProps) => {
85104
}, [router, treeContext?.root?.id]);
86105

87106
const handleRootFocus = useCallback(() => {
107+
setRootItemFocused(true);
88108
selectRoot();
89109
}, [selectRoot]);
90110

91-
// activate root document with enter or space
111+
const handleRootBlur = useCallback(() => {
112+
setRootItemFocused(false);
113+
}, []);
114+
115+
// activate root document with enter or space (only when not in actions)
92116
const handleRootKeyDown = useCallback(
93117
(e: React.KeyboardEvent) => {
118+
// Ignore if focus is in actions
119+
const target = e.target as HTMLElement | null;
120+
if (target?.closest('.doc-tree-root-item-actions')) {
121+
return;
122+
}
123+
94124
if (e.key === 'Enter' || e.key === ' ') {
95125
e.preventDefault();
96126
selectRoot();
@@ -206,12 +236,14 @@ export const DocTree = ({ currentDoc }: DocTreeProps) => {
206236
`}
207237
>
208238
<Box
239+
ref={rootItemRef}
209240
data-testid="doc-tree-root-item"
210241
role="treeitem"
211242
aria-label={`${t('Root document {{title}}', { title: treeContext.root?.title || t('Untitled document') })}`}
212243
aria-selected={rootIsSelected}
213244
tabIndex={0}
214245
onFocus={handleRootFocus}
246+
onBlur={handleRootBlur}
215247
onKeyDown={handleRootKeyDown}
216248
$css={css`
217249
padding: ${spacingsTokens['2xs']};
@@ -234,20 +266,25 @@ export const DocTree = ({ currentDoc }: DocTreeProps) => {
234266
}
235267
236268
.doc-tree-root-item-actions {
237-
display: ${rootActionsOpen ? 'flex' : 'none'};
269+
display: flex;
238270
opacity: ${rootActionsOpen ? '1' : '0'};
239271
240272
&:has(.isOpen) {
241273
opacity: 1;
242274
}
243275
}
244276
&:hover,
245-
&:focus-visible {
277+
&:focus-visible,
278+
&:focus-within {
246279
.doc-tree-root-item-actions {
247280
display: flex;
248281
opacity: 1;
249282
}
250283
}
284+
/* Retirer le focus visuel du root item quand le focus est sur les actions */
285+
&:has(.doc-tree-root-item-actions *:focus) {
286+
box-shadow: none !important;
287+
}
251288
`}
252289
>
253290
<StyledLink
@@ -282,6 +319,8 @@ export const DocTree = ({ currentDoc }: DocTreeProps) => {
282319
isOpen={rootActionsOpen}
283320
isRoot={true}
284321
onOpenChange={setRootActionsOpen}
322+
actionsRef={rootActionsRef}
323+
onKeyDownCapture={onRootToolbarKeys}
285324
/>
286325
</Box>
287326
</StyledLink>

src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTreeItemActions.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ export const DocTreeItemActions = ({
195195
onOpenChange?.(!isOpen);
196196
}}
197197
aria-label={t('More options')}
198+
tabIndex={-1}
198199
$css={css`
199200
background: transparent;
200201
border: none;
@@ -233,6 +234,7 @@ export const DocTreeItemActions = ({
233234
$variation="secondary"
234235
aria-label={t('Add a sub page')}
235236
data-testid="doc-tree-item-actions-add-child"
237+
tabIndex={-1}
236238
$css={css`
237239
&:focus-visible {
238240
outline: 2px solid var(--c--globals--colors--brand-500) !important;

0 commit comments

Comments
 (0)