LibreChat/packages/client/src/components/DataTable/DataTableComponents.tsx
Danny Avila bfb6b224d2
🔧 chore: Update ESLint config, Import Sorting script, Test Sharding, Bump @librechat/agents (#13552)
* 🔧 chore: Update ESLint config, add import sorting script, Test Sharding, Bump `@librechat/agents`

* Change 'no-nested-ternary' rule from 'warn' to 'error' in ESLint config
* Add new scripts for sorting imports in the project
* Update lint-staged configuration to include import sorting
* Modify GitHub Actions workflows to support sharding for unit tests

* chore: remove nested ternary expressions

* refactor: Extract scale multiplier logic into a separate function in CircleRender component
* refactor: Simplify auto-refill rendering logic in Balance component for better readability
* refactor: Improve width style handling in DataTable components for clarity and maintainability

* chore: remove CircleRender component

* delete: Remove CircleRender component as it is no longer needed in the project

* chore: Bump @librechat/agents to version 3.2.31 and update Node.js engine requirement

* Update @librechat/agents dependency from 3.2.2 to 3.2.31 in package-lock.json, api/package.json, and packages/api/package.json
* Change Node.js engine requirement from >=20.0.0 to >=24.0.0 in @librechat/agents

* chore: Add import sorting check to ESLint CI workflow

* Implement a new job in the GitHub Actions workflow to verify import ordering on changed files.
* The job checks for changes in specific file types and reports any import order drift, providing instructions for local fixes.
2026-06-06 12:31:55 -04:00

168 lines
5.2 KiB
TypeScript

import React, { memo, forwardRef } from 'react';
import { flexRender } from '@tanstack/react-table';
import type { Row } from '@tanstack/react-table';
import type { TableColumn } from './DataTable.types';
import { TableCell, TableRow, TableRowHeader } from '../Table';
import { Checkbox } from '../Checkbox';
import { Skeleton } from '../Skeleton';
import { cn } from '~/utils';
export const SelectionCheckbox = memo(
({
checked,
onChange,
ariaLabel,
}: {
checked: boolean;
onChange: (value: boolean) => void;
ariaLabel: string;
}) => (
<div
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
onChange(!checked);
}
e.stopPropagation();
}}
className="flex h-full w-8 items-center justify-center"
onClick={(e) => {
e.stopPropagation();
onChange(!checked);
}}
>
<Checkbox checked={checked} onCheckedChange={onChange} aria-label={ariaLabel} />
</div>
),
);
SelectionCheckbox.displayName = 'SelectionCheckbox';
interface TableRowComponentProps<TData extends Record<string, unknown>> {
row: Row<TData>;
virtualIndex?: number;
style?: React.CSSProperties;
selected: boolean;
}
// ...existing code...
const TableRowComponent = <TData extends Record<string, unknown>>(
{ row, virtualIndex, style, selected }: TableRowComponentProps<TData>,
ref: React.Ref<HTMLTableRowElement>,
) => {
// Check if we're on mobile - use window.innerWidth for component-level check
const isSmallScreen = typeof window !== 'undefined' && window.innerWidth < 768;
return (
<TableRow
ref={ref}
data-state={selected ? 'selected' : undefined}
data-index={virtualIndex}
className="border-none hover:bg-surface-secondary"
style={style}
>
{row.getVisibleCells().map((cell) => {
const meta = cell.column.columnDef.meta as
| { className?: string; desktopOnly?: boolean; width?: number; isRowHeader?: boolean }
| undefined;
const isDesktopOnly = meta?.desktopOnly;
const isRowHeader = meta?.isRowHeader;
const percent = meta?.width;
let widthStyle: React.CSSProperties | undefined;
if (cell.column.id === 'select') {
widthStyle = { width: '32px', maxWidth: '32px', minWidth: '32px' };
} else if (percent) {
widthStyle = {
width: `${percent}%`,
maxWidth: `${percent}%`,
minWidth: `${percent}%`, // Don't shrink on mobile
};
}
const CellComponent = isRowHeader ? TableRowHeader : TableCell;
// For desktop-only columns on mobile, keep them in DOM but visually hidden
// This ensures screen readers can still access the content
const cellProps =
isDesktopOnly && isSmallScreen
? { 'aria-hidden': false as const } // Keep accessible to screen readers
: {};
return (
<CellComponent
key={cell.id}
className={cn(
'max-w-0 truncate px-2 py-2 md:px-3 md:py-3',
cell.column.id === 'select' && 'w-8 p-1',
meta?.className,
isDesktopOnly && 'hidden md:table-cell',
)}
style={widthStyle}
{...cellProps}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</CellComponent>
);
})}
</TableRow>
);
};
// ...existing code...
type ForwardTableRowComponentType = <TData extends Record<string, unknown>>(
props: TableRowComponentProps<TData> & React.RefAttributes<HTMLTableRowElement>,
) => JSX.Element;
const ForwardTableRowComponent = forwardRef(TableRowComponent) as ForwardTableRowComponentType;
interface GenericRowProps {
row: Row<Record<string, unknown>>;
virtualIndex?: number;
style?: React.CSSProperties;
selected: boolean;
}
export const MemoizedTableRow = memo(
ForwardTableRowComponent as (props: GenericRowProps) => JSX.Element,
(prev: GenericRowProps, next: GenericRowProps) =>
prev.row.original === next.row.original && prev.selected === next.selected,
);
export const SkeletonRows = memo(
<TData extends Record<string, unknown>, TValue>({
count = 10,
columns,
}: {
count?: number;
columns: TableColumn<TData, TValue>[];
}) => (
<>
{Array.from({ length: count }, (_, index) => (
<TableRow key={`skeleton-${index}`} className="h-[56px] border-b border-border-light">
{columns.map((column) => {
const columnKey = String(
column.id ?? ('accessorKey' in column && column.accessorKey) ?? '',
);
const meta = column.meta as { className?: string; desktopOnly?: boolean } | undefined;
return (
<TableCell
key={columnKey}
className={cn(
'px-2 py-2 md:px-3',
meta?.className,
meta?.desktopOnly && 'hidden md:table-cell',
)}
>
<Skeleton className="h-6 w-full" />
</TableCell>
);
})}
</TableRow>
))}
</>
),
);
SkeletonRows.displayName = 'SkeletonRows';