Return focus to View trace when the drawer closes

The drawer is opened from a Logs row rather than a SheetTrigger, so Radix
had nothing to restore focus to. It now remembers what had focus when it
opened and returns there on close; the e2e spec checks it.
This commit is contained in:
arc53-machine committed 2026-09-23 21:08:27 +01:00
1 parent 56e8437a83
commit 5272440a21
2 files changed
+23 -1

No files matched your search

+18 -1
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
@@ -39,6 +39,9 @@ export default function TraceSheet({
const [traces, setTraces] = useState<Trace[]>([]);
const [loading, setLoading] = useState(false);
const [failed, setFailed] = useState(false);
// The sheet is opened from a Logs row, not a SheetTrigger, so Radix has no
// trigger to return focus to on close; remember what had focus instead.
const returnFocusRef = useRef<HTMLElement | null>(null);
useEffect(() => {
if (!traceRef) return;
@@ -76,6 +79,20 @@ export default function TraceSheet({
side="right"
title={t('settings.logs.trace.title')}
aria-describedby={undefined}
onOpenAutoFocus={() => {
returnFocusRef.current =
document.activeElement instanceof HTMLElement
? document.activeElement
: null;
}}
onCloseAutoFocus={(event) => {
const target = returnFocusRef.current;
returnFocusRef.current = null;
if (target?.isConnected) {
event.preventDefault();
target.focus();
}
}}
className="w-full overflow-y-auto sm:max-w-3xl"
>
<div className="flex flex-col gap-8 px-4 pt-4 pb-6">
@@ -114,6 +114,11 @@ test.describe('tier-a · execution traces', () => {
if (process.env.TRACE_SCREENSHOT) {
await page.screenshot({ path: process.env.TRACE_SCREENSHOT, fullPage: false });
}
// Closing returns focus to the button that opened the drawer.
await page.keyboard.press('Escape');
await expect(sheet).toBeHidden();
await expect(page.getByRole('button', { name: 'View trace' })).toBeFocused();
} finally {
await api.dispose();
await context.close();