From f82f3436313fa2e2ccf04ff0e7886bf4efffd78e Mon Sep 17 00:00:00 2001 From: Meier Lukas Date: Thu, 2 Oct 2025 19:54:30 +0200 Subject: [PATCH] feat(about): add hotkeys list (#4165) --- .../boards/(content)/_header-actions.tsx | 3 +- .../src/app/[locale]/manage/about/page.tsx | 50 ++++++++++++++++++- .../src/components/user-avatar-menu.tsx | 3 +- packages/definitions/src/hotkeys.ts | 6 +++ packages/definitions/src/index.ts | 1 + .../spotlight/src/components/spotlight.tsx | 2 + packages/translation/src/lang/en.json | 15 ++++++ packages/widgets/src/notebook/notebook.tsx | 3 +- 8 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 packages/definitions/src/hotkeys.ts 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 883fce49a..1d7f8aed8 100644 --- a/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx +++ b/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx @@ -23,6 +23,7 @@ import { useRequiredBoard } from "@homarr/boards/context"; import { useEditMode } from "@homarr/boards/edit-mode"; import { revalidatePathActionAsync } from "@homarr/common/client"; import { env } from "@homarr/common/env"; +import { hotkeys } from "@homarr/definitions"; import { useConfirmModal, useModalAction } from "@homarr/modals"; import { AppSelectModal } from "@homarr/modals-collection"; import { showErrorNotification, showSuccessNotification } from "@homarr/notifications"; @@ -165,7 +166,7 @@ const EditModeMenu = () => { open(); }, [board, isEditMode, saveBoard, open]); - useHotkeys([["mod+e", toggle]]); + useHotkeys([[hotkeys.toggleBoardEdit, toggle]]); usePreventLeaveWithDirty(isEditMode); return ( diff --git a/apps/nextjs/src/app/[locale]/manage/about/page.tsx b/apps/nextjs/src/app/[locale]/manage/about/page.tsx index ece275534..8e336449d 100644 --- a/apps/nextjs/src/app/[locale]/manage/about/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/about/page.tsx @@ -11,14 +11,23 @@ import { Center, Flex, Group, + Kbd, List, ListItem, Stack, + Table, + TableTbody, + TableTd, + TableTh, + TableThead, + TableTr, Text, Title, } from "@mantine/core"; -import { IconLanguage, IconLibrary, IconUsers } from "@tabler/icons-react"; +import { IconKeyboard, IconLanguage, IconLibrary, IconUsers } from "@tabler/icons-react"; +import { capitalize, objectEntries } from "@homarr/common"; +import { hotkeys } from "@homarr/definitions"; import { getScopedI18n } from "@homarr/translation/server"; import { homarrLogoPath } from "~/components/layout/logo/homarr-logo"; @@ -149,6 +158,45 @@ export default async function AboutPage() { + + }> + + {t("accordion.hotkeys.title")} + + {t("accordion.hotkeys.subtitle")} + + + + + + + + {t("accordion.hotkeys.field.shortcut")} + {t("accordion.hotkeys.field.action")} + + + + {objectEntries(hotkeys).map(([key, shortcut]) => ( + + + + {shortcut + .split("+") + .map((key) => capitalize(key.trim())) + .join(" + ")} + + + {t(`accordion.hotkeys.action.${key}`)} + + ))} + +
+ + + {t("accordion.hotkeys.note")} + +
+
); diff --git a/apps/nextjs/src/components/user-avatar-menu.tsx b/apps/nextjs/src/components/user-avatar-menu.tsx index b0f9c6a10..9b3e3647a 100644 --- a/apps/nextjs/src/components/user-avatar-menu.tsx +++ b/apps/nextjs/src/components/user-avatar-menu.tsx @@ -19,6 +19,7 @@ import { import type { RouterOutputs } from "@homarr/api"; import { signOut, useSession } from "@homarr/auth/client"; +import { hotkeys } from "@homarr/definitions"; import { createModal, useModalAction } from "@homarr/modals"; import { useScopedI18n } from "@homarr/translation/client"; @@ -34,7 +35,7 @@ interface UserAvatarMenuProps { export const UserAvatarMenu = ({ children, availableUpdatesPromise }: UserAvatarMenuProps) => { const t = useScopedI18n("common.userAvatar.menu"); const { colorScheme, toggleColorScheme } = useMantineColorScheme(); - useHotkeys([["mod+J", toggleColorScheme]]); + useHotkeys([[hotkeys.toggleColorScheme, toggleColorScheme]]); const ColorSchemeIcon = colorScheme === "dark" ? IconSun : IconMoon; diff --git a/packages/definitions/src/hotkeys.ts b/packages/definitions/src/hotkeys.ts new file mode 100644 index 000000000..b3a8cab42 --- /dev/null +++ b/packages/definitions/src/hotkeys.ts @@ -0,0 +1,6 @@ +export const hotkeys = { + toggleBoardEdit: "mod+e", + toggleColorScheme: "mod+j", + saveNotebook: "mod+s", + openSpotlight: "mod+k", +}; diff --git a/packages/definitions/src/index.ts b/packages/definitions/src/index.ts index 22ca76b84..3eb639ba6 100644 --- a/packages/definitions/src/index.ts +++ b/packages/definitions/src/index.ts @@ -13,3 +13,4 @@ export * from "./cookie"; export * from "./search-engine"; export * from "./onboarding"; export * from "./emptysuperjson"; +export * from "./hotkeys"; diff --git a/packages/spotlight/src/components/spotlight.tsx b/packages/spotlight/src/components/spotlight.tsx index 1f4158076..c99a76973 100644 --- a/packages/spotlight/src/components/spotlight.tsx +++ b/packages/spotlight/src/components/spotlight.tsx @@ -6,6 +6,7 @@ import { ActionIcon, Center, Group, Kbd } from "@mantine/core"; import { Spotlight as MantineSpotlight } from "@mantine/spotlight"; import { IconQuestionMark, IconSearch, IconX } from "@tabler/icons-react"; +import { hotkeys } from "@homarr/definitions"; import type { TranslationObject } from "@homarr/translation"; import { useI18n } from "@homarr/translation/client"; @@ -49,6 +50,7 @@ const SpotlightWithActiveMode = ({ modeState, activeMode }: SpotlightWithActiveM return ( { setMode(defaultMode); diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json index 0fad66d35..043cf8af3 100644 --- a/packages/translation/src/lang/en.json +++ b/packages/translation/src/lang/en.json @@ -3446,6 +3446,21 @@ "libraries": { "title": "Libraries", "subtitle": "{count} used in the Code of Homarr" + }, + "hotkeys": { + "title": "Hotkeys", + "subtitle": "Keyboard shortcuts to enhance your workflow", + "field": { + "shortcut": "Shortcut", + "action": "Action" + }, + "action": { + "toggleBoardEdit": "Toggle board edit mode", + "toggleColorScheme": "Toggle light/dark mode", + "saveNotebook": "Save notebook (only inside notebook widget)", + "openSpotlight": "Open search" + }, + "note": "Tip: Mod refers to both Ctrl key and ⌘ key on macOS" } } } diff --git a/packages/widgets/src/notebook/notebook.tsx b/packages/widgets/src/notebook/notebook.tsx index af8062945..09bad2b5f 100644 --- a/packages/widgets/src/notebook/notebook.tsx +++ b/packages/widgets/src/notebook/notebook.tsx @@ -72,6 +72,7 @@ import "./notebook.css"; import { useSession } from "@homarr/auth/client"; import { constructBoardPermissions } from "@homarr/auth/shared"; import { useRequiredBoard } from "@homarr/boards/context"; +import { hotkeys } from "@homarr/definitions"; import { useConfirmModal } from "@homarr/modals"; const iconProps = { @@ -266,7 +267,7 @@ export function Notebook({ options, setOptions, isEditMode, boardId, itemId }: W p={0} mt={0} h="100%" - onKeyDown={isEditing ? getHotkeyHandler([["mod+s", handleEditToggle]]) : undefined} + onKeyDown={isEditing ? getHotkeyHandler([[hotkeys.saveNotebook, handleEditToggle]]) : undefined} editor={editor} styles={(theme) => ({ root: {