Skip to content

Commit 9fafcb5

Browse files
committed
Adds “v” shortcut to quick view charts full screen
1 parent f67c131 commit 9fafcb5

1 file changed

Lines changed: 37 additions & 3 deletions

File tree

apps/webapp/app/components/metrics/QueryWidget.tsx

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ import { DialogClose } from "@radix-ui/react-dialog";
66
import { IconBraces, IconChartHistogram, IconFileTypeCsv } from "@tabler/icons-react";
77
import { assertNever } from "assert-never";
88
import { Maximize2 } from "lucide-react";
9-
import { useCallback, useState, type ReactNode } from "react";
9+
import { useCallback, useEffect, useRef, useState, type ReactNode } from "react";
1010
import { z } from "zod";
1111
import { Card } from "~/components/primitives/charts/Card";
12+
import { ShortcutKey } from "~/components/primitives/ShortcutKey";
1213
import { SimpleTooltip } from "~/components/primitives/Tooltip";
1314
import { cn } from "~/utils/cn";
1415
import { rowsToCSV, rowsToJSON } from "~/utils/dataExport";
@@ -174,10 +175,38 @@ export function QueryWidget({
174175
const [isMenuOpen, setIsMenuOpen] = useState(false);
175176
const [isRenameDialogOpen, setIsRenameDialogOpen] = useState(false);
176177
const [renameValue, setRenameValue] = useState(titleString ?? "");
178+
const containerRef = useRef<HTMLDivElement>(null);
177179

178180
const hasEditActions = onEdit || onRename || onDelete || onDuplicate;
179181
const hasData = props.data.rows.length > 0;
180182

183+
// "v" to fullscreen the hovered widget
184+
useEffect(() => {
185+
const handleKeyDown = (e: KeyboardEvent) => {
186+
if (isRenameDialogOpen || isMenuOpen) return;
187+
if (e.key !== "v" || e.metaKey || e.ctrlKey || e.altKey || e.shiftKey) return;
188+
189+
// Ignore when typing in inputs/textareas/contenteditable
190+
const target = e.target as HTMLElement;
191+
if (
192+
target.tagName === "INPUT" ||
193+
target.tagName === "TEXTAREA" ||
194+
target.tagName === "SELECT" ||
195+
target.isContentEditable
196+
) {
197+
return;
198+
}
199+
200+
// When not fullscreen, require hover to activate
201+
if (!isFullscreen && !containerRef.current?.matches(":hover")) return;
202+
203+
e.preventDefault();
204+
setIsFullscreen((prev) => !prev);
205+
};
206+
document.addEventListener("keydown", handleKeyDown);
207+
return () => document.removeEventListener("keydown", handleKeyDown);
208+
}, [isFullscreen, isRenameDialogOpen, isMenuOpen]);
209+
181210
const copyToClipboard = useCallback((text: string) => {
182211
navigator.clipboard.writeText(text);
183212
}, []);
@@ -197,7 +226,7 @@ export function QueryWidget({
197226
}, [props.data, copyToClipboard]);
198227

199228
return (
200-
<div className="group h-full">
229+
<div ref={containerRef} className="group h-full">
201230
<Card className={cn("h-full overflow-hidden px-0 pb-0", className)}>
202231
<Card.Header draggable={isDraggable}>
203232
<div className="flex items-center gap-1.5">{title}</div>
@@ -214,7 +243,12 @@ export function QueryWidget({
214243
/>
215244
</span>
216245
}
217-
content="Maximize"
246+
content={
247+
<span className="flex items-center gap-1">
248+
Maximize
249+
<ShortcutKey shortcut={{ key: "v" }} variant="small/bright" />
250+
</span>
251+
}
218252
asChild
219253
/>
220254
<Popover open={isMenuOpen} onOpenChange={setIsMenuOpen}>

0 commit comments

Comments
 (0)