Skip to content

Commit 8fde837

Browse files
committed
Focus buttons when using key bindings
1 parent b53bfd4 commit 8fde837

File tree

2 files changed

+35
-4
lines changed

2 files changed

+35
-4
lines changed

ui/src/components/Button.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
import { FC, ButtonHTMLAttributes } from "react";
1+
import { forwardRef, ButtonHTMLAttributes } from "react";
22

33
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
44
variant?: "primary" | "secondary" | "danger";
55
size?: "sm" | "md";
66
}
77

8-
export const Button: FC<ButtonProps> = ({
8+
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(({
99
children,
1010
className = "",
1111
variant = "secondary",
1212
size = "md",
1313
disabled,
1414
...props
15-
}) => {
15+
}, ref) => {
1616
const baseStyles = "rounded-md font-medium transition-all duration-150 active:scale-95 focus:outline-none focus:ring-2 focus:ring-offset-2";
1717

1818
const variantStyles = {
@@ -30,11 +30,14 @@ export const Button: FC<ButtonProps> = ({
3030

3131
return (
3232
<button
33+
ref={ref}
3334
className={`${baseStyles} ${variantStyles[variant]} ${sizeStyles[size]} ${disabledStyles} ${className}`}
3435
disabled={disabled}
3536
{...props}
3637
>
3738
{children}
3839
</button>
3940
);
40-
};
41+
});
42+
43+
Button.displayName = "Button";

ui/src/components/Sim/modules/Playback.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,21 @@ export const Playback: FC = () => {
149149

150150
const disabled = events.length === 0;
151151

152+
// Button refs for focus management
153+
const playPauseRef = useRef<HTMLButtonElement>(null);
154+
const stepSmallBackwardRef = useRef<HTMLButtonElement>(null);
155+
const stepBigBackwardRef = useRef<HTMLButtonElement>(null);
156+
const stepSmallForwardRef = useRef<HTMLButtonElement>(null);
157+
const stepBigForwardRef = useRef<HTMLButtonElement>(null);
158+
const speedSelectRef = useRef<HTMLSelectElement>(null);
159+
160+
const focusButton = (ref: React.RefObject<HTMLElement | null>) => {
161+
ref.current?.focus();
162+
setTimeout(() => {
163+
ref.current?.blur();
164+
}, 200); // Show focus for 200ms
165+
};
166+
152167
// Keyboard event handler
153168
useEffect(() => {
154169
const handleKeyDown = (event: KeyboardEvent) => {
@@ -157,26 +172,32 @@ export const Playback: FC = () => {
157172
switch (event.code) {
158173
case "Space":
159174
event.preventDefault();
175+
focusButton(playPauseRef);
160176
handlePlayPause();
161177
break;
162178
case "ArrowRight":
163179
event.preventDefault();
164180
if (event.ctrlKey) {
181+
focusButton(stepBigForwardRef);
165182
handleStep(1.0 * speedMultiplier); // 10x forward (big step)
166183
} else {
184+
focusButton(stepSmallForwardRef);
167185
handleStep(0.1 * speedMultiplier); // 1x forward (small step)
168186
}
169187
break;
170188
case "ArrowLeft":
171189
event.preventDefault();
172190
if (event.ctrlKey) {
191+
focusButton(stepBigBackwardRef);
173192
handleStep(-1.0 * speedMultiplier); // 10x backward (big step)
174193
} else {
194+
focusButton(stepSmallBackwardRef);
175195
handleStep(-0.1 * speedMultiplier); // 1x backward (small step)
176196
}
177197
break;
178198
case "ArrowUp":
179199
event.preventDefault();
200+
focusButton(speedSelectRef);
180201
// Increase speed to next available option
181202
{
182203
const currentIndex = SPEED_OPTIONS.indexOf(speedMultiplier);
@@ -190,6 +211,7 @@ export const Playback: FC = () => {
190211
break;
191212
case "ArrowDown":
192213
event.preventDefault();
214+
focusButton(speedSelectRef);
193215
// Decrease speed to previous available option
194216
{
195217
const currentIndex = SPEED_OPTIONS.indexOf(speedMultiplier);
@@ -211,6 +233,7 @@ export const Playback: FC = () => {
211233
return (
212234
<div className="flex items-center gap-2">
213235
<Button
236+
ref={playPauseRef}
214237
onClick={handlePlayPause}
215238
disabled={disabled}
216239
variant="primary"
@@ -221,6 +244,7 @@ export const Playback: FC = () => {
221244
</Button>
222245

223246
<Button
247+
ref={stepBigBackwardRef}
224248
onClick={() => handleStep(-1.0 * speedMultiplier)}
225249
onMouseDown={(e) => {
226250
e.preventDefault();
@@ -241,6 +265,7 @@ export const Playback: FC = () => {
241265
</Button>
242266

243267
<Button
268+
ref={stepSmallBackwardRef}
244269
onClick={() => handleStep(-0.1 * speedMultiplier)}
245270
onMouseDown={(e) => {
246271
e.preventDefault();
@@ -261,6 +286,7 @@ export const Playback: FC = () => {
261286
</Button>
262287

263288
<Button
289+
ref={stepSmallForwardRef}
264290
onClick={() => handleStep(0.1 * speedMultiplier)}
265291
onMouseDown={(e) => {
266292
e.preventDefault();
@@ -281,6 +307,7 @@ export const Playback: FC = () => {
281307
</Button>
282308

283309
<Button
310+
ref={stepBigForwardRef}
284311
onClick={() => handleStep(1.0 * speedMultiplier)}
285312
onMouseDown={(e) => {
286313
e.preventDefault();
@@ -305,6 +332,7 @@ export const Playback: FC = () => {
305332
Speed
306333
</label>
307334
<select
335+
ref={speedSelectRef}
308336
name="timelineSpeed"
309337
className="mt-1 w-full text-sm"
310338
value={speedMultiplier}

0 commit comments

Comments
 (0)