👁️ fix: Return Focus on My Files Modal Close (#11032)

* fix: focus returns to manage files button on modal close

* refactor: remove atoms and re-use components instead

* refactor: FilesView to MyFilesModal

* chore: import styling

* chore: delete file that was meant to be renamed

* feat: add trigger ref for settings button as well
This commit is contained in:
Dustin Healy 2025-12-18 10:36:19 -08:00 committed by GitHub
parent 1f695e0cdc
commit 98294755ee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 33 additions and 14 deletions

View file

@ -5,7 +5,15 @@ import { useGetFiles } from '~/data-provider';
import { DataTable, columns } from './Table';
import { useLocalize } from '~/hooks';
export default function Files({ open, onOpenChange }) {
export function MyFilesModal({
open,
onOpenChange,
triggerRef,
}: {
open: boolean;
onOpenChange: (open: boolean) => void;
triggerRef?: React.RefObject<HTMLButtonElement | HTMLDivElement | null>;
}) {
const localize = useLocalize();
const { data: files = [] } = useGetFiles<TFile[]>({
@ -18,7 +26,7 @@ export default function Files({ open, onOpenChange }) {
});
return (
<OGDialog open={open} onOpenChange={onOpenChange}>
<OGDialog open={open} onOpenChange={onOpenChange} triggerRef={triggerRef}>
<OGDialogContent
title={localize('com_nav_my_files')}
className="w-11/12 bg-background text-text-primary shadow-2xl"

View file

@ -1,14 +1,12 @@
import { useState, memo } from 'react';
import { useRecoilState } from 'recoil';
import { useState, memo, useRef } from 'react';
import * as Select from '@ariakit/react/select';
import { FileText, LogOut } from 'lucide-react';
import { LinkIcon, GearIcon, DropdownMenuSeparator, Avatar } from '@librechat/client';
import { MyFilesModal } from '~/components/Chat/Input/Files/MyFilesModal';
import { useGetStartupConfig, useGetUserBalance } from '~/data-provider';
import FilesView from '~/components/Chat/Input/Files/FilesView';
import { useAuthContext } from '~/hooks/AuthContext';
import { useLocalize } from '~/hooks';
import Settings from './Settings';
import store from '~/store';
function AccountSettings() {
const localize = useLocalize();
@ -18,11 +16,13 @@ function AccountSettings() {
enabled: !!isAuthenticated && startupConfig?.balance?.enabled,
});
const [showSettings, setShowSettings] = useState(false);
const [showFiles, setShowFiles] = useRecoilState(store.showFiles);
const [showFiles, setShowFiles] = useState(false);
const accountSettingsButtonRef = useRef<HTMLButtonElement>(null);
return (
<Select.SelectProvider>
<Select.Select
ref={accountSettingsButtonRef}
aria-label={localize('com_nav_account_settings')}
data-testid="nav-user"
className="mt-text-sm flex h-auto w-full items-center gap-2 rounded-xl p-2 text-sm transition-all duration-200 ease-in-out hover:bg-surface-hover aria-[expanded=true]:bg-surface-hover"
@ -96,7 +96,13 @@ function AccountSettings() {
{localize('com_nav_log_out')}
</Select.SelectItem>
</Select.SelectPopover>
{showFiles && <FilesView open={showFiles} onOpenChange={setShowFiles} />}
{showFiles && (
<MyFilesModal
open={showFiles}
onOpenChange={setShowFiles}
triggerRef={accountSettingsButtonRef}
/>
)}
{showSettings && <Settings open={showSettings} onOpenChange={setShowSettings} />}
</Select.SelectProvider>
);

View file

@ -1,6 +1,5 @@
import { useState, useCallback, useMemo } from 'react';
import { useState, useCallback, useMemo, useRef } from 'react';
import { ArrowUpLeft } from 'lucide-react';
import { useSetRecoilState } from 'recoil';
import {
Button,
Input,
@ -33,10 +32,10 @@ import {
getEndpointFileConfig,
type TFile,
} from 'librechat-data-provider';
import { MyFilesModal } from '~/components/Chat/Input/Files/MyFilesModal';
import { useFileMapContext, useChatContext } from '~/Providers';
import { useLocalize, useUpdateFiles } from '~/hooks';
import { useGetFileConfig } from '~/data-provider';
import store from '~/store';
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
@ -49,7 +48,8 @@ export default function DataTable<TData, TValue>({ columns, data }: DataTablePro
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
const [{ pageIndex, pageSize }, setPagination] = useState({ pageIndex: 0, pageSize: 10 });
const setShowFiles = useSetRecoilState(store.showFiles);
const [showFilesModal, setShowFilesModal] = useState(false);
const manageFilesRef = useRef<HTMLButtonElement>(null);
const pagination = useMemo(
() => ({
@ -301,9 +301,10 @@ export default function DataTable<TData, TValue>({ columns, data }: DataTablePro
<div className="flex items-center justify-between">
<Button
ref={manageFilesRef}
variant="outline"
size="sm"
onClick={() => setShowFiles(true)}
onClick={() => setShowFilesModal(true)}
aria-label={localize('com_sidepanel_manage_files')}
>
<ArrowUpLeft className="h-4 w-4" aria-hidden="true" />
@ -334,6 +335,11 @@ export default function DataTable<TData, TValue>({ columns, data }: DataTablePro
</Button>
</div>
</div>
<MyFilesModal
open={showFilesModal}
onOpenChange={setShowFilesModal}
triggerRef={manageFilesRef}
/>
</div>
);
}

View file

@ -6,7 +6,6 @@ import type { TOptionSettings } from '~/common';
// Static atoms without localStorage
const staticAtoms = {
abortScroll: atom<boolean>({ key: 'abortScroll', default: false }),
showFiles: atom<boolean>({ key: 'showFiles', default: false }),
optionSettings: atom<TOptionSettings>({ key: 'optionSettings', default: {} }),
currentSettingsView: atom<SettingsViews>({
key: 'currentSettingsView',