feat: enhance keyboard navigation with grid support, refined selection, and arrow key navigation.
This commit is contained in:
@@ -32,6 +32,8 @@ const AbstractDocumentsView = <CProps extends { clearSelection: () => void; chil
|
||||
entries,
|
||||
onFolderSelect,
|
||||
onPreview: openPreview,
|
||||
viewMode: props.viewMode,
|
||||
scrollRef: props.scrollRef,
|
||||
});
|
||||
const { clearSelection } = viewLogic;
|
||||
|
||||
@@ -94,6 +96,7 @@ export const DocumentsList: React.FC<DocumentsViewProps> = (props) => {
|
||||
<AbstractDocumentsView
|
||||
ContainerComponent={DocumentsListContainer}
|
||||
ItemComponent={DocumentsListRow}
|
||||
viewMode="list"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -105,6 +108,7 @@ export const DocumentsGrid: React.FC<DocumentsViewProps & { gridIconSize?: numbe
|
||||
ContainerComponent={DocumentsGridContainer}
|
||||
ItemComponent={DocumentsGridCard}
|
||||
containerProps={{ gridIconSize: props.gridIconSize }}
|
||||
viewMode="grid"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -6,18 +6,23 @@ interface UseDocumentsNavigationProps {
|
||||
entries: DocumentsListEntry[];
|
||||
onFolderSelect?: (folderId: string) => void;
|
||||
onPreview?: (doc: any) => void;
|
||||
viewMode?: string;
|
||||
scrollRef?: React.RefObject<HTMLElement | null>;
|
||||
}
|
||||
|
||||
export const useDocumentsNavigation = ({
|
||||
entries,
|
||||
onFolderSelect,
|
||||
onPreview,
|
||||
viewMode,
|
||||
scrollRef,
|
||||
}: UseDocumentsNavigationProps) => {
|
||||
const {
|
||||
selectedEntries,
|
||||
focusedEntryKey,
|
||||
setFocusedEntryKey,
|
||||
handleEntrySelection,
|
||||
applySelection,
|
||||
} = useWorkspaceSelectionContext();
|
||||
|
||||
const navigableRows = useMemo(
|
||||
@@ -31,10 +36,20 @@ export const useDocumentsNavigation = ({
|
||||
[entries],
|
||||
);
|
||||
|
||||
const getGridColumns = useCallback(() => {
|
||||
if (!scrollRef?.current) return 1;
|
||||
const grid = scrollRef.current.querySelector('.documents-grid');
|
||||
if (!grid) return 1;
|
||||
const style = window.getComputedStyle(grid);
|
||||
const templateColumns = style.gridTemplateColumns;
|
||||
if (!templateColumns) return 1;
|
||||
return templateColumns.split(' ').length;
|
||||
}, [scrollRef]);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(event: React.KeyboardEvent) => {
|
||||
const { key, shiftKey } = event;
|
||||
const triggers = ['ArrowUp', 'ArrowDown', 'Home', 'End', 'Enter', ' ', 'Space', 'Spacebar'];
|
||||
const triggers = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End', 'Enter', ' ', 'Space', 'Spacebar'];
|
||||
if (!triggers.includes(key)) {
|
||||
return;
|
||||
}
|
||||
@@ -43,7 +58,13 @@ export const useDocumentsNavigation = ({
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
// Allow default scrolling for Home/End if not preventing default
|
||||
if (key !== 'Home' && key !== 'End') {
|
||||
event.preventDefault();
|
||||
} else {
|
||||
// Prevent default only if we are handling selection move, otherwise let browser scroll
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
let activeKey =
|
||||
focusedEntryKey && navigableEntryKeys.includes(focusedEntryKey)
|
||||
@@ -62,7 +83,9 @@ export const useDocumentsNavigation = ({
|
||||
}
|
||||
|
||||
if (!activeKey) {
|
||||
activeKey = key === 'ArrowUp' ? navigableEntryKeys[navigableEntryKeys.length - 1] : navigableEntryKeys[0];
|
||||
activeKey = (key === 'ArrowUp' || key === 'ArrowLeft')
|
||||
? navigableEntryKeys[navigableEntryKeys.length - 1]
|
||||
: navigableEntryKeys[0];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +94,11 @@ export const useDocumentsNavigation = ({
|
||||
|
||||
if (key === 'Enter' || key === ' ' || key === 'Space' || key === 'Spacebar') {
|
||||
if (activeRow) {
|
||||
handleEntrySelection(activeRow.key, event);
|
||||
event.preventDefault();
|
||||
// Do not call handleEntrySelection here, as it resets selection if multiple items are selected.
|
||||
// Space/Enter should just trigger the action (Preview/Open) on the focused item
|
||||
// without modifying the selection state.
|
||||
|
||||
if (activeRow.type === 'folder') {
|
||||
onFolderSelect?.(activeRow.id as string);
|
||||
} else {
|
||||
@@ -87,17 +114,36 @@ export const useDocumentsNavigation = ({
|
||||
}
|
||||
|
||||
let nextIndex = currentIndex;
|
||||
const isGrid = viewMode === 'grid';
|
||||
const columns = isGrid ? getGridColumns() : 1;
|
||||
|
||||
if (key === 'ArrowDown') {
|
||||
nextIndex = currentIndex === -1 ? 0 : Math.min(currentIndex + 1, navigableRows.length - 1);
|
||||
if (isGrid) {
|
||||
if (currentIndex + columns < navigableRows.length) {
|
||||
nextIndex = currentIndex + columns;
|
||||
}
|
||||
} else {
|
||||
nextIndex = currentIndex === -1 ? 0 : Math.min(currentIndex + 1, navigableRows.length - 1);
|
||||
}
|
||||
} else if (key === 'ArrowUp') {
|
||||
nextIndex = currentIndex === -1 ? navigableRows.length - 1 : Math.max(currentIndex - 1, 0);
|
||||
if (isGrid) {
|
||||
if (currentIndex - columns >= 0) {
|
||||
nextIndex = currentIndex - columns;
|
||||
}
|
||||
} else {
|
||||
nextIndex = currentIndex === -1 ? navigableRows.length - 1 : Math.max(currentIndex - 1, 0);
|
||||
}
|
||||
} else if (key === 'ArrowLeft') {
|
||||
nextIndex = Math.max(currentIndex - 1, 0);
|
||||
} else if (key === 'ArrowRight') {
|
||||
nextIndex = Math.min(currentIndex + 1, navigableRows.length - 1);
|
||||
} else if (key === 'Home') {
|
||||
nextIndex = 0;
|
||||
} else if (key === 'End') {
|
||||
nextIndex = navigableRows.length - 1;
|
||||
}
|
||||
|
||||
if (nextIndex === -1 || nextIndex >= navigableRows.length) {
|
||||
if (nextIndex === -1 || nextIndex >= navigableRows.length || nextIndex === currentIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -107,10 +153,17 @@ export const useDocumentsNavigation = ({
|
||||
}
|
||||
|
||||
setFocusedEntryKey(targetRow.key);
|
||||
handleEntrySelection(targetRow.key, {
|
||||
shiftKey,
|
||||
preventDefault: () => { },
|
||||
});
|
||||
|
||||
if (shiftKey) {
|
||||
// Additive selection for Shift+Arrow (Finder style)
|
||||
const newSelection = Array.from(new Set([...selectedEntries, targetRow.key]));
|
||||
applySelection(newSelection, { anchor: targetRow.key, interactedKeys: [targetRow.key] });
|
||||
} else {
|
||||
handleEntrySelection(targetRow.key, {
|
||||
shiftKey,
|
||||
preventDefault: () => { },
|
||||
});
|
||||
}
|
||||
},
|
||||
[
|
||||
focusedEntryKey,
|
||||
@@ -122,6 +175,9 @@ export const useDocumentsNavigation = ({
|
||||
onPreview,
|
||||
handleEntrySelection,
|
||||
setFocusedEntryKey,
|
||||
viewMode,
|
||||
getGridColumns,
|
||||
applySelection,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user