Skip to content

Commit 6fdb155

Browse files
authored
feat: display keyboard shortcuts on toolbar buttons (#788)
- Add shortcut hints to toolbar buttons via tooltip text - Integrate useKeyboardShortcutsHelp hook for consistent shortcut retrieval - Timeline toolbar: split, duplicate, delete, snapping, bookmark buttons - Preview toolbar: play/pause button - Format: tooltip text appended with '(Key)' or '(Key1 or Key2)' - Gracefully handle actions without defined shortcuts
1 parent fbe3db7 commit 6fdb155

2 files changed

Lines changed: 53 additions & 18 deletions

File tree

apps/web/src/preview/components/toolbar.tsx

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useState, useEffect } from "react";
44
import { useEditor } from "@/editor/use-editor";
55
import { formatTimecode } from "opencut-wasm";
66
import { invokeAction } from "@/actions";
7+
import { useKeyboardShortcutsHelp } from "@/actions/use-keyboard-shortcuts-help";
78
import { EditableTimecode } from "@/components/editable-timecode";
89
import { Button } from "@/components/ui/button";
910
import {
@@ -24,6 +25,11 @@ import { PREVIEW_ZOOM_PRESETS } from "@/preview/zoom";
2425
import { usePreviewViewport } from "./preview-viewport";
2526
import { GridPopover } from "./guide-popover";
2627
import { usePreviewStore } from "@/preview/preview-store";
28+
import {
29+
Tooltip,
30+
TooltipTrigger,
31+
TooltipContent,
32+
} from "@/components/ui/tooltip";
2733
import type { MediaTime } from "@/wasm";
2834

2935
export function PreviewToolbar({
@@ -133,14 +139,24 @@ function ZoomSelect() {
133139

134140
function PlayPauseButton() {
135141
const isPlaying = useEditor((e) => e.playback.getIsPlaying());
142+
const { shortcuts } = useKeyboardShortcutsHelp();
143+
const shortcut = shortcuts.find((s) => s.action === "toggle-play");
144+
const tooltipText = shortcut
145+
? `Play/Pause (${shortcut.keys.join(" or ")})`
146+
: "Play/Pause";
136147

137148
return (
138-
<Button
139-
variant="text"
140-
size="icon"
141-
onClick={() => invokeAction("toggle-play")}
142-
>
143-
<HugeiconsIcon icon={isPlaying ? PauseIcon : PlayIcon} />
144-
</Button>
149+
<Tooltip delayDuration={200}>
150+
<TooltipTrigger asChild>
151+
<Button
152+
variant="text"
153+
size="icon"
154+
onClick={() => invokeAction("toggle-play")}
155+
>
156+
<HugeiconsIcon icon={isPlaying ? PauseIcon : PlayIcon} />
157+
</Button>
158+
</TooltipTrigger>
159+
<TooltipContent>{tooltipText}</TooltipContent>
160+
</Tooltip>
145161
);
146162
}

apps/web/src/timeline/components/timeline-toolbar.tsx

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { TIMELINE_ZOOM_MAX } from "@/timeline/scale";
1919
import { sliderToZoom, zoomToSlider } from "@/timeline/zoom-utils";
2020
import { ScenesView } from "@/components/editor/scenes-view";
2121
import { type TActionWithOptionalArgs, invokeAction } from "@/actions";
22+
import { useKeyboardShortcutsHelp } from "@/actions/use-keyboard-shortcuts-help";
2223
import {
2324
canToggleSourceAudio,
2425
getSourceAudioActionLabel,
@@ -143,18 +144,21 @@ function ToolbarLeftSection() {
143144
<div className="flex items-center gap-1">
144145
<TooltipProvider delayDuration={500}>
145146
<ToolbarButton
147+
action="split"
146148
icon={<HugeiconsIcon icon={ScissorIcon} />}
147149
tooltip="Split element"
148150
onClick={({ event }) => handleAction({ action: "split", event })}
149151
/>
150152

151153
<ToolbarButton
154+
action="split-left"
152155
icon={<HugeiconsIcon icon={AlignLeftIcon} />}
153156
tooltip="Split left"
154157
onClick={({ event }) => handleAction({ action: "split-left", event })}
155158
/>
156159

157160
<ToolbarButton
161+
action="split-right"
158162
icon={<HugeiconsIcon icon={AlignRightIcon} />}
159163
tooltip="Split right"
160164
onClick={({ event }) =>
@@ -163,6 +167,7 @@ function ToolbarLeftSection() {
163167
/>
164168

165169
<ToolbarButton
170+
action="toggle-source-audio"
166171
icon={
167172
<HugeiconsIcon
168173
icon={isSelectedSourceAudioSeparated ? Unlink02Icon : Link02Icon}
@@ -176,6 +181,7 @@ function ToolbarLeftSection() {
176181
/>
177182

178183
<ToolbarButton
184+
action="duplicate-selected"
179185
icon={<HugeiconsIcon icon={Copy01Icon} />}
180186
tooltip="Duplicate element"
181187
onClick={({ event }) =>
@@ -191,6 +197,7 @@ function ToolbarLeftSection() {
191197
/>
192198

193199
<ToolbarButton
200+
action="delete-selected"
194201
icon={<HugeiconsIcon icon={Delete02Icon} />}
195202
tooltip="Delete element"
196203
onClick={({ event }) =>
@@ -200,16 +207,15 @@ function ToolbarLeftSection() {
200207

201208
<div className="bg-border mx-1 h-6 w-px" />
202209

203-
<Tooltip>
204-
<ToolbarButton
205-
icon={<HugeiconsIcon icon={Bookmark02Icon} />}
206-
isActive={isCurrentlyBookmarked}
207-
tooltip={isCurrentlyBookmarked ? "Remove bookmark" : "Add bookmark"}
208-
onClick={({ event }) =>
209-
handleAction({ action: "toggle-bookmark", event })
210-
}
211-
/>
212-
</Tooltip>
210+
<ToolbarButton
211+
action="toggle-bookmark"
212+
icon={<HugeiconsIcon icon={Bookmark02Icon} />}
213+
isActive={isCurrentlyBookmarked}
214+
tooltip={isCurrentlyBookmarked ? "Remove bookmark" : "Add bookmark"}
215+
onClick={({ event }) =>
216+
handleAction({ action: "toggle-bookmark", event })
217+
}
218+
/>
213219

214220
<GraphEditorPopover
215221
open={graphEditor.open}
@@ -284,13 +290,15 @@ function ToolbarRightSection({
284290
<div className="flex items-center gap-1">
285291
<TooltipProvider delayDuration={500}>
286292
<ToolbarButton
293+
action="toggle-snapping"
287294
icon={<HugeiconsIcon icon={MagnetIcon} />}
288295
isActive={snappingEnabled}
289296
tooltip="Auto snapping"
290297
onClick={() => toggleSnapping()}
291298
/>
292299

293300
<ToolbarButton
301+
action="toggle-ripple-editing"
294302
icon={<OcRippleIcon size={24} className="scale-110" />}
295303
isActive={rippleEditingEnabled}
296304
tooltip="Ripple editing"
@@ -337,14 +345,25 @@ function ToolbarButton({
337345
disabled,
338346
isActive,
339347
buttonWrapper,
348+
action,
340349
}: {
341350
icon: React.ReactNode;
342351
tooltip: string;
343352
onClick?: ({ event }: { event: React.MouseEvent }) => void;
344353
disabled?: boolean;
345354
isActive?: boolean;
346355
buttonWrapper?: (button: React.ReactElement) => React.ReactElement;
356+
action?: TActionWithOptionalArgs;
347357
}) {
358+
const { shortcuts } = useKeyboardShortcutsHelp();
359+
const shortcut = action
360+
? shortcuts.find((s) => s.action === action)
361+
: null;
362+
363+
const tooltipContent = shortcut
364+
? `${tooltip} (${shortcut.keys.join(" or ")})`
365+
: tooltip;
366+
348367
const button = (
349368
<Button
350369
variant={isActive ? "secondary" : "text"}
@@ -370,7 +389,7 @@ function ToolbarButton({
370389
return (
371390
<Tooltip delayDuration={200}>
372391
<TooltipTrigger asChild>{trigger}</TooltipTrigger>
373-
<TooltipContent>{tooltip}</TooltipContent>
392+
<TooltipContent>{tooltipContent}</TooltipContent>
374393
</Tooltip>
375394
);
376395
}

0 commit comments

Comments
 (0)