From 16091290e690b7998ae458ef7ef8403c213d2f50 Mon Sep 17 00:00:00 2001 From: Thomas Camlong Date: Sat, 18 Jul 2026 18:48:45 +0200 Subject: [PATCH] feat(boards): add keyboard shortcuts for board switching - mod+shift+ArrowRight / mod+shift+ArrowLeft to cycle boards - mod+shift+1-9 to jump to board by index - Uses Mantine useHotkeys (ignores inputs, already in use in the codebase) --- .../boards/(content)/_header-actions.tsx | 63 ++++++++++++++----- 1 file changed, 49 insertions(+), 14 deletions(-) diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx index 30c91de40..304b251eb 100644 --- a/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx +++ b/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx @@ -1,6 +1,6 @@ "use client"; -import { useCallback, useEffect } from "react"; +import { useCallback, useEffect, useMemo } from "react"; import { useRouter } from "next/navigation"; import { OnboardingTour } from "@gfazioli/mantine-onboarding-tour"; import { Box, Group, Menu, ScrollArea } from "@mantine/core"; @@ -46,22 +46,22 @@ export const BoardContentHeaderActions = ({ demoReadOnly }: { demoReadOnly: bool const board = useRequiredBoard(); const { hasChangeAccess } = useBoardPermissions(board); - if (!hasChangeAccess) { - return ; - } - return ( <> - {isEditMode && } + - - - {!demoReadOnly && ( - - - - - + {hasChangeAccess && ( + <> + {isEditMode && } + + {!demoReadOnly && ( + + + + + + )} + )} @@ -205,6 +205,41 @@ const EditModeMenu = ({ demoReadOnly }: { demoReadOnly: boolean }) => { ); }; +const BoardNavigationHotkeys = () => { + const router = useRouter(); + const { data: boards = [] } = clientApi.board.getAllBoards.useQuery(); + const board = useRequiredBoard(); + + const currentIndex = useMemo(() => boards.findIndex((b) => b.id === board.id), [boards, board.id]); + + const navigateToBoard = useCallback( + (index: number) => { + const target = boards[index]; + if (target) router.push(`/boards/${target.name}`); + }, + [boards, router], + ); + + const hotkeyEntries = useMemo(() => { + const entries: [string, () => void][] = []; + + if (boards.length > 1) { + entries.push(["mod+shift+ArrowRight", () => navigateToBoard((currentIndex + 1) % boards.length)]); + entries.push(["mod+shift+ArrowLeft", () => navigateToBoard((currentIndex - 1 + boards.length) % boards.length)]); + } + + for (let i = 0; i < Math.min(boards.length, 9); i++) { + entries.push([`mod+shift+${i + 1}`, () => navigateToBoard(i)]); + } + + return entries; + }, [boards, currentIndex, navigateToBoard]); + + useHotkeys(hotkeyEntries); + + return null; +}; + const SelectBoardsMenu = () => { const { data: boards = [] } = clientApi.board.getAllBoards.useQuery();