From 9c67f319f77cbb705139308f73ac8afdac8ddee6 Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Sun, 22 Dec 2024 16:03:47 +0100 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9C=A8=20feat:=20Add=20localization=20fo?= =?UTF-8?q?r=20page=20display=20and=20enhance=20button=20styles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/package.json | 1 + .../Chat/Input/Files/ImagePreview.tsx | 179 +++++++++++--- .../General/ArchivedChatsTable.tsx | 2 +- .../SidePanel/Bookmarks/BookmarkTable.tsx | 99 ++++---- .../SidePanel/Files/PanelColumns.tsx | 6 +- .../SidePanel/Files/PanelFileCell.tsx | 93 +------- .../components/SidePanel/Files/PanelTable.tsx | 224 ++++++++++++------ client/src/components/ui/Button.tsx | 8 +- client/src/localization/languages/Eng.ts | 1 + package-lock.json | 59 ++++- 10 files changed, 424 insertions(+), 248 deletions(-) diff --git a/client/package.json b/client/package.json index 70bd2daeddc..60ceec7155c 100644 --- a/client/package.json +++ b/client/package.json @@ -79,6 +79,7 @@ "react-dnd-html5-backend": "^16.0.1", "react-dom": "^18.2.0", "react-flip-toolkit": "^7.1.0", + "react-focus-lock": "^2.13.5", "react-gtm-module": "^2.0.11", "react-hook-form": "^7.43.9", "react-lazy-load-image-component": "^1.6.0", diff --git a/client/src/components/Chat/Input/Files/ImagePreview.tsx b/client/src/components/Chat/Input/Files/ImagePreview.tsx index 2876c2aef7b..bf4db894c12 100644 --- a/client/src/components/Chat/Input/Files/ImagePreview.tsx +++ b/client/src/components/Chat/Input/Files/ImagePreview.tsx @@ -1,3 +1,5 @@ +import { useState, useEffect, useCallback } from 'react'; +import { Maximize2 } from 'lucide-react'; import { FileSources } from 'librechat-data-provider'; import ProgressCircle from './ProgressCircle'; import SourceIcon from './SourceIcon'; @@ -16,61 +18,174 @@ const ImagePreview = ({ progress = 1, className = '', source, + alt = 'Preview image', }: { imageBase64?: string; url?: string; - progress?: number; // between 0 and 1 + progress?: number; className?: string; source?: FileSources; + alt?: string; }) => { - let style: styleProps = { + const [isModalOpen, setIsModalOpen] = useState(false); + const [isHovered, setIsHovered] = useState(false); + const [previousActiveElement, setPreviousActiveElement] = useState(null); + + const openModal = useCallback(() => { + setPreviousActiveElement(document.activeElement); + setIsModalOpen(true); + }, []); + + const closeModal = useCallback(() => { + setIsModalOpen(false); + if (previousActiveElement instanceof HTMLElement) { + previousActiveElement.focus(); + } + }, [previousActiveElement]); + + const handleKeyDown = useCallback( + (e: KeyboardEvent) => { + if (e.key === 'Escape') { + closeModal(); + } + }, + [closeModal], + ); + + useEffect(() => { + if (isModalOpen) { + document.addEventListener('keydown', handleKeyDown); + document.body.style.overflow = 'hidden'; + const closeButton = document.querySelector('[aria-label="Close full view"]') as HTMLElement; + if (closeButton) { + setTimeout(() => closeButton.focus(), 0); + } + } + + return () => { + document.removeEventListener('keydown', handleKeyDown); + document.body.style.overflow = 'unset'; + }; + }, [isModalOpen, handleKeyDown]); + + const baseStyle: styleProps = { backgroundSize: 'cover', backgroundPosition: 'center', backgroundRepeat: 'no-repeat', }; - if (imageBase64) { - style = { - ...style, - backgroundImage: `url(${imageBase64})`, - }; - } else if (url) { - style = { - ...style, - backgroundImage: `url(${url})`, - }; - } - if (!style.backgroundImage) { + const imageUrl = imageBase64 ?? url ?? ''; + + const style: styleProps = imageUrl + ? { + ...baseStyle, + backgroundImage: `url(${imageUrl})`, + } + : baseStyle; + + if (typeof style.backgroundImage !== 'string' || style.backgroundImage.length === 0) { return null; } - const radius = 55; // Radius of the SVG circle + const radius = 55; const circumference = 2 * Math.PI * radius; - - // Calculate the offset based on the loading progress const offset = circumference - progress * circumference; const circleCSSProperties = { transition: 'stroke-dashoffset 0.3s linear', }; return ( -
- + {progress < 1 && ( + + )} + +
+ + {isModalOpen && ( +
+
+ +
+ {alt} e.stopPropagation()} + /> +
+
+
)} - - + ); }; diff --git a/client/src/components/Nav/SettingsTabs/General/ArchivedChatsTable.tsx b/client/src/components/Nav/SettingsTabs/General/ArchivedChatsTable.tsx index 85b193ea248..c9da4da941a 100644 --- a/client/src/components/Nav/SettingsTabs/General/ArchivedChatsTable.tsx +++ b/client/src/components/Nav/SettingsTabs/General/ArchivedChatsTable.tsx @@ -227,7 +227,7 @@ export default function ArchivedChatsTable() {
- Page {currentPage} of {totalPages} + {localize('com_ui_page')} {currentPage} {localize('com_ui_of')} {totalPages}
{/* - +
+
+ {localize('com_ui_page')} {pageIndex + 1} {localize('com_ui_of')}{' '} + {Math.ceil(filteredRows.length / pageSize)} +
+
+ + +
diff --git a/client/src/components/SidePanel/Files/PanelColumns.tsx b/client/src/components/SidePanel/Files/PanelColumns.tsx index 3fc6e941808..d8fc15f6c61 100644 --- a/client/src/components/SidePanel/Files/PanelColumns.tsx +++ b/client/src/components/SidePanel/Files/PanelColumns.tsx @@ -12,6 +12,7 @@ export const columns: ColumnDef[] = [ return ( + ))} + + + {table.getRowModel().rows.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + handleFileClick(row.original)} + className="rounded-lg p-4 text-sm text-text-primary outline-none focus-visible:bg-surface-active" + style={{ + maxWidth: (cell.column.columnDef as AugmentedColumnDef) + .meta?.size, + }} + tabIndex={0} + role="gridcell" + > + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + )) + ) : ( + + + {localize('com_files_no_results')} + + + )} + +
+ + +
+ +
@@ -159,11 +250,12 @@ export default function DataTable({ columns, data }: DataTablePro size="sm" onClick={() => table.nextPage()} disabled={!table.getCanNextPage()} + aria-label={localize('com_ui_next')} > {localize('com_ui_next')}
- + ); -} +} \ No newline at end of file diff --git a/client/src/components/ui/Button.tsx b/client/src/components/ui/Button.tsx index e86c261ac8f..7ebe99e7cad 100644 --- a/client/src/components/ui/Button.tsx +++ b/client/src/components/ui/Button.tsx @@ -4,14 +4,14 @@ import { cva, type VariantProps } from 'class-variance-authority'; import { cn } from '~/utils'; const buttonVariants = cva( - 'inline-flex items-center justify-center whitespace-nowrap rounded-lg text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', + 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', { variants: { variant: { default: 'bg-primary text-primary-foreground hover:bg-primary/90', destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/80', outline: - 'text-text-primary border border-input bg-background hover:bg-accent hover:text-accent-foreground', + 'text-text-primary border border-border-light bg-background hover:bg-accent hover:text-accent-foreground', secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', ghost: 'hover:bg-accent hover:text-accent-foreground', link: 'text-primary underline-offset-4 hover:underline', @@ -19,8 +19,8 @@ const buttonVariants = cva( }, size: { default: 'h-10 px-4 py-2', - sm: 'h-9 rounded-md px-3', - lg: 'h-11 rounded-md px-8', + sm: 'h-9 rounded-lg px-3', + lg: 'h-11 rounded-lg px-8', icon: 'size-10', }, }, diff --git a/client/src/localization/languages/Eng.ts b/client/src/localization/languages/Eng.ts index 15e8c180706..3b309af9fd1 100644 --- a/client/src/localization/languages/Eng.ts +++ b/client/src/localization/languages/Eng.ts @@ -438,6 +438,7 @@ export default { com_ui_no_conversation_id: 'No conversation ID found', com_ui_add_multi_conversation: 'Add multi-conversation', com_ui_duplicate_agent_confirm: 'Are you sure you want to duplicate this agent?', + com_ui_page: 'Page', com_auth_error_login: 'Unable to login with the information provided. Please check your credentials and try again.', com_auth_error_login_rl: diff --git a/package-lock.json b/package-lock.json index 7d6d252f43a..c7912cba0eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1041,6 +1041,7 @@ "react-dnd-html5-backend": "^16.0.1", "react-dom": "^18.2.0", "react-flip-toolkit": "^7.1.0", + "react-focus-lock": "^2.13.5", "react-gtm-module": "^2.0.11", "react-hook-form": "^7.43.9", "react-lazy-load-image-component": "^1.6.0", @@ -20450,6 +20451,18 @@ "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, + "node_modules/focus-lock": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-1.3.5.tgz", + "integrity": "sha512-QFaHbhv9WPUeLYBDe/PAuLKJ4Dd9OPvKs9xZBr3yLXnUrDNaVXKu2baDBXe3naPY30hgHYSsf2JW4jzas2mDEQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/follow-redirects": { "version": "1.15.6", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", @@ -30797,6 +30810,18 @@ "react-dom": "^0.14.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-clientside-effect": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/react-clientside-effect/-/react-clientside-effect-1.2.7.tgz", + "integrity": "sha512-gce9m0Pk/xYYMEojRI9bgvqQAkl6hm7ozQvqWPyQx+kULiatdHgkNM1QG4DQRx5N9BAzWSCJmt9mMV8/KsdgVg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.13" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/react-devtools-inline": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/react-devtools-inline/-/react-devtools-inline-4.4.0.tgz", @@ -30871,6 +30896,29 @@ "react-dom": ">= 16.x" } }, + "node_modules/react-focus-lock": { + "version": "2.13.5", + "resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.13.5.tgz", + "integrity": "sha512-HjHuZFFk2+j6ZT3LDQpyqffue541HrxUG/OFchCEwis9nstgNg0rREVRAxHBcB1lHJ5Fsxtx1qya/5xFwxDb4g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.0.0", + "focus-lock": "^1.3.5", + "prop-types": "^15.6.2", + "react-clientside-effect": "^1.2.6", + "use-callback-ref": "^1.3.2", + "use-sidecar": "^1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-gtm-module": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/react-gtm-module/-/react-gtm-module-2.0.11.tgz", @@ -34920,9 +34968,10 @@ "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" }, "node_modules/use-callback-ref": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.1.tgz", - "integrity": "sha512-Lg4Vx1XZQauB42Hw3kK7JM6yjVjgFmFC5/Ab797s79aARomD2nEErc4mCgM8EZrARLmmbWpi5DGCadmK50DcAQ==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", "dependencies": { "tslib": "^2.0.0" }, @@ -34930,8 +34979,8 @@ "node": ">=10" }, "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { From 49a96c51be6c4d10af366e12df4cab6c45c28d54 Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Mon, 23 Dec 2024 02:09:44 +0100 Subject: [PATCH 2/4] =?UTF-8?q?=E2=9C=A8=20refactor:=20improve=20image=20p?= =?UTF-8?q?review=20component=20styles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/package.json | 1 - .../Chat/Input/Files/ImagePreview.tsx | 53 ++-- .../SidePanel/Files/PanelFileCell.tsx | 24 +- .../components/SidePanel/Files/PanelTable.tsx | 238 +++++++++++------- package-lock.json | 48 ---- 5 files changed, 183 insertions(+), 181 deletions(-) diff --git a/client/package.json b/client/package.json index 60ceec7155c..70bd2daeddc 100644 --- a/client/package.json +++ b/client/package.json @@ -79,7 +79,6 @@ "react-dnd-html5-backend": "^16.0.1", "react-dom": "^18.2.0", "react-flip-toolkit": "^7.1.0", - "react-focus-lock": "^2.13.5", "react-gtm-module": "^2.0.11", "react-hook-form": "^7.43.9", "react-lazy-load-image-component": "^1.6.0", diff --git a/client/src/components/Chat/Input/Files/ImagePreview.tsx b/client/src/components/Chat/Input/Files/ImagePreview.tsx index bf4db894c12..ee6caf5fefd 100644 --- a/client/src/components/Chat/Input/Files/ImagePreview.tsx +++ b/client/src/components/Chat/Input/Files/ImagePreview.tsx @@ -78,9 +78,9 @@ const ImagePreview = ({ const style: styleProps = imageUrl ? { - ...baseStyle, - backgroundImage: `url(${imageUrl})`, - } + ...baseStyle, + backgroundImage: `url(${imageUrl})`, + } : baseStyle; if (typeof style.backgroundImage !== 'string' || style.backgroundImage.length === 0) { @@ -103,37 +103,42 @@ const ImagePreview = ({ > - {progress < 1 && ( + /> + {progress < 1 ? ( + ) : ( +
{ + e.stopPropagation(); + openModal(); + }} + aria-hidden="true" + > + +
)} @@ -149,7 +154,7 @@ const ImagePreview = ({
diff --git a/client/src/components/SidePanel/Files/PanelFileCell.tsx b/client/src/components/SidePanel/Files/PanelFileCell.tsx index db09ea351a7..1d5e9f5f3db 100644 --- a/client/src/components/SidePanel/Files/PanelFileCell.tsx +++ b/client/src/components/SidePanel/Files/PanelFileCell.tsx @@ -6,24 +6,20 @@ import { getFileType } from '~/utils'; export default function PanelFileCell({ row }: { row: Row }) { const file = row.original; - if (file.type?.startsWith('image')) { - return ( -
+ + return ( +
+ {file.type.startsWith('image') ? ( - {file.filename} -
- ); - } - - const fileType = getFileType(file.type); - return ( -
- {fileType && } - {file.filename} + ) : ( + + )} + {file.filename}
); } diff --git a/client/src/components/SidePanel/Files/PanelTable.tsx b/client/src/components/SidePanel/Files/PanelTable.tsx index 2453e83cb2f..d06cef44359 100644 --- a/client/src/components/SidePanel/Files/PanelTable.tsx +++ b/client/src/components/SidePanel/Files/PanelTable.tsx @@ -1,4 +1,4 @@ -import { useState, useCallback } from 'react'; +import { useState, useCallback, useMemo } from 'react'; import { ArrowUpLeft } from 'lucide-react'; import { useSetRecoilState } from 'recoil'; import { @@ -8,12 +8,10 @@ import { getPaginationRowModel, getSortedRowModel, useReactTable, -} from '@tanstack/react-table'; -import type { - ColumnDef, - SortingState, - VisibilityState, - ColumnFiltersState, + type ColumnDef, + type SortingState, + type VisibilityState, + type ColumnFiltersState, } from '@tanstack/react-table'; import { fileConfig as defaultFileConfig, @@ -21,8 +19,9 @@ import { mergeFileConfig, megabyte, isAssistantsEndpoint, + type TFile, } from 'librechat-data-provider'; -import type { AugmentedColumnDef } from '~/common'; + import { Button, Input, @@ -45,16 +44,29 @@ interface DataTableProps { export default function DataTable({ columns, data }: DataTableProps) { const localize = useLocalize(); - const [rowSelection, setRowSelection] = useState({}); const [sorting, setSorting] = useState([]); const [columnFilters, setColumnFilters] = useState([]); const [columnVisibility, setColumnVisibility] = useState({}); - const [paginationState, setPagination] = useState({ pageIndex: 0, pageSize: 10 }); + const [{ pageIndex, pageSize }, setPagination] = useState({ pageIndex: 0, pageSize: 10 }); const setShowFiles = useSetRecoilState(store.showFiles); + const pagination = useMemo( + () => ({ + pageIndex, + pageSize, + }), + [pageIndex, pageSize], + ); + const table = useReactTable({ data, columns, + state: { + sorting, + columnFilters, + columnVisibility, + pagination, + }, onSortingChange: setSorting, onPaginationChange: setPagination, getCoreRowModel: getCoreRowModel(), @@ -63,14 +75,6 @@ export default function DataTable({ columns, data }: DataTablePro getFilteredRowModel: getFilteredRowModel(), onColumnVisibilityChange: setColumnVisibility, getPaginationRowModel: getPaginationRowModel(), - onRowSelectionChange: setRowSelection, - state: { - sorting, - columnFilters, - columnVisibility, - rowSelection, - pagination: paginationState, - }, defaultColumn: { minSize: 0, size: 10, @@ -78,6 +82,7 @@ export default function DataTable({ columns, data }: DataTablePro enableResizing: true, }, }); + const fileMap = useFileMapContext(); const { showToast } = useToastContext(); const { setFiles, conversation } = useChatContext(); @@ -86,76 +91,87 @@ export default function DataTable({ columns, data }: DataTablePro }); const { addFile } = useUpdateFiles(setFiles); - const handleFileClick = useCallback((file: any) => { - const endpoint = conversation?.endpoint; - const fileData = fileMap?.[file.file_id]; + const handleFileClick = useCallback( + (file: TFile) => { + if (!fileMap?.[file.file_id] || !conversation?.endpoint) { + showToast({ + message: localize('com_ui_attach_error'), + status: 'error', + }); + return; + } - if (!fileData) { - return; - } + const fileData = fileMap[file.file_id]; + const endpoint = conversation.endpoint; - if (!endpoint) { - return showToast({ message: localize('com_ui_attach_error'), status: 'error' }); - } + if (!fileData.source) { + return; + } - if (!fileData.source) { - return; - } + const isOpenAIStorage = checkOpenAIStorage(fileData.source); + const isAssistants = isAssistantsEndpoint(endpoint); - if (checkOpenAIStorage(fileData.source) && !isAssistantsEndpoint(endpoint)) { - return showToast({ - message: localize('com_ui_attach_error_openai'), - status: 'error', - }); - } else if (!checkOpenAIStorage(fileData.source) && isAssistantsEndpoint(endpoint)) { - showToast({ - message: localize('com_ui_attach_warn_endpoint'), - status: 'warning', - }); - } + if (isOpenAIStorage && !isAssistants) { + showToast({ + message: localize('com_ui_attach_error_openai'), + status: 'error', + }); + return; + } - const { fileSizeLimit, supportedMimeTypes } = - fileConfig.endpoints[endpoint] ?? fileConfig.endpoints.default; + if (!isOpenAIStorage && isAssistants) { + showToast({ + message: localize('com_ui_attach_warn_endpoint'), + status: 'warning', + }); + } - if (fileData.bytes > fileSizeLimit) { - return showToast({ - message: `${localize('com_ui_attach_error_size')} ${ - fileSizeLimit / megabyte - } MB (${endpoint})`, - status: 'error', - }); - } + const { fileSizeLimit, supportedMimeTypes } = + fileConfig.endpoints[endpoint] ?? fileConfig.endpoints.default; - const isSupportedMimeType = defaultFileConfig.checkType(file.type, supportedMimeTypes); + if (fileData.bytes > fileSizeLimit) { + showToast({ + message: `${localize('com_ui_attach_error_size')} ${ + fileSizeLimit / megabyte + } MB (${endpoint})`, + status: 'error', + }); + return; + } - if (!isSupportedMimeType) { - return showToast({ - message: `${localize('com_ui_attach_error_type')} ${file.type} (${endpoint})`, - status: 'error', + if (!defaultFileConfig.checkType(file.type, supportedMimeTypes)) { + showToast({ + message: `${localize('com_ui_attach_error_type')} ${file.type} (${endpoint})`, + status: 'error', + }); + return; + } + + addFile({ + progress: 1, + attached: true, + file_id: fileData.file_id, + filepath: fileData.filepath, + preview: fileData.filepath, + type: fileData.type, + height: fileData.height, + width: fileData.width, + filename: fileData.filename, + source: fileData.source, + size: fileData.bytes, }); - } + }, + [addFile, fileMap, conversation, localize, showToast, fileConfig.endpoints], + ); - addFile({ - progress: 1, - attached: true, - file_id: fileData.file_id, - filepath: fileData.filepath, - preview: fileData.filepath, - type: fileData.type, - height: fileData.height, - width: fileData.width, - filename: fileData.filename, - source: fileData.source, - size: fileData.bytes, - }); - }, [addFile, fileMap, conversation, localize, showToast, fileConfig.endpoints]); + const filenameFilter = table.getColumn('filename')?.getFilterValue() as string; return (
table.getColumn('filename')?.setFilterValue(event.target.value)} aria-label={localize('com_files_filter')} /> @@ -165,14 +181,13 @@ export default function DataTable({ columns, data }: DataTablePro
- {table.getHeaderGroups().map((headerGroup, index) => ( + {table.getHeaderGroups().map((headerGroup) => ( - {headerGroup.headers.map((header) => ( + {headerGroup.headers.map((header, index) => (
{header.isPlaceholder @@ -190,23 +205,55 @@ export default function DataTable({ columns, data }: DataTablePro - {row.getVisibleCells().map((cell) => ( - handleFileClick(row.original)} - className="rounded-lg p-4 text-sm text-text-primary outline-none focus-visible:bg-surface-active" - style={{ - maxWidth: (cell.column.columnDef as AugmentedColumnDef) - .meta?.size, - }} - tabIndex={0} - role="gridcell" - > - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ))} + {row.getVisibleCells().map((cell) => { + const isFilenameCell = cell.column.id === 'filename'; + + return ( + { + if (isFilenameCell) { + const target = e.target as Node; + const cell = e.currentTarget as Node; + // Check if click was directly on cell or text content + if ( + target === cell || + (cell.contains(target) && + (target.nodeType === Node.TEXT_NODE || + (target as HTMLElement).tagName === 'TD')) + ) { + e.preventDefault(); + e.stopPropagation(); + handleFileClick(row.original as TFile); + } + } + }} + onKeyDown={(e) => { + if (isFilenameCell && (e.key === 'Enter' || e.key === ' ')) { + const target = e.target as Node; + const cell = e.currentTarget as Node; + // Check if click was directly on cell or text content + if ( + target === cell || + (cell.contains(target) && + (target.nodeType === Node.TEXT_NODE || + (target as HTMLElement).tagName === 'TD')) + ) { + e.preventDefault(); + e.stopPropagation(); + handleFileClick(row.original as TFile); + } + } + }} + > + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ); + })} )) ) : ( @@ -232,10 +279,10 @@ export default function DataTable({ columns, data }: DataTablePro aria-label={localize('com_sidepanel_manage_files')} > - {localize('com_sidepanel_manage_files')} + {localize('com_sidepanel_manage_files')} -
+
+
+ {`${pageIndex + 1} / ${table.getPageCount()}`} +
); -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index c7912cba0eb..34437fbf576 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1041,7 +1041,6 @@ "react-dnd-html5-backend": "^16.0.1", "react-dom": "^18.2.0", "react-flip-toolkit": "^7.1.0", - "react-focus-lock": "^2.13.5", "react-gtm-module": "^2.0.11", "react-hook-form": "^7.43.9", "react-lazy-load-image-component": "^1.6.0", @@ -20451,18 +20450,6 @@ "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, - "node_modules/focus-lock": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-1.3.5.tgz", - "integrity": "sha512-QFaHbhv9WPUeLYBDe/PAuLKJ4Dd9OPvKs9xZBr3yLXnUrDNaVXKu2baDBXe3naPY30hgHYSsf2JW4jzas2mDEQ==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.3" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/follow-redirects": { "version": "1.15.6", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", @@ -30810,18 +30797,6 @@ "react-dom": "^0.14.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" } }, - "node_modules/react-clientside-effect": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/react-clientside-effect/-/react-clientside-effect-1.2.7.tgz", - "integrity": "sha512-gce9m0Pk/xYYMEojRI9bgvqQAkl6hm7ozQvqWPyQx+kULiatdHgkNM1QG4DQRx5N9BAzWSCJmt9mMV8/KsdgVg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.13" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - } - }, "node_modules/react-devtools-inline": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/react-devtools-inline/-/react-devtools-inline-4.4.0.tgz", @@ -30896,29 +30871,6 @@ "react-dom": ">= 16.x" } }, - "node_modules/react-focus-lock": { - "version": "2.13.5", - "resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.13.5.tgz", - "integrity": "sha512-HjHuZFFk2+j6ZT3LDQpyqffue541HrxUG/OFchCEwis9nstgNg0rREVRAxHBcB1lHJ5Fsxtx1qya/5xFwxDb4g==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.0.0", - "focus-lock": "^1.3.5", - "prop-types": "^15.6.2", - "react-clientside-effect": "^1.2.6", - "use-callback-ref": "^1.3.2", - "use-sidecar": "^1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/react-gtm-module": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/react-gtm-module/-/react-gtm-module-2.0.11.tgz", From 40225c876d11292e4244b209e633791b01a7acef Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Mon, 23 Dec 2024 02:29:44 +0100 Subject: [PATCH 3/4] =?UTF-8?q?=E2=9C=A8=20refactor:=20enhance=20modal=20c?= =?UTF-8?q?lose=20behavior=20and=20prevent=20refocus=20on=20certain=20elem?= =?UTF-8?q?ents?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Chat/Input/Files/ImagePreview.tsx | 30 ++++++++++++++----- .../components/SidePanel/Files/PanelTable.tsx | 22 +++++--------- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/client/src/components/Chat/Input/Files/ImagePreview.tsx b/client/src/components/Chat/Input/Files/ImagePreview.tsx index ee6caf5fefd..cd02ecf7236 100644 --- a/client/src/components/Chat/Input/Files/ImagePreview.tsx +++ b/client/src/components/Chat/Input/Files/ImagePreview.tsx @@ -12,6 +12,11 @@ type styleProps = { backgroundRepeat?: string; }; +interface CloseModalEvent { + stopPropagation: () => void; + preventDefault: () => void; +} + const ImagePreview = ({ imageBase64, url, @@ -36,17 +41,26 @@ const ImagePreview = ({ setIsModalOpen(true); }, []); - const closeModal = useCallback(() => { - setIsModalOpen(false); - if (previousActiveElement instanceof HTMLElement) { - previousActiveElement.focus(); - } - }, [previousActiveElement]); + const closeModal = useCallback( + (e: CloseModalEvent): void => { + setIsModalOpen(false); + e.stopPropagation(); + e.preventDefault(); + + if ( + previousActiveElement instanceof HTMLElement && + !previousActiveElement.closest('[data-skip-refocus="true"]') + ) { + previousActiveElement.focus(); + } + }, + [previousActiveElement], + ); const handleKeyDown = useCallback( (e: KeyboardEvent) => { if (e.key === 'Escape') { - closeModal(); + closeModal(e); } }, [closeModal], @@ -157,7 +171,7 @@ const ImagePreview = ({ className="absolute right-4 top-4 z-[1000] rounded-full p-2 text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white" onClick={(e) => { e.stopPropagation(); - closeModal(); + closeModal(e); }} aria-label="Close full view" > diff --git a/client/src/components/SidePanel/Files/PanelTable.tsx b/client/src/components/SidePanel/Files/PanelTable.tsx index d06cef44359..8098aae9988 100644 --- a/client/src/components/SidePanel/Files/PanelTable.tsx +++ b/client/src/components/SidePanel/Files/PanelTable.tsx @@ -212,19 +212,17 @@ export default function DataTable({ columns, data }: DataTablePro return ( { if (isFilenameCell) { - const target = e.target as Node; - const cell = e.currentTarget as Node; - // Check if click was directly on cell or text content + const clickedElement = e.target as HTMLElement; + // Check if clicked element is within cell and not a button/link if ( - target === cell || - (cell.contains(target) && - (target.nodeType === Node.TEXT_NODE || - (target as HTMLElement).tagName === 'TD')) + clickedElement.closest('td') && + !clickedElement.closest('button, a') ) { e.preventDefault(); e.stopPropagation(); @@ -234,14 +232,10 @@ export default function DataTable({ columns, data }: DataTablePro }} onKeyDown={(e) => { if (isFilenameCell && (e.key === 'Enter' || e.key === ' ')) { - const target = e.target as Node; - const cell = e.currentTarget as Node; - // Check if click was directly on cell or text content + const clickedElement = e.target as HTMLElement; if ( - target === cell || - (cell.contains(target) && - (target.nodeType === Node.TEXT_NODE || - (target as HTMLElement).tagName === 'TD')) + clickedElement.closest('td') && + !clickedElement.closest('button, a') ) { e.preventDefault(); e.stopPropagation(); From 5cdc306425880c5131e2b0b0ca4dacb0908c2836 Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Mon, 23 Dec 2024 02:56:51 +0100 Subject: [PATCH 4/4] =?UTF-8?q?=E2=9C=A8=20refactor:=20enhance=20file=20ro?= =?UTF-8?q?w=20layout=20and=20improve=20image=20preview=20animation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/Chat/Input/Files/FileRow.tsx | 5 +++-- client/src/components/Chat/Input/Files/ImagePreview.tsx | 2 +- client/src/components/Chat/Input/Files/RemoveFile.tsx | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/client/src/components/Chat/Input/Files/FileRow.tsx b/client/src/components/Chat/Input/Files/FileRow.tsx index a7c07123cc0..7792fc23f3f 100644 --- a/client/src/components/Chat/Input/Files/FileRow.tsx +++ b/client/src/components/Chat/Input/Files/FileRow.tsx @@ -73,8 +73,9 @@ export default function FileRow({ } const renderFiles = () => { - // Inline style for RTL - const rowStyle = isRTL ? { display: 'flex', flexDirection: 'row-reverse' } : {}; + const rowStyle = isRTL + ? { display: 'flex', flexDirection: 'row-reverse', gap: '4px' } + : { display: 'flex', gap: '4px' }; return (
diff --git a/client/src/components/Chat/Input/Files/ImagePreview.tsx b/client/src/components/Chat/Input/Files/ImagePreview.tsx index cd02ecf7236..00adc643ceb 100644 --- a/client/src/components/Chat/Input/Files/ImagePreview.tsx +++ b/client/src/components/Chat/Input/Files/ImagePreview.tsx @@ -191,7 +191,7 @@ const ImagePreview = ({
void }) { return (