Louis/keyboard shortcut improve (#847)

* Scroll card into view when opened

* improve positioning

* More shortcuts (vibe-kanban 9f9f5c89)

Let's add some more shortcuts:
- When in fullscreen mode, 'j' should navigate to the previous task and 'k' to the next
- 'd' should trigger the delete task dialog

* More shortcuts (vibe-kanban 9f9f5c89)

Let's add some more shortcuts:
- When in fullscreen mode, 'j' should navigate to the previous task and 'k' to the next
- 'd' should trigger the delete task dialog

* More shortcuts (vibe-kanban 9f9f5c89)

Let's add some more shortcuts:
- When in fullscreen mode, 'j' should navigate to the previous task and 'k' to the next
- 'd' should trigger the delete task dialog

* Add h/l for column navigation (vibe-kanban eade645d)

Similar to how we have j and k for next/previous can we add h and l for next/previous column
This commit is contained in:
Louis Knight-Webb
2025-09-25 18:28:18 +01:00
committed by GitHub
parent fde7ae3efe
commit 4f7351ce16
4 changed files with 53 additions and 10 deletions

View File

@@ -1,4 +1,4 @@
import { useCallback } from 'react';
import { useCallback, useEffect, useRef } from 'react';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
@@ -45,6 +45,20 @@ export function TaskCard({
onViewDetails(task);
}, [task, onViewDetails]);
const localRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!isOpen || !localRef.current) return;
const el = localRef.current;
requestAnimationFrame(() => {
el.scrollIntoView({
block: 'center',
inline: 'nearest',
behavior: 'smooth',
});
});
}, [isOpen]);
return (
<KanbanCard
key={task.id}
@@ -54,6 +68,7 @@ export function TaskCard({
parent={status}
onClick={handleClick}
isOpen={isOpen}
forwardedRef={localRef}
>
<div className="flex flex-1 gap-2 items-center min-w-0">
<h4 className="flex-1 min-w-0 line-clamp-2 font-light text-sm">

View File

@@ -52,7 +52,7 @@ export const useKeySubmit = createSemanticHook(Action.SUBMIT);
export const useKeyFocusSearch = createSemanticHook(Action.FOCUS_SEARCH);
/**
* Navigation actions - arrow keys
* Navigation actions - arrow keys and vim keys (hjkl)
*/
export const useKeyNavUp = createSemanticHook(Action.NAV_UP);
export const useKeyNavDown = createSemanticHook(Action.NAV_DOWN);
@@ -84,3 +84,11 @@ export const useKeyShowHelp = createSemanticHook(Action.SHOW_HELP);
export const useKeyToggleFullscreen = createSemanticHook(
Action.TOGGLE_FULLSCREEN
);
/**
* Delete task action - typically 'd' key
*
* @example
* useKeyDeleteTask(() => handleDeleteTask(selectedTask), { scope: Scope.KANBAN });
*/
export const useKeyDeleteTask = createSemanticHook(Action.DELETE_TASK);

View File

@@ -18,6 +18,7 @@ export enum Action {
OPEN_DETAILS = 'open_details',
SHOW_HELP = 'show_help',
TOGGLE_FULLSCREEN = 'toggle_fullscreen',
DELETE_TASK = 'delete_task',
}
export interface KeyBinding {
@@ -87,28 +88,28 @@ export const keyBindings: KeyBinding[] = [
},
{
action: Action.NAV_UP,
keys: 'up',
keys: ['up', 'k'],
scopes: [Scope.KANBAN],
description: 'Move up within column',
group: 'Navigation',
},
{
action: Action.NAV_DOWN,
keys: 'down',
keys: ['down', 'j'],
scopes: [Scope.KANBAN],
description: 'Move down within column',
group: 'Navigation',
},
{
action: Action.NAV_LEFT,
keys: 'left',
keys: ['left', 'h'],
scopes: [Scope.KANBAN],
description: 'Move to previous column',
group: 'Navigation',
},
{
action: Action.NAV_RIGHT,
keys: 'right',
keys: ['right', 'l'],
scopes: [Scope.KANBAN],
description: 'Move to next column',
group: 'Navigation',
@@ -138,6 +139,15 @@ export const keyBindings: KeyBinding[] = [
description: 'Toggle fullscreen view',
group: 'Task Details',
},
// Task actions
{
action: Action.DELETE_TASK,
keys: 'd',
scopes: [Scope.KANBAN],
description: 'Delete selected task',
group: 'Task Details',
},
];
/**

View File

@@ -22,6 +22,7 @@ import {
useKeyOpenDetails,
Scope,
useKeyToggleFullscreen,
useKeyDeleteTask,
} from '@/keyboard';
import {
@@ -229,7 +230,6 @@ export function ProjectTasks() {
},
{
scope: Scope.KANBAN,
when: !isFullscreen,
preventDefault: true,
}
);
@@ -240,7 +240,6 @@ export function ProjectTasks() {
},
{
scope: Scope.KANBAN,
when: !isFullscreen,
preventDefault: true,
}
);
@@ -251,7 +250,6 @@ export function ProjectTasks() {
},
{
scope: Scope.KANBAN,
when: !isFullscreen,
preventDefault: true, // Prevent page scroll
}
);
@@ -262,13 +260,25 @@ export function ProjectTasks() {
},
{
scope: Scope.KANBAN,
when: !isFullscreen,
preventDefault: true, // Prevent page scroll
}
);
useKeyOpenDetails(() => {}, { scope: Scope.KANBAN });
// Delete task shortcut
useKeyDeleteTask(
() => {
if (selectedTask) {
handleDeleteTask(selectedTask.id);
}
},
{
scope: Scope.KANBAN,
preventDefault: true,
}
);
// Full screen
const fetchProject = useCallback(async () => {