feat(LogsViewer): add keyboard shortcuts functionality and help component

This commit is contained in:
Nawaz Dhandala
2026-03-13 14:46:02 +00:00
parent ec0c9c8c56
commit 2d88e4fe0e
5 changed files with 168 additions and 1 deletions

View File

@@ -0,0 +1,7 @@
enum LogScrubAction {
Mask = "mask",
Hash = "hash",
Redact = "redact",
}
export default LogScrubAction;

View File

@@ -0,0 +1,10 @@
enum LogScrubPatternType {
Email = "email",
CreditCard = "creditCard",
SSN = "ssn",
PhoneNumber = "phoneNumber",
IPAddress = "ipAddress",
Custom = "custom",
}
export default LogScrubPatternType;

View File

@@ -219,6 +219,9 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
const [internalViewMode, setInternalViewMode] =
useState<LogsViewMode>("list");
const [showKeyboardShortcuts, setShowKeyboardShortcuts] =
useState<boolean>(false);
useEffect(() => {
setFilterData(props.filterData);
}, [props.filterData]);
@@ -526,7 +529,19 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
return;
}
if (e.key === "?") {
e.preventDefault();
setShowKeyboardShortcuts((prev: boolean) => {
return !prev;
});
return;
}
if (e.key === "Escape") {
if (showKeyboardShortcuts) {
setShowKeyboardShortcuts(false);
return;
}
if (selectedLogId) {
setSelectedLogId(null);
}
@@ -574,7 +589,7 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
return () => {
document.removeEventListener("keydown", handleKeyDown);
};
}, [displayedLogs, focusedRowIndex, selectedLogId, handleSearchSubmit]);
}, [displayedLogs, focusedRowIndex, selectedLogId, showKeyboardShortcuts, handleSearchSubmit]);
const handlePageChange: (page: number) => void = (page: number): void => {
if (props.onPageChange) {
@@ -778,6 +793,12 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
onTimeRangeChange: props.onTimeRangeChange,
}
: {}),
showKeyboardShortcuts,
onToggleKeyboardShortcuts: () => {
setShowKeyboardShortcuts((prev: boolean) => {
return !prev;
});
},
};
const showSidebar: boolean =

View File

@@ -0,0 +1,92 @@
import React, { FunctionComponent, ReactElement } from "react";
export interface KeyboardShortcutsHelpProps {
onClose: () => void;
}
interface ShortcutRow {
keys: Array<string>;
description: string;
}
const SHORTCUT_ROWS: Array<ShortcutRow> = [
{ keys: ["j"], description: "Move to next log row" },
{ keys: ["k"], description: "Move to previous log row" },
{ keys: ["Enter"], description: "Expand / collapse selected log" },
{ keys: ["Esc"], description: "Close detail panel" },
{ keys: ["/"], description: "Focus search bar" },
{ keys: ["Ctrl", "Enter"], description: "Apply search filters" },
{ keys: ["?"], description: "Toggle this help" },
];
const KeyboardShortcutsHelp: FunctionComponent<KeyboardShortcutsHelpProps> = (
props: KeyboardShortcutsHelpProps,
): ReactElement => {
return (
<div className="absolute right-0 top-full z-50 mt-1 w-72 overflow-hidden rounded-lg border border-gray-200 bg-white shadow-lg">
<div className="flex items-center justify-between border-b border-gray-100 px-3 py-2">
<span className="text-[11px] font-semibold uppercase tracking-wider text-gray-400">
Keyboard shortcuts
</span>
<button
type="button"
className="text-gray-400 transition-colors hover:text-gray-600"
onClick={props.onClose}
>
<svg
className="h-3.5 w-3.5"
fill="none"
viewBox="0 0 24 24"
strokeWidth={2}
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M6 18 18 6M6 6l12 12"
/>
</svg>
</button>
</div>
<div className="py-1">
{SHORTCUT_ROWS.map((row: ShortcutRow) => {
return (
<div
key={row.description}
className="flex items-center justify-between px-3 py-1.5"
>
<span className="text-xs text-gray-600">{row.description}</span>
<div className="flex items-center gap-1">
{row.keys.map((key: string, index: number) => {
return (
<React.Fragment key={key}>
{index > 0 && (
<span className="text-[10px] text-gray-400">+</span>
)}
<kbd className="inline-flex min-w-[1.5rem] items-center justify-center rounded border border-gray-200 bg-gray-50 px-1.5 py-0.5 font-mono text-[11px] font-medium text-gray-600">
{key}
</kbd>
</React.Fragment>
);
})}
</div>
</div>
);
})}
</div>
<div className="border-t border-gray-100 px-3 py-1.5">
<span className="text-[10px] text-gray-400">
Press{" "}
<kbd className="rounded border border-gray-200 bg-gray-50 px-1 py-0.5 font-mono text-[10px]">
?
</kbd>{" "}
to toggle this panel
</span>
</div>
</div>
);
};
export default KeyboardShortcutsHelp;

View File

@@ -3,6 +3,7 @@ import LiveLogsToggle from "./LiveLogsToggle";
import LogTimeRangePicker from "./LogTimeRangePicker";
import ColumnSelector from "./ColumnSelector";
import SavedViewsDropdown from "./SavedViewsDropdown";
import KeyboardShortcutsHelp from "./KeyboardShortcutsHelp";
import {
LiveLogsOptions,
LogsSavedViewOption,
@@ -34,6 +35,8 @@ export interface LogsViewerToolbarProps {
onViewModeChange?: ((mode: LogsViewMode) => void) | undefined;
onExportCSV?: (() => void) | undefined;
onExportJSON?: (() => void) | undefined;
showKeyboardShortcuts?: boolean | undefined;
onToggleKeyboardShortcuts?: (() => void) | undefined;
}
const LogsViewerToolbar: FunctionComponent<LogsViewerToolbarProps> = (
@@ -153,6 +156,40 @@ const LogsViewerToolbar: FunctionComponent<LogsViewerToolbarProps> = (
/>
)}
{props.onToggleKeyboardShortcuts && (
<div className="relative">
<button
type="button"
className={`inline-flex items-center gap-1.5 rounded-md border px-2.5 py-1.5 text-xs font-medium shadow-sm transition-colors ${
props.showKeyboardShortcuts
? "border-indigo-300 bg-indigo-50 text-indigo-700"
: "border-gray-200 bg-white text-gray-700 hover:border-gray-300 hover:bg-gray-50"
}`}
onClick={props.onToggleKeyboardShortcuts}
title="Keyboard shortcuts (?)"
>
<svg
className="h-3.5 w-3.5"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M6.75 7.5l3 2.25-3 2.25m4.5 0h3m-9 8.25h13.5A2.25 2.25 0 0021 18V6a2.25 2.25 0 00-2.25-2.25H5.25A2.25 2.25 0 003 6v12a2.25 2.25 0 002.25 2.25z"
/>
</svg>
</button>
{props.showKeyboardShortcuts && (
<KeyboardShortcutsHelp
onClose={props.onToggleKeyboardShortcuts}
/>
)}
</div>
)}
{showExport && (
<div className="relative" ref={exportDropdownRef}>
<button