Skip to content

Commit 04c9d92

Browse files
committed
fix: clean up row ref maps, show restored row focus, and cover cold permalinks
The row ref callbacks now delete entries when rows unmount so paginated audit and user browsing does not accumulate detached table rows. The users row gets the same focus-visible treatment as the grant and audit rows so restored keyboard focus is visible, and the audit drawer takes the tab container as a fallback for cold entryId permalinks where no opener was ever focused.
1 parent 49ec02e commit 04c9d92

4 files changed

Lines changed: 16 additions & 4 deletions

File tree

src/components/grants/AuditLogDetailDrawer.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as Dialog from '@radix-ui/react-dialog';
22
import { PrincipalType } from 'librechat-data-provider';
33
import { useCallback, useEffect, useRef, useState } from 'react';
44
import { Badge, Button, Icon, IconButton } from '@clickhouse/click-ui';
5-
import type { ReactElement } from 'react';
5+
import type { ReactElement, RefObject } from 'react';
66
import type * as t from '@/types';
77
import {
88
ACTION_BADGE_STATE,
@@ -41,6 +41,8 @@ interface AuditLogDetailDrawerProps {
4141
* caller is responsible for distinguishing this from `notFound` (which is
4242
* the 404 case where the request itself succeeded but the entry is gone). */
4343
loadError?: boolean;
44+
/** Restore target for the no-opener path (cold `?entryId=` permalink), where the capture at open time is only `document.body`. */
45+
fallbackRef?: RefObject<HTMLElement | null>;
4446
}
4547

4648
function CopyableMono({
@@ -133,9 +135,10 @@ export function AuditLogDetailDrawer({
133135
notFound = false,
134136
loading = false,
135137
loadError = false,
138+
fallbackRef,
136139
}: AuditLogDetailDrawerProps): ReactElement | null {
137140
const localize = useLocalize();
138-
const returnFocus = useReturnFocus(open);
141+
const returnFocus = useReturnFocus(open, fallbackRef);
139142

140143
// Keep the last non-null entry so the close animation has content to render
141144
// while Radix Dialog slides the panel out. Without this, unmounting on

src/components/grants/AuditLogTab.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ export function AuditLogTab() {
114114
const [currentPage, setCurrentPage] = useState(1);
115115
const { message: announcement, announce } = useAnnouncement();
116116
const rowRefs = useRef<Map<string, HTMLTableRowElement>>(new Map());
117+
const tabFallbackRef = useRef<HTMLDivElement>(null);
117118

118119
const resetToFirstPage = useCallback(() => setCurrentPage(1), []);
119120
const searchFilter = useDebouncedFilter('', resetToFirstPage);
@@ -392,7 +393,11 @@ export function AuditLogTab() {
392393
const exportLabel = localize('com_audit_export_server');
393394

394395
return (
395-
<div className="flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto pt-4 pr-1 pl-1">
396+
<div
397+
ref={tabFallbackRef}
398+
tabIndex={-1}
399+
className="flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto pt-4 pr-1 pl-1"
400+
>
396401
<div className="flex items-center justify-between gap-3">
397402
<div
398403
className="flex flex-1 flex-wrap items-center gap-3"
@@ -588,6 +593,7 @@ export function AuditLogTab() {
588593
onKeyDown={(e) => handleRowKeyDown(e, entry.id)}
589594
rowRef={(el) => {
590595
if (el) rowRefs.current.set(entry.id, el);
596+
else rowRefs.current.delete(entry.id);
591597
}}
592598
localize={localize}
593599
/>
@@ -632,6 +638,7 @@ export function AuditLogTab() {
632638

633639
<AuditLogDetailDrawer
634640
entry={selectedEntry}
641+
fallbackRef={tabFallbackRef}
635642
/**
636643
* Drawer is open whenever a deep-link `entryId` is in the URL. This
637644
* keeps the panel mounted (showing a Loading state inside) while the

src/components/grants/GrantManagementTab.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ export function GrantManagementTab() {
106106
}}
107107
rowRef={(el) => {
108108
if (el) rowRefs.current.set(key, el);
109+
else rowRefs.current.delete(key);
109110
}}
110111
/>
111112
);

src/components/users/UsersPage.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ export function UsersPage() {
199199
canManage={canManage}
200200
rowRef={(el) => {
201201
if (el) rowRefs.current.set(user.id, el);
202+
else rowRefs.current.delete(user.id);
202203
}}
203204
/>
204205
))}
@@ -279,7 +280,7 @@ function UserRow({
279280
ref={rowRef}
280281
tabIndex={-1}
281282
className={cn(
282-
'cursor-pointer bg-(--cui-color-background-panel) transition-colors outline-none hover:bg-(--cui-color-background-hover)',
283+
'cursor-pointer bg-(--cui-color-background-panel) transition-colors outline-none hover:bg-(--cui-color-background-hover) focus-visible:bg-(--cui-color-background-hover) focus-visible:outline-1 focus-visible:-outline-offset-1 focus-visible:outline-(--cui-color-outline)',
283284
!isLast && 'border-b border-(--cui-color-stroke-default)',
284285
)}
285286
onClick={onViewDetails}

0 commit comments

Comments
 (0)