fixup! (frontend) add keyboard navigation for subdocs with focus activation

This commit is contained in:
Cyril
2025-08-12 17:51:11 +02:00
parent 66869c2758
commit 9cdd0bd7f9
3 changed files with 82 additions and 5 deletions

View File

@@ -17,6 +17,7 @@ import {
} from '@/features/docs/doc-management';
import { DocIcon } from '@/features/docs/doc-management/components/DocIcon';
import { useActionableMode } from '@/features/docs/doc-tree/hooks/useActionableMode';
import { useLoadChildrenOnOpen } from '@/features/docs/doc-tree/hooks/useLoadChildrenOnOpen';
import { useLeftPanelStore } from '@/features/left-panel';
import { useResponsiveStore } from '@/stores';
@@ -45,8 +46,8 @@ export const DocSubPageItem = (props: TreeViewNodeProps<Doc>) => {
const { t } = useTranslation();
const [menuOpen, setMenuOpen] = useState(false);
const isActive = node.isFocused || menuOpen;
const isSelectedNow = treeContext?.treeData.selectedNode?.id === doc.id;
const isActive = node.isFocused || menuOpen || isSelectedNow;
const router = useRouter();
const { togglePanel } = useLeftPanelStore();
@@ -89,13 +90,25 @@ export const DocSubPageItem = (props: TreeViewNodeProps<Doc>) => {
}
};
useKeyboardActivation(['Enter', ' '], isActive, handleActivate, true);
useKeyboardActivation(
['Enter', ' '],
isActive && !menuOpen,
handleActivate,
true,
);
useLoadChildrenOnOpen(
node.data.value.id,
node.isOpen,
treeContext?.treeData.handleLoadChildren,
treeContext?.treeData.setChildren,
(doc.children?.length ?? 0) > 0 || doc.childrenCount === 0,
);
// prepare the text for the screen reader
const docTitle = doc.title || untitledDocument;
const hasChildren = (doc.children?.length || 0) > 0;
const isExpanded = node.isOpen;
const isSelected = treeContext?.treeData.selectedNode?.id === doc.id;
const isSelected = isSelectedNow;
const ariaLabel = `${docTitle}${hasChildren ? `, ${isExpanded ? t('expanded') : t('collapsed')}` : ''}${isSelected ? `, ${t('selected')}` : ''}`;
@@ -155,6 +168,15 @@ export const DocSubPageItem = (props: TreeViewNodeProps<Doc>) => {
.row.preview & {
background-color: inherit;
}
/* Ensure actions are visible when hovering the whole item container */
&:hover {
.light-doc-item-actions {
display: flex;
opacity: 1;
visibility: visible;
}
}
`}
>
<TreeViewItem {...props} onClick={handleActivate}>

View File

@@ -151,7 +151,7 @@ export const DocTreeItemActions = ({
};
useDropdownFocusManagement({
isOpen: isOpen || false,
isOpen: !!isOpen,
docId: doc.id,
actionsRef,
});

View File

@@ -0,0 +1,55 @@
import { useEffect, useRef } from 'react';
/**
* Lazily loads children for a tree node the first time it is expanded.
* Works for both mouse and keyboard expansions.
*/
export const useLoadChildrenOnOpen = <T>(
nodeId: string,
isOpen: boolean,
handleLoadChildren?: (id: string) => Promise<T[]>,
setChildren?: (id: string, children: T[]) => void,
isAlreadyLoaded?: boolean,
) => {
const hasLoadedRef = useRef(false);
// Reset the local loaded flag when the node id changes
useEffect(() => {
hasLoadedRef.current = false;
}, [nodeId]);
useEffect(() => {
if (!isOpen) {
return;
}
if (isAlreadyLoaded) {
hasLoadedRef.current = true;
return;
}
if (hasLoadedRef.current) {
return;
}
if (!handleLoadChildren || !setChildren) {
return;
}
let isCancelled = false;
// Mark as loading to prevent repeated fetches/renders that can cause flicker
hasLoadedRef.current = true;
void handleLoadChildren(nodeId)
.then((children) => {
if (isCancelled) {
return;
}
setChildren(nodeId, children);
})
.catch(() => {
// allow retry on next open
hasLoadedRef.current = false;
});
return () => {
isCancelled = true;
};
}, [isOpen, nodeId, handleLoadChildren, setChildren, isAlreadyLoaded]);
};