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:
committed by
GitHub
parent
fde7ae3efe
commit
4f7351ce16
@@ -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">
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
Reference in New Issue
Block a user