From f2cf621f7f486323aabb14b9a9d83325d21e69a9 Mon Sep 17 00:00:00 2001 From: alex-mcgovern Date: Thu, 23 Jan 2025 10:54:36 +0000 Subject: [PATCH 1/4] feat: useKbdShortcuts hook & example implementation --- src/hooks/use-kbd-shortcuts.ts | 31 +++++++++++++++++++++++++++++++ src/lib/hrefs.ts | 5 +++++ src/routes/route-workspaces.tsx | 9 ++++++++- 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 src/hooks/use-kbd-shortcuts.ts create mode 100644 src/lib/hrefs.ts diff --git a/src/hooks/use-kbd-shortcuts.ts b/src/hooks/use-kbd-shortcuts.ts new file mode 100644 index 00000000..b493fd7a --- /dev/null +++ b/src/hooks/use-kbd-shortcuts.ts @@ -0,0 +1,31 @@ +import { useEffect } from "react"; + +export function useKbdShortcuts(map: [string, () => void][]) { + return useEffect(() => { + // Attach a listener to the document to listen for the "/" key + const documentListener = (e: KeyboardEvent) => { + const target = e.target as HTMLElement; + if ( + target.tagName === "INPUT" || + target.tagName === "TEXTAREA" || + target.isContentEditable + ) { + return; + } + + for (const [key, callback] of map) { + if (e.key.toLowerCase() === key.toLowerCase()) { + e.preventDefault(); + e.stopPropagation(); + callback(); + } + } + }; + + document.addEventListener("keydown", documentListener); + + return () => { + document.removeEventListener("keydown", documentListener); + }; + }, [map]); +} diff --git a/src/lib/hrefs.ts b/src/lib/hrefs.ts new file mode 100644 index 00000000..5c124f3f --- /dev/null +++ b/src/lib/hrefs.ts @@ -0,0 +1,5 @@ +export const hrefs = { + workspaces: { + create: "/workspace/create", + }, +}; diff --git a/src/routes/route-workspaces.tsx b/src/routes/route-workspaces.tsx index 22b2dda1..b089078a 100644 --- a/src/routes/route-workspaces.tsx +++ b/src/routes/route-workspaces.tsx @@ -19,6 +19,9 @@ import { useArchivedWorkspaces } from "@/features/workspace/hooks/use-archived-w import { Workspace } from "@/api/generated"; import SvgFlipBackward from "@/components/icons/FlipBackward"; import { useRestoreWorkspaceButton } from "@/features/workspace/hooks/use-restore-workspace-button"; +import { useKbdShortcuts } from "@/hooks/use-kbd-shortcuts"; +import { useNavigate } from "react-router-dom"; +import { hrefs } from "@/lib/hrefs"; function CellName({ name, @@ -89,6 +92,10 @@ export function RouteWorkspaces() { })) ?? []), ]; + const navigate = useNavigate(); + + useKbdShortcuts([["c", () => navigate(hrefs.workspaces.create)]]); + return ( <> @@ -97,7 +104,7 @@ export function RouteWorkspaces() { - + Create Workspace From 997393962e5eee68c7e1468d3f26e85a2f9f6e62 Mon Sep 17 00:00:00 2001 From: alex-mcgovern Date: Thu, 23 Jan 2025 10:56:11 +0000 Subject: [PATCH 2/4] chore: tidy up remnant --- src/hooks/use-kbd-shortcuts.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hooks/use-kbd-shortcuts.ts b/src/hooks/use-kbd-shortcuts.ts index b493fd7a..02a65b3e 100644 --- a/src/hooks/use-kbd-shortcuts.ts +++ b/src/hooks/use-kbd-shortcuts.ts @@ -2,7 +2,6 @@ import { useEffect } from "react"; export function useKbdShortcuts(map: [string, () => void][]) { return useEffect(() => { - // Attach a listener to the document to listen for the "/" key const documentListener = (e: KeyboardEvent) => { const target = e.target as HTMLElement; if ( From 13249e9c24a77da1fa1c3895a3a61441fdff2727 Mon Sep 17 00:00:00 2001 From: alex-mcgovern Date: Thu, 23 Jan 2025 15:25:44 +0000 Subject: [PATCH 3/4] feat: useToastMutation hook --- src/hooks/use-toast-mutation.ts | 42 +++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/hooks/use-toast-mutation.ts diff --git a/src/hooks/use-toast-mutation.ts b/src/hooks/use-toast-mutation.ts new file mode 100644 index 00000000..5fba63bc --- /dev/null +++ b/src/hooks/use-toast-mutation.ts @@ -0,0 +1,42 @@ +import { toast } from "@stacklok/ui-kit"; +import { + DefaultError, + useMutation, + UseMutationOptions, +} from "@tanstack/react-query"; +import { useCallback } from "react"; + +export function useToastMutation< + TData = unknown, + TError = DefaultError, + TVariables = void, + TContext = unknown, +>(options: UseMutationOptions) { + const { + mutateAsync: originalMutateAsync, + // NOTE: We are not allowing direct use of the `mutate` (sync) function. + // eslint-disable-next-line @typescript-eslint/no-unused-vars + mutate: _, + ...rest + } = useMutation(options); + + // NOTE: That we are not allowing the user to pass in customization options + // (the second arg to mutate) + const mutateAsync = useCallback( + ( + variables: Parameters[0], + options: Parameters[1], + { successMsg }: { successMsg: string }, + ) => { + const promise = originalMutateAsync(variables, options); + + toast.promise(promise, { + success: successMsg, + error: (e: TError) => (e.detail ? e.detail : "An error occurred"), + }); + }, + [originalMutateAsync], + ); + + return { mutateAsync, ...rest }; +} From 343c342d722d1e8865508ed0a9f095cc41cee67c Mon Sep 17 00:00:00 2001 From: alex-mcgovern Date: Thu, 23 Jan 2025 15:28:14 +0000 Subject: [PATCH 4/4] chore: remove junk comment --- src/hooks/use-toast-mutation.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/hooks/use-toast-mutation.ts b/src/hooks/use-toast-mutation.ts index 5fba63bc..f3ddc1a4 100644 --- a/src/hooks/use-toast-mutation.ts +++ b/src/hooks/use-toast-mutation.ts @@ -20,8 +20,6 @@ export function useToastMutation< ...rest } = useMutation(options); - // NOTE: That we are not allowing the user to pass in customization options - // (the second arg to mutate) const mutateAsync = useCallback( ( variables: Parameters[0],