Update AppDataGrid2.tsx

This commit is contained in:
Nate Kelley 2025-04-04 14:47:15 -06:00
parent 8205e72f3f
commit 4f50e908b4
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
2 changed files with 87 additions and 130 deletions

View File

@ -1,9 +1,10 @@
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { AppDataGrid2 } from './AppDataGrid2'; import { TanStackDataGrid } from './TanStackDataGrid';
import { faker } from '@faker-js/faker';
const meta: Meta<typeof AppDataGrid2> = { const meta: Meta<typeof TanStackDataGrid> = {
title: 'UI/Table/AppDataGrid2', title: 'UI/Table/TanStackDataGrid',
component: AppDataGrid2, component: TanStackDataGrid,
parameters: { parameters: {
layout: 'fullscreen' layout: 'fullscreen'
}, },
@ -11,52 +12,15 @@ const meta: Meta<typeof AppDataGrid2> = {
}; };
export default meta; export default meta;
type Story = StoryObj<typeof AppDataGrid2>; type Story = StoryObj<typeof TanStackDataGrid>;
const sampleData = [ const sampleData = Array.from({ length: 1000 }, (_, index) => ({
{ id: index + 1,
id: 1, name: faker.person.fullName(),
name: 'John Doe', age: faker.number.int({ min: 18, max: 90 }),
age: 30, email: faker.internet.email(),
email: 'john@example.com', joinDate: faker.date.past().toISOString()
joinDate: new Date('2023-01-15').toISOString() }));
},
{
id: 2,
name: 'Jane Smith',
age: 25,
email: 'jane@example.com',
joinDate: new Date('2023-02-20').toISOString()
},
{
id: 3,
name: 'Bob Johnson',
age: 35,
email: 'bob@example.com',
joinDate: new Date('2023-03-10').toISOString()
},
{
id: 4,
name: 'Alice Brown',
age: 28,
email: 'alice@example.com',
joinDate: new Date('2023-04-05').toISOString()
},
{
id: 5,
name: 'Michael Wilson',
age: 42,
email: 'michael@example.com',
joinDate: new Date('2023-05-12').toISOString()
},
{
id: 6,
name: 'Sarah Davis',
age: 31,
email: 'sarah@example.com',
joinDate: new Date('2023-06-08').toISOString()
}
];
export const Default: Story = { export const Default: Story = {
args: { args: {
@ -66,8 +30,8 @@ export const Default: Story = {
sortable: true sortable: true
}, },
render: (args) => ( render: (args) => (
<div className="h-[500px] overflow-y-auto border p-3"> <div className="h-[500px] p-0">
<AppDataGrid2 {...args} /> <TanStackDataGrid {...args} />
</div> </div>
) )
}; };

View File

@ -16,7 +16,6 @@ import {
KeyboardSensor, KeyboardSensor,
useSensor, useSensor,
useSensors, useSensors,
closestCenter,
pointerWithin, pointerWithin,
DragEndEvent, DragEndEvent,
DragOverlay, DragOverlay,
@ -27,13 +26,13 @@ import {
} from '@dnd-kit/core'; } from '@dnd-kit/core';
import { arrayMove } from '@dnd-kit/sortable'; import { arrayMove } from '@dnd-kit/sortable';
import sampleSize from 'lodash/sampleSize'; import sampleSize from 'lodash/sampleSize';
import { defaultCellFormat, defaultHeaderFormat } from './helpers'; import { defaultCellFormat, defaultHeaderFormat } from '../helpers';
import { cn } from '@/lib/classMerge'; import { cn } from '@/lib/classMerge';
import { restrictToHorizontalAxis } from '@dnd-kit/modifiers'; import { restrictToHorizontalAxis } from '@dnd-kit/modifiers';
import { useMemoizedFn } from '@/hooks'; import { useMemoizedFn } from '@/hooks';
import { CaretDown, CaretUp } from '../../icons/NucleoIconFilled'; import { CaretDown, CaretUp } from '../../../icons/NucleoIconFilled';
export interface AppDataGridProps { export interface TanStackDataGridProps {
className?: string; className?: string;
resizable?: boolean; resizable?: boolean;
sortable?: boolean; sortable?: boolean;
@ -95,22 +94,23 @@ const DraggableHeader: React.FC<DraggableHeaderProps> = React.memo(
}; };
return ( return (
<div <th
ref={setDropNodeRef} ref={setDropNodeRef}
style={style} style={style}
className={cn( className={cn(
'bg-background relative border select-none', 'relative border-r select-none last:border-r-0',
isOverTarget && 'bg-primary/10 border-primary rounded-sm border-dashed' isOverTarget &&
'bg-primary/10 border-primary inset rounded-sm border border-r border-dashed'
)} )}
// onClick toggles sorting if enabled // onClick toggles sorting if enabled
onClick={header.column.getCanSort() ? header.column.getToggleSortingHandler() : undefined}> onClick={header.column.getCanSort() ? header.column.getToggleSortingHandler() : undefined}>
<div <div
className="flex h-full flex-1 items-center space-x-1 p-2" className="flex h-full flex-1 items-center space-x-1.5 p-2"
ref={sortable ? setDragNodeRef : undefined} ref={sortable ? setDragNodeRef : undefined}
{...attributes} {...attributes}
{...listeners} {...listeners}
style={{ cursor: 'grab' }}> style={{ cursor: 'grab' }}>
<span className="text-gray-dark"> <span className="text-gray-dark text-base font-normal">
{flexRender(header.column.columnDef.header, header.getContext())} {flexRender(header.column.columnDef.header, header.getContext())}
</span> </span>
{header.column.getIsSorted() === 'asc' && ( {header.column.getIsSorted() === 'asc' && (
@ -140,7 +140,7 @@ const DraggableHeader: React.FC<DraggableHeaderProps> = React.memo(
/> />
</div> </div>
)} )}
</div> </th>
); );
} }
); );
@ -170,7 +170,7 @@ const HeaderDragOverlay = ({
); );
}; };
export const AppDataGrid2: React.FC<AppDataGridProps> = React.memo( export const TanStackDataGrid: React.FC<TanStackDataGridProps> = React.memo(
({ ({
className = '', className = '',
resizable = true, resizable = true,
@ -355,28 +355,15 @@ export const AppDataGrid2: React.FC<AppDataGridProps> = React.memo(
const rowVirtualizer = useVirtualizer({ const rowVirtualizer = useVirtualizer({
count: table.getRowModel().rows.length, count: table.getRowModel().rows.length,
getScrollElement: () => parentRef.current, getScrollElement: () => parentRef.current,
estimateSize: () => 35, // estimated row height estimateSize: () => 36, // estimated row height
overscan: 5 overscan: 5
}); });
// Create a reference to measure header height
const headerRef = useRef<HTMLDivElement>(null);
const [headerHeight, setHeaderHeight] = useState(36); // Default height
// Measure the actual header height once mounted
useEffect(() => {
if (headerRef.current) {
const height = headerRef.current.getBoundingClientRect().height;
if (height > 0) {
setHeaderHeight(height);
}
}
}, []);
return ( return (
<div ref={parentRef} className={cn('h-full w-full overflow-auto', className)}> <div ref={parentRef} className={cn('h-full w-full overflow-auto', className)}>
<table className="bg-background w-full">
{/* Header */} {/* Header */}
<div className="sticky top-0 z-10 w-full bg-gray-100" ref={headerRef}> <thead className="bg-background sticky top-0 z-10 w-full">
<DndContext <DndContext
sensors={sensors} sensors={sensors}
modifiers={[restrictToHorizontalAxis]} modifiers={[restrictToHorizontalAxis]}
@ -384,19 +371,18 @@ export const AppDataGrid2: React.FC<AppDataGridProps> = React.memo(
onDragStart={handleDragStart} onDragStart={handleDragStart}
onDragOver={handleDragOver} onDragOver={handleDragOver}
onDragEnd={handleDragEnd}> onDragEnd={handleDragEnd}>
<div className="flex"> <tr className="flex border-b">
{table {table
.getHeaderGroups()[0] .getHeaderGroups()[0]
?.headers.map((header) => ( ?.headers.map((header) => (
<DraggableHeader <DraggableHeader
key={header.id}
header={header} header={header}
sortable={sortable} sortable={sortable}
resizable={resizable} resizable={resizable}
overTargetId={overTargetId} overTargetId={overTargetId}
/> />
))} ))}
</div> </tr>
{/* Drag Overlay */} {/* Drag Overlay */}
<DragOverlay <DragOverlay
@ -406,31 +392,38 @@ export const AppDataGrid2: React.FC<AppDataGridProps> = React.memo(
{activeId && activeHeader && <HeaderDragOverlay header={activeHeader} />} {activeId && activeHeader && <HeaderDragOverlay header={activeHeader} />}
</DragOverlay> </DragOverlay>
</DndContext> </DndContext>
</div> </thead>
{/* Body */} {/* Body */}
<div className="relative" style={{ height: `${rowVirtualizer.getTotalSize()}px` }}> <tbody
className="relative"
style={{ display: 'grid', height: `${rowVirtualizer.getTotalSize()}px` }}>
{rowVirtualizer.getVirtualItems().map((virtualRow) => { {rowVirtualizer.getVirtualItems().map((virtualRow) => {
const row = table.getRowModel().rows[virtualRow.index]; const row = table.getRowModel().rows[virtualRow.index];
return ( return (
<div <tr
key={row.id} key={row.id}
className="absolute inset-x-0 flex" className="absolute inset-x-0 flex border-b last:border-b-0"
style={{ style={{
transform: `translateY(${virtualRow.start}px)`, transform: `translateY(${virtualRow.start}px)`,
height: `${virtualRow.size}px` height: `${virtualRow.size}px`
}}> }}>
{row.getVisibleCells().map((cell) => ( {row.getVisibleCells().map((cell) => (
<td key={cell.id} className="border p-2" style={{ width: cell.column.getSize() }}> <td
key={cell.id}
className="border-r p-2 last:border-r-0"
style={{ width: cell.column.getSize() }}>
{flexRender(cell.column.columnDef.cell, cell.getContext())} {flexRender(cell.column.columnDef.cell, cell.getContext())}
</td> </td>
))} ))}
</div> </tr>
); );
})} })}
</div> </tbody>
</table>
</div> </div>
); );
} }
); );
AppDataGrid2.displayName = 'AppDataGrid2'; TanStackDataGrid.displayName = 'TanStackDataGrid';