@handled-ai/design-system 0.18.53 → 0.18.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/badge.d.ts +1 -1
- package/dist/components/button.d.ts +1 -1
- package/dist/components/pill.d.ts +1 -1
- package/dist/components/tabs.d.ts +1 -1
- package/dist/components/virtualized-data-table.js +4 -4
- package/dist/components/virtualized-data-table.js.map +1 -1
- package/package.json +1 -1
- package/src/components/__tests__/virtualized-data-table-resize.test.tsx +18 -0
- package/src/components/virtualized-data-table.tsx +4 -4
|
@@ -3,7 +3,7 @@ import * as React from 'react';
|
|
|
3
3
|
import { VariantProps } from 'class-variance-authority';
|
|
4
4
|
|
|
5
5
|
declare const badgeVariants: (props?: ({
|
|
6
|
-
variant?: "default" | "secondary" | "destructive" | "outline" | "ghost" |
|
|
6
|
+
variant?: "link" | "default" | "secondary" | "destructive" | "outline" | "ghost" | null | undefined;
|
|
7
7
|
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
8
8
|
declare function Badge({ className, variant, asChild, ...props }: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants> & {
|
|
9
9
|
asChild?: boolean;
|
|
@@ -3,7 +3,7 @@ import * as React from 'react';
|
|
|
3
3
|
import { VariantProps } from 'class-variance-authority';
|
|
4
4
|
|
|
5
5
|
declare const buttonVariants: (props?: ({
|
|
6
|
-
variant?: "default" | "secondary" | "destructive" | "outline" | "ghost" |
|
|
6
|
+
variant?: "link" | "default" | "secondary" | "destructive" | "outline" | "ghost" | null | undefined;
|
|
7
7
|
size?: "default" | "sm" | "lg" | "icon" | null | undefined;
|
|
8
8
|
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
9
9
|
declare function Button({ className, variant, size, asChild, ...props }: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & {
|
|
@@ -12,7 +12,7 @@ import { VariantProps } from 'class-variance-authority';
|
|
|
12
12
|
*/
|
|
13
13
|
type PillStatus = "success" | "warning" | "error" | "neutral" | "info";
|
|
14
14
|
declare const pillVariants: (props?: ({
|
|
15
|
-
variant?: "default" | "secondary" | "destructive" | "outline" | "ghost" | "
|
|
15
|
+
variant?: "error" | "default" | "secondary" | "destructive" | "outline" | "ghost" | "neutral" | "info" | "success" | "warning" | null | undefined;
|
|
16
16
|
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
17
17
|
interface PillProps extends React.ComponentProps<"span">, VariantProps<typeof pillVariants> {
|
|
18
18
|
}
|
|
@@ -5,7 +5,7 @@ import { Tabs as Tabs$1 } from 'radix-ui';
|
|
|
5
5
|
|
|
6
6
|
declare function Tabs({ className, orientation, ...props }: React.ComponentProps<typeof Tabs$1.Root>): React.JSX.Element;
|
|
7
7
|
declare const tabsListVariants: (props?: ({
|
|
8
|
-
variant?: "
|
|
8
|
+
variant?: "line" | "default" | null | undefined;
|
|
9
9
|
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
10
10
|
declare function TabsList({ className, variant, ...props }: React.ComponentProps<typeof Tabs$1.List> & VariantProps<typeof tabsListVariants>): React.JSX.Element;
|
|
11
11
|
declare function TabsTrigger({ className, ...props }: React.ComponentProps<typeof Tabs$1.Trigger>): React.JSX.Element;
|
|
@@ -280,10 +280,10 @@ function VirtualizedDataTable({
|
|
|
280
280
|
onMouseDown: header.getResizeHandler(),
|
|
281
281
|
onTouchStart: header.getResizeHandler(),
|
|
282
282
|
className: cn(
|
|
283
|
-
"absolute right-0 top-0 h-full w-
|
|
284
|
-
"after:absolute after:right-
|
|
285
|
-
"after:bg-
|
|
286
|
-
header.column.getIsResizing() && "after:bg-primary/
|
|
283
|
+
"absolute right-0 top-0 z-20 h-full w-4 -mr-2 cursor-col-resize select-none touch-none",
|
|
284
|
+
"after:absolute after:right-2 after:top-1 after:h-[calc(100%-0.5rem)] after:w-px after:rounded-full",
|
|
285
|
+
"after:bg-border/70 after:transition-colors hover:after:bg-primary/60",
|
|
286
|
+
header.column.getIsResizing() && "after:bg-primary/70"
|
|
287
287
|
),
|
|
288
288
|
role: "separator",
|
|
289
289
|
"aria-orientation": "vertical"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/virtualized-data-table.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { useVirtualizer } from \"@tanstack/react-virtual\"\nimport {\n useReactTable,\n getCoreRowModel,\n flexRender,\n type ColumnDef,\n type SortingState,\n type ColumnFiltersState,\n type VisibilityState,\n type ColumnSizingState,\n type OnChangeFn,\n} from \"@tanstack/react-table\"\nimport type { RowData } from \"@tanstack/react-table\"\nimport { ArrowDown, ArrowUp, ArrowUpDown, ChevronDown, EyeOff, Check, SearchX, Loader2 } from \"lucide-react\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuTrigger,\n DropdownMenuItem,\n DropdownMenuSeparator,\n} from \"./dropdown-menu\"\n\nimport { cn } from \"../lib/utils\"\n\ndeclare module \"@tanstack/react-table\" {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n interface ColumnMeta<TData extends RowData, TValue> {\n /** Server-side sort key for this column. Enables sort in the header menu when onColumnSort is also provided. */\n sortKey?: string\n }\n}\n\nexport interface VirtualizedDataTableProps<TData> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n columns: ColumnDef<TData, any>[]\n data: TData[]\n\n // Virtualization\n height?: number | string\n estimateRowHeight?: number\n overscan?: number\n\n // Row interaction\n onRowClick?: (row: TData) => void\n getRowId?: (original: TData, index: number) => string\n\n // Infinite scroll\n onReachBottom?: () => void\n reachBottomThreshold?: number\n hasMore?: boolean\n isFetchingMore?: boolean\n\n // Column resizing\n enableColumnResizing?: boolean\n columnResizeMode?: \"onChange\" | \"onEnd\"\n columnSizing?: ColumnSizingState\n onColumnSizingChange?: OnChangeFn<ColumnSizingState>\n\n // Server-driven state (controlled) — omit for internal state\n sorting?: SortingState\n onSortingChange?: OnChangeFn<SortingState>\n columnFilters?: ColumnFiltersState\n onColumnFiltersChange?: OnChangeFn<ColumnFiltersState>\n columnVisibility?: VisibilityState\n onColumnVisibilityChange?: OnChangeFn<VisibilityState>\n\n // Loading / Empty state\n isLoading?: boolean\n emptyIcon?: React.ReactNode\n emptyMessage?: string\n emptyDescription?: string\n\n // Column header menu\n /** Called when user requests sorting from column header. columnId is the column's meta.sortKey. */\n onColumnSort?: (columnId: string, direction: \"asc\" | \"desc\") => void\n /** Called when user hides a column from the header menu. */\n onColumnHide?: (columnId: string) => void\n /** The currently active sort column ID — matches a column's meta.sortKey. Used for visual indicators and aria-sort. */\n activeSortColumn?: string | null\n /** The current sort direction. Used for visual indicators and aria-sort. */\n activeSortDirection?: \"asc\" | \"desc\"\n\n // Styling\n className?: string\n}\n\nexport function VirtualizedDataTable<TData>({\n columns,\n data,\n height = 600,\n estimateRowHeight = 48,\n overscan = 8,\n onRowClick,\n getRowId,\n onReachBottom,\n reachBottomThreshold = 5,\n hasMore = true,\n isFetchingMore,\n enableColumnResizing = false,\n columnResizeMode = \"onEnd\",\n columnSizing,\n onColumnSizingChange,\n sorting,\n onSortingChange,\n columnFilters,\n onColumnFiltersChange,\n columnVisibility,\n onColumnVisibilityChange,\n onColumnSort,\n onColumnHide,\n activeSortColumn,\n activeSortDirection,\n isLoading,\n emptyIcon,\n emptyMessage = \"No rows found\",\n emptyDescription = \"Try adjusting your filters\",\n className,\n}: VirtualizedDataTableProps<TData>) {\n // Controlled/uncontrolled state for sorting\n const [internalSorting, setInternalSorting] = React.useState<SortingState>([])\n const resolvedSorting = sorting ?? internalSorting\n const resolvedOnSortingChange = onSortingChange ?? setInternalSorting\n\n // Controlled/uncontrolled state for column filters\n const [internalColumnFilters, setInternalColumnFilters] =\n React.useState<ColumnFiltersState>([])\n const resolvedColumnFilters = columnFilters ?? internalColumnFilters\n const resolvedOnColumnFiltersChange =\n onColumnFiltersChange ?? setInternalColumnFilters\n\n // Controlled/uncontrolled state for column visibility\n const [internalColumnVisibility, setInternalColumnVisibility] =\n React.useState<VisibilityState>({})\n const resolvedColumnVisibility = columnVisibility ?? internalColumnVisibility\n const resolvedOnColumnVisibilityChange =\n onColumnVisibilityChange ?? setInternalColumnVisibility\n\n // Controlled/uncontrolled state for column sizing\n const [internalColumnSizing, setInternalColumnSizing] =\n React.useState<ColumnSizingState>({})\n const resolvedColumnSizing = columnSizing ?? internalColumnSizing\n const resolvedOnColumnSizingChange =\n onColumnSizingChange ?? setInternalColumnSizing\n\n // TanStack Table setup\n const table = useReactTable({\n data,\n columns,\n ...(getRowId ? { getRowId } : {}),\n state: {\n sorting: resolvedSorting,\n columnFilters: resolvedColumnFilters,\n columnVisibility: resolvedColumnVisibility,\n columnSizing: resolvedColumnSizing,\n },\n onSortingChange: resolvedOnSortingChange,\n onColumnFiltersChange: resolvedOnColumnFiltersChange,\n onColumnVisibilityChange: resolvedOnColumnVisibilityChange,\n onColumnSizingChange: resolvedOnColumnSizingChange,\n enableColumnResizing,\n columnResizeMode,\n manualSorting: true,\n manualFiltering: true,\n manualPagination: true,\n getCoreRowModel: getCoreRowModel(),\n })\n\n // Virtualizer setup\n const scrollContainerRef = React.useRef<HTMLDivElement>(null)\n const rows = table.getRowModel().rows\n\n const virtualizer = useVirtualizer({\n count: rows.length,\n getScrollElement: () => scrollContainerRef.current,\n estimateSize: () => estimateRowHeight,\n overscan,\n measureElement: (element) => element.getBoundingClientRect().height,\n })\n\n // Infinite scroll detection\n const lastTriggeredDataLengthRef = React.useRef<number>(0)\n\n // Derive a stable primitive for the last visible virtual-item index so the\n // effect below doesn't re-run on every render (getVirtualItems() returns a\n // new array reference each call).\n const virtualItems = virtualizer.getVirtualItems()\n const lastVirtualItemIndex =\n virtualItems.length > 0\n ? virtualItems[virtualItems.length - 1].index\n : -1\n\n React.useEffect(() => {\n if (!onReachBottom || isFetchingMore || hasMore === false) return\n if (lastVirtualItemIndex < 0) return\n if (lastVirtualItemIndex < rows.length - reachBottomThreshold) return\n\n // Prevent re-firing until data.length changes (i.e. new page loaded).\n if (lastTriggeredDataLengthRef.current === data.length) return\n lastTriggeredDataLengthRef.current = data.length\n\n onReachBottom()\n }, [\n lastVirtualItemIndex,\n rows.length,\n data.length,\n onReachBottom,\n isFetchingMore,\n hasMore,\n reachBottomThreshold,\n ])\n\n return (\n <div className={cn(\n \"w-full\",\n typeof height === \"string\" && height.trim().endsWith(\"%\") && \"h-full\",\n className,\n )}>\n <div\n ref={scrollContainerRef}\n className=\"relative overflow-auto\"\n style={{\n height: typeof height === \"number\" ? `${height}px` : height,\n contain: \"strict\",\n }}\n role=\"table\"\n aria-rowcount={data.length}\n aria-colcount={table.getVisibleLeafColumns().length}\n >\n {/* Sticky header */}\n <div className=\"sticky top-0 z-10 bg-background\" role=\"rowgroup\">\n {table.getHeaderGroups().map((headerGroup) => (\n <div\n key={headerGroup.id}\n className=\"flex w-max min-w-full border-b border-border/50\"\n role=\"row\"\n >\n {headerGroup.headers.map((header, colIdx) => {\n const sortKey = header.column.columnDef.meta?.sortKey\n const canServerSort = Boolean(sortKey && onColumnSort)\n\n const resolvedAriaSort = (() => {\n if (activeSortColumn !== undefined) {\n // Server-driven\n if (!sortKey) return undefined\n if (activeSortColumn === sortKey) return activeSortDirection === \"asc\" ? \"ascending\" as const : \"descending\" as const\n return \"none\" as const\n }\n // Fallback to TanStack state\n const sorted = header.column.getIsSorted()\n if (sorted === \"asc\") return \"ascending\" as const\n if (sorted === \"desc\") return \"descending\" as const\n if (header.column.getCanSort()) return \"none\" as const\n return undefined\n })()\n\n const sortIcon = (() => {\n if (!canServerSort) return null\n if (activeSortColumn === sortKey && activeSortDirection === \"asc\") return <ArrowUp className=\"w-3 h-3 shrink-0\" />\n if (activeSortColumn === sortKey && activeSortDirection === \"desc\") return <ArrowDown className=\"w-3 h-3 shrink-0\" />\n return <ArrowUpDown className=\"w-3 h-3 shrink-0 opacity-40\" />\n })()\n\n const handleHeaderClick = canServerSort ? () => {\n const newDir = activeSortColumn === sortKey\n ? (activeSortDirection === \"asc\" ? \"desc\" : \"asc\")\n : \"asc\"\n onColumnSort!(sortKey!, newDir)\n } : undefined\n\n return (\n <div\n key={header.id}\n className={cn(\n \"group/header h-9 min-w-0 px-3 flex items-center text-xs font-medium text-muted-foreground whitespace-nowrap relative\",\n header.column.getCanResize() && \"pr-4\",\n )}\n style={{\n width: header.getSize(),\n minWidth: header.getSize(),\n }}\n role=\"columnheader\"\n aria-colindex={colIdx + 1}\n aria-sort={resolvedAriaSort}\n >\n {header.isPlaceholder ? null : (\n <>\n {canServerSort ? (\n <button\n type=\"button\"\n className=\"flex min-w-0 flex-1 items-center gap-1 hover:text-foreground transition-colors\"\n onClick={handleHeaderClick}\n >\n <span className=\"min-w-0 truncate\">\n {flexRender(header.column.columnDef.header, header.getContext())}\n </span>\n {sortIcon}\n </button>\n ) : header.column.getCanSort() ? (\n <button\n type=\"button\"\n className=\"flex min-w-0 flex-1 items-center gap-1 hover:text-foreground transition-colors\"\n onClick={header.column.getToggleSortingHandler()}\n >\n <span className=\"min-w-0 truncate\">\n {flexRender(\n header.column.columnDef.header,\n header.getContext(),\n )}\n </span>\n {header.column.getIsSorted() === \"asc\" ? (\n <ArrowUp className=\"w-3 h-3 shrink-0\" />\n ) : header.column.getIsSorted() === \"desc\" ? (\n <ArrowDown className=\"w-3 h-3 shrink-0\" />\n ) : (\n <ArrowUpDown className=\"w-3 h-3 shrink-0 opacity-40\" />\n )}\n </button>\n ) : (\n <span className=\"min-w-0 flex-1 truncate\">\n {flexRender(\n header.column.columnDef.header,\n header.getContext(),\n )}\n </span>\n )}\n {(canServerSort || header.column.getCanSort() || header.column.getCanHide()) && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n className=\"ml-1 inline-flex shrink-0 items-center hover:text-foreground transition-all opacity-0 group-hover/header:opacity-100\"\n aria-label=\"Column actions\"\n >\n <ChevronDown className=\"w-3 h-3\" />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-48\">\n <DropdownMenuItem\n disabled={!canServerSort}\n onClick={() => canServerSort && onColumnSort!(sortKey!, \"asc\")}\n >\n <ArrowUp className=\"w-3.5 h-3.5 mr-2\" />\n Sort ascending\n {activeSortColumn === sortKey && activeSortDirection === \"asc\" && <Check className=\"w-3.5 h-3.5 ml-auto\" />}\n </DropdownMenuItem>\n <DropdownMenuItem\n disabled={!canServerSort}\n onClick={() => canServerSort && onColumnSort!(sortKey!, \"desc\")}\n >\n <ArrowDown className=\"w-3.5 h-3.5 mr-2\" />\n Sort descending\n {activeSortColumn === sortKey && activeSortDirection === \"desc\" && <Check className=\"w-3.5 h-3.5 ml-auto\" />}\n </DropdownMenuItem>\n {header.column.getCanHide() && (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n onClick={() => onColumnHide ? onColumnHide(header.column.id) : header.column.toggleVisibility(false)}\n >\n <EyeOff className=\"w-3.5 h-3.5 mr-2\" />\n Hide column\n </DropdownMenuItem>\n </>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </>\n )}\n {header.column.getCanResize() && (\n <div\n onMouseDown={header.getResizeHandler()}\n onTouchStart={header.getResizeHandler()}\n className={cn(\n \"absolute right-0 top-0 h-full w-3 -mr-1.5 cursor-col-resize select-none touch-none\",\n \"after:absolute after:right-1.5 after:top-0 after:h-full after:w-px\",\n \"after:bg-transparent hover:after:bg-primary/30\",\n header.column.getIsResizing() && \"after:bg-primary/50\",\n )}\n role=\"separator\"\n aria-orientation=\"vertical\"\n />\n )}\n </div>\n )\n })}\n </div>\n ))}\n </div>\n\n {/* Virtualized body or empty state */}\n {rows.length > 0 ? (\n <div\n role=\"rowgroup\"\n style={{\n height: virtualizer.getTotalSize(),\n width: \"100%\",\n position: \"relative\",\n }}\n >\n {virtualizer.getVirtualItems().map((virtualRow) => {\n const row = rows[virtualRow.index]\n return (\n <div\n key={row.id}\n data-index={virtualRow.index}\n ref={virtualizer.measureElement}\n className={cn(\n \"absolute left-0 w-max min-w-full flex group transition-colors\",\n onRowClick && \"cursor-pointer\",\n )}\n style={{\n transform: `translateY(${virtualRow.start}px)`,\n }}\n role=\"row\"\n aria-rowindex={virtualRow.index + 2}\n onClick={() => onRowClick?.(row.original)}\n tabIndex={onRowClick ? 0 : undefined}\n onKeyDown={\n onRowClick\n ? (e: React.KeyboardEvent) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault()\n onRowClick(row.original)\n }\n }\n : undefined\n }\n >\n {row.getVisibleCells().map((cell, colIdx) => (\n <div\n key={cell.id}\n className=\"px-3 py-3 flex items-center whitespace-nowrap group-hover:bg-muted/50\"\n style={{\n width: cell.column.getSize(),\n minWidth: cell.column.getSize(),\n }}\n role=\"cell\"\n aria-colindex={colIdx + 1}\n >\n {flexRender(\n cell.column.columnDef.cell,\n cell.getContext(),\n )}\n </div>\n ))}\n </div>\n )\n })}\n </div>\n ) : isLoading ? (\n <div className=\"flex flex-col items-center justify-center gap-2 text-muted-foreground py-20\">\n <Loader2 className=\"h-7 w-7 animate-spin opacity-60\" />\n <p className=\"text-sm font-medium\">Loading...</p>\n </div>\n ) : (\n <div className=\"flex flex-col items-center justify-center gap-1 text-muted-foreground py-20\">\n {emptyIcon ?? <SearchX className=\"h-7 w-7 opacity-40\" />}\n <p className=\"text-sm font-medium\">{emptyMessage}</p>\n <p className=\"text-xs\">{emptyDescription}</p>\n </div>\n )}\n\n {/* Loading indicator */}\n {isFetchingMore && (\n <div className=\"flex items-center justify-center py-4\">\n <Loader2 className=\"h-5 w-5 animate-spin text-muted-foreground\" />\n </div>\n )}\n </div>\n </div>\n )\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/virtualized-data-table.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { useVirtualizer } from \"@tanstack/react-virtual\"\nimport {\n useReactTable,\n getCoreRowModel,\n flexRender,\n type ColumnDef,\n type SortingState,\n type ColumnFiltersState,\n type VisibilityState,\n type ColumnSizingState,\n type OnChangeFn,\n} from \"@tanstack/react-table\"\nimport type { RowData } from \"@tanstack/react-table\"\nimport { ArrowDown, ArrowUp, ArrowUpDown, ChevronDown, EyeOff, Check, SearchX, Loader2 } from \"lucide-react\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuTrigger,\n DropdownMenuItem,\n DropdownMenuSeparator,\n} from \"./dropdown-menu\"\n\nimport { cn } from \"../lib/utils\"\n\ndeclare module \"@tanstack/react-table\" {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n interface ColumnMeta<TData extends RowData, TValue> {\n /** Server-side sort key for this column. Enables sort in the header menu when onColumnSort is also provided. */\n sortKey?: string\n }\n}\n\nexport interface VirtualizedDataTableProps<TData> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n columns: ColumnDef<TData, any>[]\n data: TData[]\n\n // Virtualization\n height?: number | string\n estimateRowHeight?: number\n overscan?: number\n\n // Row interaction\n onRowClick?: (row: TData) => void\n getRowId?: (original: TData, index: number) => string\n\n // Infinite scroll\n onReachBottom?: () => void\n reachBottomThreshold?: number\n hasMore?: boolean\n isFetchingMore?: boolean\n\n // Column resizing\n enableColumnResizing?: boolean\n columnResizeMode?: \"onChange\" | \"onEnd\"\n columnSizing?: ColumnSizingState\n onColumnSizingChange?: OnChangeFn<ColumnSizingState>\n\n // Server-driven state (controlled) — omit for internal state\n sorting?: SortingState\n onSortingChange?: OnChangeFn<SortingState>\n columnFilters?: ColumnFiltersState\n onColumnFiltersChange?: OnChangeFn<ColumnFiltersState>\n columnVisibility?: VisibilityState\n onColumnVisibilityChange?: OnChangeFn<VisibilityState>\n\n // Loading / Empty state\n isLoading?: boolean\n emptyIcon?: React.ReactNode\n emptyMessage?: string\n emptyDescription?: string\n\n // Column header menu\n /** Called when user requests sorting from column header. columnId is the column's meta.sortKey. */\n onColumnSort?: (columnId: string, direction: \"asc\" | \"desc\") => void\n /** Called when user hides a column from the header menu. */\n onColumnHide?: (columnId: string) => void\n /** The currently active sort column ID — matches a column's meta.sortKey. Used for visual indicators and aria-sort. */\n activeSortColumn?: string | null\n /** The current sort direction. Used for visual indicators and aria-sort. */\n activeSortDirection?: \"asc\" | \"desc\"\n\n // Styling\n className?: string\n}\n\nexport function VirtualizedDataTable<TData>({\n columns,\n data,\n height = 600,\n estimateRowHeight = 48,\n overscan = 8,\n onRowClick,\n getRowId,\n onReachBottom,\n reachBottomThreshold = 5,\n hasMore = true,\n isFetchingMore,\n enableColumnResizing = false,\n columnResizeMode = \"onEnd\",\n columnSizing,\n onColumnSizingChange,\n sorting,\n onSortingChange,\n columnFilters,\n onColumnFiltersChange,\n columnVisibility,\n onColumnVisibilityChange,\n onColumnSort,\n onColumnHide,\n activeSortColumn,\n activeSortDirection,\n isLoading,\n emptyIcon,\n emptyMessage = \"No rows found\",\n emptyDescription = \"Try adjusting your filters\",\n className,\n}: VirtualizedDataTableProps<TData>) {\n // Controlled/uncontrolled state for sorting\n const [internalSorting, setInternalSorting] = React.useState<SortingState>([])\n const resolvedSorting = sorting ?? internalSorting\n const resolvedOnSortingChange = onSortingChange ?? setInternalSorting\n\n // Controlled/uncontrolled state for column filters\n const [internalColumnFilters, setInternalColumnFilters] =\n React.useState<ColumnFiltersState>([])\n const resolvedColumnFilters = columnFilters ?? internalColumnFilters\n const resolvedOnColumnFiltersChange =\n onColumnFiltersChange ?? setInternalColumnFilters\n\n // Controlled/uncontrolled state for column visibility\n const [internalColumnVisibility, setInternalColumnVisibility] =\n React.useState<VisibilityState>({})\n const resolvedColumnVisibility = columnVisibility ?? internalColumnVisibility\n const resolvedOnColumnVisibilityChange =\n onColumnVisibilityChange ?? setInternalColumnVisibility\n\n // Controlled/uncontrolled state for column sizing\n const [internalColumnSizing, setInternalColumnSizing] =\n React.useState<ColumnSizingState>({})\n const resolvedColumnSizing = columnSizing ?? internalColumnSizing\n const resolvedOnColumnSizingChange =\n onColumnSizingChange ?? setInternalColumnSizing\n\n // TanStack Table setup\n const table = useReactTable({\n data,\n columns,\n ...(getRowId ? { getRowId } : {}),\n state: {\n sorting: resolvedSorting,\n columnFilters: resolvedColumnFilters,\n columnVisibility: resolvedColumnVisibility,\n columnSizing: resolvedColumnSizing,\n },\n onSortingChange: resolvedOnSortingChange,\n onColumnFiltersChange: resolvedOnColumnFiltersChange,\n onColumnVisibilityChange: resolvedOnColumnVisibilityChange,\n onColumnSizingChange: resolvedOnColumnSizingChange,\n enableColumnResizing,\n columnResizeMode,\n manualSorting: true,\n manualFiltering: true,\n manualPagination: true,\n getCoreRowModel: getCoreRowModel(),\n })\n\n // Virtualizer setup\n const scrollContainerRef = React.useRef<HTMLDivElement>(null)\n const rows = table.getRowModel().rows\n\n const virtualizer = useVirtualizer({\n count: rows.length,\n getScrollElement: () => scrollContainerRef.current,\n estimateSize: () => estimateRowHeight,\n overscan,\n measureElement: (element) => element.getBoundingClientRect().height,\n })\n\n // Infinite scroll detection\n const lastTriggeredDataLengthRef = React.useRef<number>(0)\n\n // Derive a stable primitive for the last visible virtual-item index so the\n // effect below doesn't re-run on every render (getVirtualItems() returns a\n // new array reference each call).\n const virtualItems = virtualizer.getVirtualItems()\n const lastVirtualItemIndex =\n virtualItems.length > 0\n ? virtualItems[virtualItems.length - 1].index\n : -1\n\n React.useEffect(() => {\n if (!onReachBottom || isFetchingMore || hasMore === false) return\n if (lastVirtualItemIndex < 0) return\n if (lastVirtualItemIndex < rows.length - reachBottomThreshold) return\n\n // Prevent re-firing until data.length changes (i.e. new page loaded).\n if (lastTriggeredDataLengthRef.current === data.length) return\n lastTriggeredDataLengthRef.current = data.length\n\n onReachBottom()\n }, [\n lastVirtualItemIndex,\n rows.length,\n data.length,\n onReachBottom,\n isFetchingMore,\n hasMore,\n reachBottomThreshold,\n ])\n\n return (\n <div className={cn(\n \"w-full\",\n typeof height === \"string\" && height.trim().endsWith(\"%\") && \"h-full\",\n className,\n )}>\n <div\n ref={scrollContainerRef}\n className=\"relative overflow-auto\"\n style={{\n height: typeof height === \"number\" ? `${height}px` : height,\n contain: \"strict\",\n }}\n role=\"table\"\n aria-rowcount={data.length}\n aria-colcount={table.getVisibleLeafColumns().length}\n >\n {/* Sticky header */}\n <div className=\"sticky top-0 z-10 bg-background\" role=\"rowgroup\">\n {table.getHeaderGroups().map((headerGroup) => (\n <div\n key={headerGroup.id}\n className=\"flex w-max min-w-full border-b border-border/50\"\n role=\"row\"\n >\n {headerGroup.headers.map((header, colIdx) => {\n const sortKey = header.column.columnDef.meta?.sortKey\n const canServerSort = Boolean(sortKey && onColumnSort)\n\n const resolvedAriaSort = (() => {\n if (activeSortColumn !== undefined) {\n // Server-driven\n if (!sortKey) return undefined\n if (activeSortColumn === sortKey) return activeSortDirection === \"asc\" ? \"ascending\" as const : \"descending\" as const\n return \"none\" as const\n }\n // Fallback to TanStack state\n const sorted = header.column.getIsSorted()\n if (sorted === \"asc\") return \"ascending\" as const\n if (sorted === \"desc\") return \"descending\" as const\n if (header.column.getCanSort()) return \"none\" as const\n return undefined\n })()\n\n const sortIcon = (() => {\n if (!canServerSort) return null\n if (activeSortColumn === sortKey && activeSortDirection === \"asc\") return <ArrowUp className=\"w-3 h-3 shrink-0\" />\n if (activeSortColumn === sortKey && activeSortDirection === \"desc\") return <ArrowDown className=\"w-3 h-3 shrink-0\" />\n return <ArrowUpDown className=\"w-3 h-3 shrink-0 opacity-40\" />\n })()\n\n const handleHeaderClick = canServerSort ? () => {\n const newDir = activeSortColumn === sortKey\n ? (activeSortDirection === \"asc\" ? \"desc\" : \"asc\")\n : \"asc\"\n onColumnSort!(sortKey!, newDir)\n } : undefined\n\n return (\n <div\n key={header.id}\n className={cn(\n \"group/header h-9 min-w-0 px-3 flex items-center text-xs font-medium text-muted-foreground whitespace-nowrap relative\",\n header.column.getCanResize() && \"pr-4\",\n )}\n style={{\n width: header.getSize(),\n minWidth: header.getSize(),\n }}\n role=\"columnheader\"\n aria-colindex={colIdx + 1}\n aria-sort={resolvedAriaSort}\n >\n {header.isPlaceholder ? null : (\n <>\n {canServerSort ? (\n <button\n type=\"button\"\n className=\"flex min-w-0 flex-1 items-center gap-1 hover:text-foreground transition-colors\"\n onClick={handleHeaderClick}\n >\n <span className=\"min-w-0 truncate\">\n {flexRender(header.column.columnDef.header, header.getContext())}\n </span>\n {sortIcon}\n </button>\n ) : header.column.getCanSort() ? (\n <button\n type=\"button\"\n className=\"flex min-w-0 flex-1 items-center gap-1 hover:text-foreground transition-colors\"\n onClick={header.column.getToggleSortingHandler()}\n >\n <span className=\"min-w-0 truncate\">\n {flexRender(\n header.column.columnDef.header,\n header.getContext(),\n )}\n </span>\n {header.column.getIsSorted() === \"asc\" ? (\n <ArrowUp className=\"w-3 h-3 shrink-0\" />\n ) : header.column.getIsSorted() === \"desc\" ? (\n <ArrowDown className=\"w-3 h-3 shrink-0\" />\n ) : (\n <ArrowUpDown className=\"w-3 h-3 shrink-0 opacity-40\" />\n )}\n </button>\n ) : (\n <span className=\"min-w-0 flex-1 truncate\">\n {flexRender(\n header.column.columnDef.header,\n header.getContext(),\n )}\n </span>\n )}\n {(canServerSort || header.column.getCanSort() || header.column.getCanHide()) && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n className=\"ml-1 inline-flex shrink-0 items-center hover:text-foreground transition-all opacity-0 group-hover/header:opacity-100\"\n aria-label=\"Column actions\"\n >\n <ChevronDown className=\"w-3 h-3\" />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-48\">\n <DropdownMenuItem\n disabled={!canServerSort}\n onClick={() => canServerSort && onColumnSort!(sortKey!, \"asc\")}\n >\n <ArrowUp className=\"w-3.5 h-3.5 mr-2\" />\n Sort ascending\n {activeSortColumn === sortKey && activeSortDirection === \"asc\" && <Check className=\"w-3.5 h-3.5 ml-auto\" />}\n </DropdownMenuItem>\n <DropdownMenuItem\n disabled={!canServerSort}\n onClick={() => canServerSort && onColumnSort!(sortKey!, \"desc\")}\n >\n <ArrowDown className=\"w-3.5 h-3.5 mr-2\" />\n Sort descending\n {activeSortColumn === sortKey && activeSortDirection === \"desc\" && <Check className=\"w-3.5 h-3.5 ml-auto\" />}\n </DropdownMenuItem>\n {header.column.getCanHide() && (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n onClick={() => onColumnHide ? onColumnHide(header.column.id) : header.column.toggleVisibility(false)}\n >\n <EyeOff className=\"w-3.5 h-3.5 mr-2\" />\n Hide column\n </DropdownMenuItem>\n </>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </>\n )}\n {header.column.getCanResize() && (\n <div\n onMouseDown={header.getResizeHandler()}\n onTouchStart={header.getResizeHandler()}\n className={cn(\n \"absolute right-0 top-0 z-20 h-full w-4 -mr-2 cursor-col-resize select-none touch-none\",\n \"after:absolute after:right-2 after:top-1 after:h-[calc(100%-0.5rem)] after:w-px after:rounded-full\",\n \"after:bg-border/70 after:transition-colors hover:after:bg-primary/60\",\n header.column.getIsResizing() && \"after:bg-primary/70\",\n )}\n role=\"separator\"\n aria-orientation=\"vertical\"\n />\n )}\n </div>\n )\n })}\n </div>\n ))}\n </div>\n\n {/* Virtualized body or empty state */}\n {rows.length > 0 ? (\n <div\n role=\"rowgroup\"\n style={{\n height: virtualizer.getTotalSize(),\n width: \"100%\",\n position: \"relative\",\n }}\n >\n {virtualizer.getVirtualItems().map((virtualRow) => {\n const row = rows[virtualRow.index]\n return (\n <div\n key={row.id}\n data-index={virtualRow.index}\n ref={virtualizer.measureElement}\n className={cn(\n \"absolute left-0 w-max min-w-full flex group transition-colors\",\n onRowClick && \"cursor-pointer\",\n )}\n style={{\n transform: `translateY(${virtualRow.start}px)`,\n }}\n role=\"row\"\n aria-rowindex={virtualRow.index + 2}\n onClick={() => onRowClick?.(row.original)}\n tabIndex={onRowClick ? 0 : undefined}\n onKeyDown={\n onRowClick\n ? (e: React.KeyboardEvent) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault()\n onRowClick(row.original)\n }\n }\n : undefined\n }\n >\n {row.getVisibleCells().map((cell, colIdx) => (\n <div\n key={cell.id}\n className=\"px-3 py-3 flex items-center whitespace-nowrap group-hover:bg-muted/50\"\n style={{\n width: cell.column.getSize(),\n minWidth: cell.column.getSize(),\n }}\n role=\"cell\"\n aria-colindex={colIdx + 1}\n >\n {flexRender(\n cell.column.columnDef.cell,\n cell.getContext(),\n )}\n </div>\n ))}\n </div>\n )\n })}\n </div>\n ) : isLoading ? (\n <div className=\"flex flex-col items-center justify-center gap-2 text-muted-foreground py-20\">\n <Loader2 className=\"h-7 w-7 animate-spin opacity-60\" />\n <p className=\"text-sm font-medium\">Loading...</p>\n </div>\n ) : (\n <div className=\"flex flex-col items-center justify-center gap-1 text-muted-foreground py-20\">\n {emptyIcon ?? <SearchX className=\"h-7 w-7 opacity-40\" />}\n <p className=\"text-sm font-medium\">{emptyMessage}</p>\n <p className=\"text-xs\">{emptyDescription}</p>\n </div>\n )}\n\n {/* Loading indicator */}\n {isFetchingMore && (\n <div className=\"flex items-center justify-center py-4\">\n <Loader2 className=\"h-5 w-5 animate-spin text-muted-foreground\" />\n </div>\n )}\n </div>\n </div>\n )\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -84,6 +84,24 @@ describe("VirtualizedDataTable — resize handles render when enabled", () => {
|
|
|
84
84
|
});
|
|
85
85
|
});
|
|
86
86
|
|
|
87
|
+
it("each handle has a wider grab target and visible divider", () => {
|
|
88
|
+
const { container } = render(
|
|
89
|
+
<VirtualizedDataTable
|
|
90
|
+
columns={testColumns}
|
|
91
|
+
data={testData}
|
|
92
|
+
height={300}
|
|
93
|
+
enableColumnResizing
|
|
94
|
+
/>,
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
container.querySelectorAll('[role="separator"]').forEach((sep) => {
|
|
98
|
+
expect(sep.classList.contains("w-4")).toBe(true);
|
|
99
|
+
expect(sep.classList.contains("-mr-2")).toBe(true);
|
|
100
|
+
expect(sep.className).toContain("after:bg-border/70");
|
|
101
|
+
expect(sep.className).toContain("hover:after:bg-primary/60");
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
87
105
|
it("header cells have 'relative' class when resizing is enabled", () => {
|
|
88
106
|
const { container } = render(
|
|
89
107
|
<VirtualizedDataTable
|
|
@@ -375,10 +375,10 @@ export function VirtualizedDataTable<TData>({
|
|
|
375
375
|
onMouseDown={header.getResizeHandler()}
|
|
376
376
|
onTouchStart={header.getResizeHandler()}
|
|
377
377
|
className={cn(
|
|
378
|
-
"absolute right-0 top-0 h-full w-
|
|
379
|
-
"after:absolute after:right-
|
|
380
|
-
"after:bg-
|
|
381
|
-
header.column.getIsResizing() && "after:bg-primary/
|
|
378
|
+
"absolute right-0 top-0 z-20 h-full w-4 -mr-2 cursor-col-resize select-none touch-none",
|
|
379
|
+
"after:absolute after:right-2 after:top-1 after:h-[calc(100%-0.5rem)] after:w-px after:rounded-full",
|
|
380
|
+
"after:bg-border/70 after:transition-colors hover:after:bg-primary/60",
|
|
381
|
+
header.column.getIsResizing() && "after:bg-primary/70",
|
|
382
382
|
)}
|
|
383
383
|
role="separator"
|
|
384
384
|
aria-orientation="vertical"
|