66 useTreeContext ,
77} from '@gouvfr-lasuite/ui-kit' ;
88import { useRouter } from 'next/navigation' ;
9- import { useCallback , useEffect , useState } from 'react' ;
9+ import { useCallback , useEffect , useRef , useState } from 'react' ;
1010import { useTranslation } from 'react-i18next' ;
1111import { css } from 'styled-components' ;
1212
@@ -16,6 +16,8 @@ import { Doc, SimpleDocItem } from '@/docs/doc-management';
1616
1717import { KEY_DOC_TREE , useDocTree } from '../api/useDocTree' ;
1818import { useMoveDoc } from '../api/useMove' ;
19+ import { useActionableMode } from '../hooks/useActionableMode' ;
20+ import type { ActionableNodeLike } from '../hooks/useActionableMode' ;
1921import { findIndexInTree } from '../utils' ;
2022
2123import { 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 >
0 commit comments