From ea77edcda2fb66f299138b68c7e0271340985d44 Mon Sep 17 00:00:00 2001 From: Anastasiia Solop <35258279+anastasiya1155@users.noreply.github.com> Date: Thu, 17 Jul 2025 11:21:49 +0200 Subject: [PATCH] Improve keyboard navigation (#234) * update ticket in the panel as we toggle through the tasks * shortcut to delete task, fix enter key triggering multiple events * cleanup --- frontend/src/components/tasks/TaskCard.tsx | 21 +++++++++++++++++-- .../src/components/tasks/TaskKanbanBoard.tsx | 13 ++++++++++-- .../components/ui/shadcn-io/kanban/index.tsx | 5 ++++- frontend/src/lib/keyboard-shortcuts.ts | 4 ++-- frontend/src/pages/project-tasks.tsx | 1 + 5 files changed, 37 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/tasks/TaskCard.tsx b/frontend/src/components/tasks/TaskCard.tsx index 9cc39380..f2aa5bf4 100644 --- a/frontend/src/components/tasks/TaskCard.tsx +++ b/frontend/src/components/tasks/TaskCard.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef } from 'react'; +import { KeyboardEvent, useCallback, useEffect, useRef } from 'react'; import { Button } from '@/components/ui/button'; import { DropdownMenu, @@ -48,6 +48,21 @@ export function TaskCard({ } }, [isFocused]); + const handleKeyDown = useCallback( + (e: KeyboardEvent) => { + if (e.key === 'Backspace') { + onDelete(task.id); + } else if (e.key === 'Enter' || e.key === ' ') { + onViewDetails(task); + } + }, + [task, onDelete, onViewDetails] + ); + + const handleClick = useCallback(() => { + onViewDetails(task); + }, [task, onViewDetails]); + return ( onViewDetails(task)} + onClick={handleClick} tabIndex={tabIndex} forwardedRef={localRef} + onKeyDown={handleKeyDown} >
@@ -82,6 +98,7 @@ export function TaskCard({ onPointerDown={(e) => e.stopPropagation()} onMouseDown={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()} + onKeyDown={(e) => e.stopPropagation()} > diff --git a/frontend/src/components/tasks/TaskKanbanBoard.tsx b/frontend/src/components/tasks/TaskKanbanBoard.tsx index 623f512d..525e3d09 100644 --- a/frontend/src/components/tasks/TaskKanbanBoard.tsx +++ b/frontend/src/components/tasks/TaskKanbanBoard.tsx @@ -23,6 +23,7 @@ interface TaskKanbanBoardProps { onEditTask: (task: Task) => void; onDeleteTask: (taskId: string) => void; onViewTaskDetails: (task: Task) => void; + isPanelOpen: boolean; } const allTaskStatuses: TaskStatus[] = [ @@ -56,6 +57,7 @@ function TaskKanbanBoard({ onEditTask, onDeleteTask, onViewTaskDetails, + isPanelOpen, }: TaskKanbanBoardProps) { const { projectId, taskId } = useParams<{ projectId: string; @@ -130,13 +132,20 @@ function TaskKanbanBoard({ // Keyboard navigation handler useKanbanKeyboardNavigation({ focusedTaskId, - setFocusedTaskId: (id) => setFocusedTaskId(id as string | null), + setFocusedTaskId: (id) => { + setFocusedTaskId(id as string | null); + if (isPanelOpen) { + const task = filteredTasks.find((t: any) => t.id === id); + if (task) { + onViewTaskDetails(task); + } + } + }, focusedStatus, setFocusedStatus: (status) => setFocusedStatus(status as TaskStatus | null), groupedTasks, filteredTasks, allTaskStatuses, - onViewTaskDetails, }); return ( diff --git a/frontend/src/components/ui/shadcn-io/kanban/index.tsx b/frontend/src/components/ui/shadcn-io/kanban/index.tsx index 66cb2f2d..9b697969 100644 --- a/frontend/src/components/ui/shadcn-io/kanban/index.tsx +++ b/frontend/src/components/ui/shadcn-io/kanban/index.tsx @@ -12,7 +12,7 @@ import { useSensor, useSensors, } from '@dnd-kit/core'; -import type { ReactNode, Ref } from 'react'; +import type { ReactNode, Ref, KeyboardEvent } from 'react'; export type { DragEndEvent } from '@dnd-kit/core'; @@ -61,6 +61,7 @@ export type KanbanCardProps = Pick & { onClick?: () => void; tabIndex?: number; forwardedRef?: Ref; + onKeyDown?: (e: KeyboardEvent) => void; }; export const KanbanCard = ({ @@ -73,6 +74,7 @@ export const KanbanCard = ({ onClick, tabIndex, forwardedRef, + onKeyDown, }: KanbanCardProps) => { const { attributes, listeners, setNodeRef, transform, isDragging } = useDraggable({ @@ -108,6 +110,7 @@ export const KanbanCard = ({ ref={combinedRef} tabIndex={tabIndex} onClick={onClick} + onKeyDown={onKeyDown} > {children ??

{name}

} diff --git a/frontend/src/lib/keyboard-shortcuts.ts b/frontend/src/lib/keyboard-shortcuts.ts index 1db7f36d..84424bbc 100644 --- a/frontend/src/lib/keyboard-shortcuts.ts +++ b/frontend/src/lib/keyboard-shortcuts.ts @@ -174,7 +174,7 @@ export function useKanbanKeyboardNavigation({ groupedTasks: Record; filteredTasks: any[]; allTaskStatuses: string[]; - onViewTaskDetails: (task: any) => void; + onViewTaskDetails?: (task: any) => void; preserveIndexOnColumnSwitch?: boolean; }) { useEffect(() => { @@ -237,7 +237,7 @@ export function useKanbanKeyboardNavigation({ break; } } - } else if (e.key === 'Enter' || e.key === ' ') { + } else if ((e.key === 'Enter' || e.key === ' ') && onViewTaskDetails) { const task = filteredTasks.find((t: any) => t.id === focusedTaskId); if (task) { onViewTaskDetails(task); diff --git a/frontend/src/pages/project-tasks.tsx b/frontend/src/pages/project-tasks.tsx index 4ab85ffe..a7c3063a 100644 --- a/frontend/src/pages/project-tasks.tsx +++ b/frontend/src/pages/project-tasks.tsx @@ -495,6 +495,7 @@ export function ProjectTasks() { onEditTask={handleEditTask} onDeleteTask={handleDeleteTask} onViewTaskDetails={handleViewTaskDetails} + isPanelOpen={isPanelOpen} />