mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-26 13:18:51 +01:00
refactor(DataTable): improve column sizing and visibility handling; remove deprecated features
This commit is contained in:
parent
1cd82247ce
commit
9c5bbdaa28
10 changed files with 179 additions and 147 deletions
|
|
@ -132,7 +132,7 @@ export default function Conversation({ conversation, retainView, toggleNav }: Co
|
|||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'group relative flex h-12 w-full items-center rounded-lg transition-colors duration-200 md:h-9',
|
||||
'group relative flex h-12 w-full items-center rounded-lg md:h-9',
|
||||
isActiveConvo ? 'bg-surface-active-alt' : 'hover:bg-surface-active-alt',
|
||||
)}
|
||||
role="button"
|
||||
|
|
|
|||
|
|
@ -129,17 +129,6 @@ export default function ArchivedChatsTable() {
|
|||
[],
|
||||
);
|
||||
|
||||
const handleError = useCallback(
|
||||
(error: Error) => {
|
||||
console.error('DataTable error:', error);
|
||||
showToast({
|
||||
message: localize('com_ui_unarchive_error'),
|
||||
severity: NotificationSeverity.ERROR,
|
||||
});
|
||||
},
|
||||
[showToast, localize],
|
||||
);
|
||||
|
||||
const flattenedConversations = useMemo(
|
||||
() => data?.pages?.flatMap((page) => page?.conversations?.filter(Boolean) ?? []) ?? [],
|
||||
[data?.pages],
|
||||
|
|
@ -259,7 +248,8 @@ export default function ArchivedChatsTable() {
|
|||
);
|
||||
},
|
||||
meta: {
|
||||
className: 'min-w-[150px] flex-1',
|
||||
width: 65,
|
||||
className: 'min-w-[150px]',
|
||||
},
|
||||
enableSorting: true,
|
||||
},
|
||||
|
|
@ -279,8 +269,9 @@ export default function ArchivedChatsTable() {
|
|||
return formatDate(convo.createdAt?.toString() ?? '', isSmallScreen);
|
||||
},
|
||||
meta: {
|
||||
className: 'w-32 sm:w-40',
|
||||
// desktopOnly: true, // Potential future use
|
||||
width: 20,
|
||||
className: 'min-w-[6rem]',
|
||||
desktopOnly: true,
|
||||
},
|
||||
enableSorting: true,
|
||||
},
|
||||
|
|
@ -298,13 +289,13 @@ export default function ArchivedChatsTable() {
|
|||
const isRowUnarchiving = unarchivingId === convo.conversationId;
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-1.5 md:gap-2">
|
||||
<TooltipAnchor
|
||||
description={localize('com_ui_unarchive')}
|
||||
render={
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="h-8 w-8 p-0 hover:bg-surface-hover"
|
||||
className="h-9 w-9 p-0 hover:bg-surface-hover md:h-8 md:w-8"
|
||||
onClick={() => {
|
||||
const conversationId = convo.conversationId;
|
||||
if (!conversationId) return;
|
||||
|
|
@ -322,7 +313,7 @@ export default function ArchivedChatsTable() {
|
|||
render={
|
||||
<Button
|
||||
variant="destructive"
|
||||
className="h-8 w-8 p-0"
|
||||
className="h-9 w-9 p-0 md:h-8 md:w-8"
|
||||
onClick={() => {
|
||||
setDeleteRow(convo);
|
||||
setIsDeleteOpen(true);
|
||||
|
|
@ -337,7 +328,8 @@ export default function ArchivedChatsTable() {
|
|||
);
|
||||
},
|
||||
meta: {
|
||||
className: 'w-24',
|
||||
width: 30,
|
||||
className: 'min-w-[5rem]',
|
||||
},
|
||||
enableSorting: false,
|
||||
},
|
||||
|
|
@ -358,12 +350,6 @@ export default function ArchivedChatsTable() {
|
|||
<OGDialogHeader>
|
||||
<OGDialogTitle>{localize('com_nav_archived_chats')}</OGDialogTitle>
|
||||
</OGDialogHeader>
|
||||
{/*
|
||||
Server-only sorting strategy:
|
||||
- Query key changes (sort/search) trigger immediate empty state (keepPreviousData:false)
|
||||
- DataTable shows skeletons instead of client re-sorting stale rows
|
||||
- No local caching/aggregation layer (flatten derived straight from query pages)
|
||||
*/}
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={flattenedConversations}
|
||||
|
|
@ -388,7 +374,6 @@ export default function ArchivedChatsTable() {
|
|||
isFetchingNextPage={isFetchingNextPage}
|
||||
sorting={sorting}
|
||||
onSortingChange={handleSortingChange}
|
||||
onError={handleError}
|
||||
/>
|
||||
</OGDialogContent>
|
||||
</OGDialog>
|
||||
|
|
|
|||
|
|
@ -15,11 +15,9 @@ import type { DataTableProps, ProcessedDataRow } from './DataTable.types';
|
|||
import { SelectionCheckbox, MemoizedTableRow, SkeletonRows } from './DataTableComponents';
|
||||
import { Table, TableBody, TableHead, TableHeader, TableCell, TableRow } from '../Table';
|
||||
import { useDebounced, useOptimizedRowSelection } from './DataTable.hooks';
|
||||
import { DataTableErrorBoundary } from './DataTableErrorBoundary';
|
||||
import { useMediaQuery, useLocalize } from '~/hooks';
|
||||
import { DataTableSearch } from './DataTableSearch';
|
||||
import { cn, logger } from '~/utils';
|
||||
import { Button } from '../Button';
|
||||
import { Label } from '../Label';
|
||||
import { Spinner } from '~/svgs';
|
||||
|
||||
|
|
@ -36,7 +34,6 @@ function DataTable<TData extends Record<string, unknown>, TValue>({
|
|||
isFetchingNextPage = false,
|
||||
hasNextPage = false,
|
||||
fetchNextPage,
|
||||
onReset,
|
||||
sorting,
|
||||
onSortingChange,
|
||||
customActionsRenderer,
|
||||
|
|
@ -84,7 +81,6 @@ function DataTable<TData extends Record<string, unknown>, TValue>({
|
|||
|
||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
|
||||
const [optimizedRowSelection, setOptimizedRowSelection] = useOptimizedRowSelection();
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
const [searchTerm, setSearchTerm] = useState(filterValue);
|
||||
const [internalSorting, setInternalSorting] = useState<SortingState>(defaultSort);
|
||||
|
||||
|
|
@ -123,6 +119,17 @@ function DataTable<TData extends Record<string, unknown>, TValue>({
|
|||
const debouncedTerm = useDebounced(searchTerm, debounceDelay);
|
||||
const finalSorting = sorting ?? internalSorting;
|
||||
|
||||
/**
|
||||
* Mobile responsive column visibility system
|
||||
*
|
||||
* Calculates which columns should be hidden on mobile devices based on the `desktopOnly` meta property.
|
||||
* When a column has `meta.desktopOnly: true`, it will be hidden on viewports < 768px (mobile).
|
||||
*
|
||||
* This works in conjunction with:
|
||||
* - Header rendering (lines 479-485): Conditionally renders headers based on visibility
|
||||
* - Cell rendering (DataTableComponents.tsx): Applies CSS classes to hide cells
|
||||
* - Skeleton rendering (DataTableComponents.tsx): Applies same CSS to skeleton cells
|
||||
*/
|
||||
const calculatedVisibility = useMemo(() => {
|
||||
const newVisibility: VisibilityState = {};
|
||||
columns.forEach((col) => {
|
||||
|
|
@ -171,6 +178,7 @@ function DataTable<TData extends Record<string, unknown>, TValue>({
|
|||
|
||||
const selectColumn: ColumnDef<TData, TValue> = {
|
||||
id: 'select',
|
||||
enableResizing: false,
|
||||
header: () => {
|
||||
const extraCheckboxProps = (isIndeterminate ? { indeterminate: true } : {}) as Record<
|
||||
string,
|
||||
|
|
@ -220,16 +228,29 @@ function DataTable<TData extends Record<string, unknown>, TValue>({
|
|||
);
|
||||
},
|
||||
meta: {
|
||||
className: 'w-12',
|
||||
className: 'max-w-[20px] flex-1',
|
||||
},
|
||||
};
|
||||
|
||||
return [selectColumn, ...columns.map((col) => col as unknown as ColumnDef<TData, TValue>)];
|
||||
}, [columns, enableRowSelection, showCheckboxes, localize]);
|
||||
}, [
|
||||
columns,
|
||||
enableRowSelection,
|
||||
showCheckboxes,
|
||||
localize,
|
||||
data,
|
||||
getRowId,
|
||||
isAllSelected,
|
||||
isIndeterminate,
|
||||
setOptimizedRowSelection,
|
||||
]);
|
||||
|
||||
// No transformation for sizing; width handled via meta.width percentages.
|
||||
const sizedColumns = tableColumns;
|
||||
|
||||
const table = useReactTable<TData>({
|
||||
data,
|
||||
columns: tableColumns,
|
||||
columns: sizedColumns,
|
||||
getRowId: getRowId,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
enableRowSelection,
|
||||
|
|
@ -246,6 +267,8 @@ function DataTable<TData extends Record<string, unknown>, TValue>({
|
|||
onRowSelectionChange: setOptimizedRowSelection,
|
||||
});
|
||||
|
||||
// Removed column size initialization (deprecated)
|
||||
|
||||
const rowVirtualizer = useVirtualizer({
|
||||
enabled: virtualizationActive,
|
||||
count: data.length,
|
||||
|
|
@ -267,15 +290,60 @@ function DataTable<TData extends Record<string, unknown>, TValue>({
|
|||
const showSkeletons = isLoading || (isFetching && !isFetchingNextPage);
|
||||
const shouldShowSearch = enableSearch && onFilterChange;
|
||||
|
||||
// useEffect(() => {
|
||||
// if (data.length > 1000) {
|
||||
// const cleanup = setTimeout(() => {
|
||||
// rowVirtualizer.scrollToIndex(0, { align: 'start' });
|
||||
// rowVirtualizer.measure();
|
||||
// }, 1000);
|
||||
// return () => clearTimeout(cleanup);
|
||||
// }
|
||||
// }, [data.length, rowVirtualizer]);
|
||||
let tableBodyContent: React.ReactNode;
|
||||
if (showSkeletons) {
|
||||
tableBodyContent = (
|
||||
<SkeletonRows
|
||||
count={skeletonCount}
|
||||
columns={tableColumns as ColumnDef<Record<string, unknown>>[]}
|
||||
/>
|
||||
);
|
||||
} else if (virtualizationActive) {
|
||||
tableBodyContent = (
|
||||
<>
|
||||
{paddingTop > 0 && (
|
||||
<TableRow aria-hidden="true">
|
||||
<TableCell
|
||||
colSpan={tableColumns.length}
|
||||
style={{ height: paddingTop, padding: 0, border: 0 }}
|
||||
/>
|
||||
</TableRow>
|
||||
)}
|
||||
{virtualRows.map((virtualRow) => {
|
||||
const row = rows[virtualRow.index];
|
||||
if (!row) return null;
|
||||
|
||||
return (
|
||||
<MemoizedTableRow
|
||||
key={virtualRow.key}
|
||||
row={row as unknown as Row<Record<string, unknown>>}
|
||||
virtualIndex={virtualRow.index}
|
||||
selected={row.getIsSelected()}
|
||||
style={{ height: rowHeight }}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{paddingBottom > 0 && (
|
||||
<TableRow aria-hidden="true">
|
||||
<TableCell
|
||||
colSpan={tableColumns.length}
|
||||
style={{ height: paddingBottom, padding: 0, border: 0 }}
|
||||
/>
|
||||
</TableRow>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
tableBodyContent = rows.map((row) => (
|
||||
<MemoizedTableRow
|
||||
key={getRowId(row.original as TData, row.index)}
|
||||
row={row as unknown as Row<Record<string, unknown>>}
|
||||
virtualIndex={row.index}
|
||||
selected={row.getIsSelected()}
|
||||
style={{ height: rowHeight }}
|
||||
/>
|
||||
));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setSearchTerm(filterValue);
|
||||
|
|
@ -295,6 +363,8 @@ function DataTable<TData extends Record<string, unknown>, TValue>({
|
|||
rowVirtualizer.calculateRange();
|
||||
}, [data.length, finalSorting, columnVisibility, virtualizationActive, rowVirtualizer]);
|
||||
|
||||
// Removed manual column sizing dependency effect
|
||||
|
||||
// ResizeObserver to re-measure when container size changes
|
||||
useEffect(() => {
|
||||
if (!virtualizationActive) return;
|
||||
|
|
@ -374,24 +444,6 @@ function DataTable<TData extends Record<string, unknown>, TValue>({
|
|||
};
|
||||
}, [handleScroll, cleanupTimers]);
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
setError(null);
|
||||
setOptimizedRowSelection({});
|
||||
setSearchTerm('');
|
||||
onReset?.();
|
||||
}, [onReset, setOptimizedRowSelection]);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<DataTableErrorBoundary onReset={handleReset}>
|
||||
<div className="flex flex-col items-center justify-center p-8">
|
||||
<p className="mb-4 text-red-500">{error.message}</p>
|
||||
<Button onClick={handleReset}>{localize('com_ui_retry')}</Button>
|
||||
</div>
|
||||
</DataTableErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
|
|
@ -402,7 +454,7 @@ function DataTable<TData extends Record<string, unknown>, TValue>({
|
|||
role="region"
|
||||
aria-label={localize('com_ui_data_table')}
|
||||
>
|
||||
<div className="flex w-full shrink-0 items-center gap-3 border-b border-border-light">
|
||||
<div className="flex w-full shrink-0 items-center gap-2 border-b border-border-light md:gap-3">
|
||||
{shouldShowSearch && <DataTableSearch value={searchTerm} onChange={setSearchTerm} />}
|
||||
{customActionsRenderer &&
|
||||
customActionsRenderer({
|
||||
|
|
@ -424,15 +476,22 @@ function DataTable<TData extends Record<string, unknown>, TValue>({
|
|||
aria-label={localize('com_ui_data_table_scroll_area')}
|
||||
aria-describedby={showSkeletons ? 'loading-status' : undefined}
|
||||
>
|
||||
<Table role="table" aria-label={localize('com_ui_data_table')} aria-rowcount={data.length}>
|
||||
<Table
|
||||
role="table"
|
||||
aria-label={localize('com_ui_data_table')}
|
||||
aria-rowcount={data.length}
|
||||
className="table-fixed"
|
||||
>
|
||||
<TableHeader className="sticky top-0 z-10 bg-surface-secondary">
|
||||
{headerGroups.map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => {
|
||||
// Check if this column should be hidden on mobile (desktopOnly feature)
|
||||
const isDesktopOnly =
|
||||
(header.column.columnDef.meta as { desktopOnly?: boolean } | undefined)
|
||||
?.desktopOnly ?? false;
|
||||
|
||||
// Hide header if column is not visible or if it's desktop-only on a mobile viewport
|
||||
if (!header.column.getIsVisible() || (isSmallScreen && isDesktopOnly)) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -465,15 +524,24 @@ function DataTable<TData extends Record<string, unknown>, TValue>({
|
|||
}
|
||||
};
|
||||
|
||||
const metaWidth = (header.column.columnDef.meta as { width?: number } | undefined)
|
||||
?.width;
|
||||
const widthStyle = isSelectHeader
|
||||
? { width: '32px', maxWidth: '32px' }
|
||||
: metaWidth && metaWidth >= 1 && metaWidth <= 100
|
||||
? { width: `${metaWidth}%`, maxWidth: `${metaWidth}%` }
|
||||
: {};
|
||||
return (
|
||||
<TableHead
|
||||
key={header.id}
|
||||
className={cn(
|
||||
'border-b border-border-light py-2',
|
||||
isSelectHeader ? 'px-0 text-center' : 'px-3',
|
||||
'border-b border-border-light px-2 py-2 md:px-3 md:py-2',
|
||||
isSelectHeader && 'px-0 text-center',
|
||||
canSort && 'cursor-pointer hover:bg-surface-tertiary',
|
||||
meta?.className,
|
||||
header.column.getIsResizing() && 'bg-surface-tertiary/60',
|
||||
)}
|
||||
style={widthStyle}
|
||||
onClick={header.column.getToggleSortingHandler()}
|
||||
onKeyDown={handleSortingKeyDown}
|
||||
role={canSort ? 'button' : undefined}
|
||||
|
|
@ -490,7 +558,7 @@ function DataTable<TData extends Record<string, unknown>, TValue>({
|
|||
{isSelectHeader ? (
|
||||
flexRender(header.column.columnDef.header, header.getContext())
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-1 md:gap-2">
|
||||
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||
{canSort && (
|
||||
<span className="text-text-primary" aria-hidden="true">
|
||||
|
|
@ -504,6 +572,7 @@ function DataTable<TData extends Record<string, unknown>, TValue>({
|
|||
)}
|
||||
</div>
|
||||
)}
|
||||
{/* Resizer removed (manual resizing deprecated) */}
|
||||
</TableHead>
|
||||
);
|
||||
})}
|
||||
|
|
@ -512,54 +581,7 @@ function DataTable<TData extends Record<string, unknown>, TValue>({
|
|||
</TableHeader>
|
||||
|
||||
<TableBody>
|
||||
{showSkeletons ? (
|
||||
<SkeletonRows
|
||||
count={skeletonCount}
|
||||
columns={tableColumns as ColumnDef<Record<string, unknown>>[]}
|
||||
/>
|
||||
) : virtualizationActive ? (
|
||||
<>
|
||||
{paddingTop > 0 && (
|
||||
<TableRow aria-hidden="true">
|
||||
<TableCell
|
||||
colSpan={tableColumns.length}
|
||||
style={{ height: paddingTop, padding: 0, border: 0 }}
|
||||
/>
|
||||
</TableRow>
|
||||
)}
|
||||
{virtualRows.map((virtualRow) => {
|
||||
const row = rows[virtualRow.index];
|
||||
if (!row) return null;
|
||||
return (
|
||||
<MemoizedTableRow
|
||||
key={virtualRow.key}
|
||||
row={row as unknown as Row<TData>}
|
||||
virtualIndex={virtualRow.index}
|
||||
selected={row.getIsSelected()}
|
||||
style={{ height: rowHeight }}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{paddingBottom > 0 && (
|
||||
<TableRow aria-hidden="true">
|
||||
<TableCell
|
||||
colSpan={tableColumns.length}
|
||||
style={{ height: paddingBottom, padding: 0, border: 0 }}
|
||||
/>
|
||||
</TableRow>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
rows.map((row) => (
|
||||
<MemoizedTableRow
|
||||
key={getRowId(row.original as TData, row.index)}
|
||||
row={row as unknown as Row<TData>}
|
||||
virtualIndex={row.index}
|
||||
selected={row.getIsSelected()}
|
||||
style={{ height: rowHeight }}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
{tableBodyContent}
|
||||
{isFetchingNextPage && (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
|
|
|
|||
|
|
@ -8,11 +8,33 @@ export type TableColumnDef<TData, TValue> = ColumnDef<ProcessedDataRow<TData>, T
|
|||
export type TableColumn<TData, TValue> = ColumnDef<TData, TValue> & {
|
||||
accessorKey?: string | number;
|
||||
meta?: {
|
||||
size?: string | number;
|
||||
mobileSize?: string | number;
|
||||
minWidth?: string | number;
|
||||
priority?: number;
|
||||
/** Column width as a percentage (1-100). Used for proportional column sizing. */
|
||||
width?: number;
|
||||
/** Additional CSS classes to apply to the column cells and header. */
|
||||
className?: string;
|
||||
/**
|
||||
* When true, this column will be hidden on mobile devices (viewport < 768px).
|
||||
* This is useful for hiding less critical information on smaller screens.
|
||||
*
|
||||
* **Usage Example:**
|
||||
* ```typescript
|
||||
* {
|
||||
* accessorKey: 'createdAt',
|
||||
* header: 'Date Created',
|
||||
* cell: ({ row }) => formatDate(row.original.createdAt),
|
||||
* meta: {
|
||||
* desktopOnly: true, // Hide this column on mobile
|
||||
* width: 20,
|
||||
* className: 'min-w-[6rem]'
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The column will be completely hidden including:
|
||||
* - Header cell
|
||||
* - Data cells
|
||||
* - Skeleton loading cells
|
||||
*/
|
||||
desktopOnly?: boolean;
|
||||
};
|
||||
};
|
||||
|
|
@ -33,8 +55,8 @@ export interface DataTableConfig {
|
|||
virtualization?: {
|
||||
overscan?: number;
|
||||
minRows?: number;
|
||||
rowHeight?: number; // fixed row height to disable costly dynamic measurements
|
||||
fastOverscanMultiplier?: number; // multiplier applied during fast scroll bursts
|
||||
rowHeight?: number;
|
||||
fastOverscanMultiplier?: number;
|
||||
};
|
||||
pinning?: {
|
||||
enableColumnPinning?: boolean;
|
||||
|
|
@ -55,8 +77,6 @@ export interface DataTableProps<TData extends Record<string, unknown>, TValue> {
|
|||
isFetchingNextPage?: boolean;
|
||||
hasNextPage?: boolean;
|
||||
fetchNextPage?: () => Promise<unknown>;
|
||||
onError?: (error: Error) => void;
|
||||
onReset?: () => void;
|
||||
sorting?: SortingState;
|
||||
onSortingChange?: (updater: SortingState | ((old: SortingState) => SortingState)) => void;
|
||||
conversationIndex?: number;
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export const SelectionCheckbox = memo(
|
|||
}
|
||||
e.stopPropagation();
|
||||
}}
|
||||
className="flex h-full w-[30px] items-center justify-center"
|
||||
className="flex h-full w-8 items-center justify-center"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onChange(!checked);
|
||||
|
|
@ -40,18 +40,15 @@ export const SelectionCheckbox = memo(
|
|||
|
||||
SelectionCheckbox.displayName = 'SelectionCheckbox';
|
||||
|
||||
interface TableRowComponentProps<TData extends Record<string, unknown>> {
|
||||
row: Row<TData>;
|
||||
virtualIndex?: number;
|
||||
style?: React.CSSProperties;
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
const TableRowComponent = <TData extends Record<string, unknown>>(
|
||||
{
|
||||
row,
|
||||
virtualIndex,
|
||||
style,
|
||||
selected,
|
||||
}: {
|
||||
row: Row<TData>;
|
||||
virtualIndex?: number;
|
||||
style?: React.CSSProperties;
|
||||
selected: boolean;
|
||||
},
|
||||
{ row, virtualIndex, style, selected }: TableRowComponentProps<TData>,
|
||||
ref: React.Ref<HTMLTableRowElement>,
|
||||
) => (
|
||||
<TableRow
|
||||
|
|
@ -63,18 +60,27 @@ const TableRowComponent = <TData extends Record<string, unknown>>(
|
|||
>
|
||||
{row.getVisibleCells().map((cell) => {
|
||||
const meta = cell.column.columnDef.meta as
|
||||
| { className?: string; desktopOnly?: boolean }
|
||||
| { className?: string; desktopOnly?: boolean; width?: number }
|
||||
| undefined;
|
||||
const isDesktopOnly = meta?.desktopOnly;
|
||||
const percent = meta?.width;
|
||||
const widthStyle =
|
||||
cell.column.id === 'select'
|
||||
? { width: '32px', maxWidth: '32px' }
|
||||
: percent && percent >= 1 && percent <= 100
|
||||
? { width: `${percent}%`, maxWidth: `${percent}%` }
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<TableCell
|
||||
key={cell.id}
|
||||
className={cn(
|
||||
'truncate p-3',
|
||||
cell.column.id === 'select' && 'p-1',
|
||||
'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}
|
||||
>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</TableCell>
|
||||
|
|
@ -84,11 +90,7 @@ const TableRowComponent = <TData extends Record<string, unknown>>(
|
|||
);
|
||||
|
||||
type ForwardTableRowComponentType = <TData extends Record<string, unknown>>(
|
||||
props: {
|
||||
row: Row<TData>;
|
||||
virtualIndex?: number;
|
||||
style?: React.CSSProperties;
|
||||
} & React.RefAttributes<HTMLTableRowElement>,
|
||||
props: TableRowComponentProps<TData> & React.RefAttributes<HTMLTableRowElement>,
|
||||
) => JSX.Element;
|
||||
|
||||
const ForwardTableRowComponent = forwardRef(TableRowComponent) as ForwardTableRowComponentType;
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export const DataTableSearch = memo(
|
|||
aria-label={localize('com_ui_search_table')}
|
||||
aria-describedby="search-description"
|
||||
placeholder={placeholder || localize('com_ui_search')}
|
||||
className={cn('h-12 rounded-b-none border-0 bg-surface-secondary', className)}
|
||||
className={cn('h-10 rounded-b-none border-0 bg-surface-secondary md:h-12', className)}
|
||||
/>
|
||||
<span id="search-description" className="sr-only">
|
||||
{localize('com_ui_search_table_description')}
|
||||
|
|
|
|||
3
packages/client/src/components/DataTable/index.ts
Normal file
3
packages/client/src/components/DataTable/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export { default as DataTable } from './DataTable';
|
||||
export * from './DataTable.types';
|
||||
// Removed legacy DataTableSettings exports (store/context) as column resizing & dynamic sizing were deprecated.
|
||||
|
|
@ -4,7 +4,6 @@ export * from './AlertDialog';
|
|||
export * from './Breadcrumb';
|
||||
export * from './Button';
|
||||
export * from './Checkbox';
|
||||
|
||||
export * from './Dialog';
|
||||
export * from './DropdownMenu';
|
||||
export * from './HoverCard';
|
||||
|
|
@ -31,6 +30,7 @@ export * from './InputOTP';
|
|||
export * from './MultiSearch';
|
||||
export * from './Resizable';
|
||||
export * from './Select';
|
||||
export * from './DataTable/index';
|
||||
export { default as Radio } from './Radio';
|
||||
export { default as Badge } from './Badge';
|
||||
export { default as Avatar } from './Avatar';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
// Components
|
||||
export * from './components';
|
||||
export { default as DataTable } from './components/DataTable/DataTable';
|
||||
export * from './components/DataTable/DataTable.types';
|
||||
|
||||
// Hooks
|
||||
export * from './hooks';
|
||||
|
|
|
|||
|
|
@ -16,10 +16,12 @@
|
|||
"com_ui_search_table_description": "Type to filter results",
|
||||
"com_ui_search": "Search",
|
||||
"com_ui_data_table_scroll_area": "Scrollable data table area",
|
||||
"com_ui_select_row": "Select Row {{0}}",
|
||||
"com_ui_select_row": "Select row {{0}}",
|
||||
"com_ui_loading_more_data": "Loading more data...",
|
||||
"com_ui_no_search_results": "No search results found",
|
||||
"com_ui_table_error": "Table Error",
|
||||
"com_ui_table_error_description": "Table failed to load. Please refresh or try again.",
|
||||
"com_ui_error_details": "Error Details (Dev)"
|
||||
"com_ui_error_details": "Error Details (Dev)",
|
||||
"com_ui_enabled": "Enabled",
|
||||
"com_ui_disabled": "Disabled"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue