mirror of
https://github.com/suitenumerique/docs.git
synced 2026-05-09 08:32:12 +02:00
fixup! ✨(frontend) add keyboard navigation for subdocs with focus activation
This commit is contained in:
@@ -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}>
|
||||
|
||||
@@ -151,7 +151,7 @@ export const DocTreeItemActions = ({
|
||||
};
|
||||
|
||||
useDropdownFocusManagement({
|
||||
isOpen: isOpen || false,
|
||||
isOpen: !!isOpen,
|
||||
docId: doc.id,
|
||||
actionsRef,
|
||||
});
|
||||
|
||||
@@ -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]);
|
||||
};
|
||||
Reference in New Issue
Block a user