@eml-payments/ui-kit 1.3.3 → 1.4.3

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.
Files changed (86) hide show
  1. package/dist/index.css +1 -1
  2. package/dist/index.d.cts +488 -0
  3. package/dist/index.d.ts +488 -0
  4. package/dist/src/components/Alert/Alert.js +9 -5
  5. package/dist/src/components/DatePicker/DatePicker.js +7 -3
  6. package/dist/src/components/Dialog/DialogContainer.stories.js +5 -4
  7. package/dist/src/components/Dropdown/Dropdown.stories.js +11 -9
  8. package/dist/src/components/DropdownWrapper/DropdownWrapper.stories.js +20 -18
  9. package/dist/src/components/Table/BaseTable/BaseTable.d.ts +2 -0
  10. package/dist/src/components/Table/BaseTable/BaseTable.js +8 -0
  11. package/dist/src/components/Table/BaseTable/BaseTable.stories.d.ts +7 -0
  12. package/dist/src/components/Table/BaseTable/BaseTable.stories.js +42 -0
  13. package/dist/src/components/Table/BaseTable/BaseTable.types.d.ts +0 -0
  14. package/dist/src/components/Table/BaseTable/BaseTable.types.js +1 -0
  15. package/dist/src/components/Table/BaseTable/TableHeader.d.ts +7 -0
  16. package/dist/src/components/Table/BaseTable/TableHeader.js +15 -0
  17. package/dist/src/components/Table/BaseTable/index.d.ts +1 -0
  18. package/dist/src/components/Table/BaseTable/index.js +1 -0
  19. package/dist/src/components/Table/InfiniteTable/InfiniteTable.d.ts +4 -0
  20. package/dist/src/components/Table/InfiniteTable/InfiniteTable.js +49 -0
  21. package/dist/src/components/Table/InfiniteTable/InfiniteTable.props.d.ts +32 -0
  22. package/dist/src/components/Table/InfiniteTable/InfiniteTable.props.js +1 -0
  23. package/dist/src/components/Table/InfiniteTable/InfiniteTable.stories.d.ts +8 -0
  24. package/dist/src/components/Table/InfiniteTable/InfiniteTable.stories.js +109 -0
  25. package/dist/src/components/Table/InfiniteTable/index.d.ts +2 -0
  26. package/dist/src/components/Table/InfiniteTable/index.js +1 -0
  27. package/dist/src/components/Table/InfiniteTable/useInfiniteScrollTrigger.d.ts +12 -0
  28. package/dist/src/components/Table/InfiniteTable/useInfiniteScrollTrigger.js +31 -0
  29. package/dist/src/components/Table/InfiniteTable/useInfiniteScrolling.d.ts +29 -0
  30. package/dist/src/components/Table/InfiniteTable/useInfiniteScrolling.js +103 -0
  31. package/dist/src/components/Table/InfiniteTable/useInfiniteTableController.d.ts +14 -0
  32. package/dist/src/components/Table/InfiniteTable/useInfiniteTableController.js +76 -0
  33. package/dist/src/components/Table/Pagination/PageSizeSelector.d.ts +2 -0
  34. package/dist/src/components/Table/Pagination/PageSizeSelector.js +9 -0
  35. package/dist/src/components/Table/Pagination/Pagination.types.d.ts +21 -0
  36. package/dist/src/components/Table/Pagination/Pagination.types.js +1 -0
  37. package/dist/src/components/Table/Pagination/PaginationFooter.d.ts +2 -0
  38. package/dist/src/components/Table/Pagination/PaginationFooter.js +10 -0
  39. package/dist/src/components/Table/Pagination/index.d.ts +3 -2
  40. package/dist/src/components/Table/Pagination/index.js +3 -2
  41. package/dist/src/components/Table/Pagination/usePaginationController.d.ts +16 -0
  42. package/dist/src/components/Table/Pagination/usePaginationController.js +33 -0
  43. package/dist/src/components/Table/StandardTable/StandardTable.d.ts +4 -0
  44. package/dist/src/components/Table/StandardTable/StandardTable.js +47 -0
  45. package/dist/src/components/Table/StandardTable/StandardTable.stories.d.ts +25 -0
  46. package/dist/src/components/Table/StandardTable/StandardTable.stories.js +268 -0
  47. package/dist/src/components/Table/StandardTable/StandardTable.types.d.ts +4 -0
  48. package/dist/src/components/Table/StandardTable/StandardTable.types.js +1 -0
  49. package/dist/src/components/Table/StandardTable/index.d.ts +2 -0
  50. package/dist/src/components/Table/StandardTable/index.js +1 -0
  51. package/dist/src/components/Table/StandardTable/useStandardTableController.d.ts +22 -0
  52. package/dist/src/components/Table/StandardTable/useStandardTableController.js +100 -0
  53. package/dist/src/components/Table/Table.js +1 -1
  54. package/dist/src/components/Table/Table.types.d.ts +72 -29
  55. package/dist/src/components/Table/body/Body.types.d.ts +0 -0
  56. package/dist/src/components/Table/body/Body.types.js +1 -0
  57. package/dist/src/components/Table/body/renderGroupRow.d.ts +13 -0
  58. package/dist/src/components/Table/body/renderGroupRow.js +14 -0
  59. package/dist/src/components/Table/body/renderLeafRow.d.ts +11 -0
  60. package/dist/src/components/Table/body/renderLeafRow.js +17 -0
  61. package/dist/src/components/Table/body/renderTableBody.d.ts +22 -0
  62. package/dist/src/components/Table/body/renderTableBody.js +21 -0
  63. package/dist/src/components/Table/body/utils/getColSpan.d.ts +2 -0
  64. package/dist/src/components/Table/body/utils/getColSpan.js +4 -0
  65. package/dist/src/components/Table/body/utils/getTableBodyState.d.ts +16 -0
  66. package/dist/src/components/Table/body/utils/getTableBodyState.js +9 -0
  67. package/dist/src/components/Table/body/utils/index.d.ts +2 -0
  68. package/dist/src/components/Table/body/utils/index.js +2 -0
  69. package/dist/src/components/Table/hooks/useTableColumns.d.ts +2 -0
  70. package/dist/src/components/Table/hooks/useTableColumns.js +5 -0
  71. package/dist/src/components/Table/hooks/useTableLayout.d.ts +5 -0
  72. package/dist/src/components/Table/hooks/useTableLayout.js +21 -0
  73. package/dist/src/components/Table/hooks/useTableSorting.d.ts +5 -0
  74. package/dist/src/components/Table/hooks/useTableSorting.js +14 -0
  75. package/dist/src/components/Table/hooks/useUrlPaginationSync.d.ts +4 -5
  76. package/dist/src/components/Table/hooks/useUrlPaginationSync.js +23 -9
  77. package/dist/src/components/Table/index.d.ts +5 -2
  78. package/dist/src/components/Table/index.js +2 -2
  79. package/dist/src/components/Table/render-rows/renderTableRows.d.ts +22 -0
  80. package/dist/src/components/Table/render-rows/renderTableRows.js +31 -0
  81. package/dist/src/components/Table/table.helpers.d.ts +5 -2
  82. package/dist/src/components/Table/table.helpers.js +13 -7
  83. package/dist/src/components/Tooltip/Tooltip.stories.js +1 -1
  84. package/dist/src/components/UICreditCard/UICreditCard.js +1 -1
  85. package/dist/src/components/UICreditCard/UICreditCard.stories.js +10 -8
  86. package/package.json +2 -2
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createColumnHelper, getCoreRowModel, getSortedRowModel, useReactTable, flexRender, } from '@tanstack/react-table';
3
+ import { useState } from 'react';
4
+ import { BaseTable } from './BaseTable';
5
+ const data = Array.from({ length: 5 }).map((_, i) => ({
6
+ id: `user-${i + 1}`,
7
+ name: `User ${i + 1}`,
8
+ email: `user${i + 1}@example.com`,
9
+ role: i % 2 === 0 ? 'Admin' : 'User',
10
+ }));
11
+ const columnHelper = createColumnHelper();
12
+ const columns = [
13
+ columnHelper.accessor('name', { header: 'Name' }),
14
+ columnHelper.accessor('email', { header: 'Email' }),
15
+ columnHelper.accessor('role', { header: 'Role' }),
16
+ ];
17
+ function BaseTableHarness({ rows, isLoading, }) {
18
+ const [sorting, setSorting] = useState([]);
19
+ const table = useReactTable({
20
+ data: rows,
21
+ columns,
22
+ state: { sorting },
23
+ onSortingChange: setSorting,
24
+ getCoreRowModel: getCoreRowModel(),
25
+ getSortedRowModel: getSortedRowModel(),
26
+ getRowId: (row) => row.id,
27
+ });
28
+ return (_jsx(BaseTable, { table: table, height: 400, isLoading: isLoading, renderBody: () => table.getRowModel().rows.map((row) => (_jsx("tr", { className: "border-t", children: row.getVisibleCells().map((cell) => (_jsx("td", { className: "p-4", children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id))) }, row.id))) }));
29
+ }
30
+ const meta = {
31
+ title: 'UIKit/Table/BaseTable',
32
+ };
33
+ export default meta;
34
+ export const Basic = {
35
+ render: () => _jsx(BaseTableHarness, { rows: data }),
36
+ };
37
+ export const Empty = {
38
+ render: () => _jsx(BaseTableHarness, { rows: [] }),
39
+ };
40
+ export const Loading = {
41
+ render: () => _jsx(BaseTableHarness, { rows: [], isLoading: true }),
42
+ };
@@ -0,0 +1 @@
1
+ "use strict";
@@ -0,0 +1,7 @@
1
+ import { type Table } from '@tanstack/react-table';
2
+ interface TableHeaderProps<T> {
3
+ table: Table<T>;
4
+ hasActions?: boolean;
5
+ }
6
+ export declare function TableHeader<T>({ table, hasActions }: Readonly<TableHeaderProps<T>>): import("react/jsx-runtime").JSX.Element;
7
+ export {};
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { classNames } from '../../../utils';
3
+ import { flexRender } from '@tanstack/react-table';
4
+ import { FaChevronDown, FaChevronUp } from 'react-icons/fa';
5
+ export function TableHeader({ table, hasActions }) {
6
+ return (_jsx("thead", { className: "bg-(--uikit-tertiary)", children: table.getHeaderGroups().map((headerGroup) => (_jsxs("tr", { children: [headerGroup.headers.map((header) => {
7
+ var _a;
8
+ return (_jsx("th", { scope: "col", style: { width: header.column.getSize() }, className: classNames('select-none text-[14px] font-bold', header.id === 'select' ? 'p-0' : 'p-4 text-left cursor-pointer'), onClick: header.column.getCanSort() ? header.column.getToggleSortingHandler() : undefined, children: _jsxs("div", { className: classNames('w-full h-full', header.id === 'select'
9
+ ? 'flex justify-center items-center'
10
+ : 'flex items-center gap-2'), children: [flexRender(header.column.columnDef.header, header.getContext()), header.column.getCanSort() && (_jsx("span", { className: "text-xs", children: (_a = {
11
+ asc: _jsx(FaChevronUp, {}),
12
+ desc: _jsx(FaChevronDown, {}),
13
+ }[header.column.getIsSorted()]) !== null && _a !== void 0 ? _a : null }))] }) }, header.id));
14
+ }), hasActions && _jsx("th", { className: "w-[40px] px-2 py-2 text-right" })] }, headerGroup.id))) }));
15
+ }
@@ -0,0 +1 @@
1
+ export { BaseTable } from './BaseTable';
@@ -0,0 +1 @@
1
+ export { BaseTable } from './BaseTable';
@@ -0,0 +1,4 @@
1
+ import type { InfiniteTableProps } from './InfiniteTable.props';
2
+ export declare function InfiniteTable<T extends {
3
+ id: string;
4
+ }>(props: Readonly<InfiniteTableProps<T>>): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,49 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { flexRender } from '@tanstack/react-table';
3
+ import { BaseTable } from '../BaseTable/BaseTable';
4
+ import { classNames } from '../../../utils';
5
+ import { useInfiniteTableController } from './useInfiniteTableController';
6
+ import { DropdownWrapper } from '../../DropdownWrapper';
7
+ import { Button } from '../../Button';
8
+ import { FiMoreHorizontal } from 'react-icons/fi';
9
+ export function InfiniteTable(props) {
10
+ var _a, _b;
11
+ const { table, rows, parentScrollRef, loaderRef, virtualizationEnabled, rowVirtualizer, hasNextPage, isFetchingNextPage, tableActionsDropdown, } = useInfiniteTableController(props);
12
+ const hasActions = Boolean(tableActionsDropdown);
13
+ const colSpanFull = table.getVisibleFlatColumns().length + (hasActions ? 1 : 0);
14
+ const virtualItems = virtualizationEnabled && rowVirtualizer ? rowVirtualizer.getVirtualItems() : [];
15
+ const paddingTop = virtualItems.length ? virtualItems[0].start : 0;
16
+ const lastVirtual = virtualItems.at(-1);
17
+ const totalSize = (_b = (_a = rowVirtualizer === null || rowVirtualizer === void 0 ? void 0 : rowVirtualizer.getTotalSize) === null || _a === void 0 ? void 0 : _a.call(rowVirtualizer)) !== null && _b !== void 0 ? _b : 0;
18
+ const end = lastVirtual ? lastVirtual.end : 0;
19
+ const paddingBottom = Math.max(totalSize - end, 0);
20
+ const isTrulyEmpty = rows.length === 0 && !hasNextPage && !isFetchingNextPage;
21
+ const renderActionsCell = (row) => {
22
+ var _a, _b, _c, _d;
23
+ if (!tableActionsDropdown) {
24
+ return null;
25
+ }
26
+ return (_jsx("td", { className: "w-[40px] px-2 py-2 text-right", children: _jsx(DropdownWrapper, { structure: (_a = tableActionsDropdown.structure) !== null && _a !== void 0 ? _a : 'default', options: tableActionsDropdown.getOptions(row.original), menuAlignment: tableActionsDropdown.menuAlignment, isTriggerElementDisabled: typeof tableActionsDropdown.isDisabled === 'function'
27
+ ? tableActionsDropdown.isDisabled(row.original)
28
+ : tableActionsDropdown.isDisabled, triggerElement: (_c = (_b = tableActionsDropdown.renderCustomTrigger) === null || _b === void 0 ? void 0 : _b.call(tableActionsDropdown, row.original)) !== null && _c !== void 0 ? _c : (_jsx(Button, { "aria-label": "Open actions menu", title: "Open actions menu", size: "icon", variant: "ghost", disabled: typeof tableActionsDropdown.isDisabled === 'function'
29
+ ? tableActionsDropdown.isDisabled(row.original)
30
+ : ((_d = tableActionsDropdown.isDisabled) !== null && _d !== void 0 ? _d : false), className: "p-1 focus-visible:outline-hidden focus-visible:ring-0", children: _jsx(FiMoreHorizontal, { size: 16 }) })) }) }));
31
+ };
32
+ return (_jsx(BaseTable, { table: table, height: props.height, showHeader: true, isLoading: props.isLoading, className: props.className, containerRef: parentScrollRef, renderBody: () => {
33
+ var _a, _b;
34
+ return (_jsx(_Fragment, { children: virtualizationEnabled && rowVirtualizer ? (_jsxs(_Fragment, { children: [_jsx("tr", { style: { height: `${paddingTop}px` }, children: _jsx("td", { colSpan: colSpanFull }) }), isTrulyEmpty && (_jsx("tr", { children: _jsx("td", { colSpan: colSpanFull, className: "px-4 py-8 text-center text-muted-foreground", children: (_a = props.noRowsMessage) !== null && _a !== void 0 ? _a : 'No rows to display' }) })), virtualItems.map((vi) => {
35
+ var _a;
36
+ const isLoaderRow = vi.index >= rows.length;
37
+ if (isLoaderRow) {
38
+ return (_jsx("tr", { className: "border-t", children: _jsx("td", { colSpan: colSpanFull, className: "px-4 py-2", children: hasNextPage ? 'Loading more...' : 'Nothing more to load' }) }, `loader-${vi.key}`));
39
+ }
40
+ const row = rows[vi.index];
41
+ const clickable = (_a = props.isRowClickable) === null || _a === void 0 ? void 0 : _a.call(props, row.original);
42
+ return (_jsxs("tr", { className: classNames('border-t hover:bg-gray-50', clickable && 'cursor-pointer'), onClick: () => { var _a; return clickable && ((_a = props.onRowClick) === null || _a === void 0 ? void 0 : _a.call(props, row.original)); }, children: [row.getVisibleCells().map((cell) => (_jsx("td", { style: { width: cell.column.getSize() }, className: "p-4", children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id))), hasActions && renderActionsCell(row)] }, row.id));
43
+ }), _jsx("tr", { style: { height: `${paddingBottom}px` }, children: _jsx("td", { colSpan: colSpanFull }) })] })) : (_jsxs(_Fragment, { children: [isTrulyEmpty && (_jsx("tr", { children: _jsx("td", { colSpan: colSpanFull, className: "px-4 py-8 text-center text-muted-foreground", children: (_b = props.noRowsMessage) !== null && _b !== void 0 ? _b : 'No rows to display' }) })), rows.map((row) => {
44
+ var _a;
45
+ const clickable = (_a = props.isRowClickable) === null || _a === void 0 ? void 0 : _a.call(props, row.original);
46
+ return (_jsxs("tr", { className: classNames('border-t hover:bg-gray-50', clickable && 'cursor-pointer'), onClick: () => { var _a; return clickable && ((_a = props.onRowClick) === null || _a === void 0 ? void 0 : _a.call(props, row.original)); }, children: [row.getVisibleCells().map((cell) => (_jsx("td", { style: { width: cell.column.getSize() }, className: "p-4", children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id))), hasActions && renderActionsCell(row)] }, row.id));
47
+ }), hasNextPage && (_jsx("tr", { ref: loaderRef, className: "border-t", children: _jsx("td", { colSpan: colSpanFull, className: "px-4 py-2", children: isFetchingNextPage ? 'Loading more...' : 'Scroll to load more' }) }))] })) }));
48
+ } }));
49
+ }
@@ -0,0 +1,32 @@
1
+ import type { ColumnDef, SortingState } from '@tanstack/react-table';
2
+ import type { ITableActionsDropdownProps } from '../Table.types';
3
+ export interface InfiniteTableProps<T extends {
4
+ id: string;
5
+ }> {
6
+ id: string;
7
+ data: T[];
8
+ columns: ColumnDef<T, unknown>[];
9
+ height?: number;
10
+ hasMore: boolean;
11
+ isFetchingMore: boolean;
12
+ onLoadMore: () => void;
13
+ virtualization?: {
14
+ enabled: boolean;
15
+ rowEstimate?: number;
16
+ overscan?: number;
17
+ };
18
+ sorting?: SortingState;
19
+ onSortingChange?: (state: SortingState) => void;
20
+ grouping?: string[];
21
+ isLoading?: boolean;
22
+ className?: string;
23
+ noRowsMessage?: string;
24
+ onRowClick?: (row: T) => void;
25
+ isRowClickable?: (row: T) => boolean;
26
+ onGroupRowClick?: (args: {
27
+ columnId: string;
28
+ value: unknown;
29
+ rows: T[];
30
+ }) => void;
31
+ tableActionsDropdown?: ITableActionsDropdownProps<T>;
32
+ }
@@ -0,0 +1,8 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { InfiniteTable } from './InfiniteTable';
3
+ declare const meta: Meta<typeof InfiniteTable>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof InfiniteTable>;
6
+ export declare const InfiniteScrollVirtualized: Story;
7
+ export declare const InfiniteScrollNonVirtual: Story;
8
+ export declare const InfiniteScrollVirtualizedWithActions: Story;
@@ -0,0 +1,109 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useState } from 'react';
3
+ import { InfiniteTable } from './InfiniteTable';
4
+ const TOTAL_USERS = 100;
5
+ const ALL_USERS = Array.from({ length: TOTAL_USERS }).map((_, i) => ({
6
+ id: `user-${i + 1}`,
7
+ name: `User ${i + 1}`,
8
+ email: `user${i + 1}@example.com`,
9
+ age: 18 + ((i * 7) % 50),
10
+ }));
11
+ async function fetchInfiniteUsers(limit, offset) {
12
+ await new Promise((r) => setTimeout(r, 500));
13
+ const rows = ALL_USERS.slice(offset, offset + limit);
14
+ const nextOffset = offset + rows.length;
15
+ const hasMore = nextOffset < TOTAL_USERS;
16
+ return {
17
+ rows,
18
+ nextOffset,
19
+ hasMore,
20
+ };
21
+ }
22
+ const columns = [
23
+ { accessorKey: 'name', header: 'Name', cell: (info) => info.getValue() },
24
+ { accessorKey: 'email', header: 'Email', cell: (info) => info.getValue() },
25
+ { accessorKey: 'age', header: 'Age', cell: (info) => info.getValue() },
26
+ ];
27
+ const meta = {
28
+ title: 'UIKit/Table/InfiniteTable',
29
+ component: InfiniteTable,
30
+ };
31
+ export default meta;
32
+ function useInfiniteUsers(pageSize = 20) {
33
+ const [rows, setRows] = useState([]);
34
+ const [nextOffset, setNextOffset] = useState(0);
35
+ const [hasMore, setHasMore] = useState(true);
36
+ const [isFetchingMore, setIsFetchingMore] = useState(false);
37
+ useEffect(() => {
38
+ (async () => {
39
+ setIsFetchingMore(true);
40
+ const res = await fetchInfiniteUsers(pageSize, 0);
41
+ setRows(res.rows);
42
+ setNextOffset(res.nextOffset);
43
+ setHasMore(res.hasMore);
44
+ setIsFetchingMore(false);
45
+ })();
46
+ }, [pageSize]);
47
+ const onLoadMore = useCallback(async () => {
48
+ if (isFetchingMore || !hasMore)
49
+ return;
50
+ setIsFetchingMore(true);
51
+ const res = await fetchInfiniteUsers(pageSize, nextOffset);
52
+ setRows((prev) => [...prev, ...res.rows]);
53
+ setNextOffset(res.nextOffset);
54
+ setHasMore(res.hasMore);
55
+ setIsFetchingMore(false);
56
+ }, [isFetchingMore, hasMore, nextOffset, pageSize]);
57
+ return { rows, hasMore, isFetchingMore, onLoadMore };
58
+ }
59
+ function InfiniteScrollVirtualizedExample() {
60
+ const { rows, hasMore, isFetchingMore, onLoadMore } = useInfiniteUsers();
61
+ return (_jsx("div", { children: _jsx(InfiniteTable, { id: "infinite-virtualized", height: 500, data: rows, columns: columns, hasMore: hasMore, isFetchingMore: isFetchingMore, onLoadMore: onLoadMore, virtualization: {
62
+ enabled: true,
63
+ rowEstimate: 48,
64
+ overscan: 6,
65
+ } }) }));
66
+ }
67
+ export const InfiniteScrollVirtualized = {
68
+ name: 'Infinite Scroll — Virtualized',
69
+ render: () => _jsx(InfiniteScrollVirtualizedExample, {}),
70
+ };
71
+ function InfiniteScrollNonVirtualExample() {
72
+ const { rows, hasMore, isFetchingMore, onLoadMore } = useInfiniteUsers();
73
+ return (_jsx("div", { children: _jsx(InfiniteTable, { id: "infinite-nonvirtual", height: 500, data: rows, columns: columns, hasMore: hasMore, isFetchingMore: isFetchingMore, onLoadMore: onLoadMore, virtualization: { enabled: false } }) }));
74
+ }
75
+ export const InfiniteScrollNonVirtual = {
76
+ name: 'Infinite Scroll — Non-virtual',
77
+ render: () => _jsx(InfiniteScrollNonVirtualExample, {}),
78
+ };
79
+ function InfiniteScrollVirtualizedWithActionsExample() {
80
+ const { rows, hasMore, isFetchingMore, onLoadMore } = useInfiniteUsers();
81
+ return (_jsx(InfiniteTable, { id: "infinite-virtualized-actions", height: 500, data: rows, columns: columns, hasMore: hasMore, isFetchingMore: isFetchingMore, onLoadMore: onLoadMore, virtualization: {
82
+ enabled: true,
83
+ rowEstimate: 48,
84
+ overscan: 6,
85
+ }, onRowClick: (row) => {
86
+ alert(`Row clicked: ${row.name}`);
87
+ }, isRowClickable: () => true, tableActionsDropdown: {
88
+ structure: 'default',
89
+ getOptions: (row) => [
90
+ {
91
+ label: 'View',
92
+ onClick: () => alert(`View ${row.name}`),
93
+ },
94
+ {
95
+ label: 'Edit',
96
+ onClick: () => alert(`Edit ${row.name}`),
97
+ },
98
+ {
99
+ label: 'Delete',
100
+ variant: 'destructive',
101
+ onClick: () => alert(`Delete ${row.name}`),
102
+ },
103
+ ],
104
+ } }));
105
+ }
106
+ export const InfiniteScrollVirtualizedWithActions = {
107
+ name: 'Infinite Scroll — Virtualized (Row Click & Actions)',
108
+ render: () => _jsx(InfiniteScrollVirtualizedWithActionsExample, {}),
109
+ };
@@ -0,0 +1,2 @@
1
+ export { InfiniteTable } from './InfiniteTable';
2
+ export type { InfiniteTableProps } from './InfiniteTable.props';
@@ -0,0 +1 @@
1
+ export { InfiniteTable } from './InfiniteTable';
@@ -0,0 +1,12 @@
1
+ interface UseInfiniteScrollTriggerOptions {
2
+ hasMore: boolean;
3
+ isFetching: boolean;
4
+ onLoadMore: () => void;
5
+ rootRef?: React.RefObject<Element | null>;
6
+ rootMargin?: string;
7
+ disabled?: boolean;
8
+ }
9
+ export declare function useInfiniteScrollTrigger({ hasMore, isFetching, onLoadMore, rootRef, rootMargin, disabled, }: UseInfiniteScrollTriggerOptions): {
10
+ sentinelRef: (el: HTMLDivElement | null) => void;
11
+ };
12
+ export {};
@@ -0,0 +1,31 @@
1
+ import { useState, useCallback, useRef, useEffect } from 'react';
2
+ export function useInfiniteScrollTrigger({ hasMore, isFetching, onLoadMore, rootRef, rootMargin = '200px', disabled = false, }) {
3
+ const [node, setNode] = useState(null);
4
+ const sentinelRef = useCallback((el) => setNode(el), []);
5
+ const lockRef = useRef(false);
6
+ useEffect(() => {
7
+ var _a;
8
+ if (disabled) {
9
+ return;
10
+ }
11
+ if (!node) {
12
+ return;
13
+ }
14
+ const observer = new IntersectionObserver(([entry]) => {
15
+ if (!entry.isIntersecting) {
16
+ return;
17
+ }
18
+ if (!hasMore || isFetching) {
19
+ return;
20
+ }
21
+ if (lockRef.current) {
22
+ return;
23
+ }
24
+ lockRef.current = true;
25
+ Promise.resolve(onLoadMore()).finally(() => (lockRef.current = false));
26
+ }, { root: (_a = rootRef === null || rootRef === void 0 ? void 0 : rootRef.current) !== null && _a !== void 0 ? _a : null, rootMargin, threshold: 0.01 });
27
+ observer.observe(node);
28
+ return () => observer.disconnect();
29
+ }, [disabled, node, hasMore, isFetching, onLoadMore, rootMargin, rootRef]);
30
+ return { sentinelRef };
31
+ }
@@ -0,0 +1,29 @@
1
+ import { type RefObject } from 'react';
2
+ import { type Virtualizer } from '@tanstack/react-virtual';
3
+ import type { InfiniteScrollOptions, VirtualizationOptions } from '../Table.types';
4
+ type PageEnvelope<T> = {
5
+ rows: T[];
6
+ };
7
+ export interface UseInfiniteScrollingParams<T extends {
8
+ id: string;
9
+ }> {
10
+ dataPages?: PageEnvelope<T>[];
11
+ flatData?: T[];
12
+ infiniteScroll?: InfiniteScrollOptions;
13
+ virtualization?: VirtualizationOptions;
14
+ parentScrollRef: RefObject<HTMLDivElement | null>;
15
+ loaderRef?: RefObject<HTMLElement | null>;
16
+ }
17
+ export interface UseInfiniteScrollingReturn<T extends {
18
+ id: string;
19
+ }> {
20
+ allRows: T[];
21
+ virtualizationEnabled: boolean;
22
+ rowVirtualizer?: Virtualizer<HTMLDivElement, Element>;
23
+ hasNextPage: boolean;
24
+ isFetchingNextPage: boolean;
25
+ }
26
+ export declare function useInfiniteScrolling<T extends {
27
+ id: string;
28
+ }>(params: UseInfiniteScrollingParams<T>): UseInfiniteScrollingReturn<T>;
29
+ export {};
@@ -0,0 +1,103 @@
1
+ import { useEffect, useMemo, useRef } from 'react';
2
+ import { useVirtualizer } from '@tanstack/react-virtual';
3
+ const ROOT_MARGIN_PX = 300;
4
+ export function useInfiniteScrolling(params) {
5
+ var _a, _b, _c;
6
+ const { dataPages, flatData, infiniteScroll, virtualization, parentScrollRef, loaderRef } = params;
7
+ const isInfinite = Boolean(infiniteScroll === null || infiniteScroll === void 0 ? void 0 : infiniteScroll.enabled);
8
+ const hasNextPage = Boolean(infiniteScroll === null || infiniteScroll === void 0 ? void 0 : infiniteScroll.hasNextPage);
9
+ const isFetchingNextPage = Boolean(infiniteScroll === null || infiniteScroll === void 0 ? void 0 : infiniteScroll.isFetchingNextPage);
10
+ const fetchNextPage = infiniteScroll === null || infiniteScroll === void 0 ? void 0 : infiniteScroll.fetchNextPage;
11
+ const allRows = useMemo(() => {
12
+ if (isInfinite && Array.isArray(dataPages)) {
13
+ const byId = new Map();
14
+ for (const p of dataPages) {
15
+ for (const r of p.rows) {
16
+ byId.set(String(r.id), r);
17
+ }
18
+ }
19
+ return Array.from(byId.values());
20
+ }
21
+ return flatData !== null && flatData !== void 0 ? flatData : [];
22
+ }, [isInfinite, dataPages, flatData]);
23
+ const virtualizationEnabled = Boolean(virtualization === null || virtualization === void 0 ? void 0 : virtualization.enabled);
24
+ const rowVirtualizer = useVirtualizer({
25
+ count: allRows.length + (isInfinite && hasNextPage ? 1 : 0),
26
+ getScrollElement: () => parentScrollRef.current,
27
+ estimateSize: () => { var _a; return (_a = virtualization === null || virtualization === void 0 ? void 0 : virtualization.rowEstimate) !== null && _a !== void 0 ? _a : 48; },
28
+ overscan: (_a = virtualization === null || virtualization === void 0 ? void 0 : virtualization.overscan) !== null && _a !== void 0 ? _a : 6,
29
+ });
30
+ const loadLockRef = useRef(false);
31
+ const virtualItems = rowVirtualizer.getVirtualItems();
32
+ const lastVirtualIndex = (_c = (_b = virtualItems.at(-1)) === null || _b === void 0 ? void 0 : _b.index) !== null && _c !== void 0 ? _c : -1;
33
+ const loaderIndex = allRows.length;
34
+ useEffect(() => {
35
+ if (!virtualizationEnabled || !isInfinite) {
36
+ return;
37
+ }
38
+ if (!fetchNextPage || !hasNextPage || isFetchingNextPage) {
39
+ return;
40
+ }
41
+ if (allRows.length === 0) {
42
+ return;
43
+ }
44
+ if (lastVirtualIndex < loaderIndex || loadLockRef.current) {
45
+ return;
46
+ }
47
+ loadLockRef.current = true;
48
+ Promise.resolve(fetchNextPage()).finally(() => {
49
+ loadLockRef.current = false;
50
+ });
51
+ }, [
52
+ virtualizationEnabled,
53
+ isInfinite,
54
+ hasNextPage,
55
+ isFetchingNextPage,
56
+ fetchNextPage,
57
+ lastVirtualIndex,
58
+ loaderIndex,
59
+ allRows.length,
60
+ ]);
61
+ useEffect(() => {
62
+ var _a, _b;
63
+ if (!isInfinite || virtualizationEnabled) {
64
+ return;
65
+ }
66
+ if (!(loaderRef === null || loaderRef === void 0 ? void 0 : loaderRef.current) || !fetchNextPage) {
67
+ return;
68
+ }
69
+ const rootMargin = `${(_a = infiniteScroll === null || infiniteScroll === void 0 ? void 0 : infiniteScroll.rootMarginPx) !== null && _a !== void 0 ? _a : ROOT_MARGIN_PX}px`;
70
+ const observer = new IntersectionObserver((entries) => {
71
+ const entry = entries[0];
72
+ if (entry.isIntersecting && hasNextPage && !isFetchingNextPage) {
73
+ fetchNextPage();
74
+ }
75
+ }, {
76
+ root: (_b = parentScrollRef.current) !== null && _b !== void 0 ? _b : null,
77
+ rootMargin: `0px 0px ${rootMargin} 0px`,
78
+ threshold: 0.01,
79
+ });
80
+ const el = loaderRef.current;
81
+ observer.observe(el);
82
+ return () => {
83
+ observer.unobserve(el);
84
+ observer.disconnect();
85
+ };
86
+ }, [
87
+ isInfinite,
88
+ virtualizationEnabled,
89
+ hasNextPage,
90
+ isFetchingNextPage,
91
+ fetchNextPage,
92
+ infiniteScroll === null || infiniteScroll === void 0 ? void 0 : infiniteScroll.rootMarginPx,
93
+ parentScrollRef,
94
+ loaderRef,
95
+ ]);
96
+ return {
97
+ allRows,
98
+ virtualizationEnabled,
99
+ rowVirtualizer,
100
+ hasNextPage,
101
+ isFetchingNextPage,
102
+ };
103
+ }
@@ -0,0 +1,14 @@
1
+ import type { InfiniteTableProps } from './InfiniteTable.props';
2
+ export declare function useInfiniteTableController<T extends {
3
+ id: string;
4
+ }>(props: Readonly<InfiniteTableProps<T>>): {
5
+ table: import("@tanstack/table-core").Table<T>;
6
+ rows: import("@tanstack/table-core").Row<T>[];
7
+ parentScrollRef: import("react").MutableRefObject<HTMLDivElement | null>;
8
+ loaderRef: import("react").MutableRefObject<HTMLTableRowElement | null>;
9
+ virtualizationEnabled: boolean;
10
+ rowVirtualizer: import("@tanstack/virtual-core").Virtualizer<HTMLDivElement, Element> | undefined;
11
+ hasNextPage: boolean;
12
+ isFetchingNextPage: boolean;
13
+ tableActionsDropdown: import("..").ITableActionsDropdownProps<T> | undefined;
14
+ };
@@ -0,0 +1,76 @@
1
+ import { useLayoutEffect, useMemo, useRef, useState } from 'react';
2
+ import { useReactTable, getCoreRowModel } from '@tanstack/react-table';
3
+ import { applyFlexSizes } from '../table.helpers';
4
+ import { useInfiniteScrolling } from './useInfiniteScrolling';
5
+ export function useInfiniteTableController(props) {
6
+ const { data, columns, hasMore, isFetchingMore, onLoadMore, virtualization, tableActionsDropdown } = props;
7
+ const safeData = useMemo(() => (Array.isArray(data) ? data : []), [data]);
8
+ const parentScrollRef = useRef(null);
9
+ const loaderRef = useRef(null);
10
+ const [containerWidth, setContainerWidth] = useState(0);
11
+ useLayoutEffect(() => {
12
+ if (!parentScrollRef.current) {
13
+ return;
14
+ }
15
+ if (typeof ResizeObserver === 'undefined') {
16
+ return;
17
+ }
18
+ const ro = new ResizeObserver(([entry]) => {
19
+ setContainerWidth(entry.contentRect.width);
20
+ });
21
+ ro.observe(parentScrollRef.current);
22
+ return () => ro.disconnect();
23
+ }, []);
24
+ const virtualizationOpts = useMemo(() => {
25
+ if (!virtualization) {
26
+ return undefined;
27
+ }
28
+ return {
29
+ enabled: virtualization.enabled,
30
+ rowEstimate: virtualization.rowEstimate,
31
+ overscan: virtualization.overscan,
32
+ };
33
+ }, [virtualization]);
34
+ const infiniteScrollOpts = useMemo(() => ({
35
+ enabled: true,
36
+ hasNextPage: hasMore,
37
+ isFetchingNextPage: isFetchingMore,
38
+ fetchNextPage: onLoadMore,
39
+ }), [hasMore, isFetchingMore, onLoadMore]);
40
+ const dataPages = useMemo(() => [{ rows: safeData }], [safeData]);
41
+ const { allRows, virtualizationEnabled, rowVirtualizer, hasNextPage } = useInfiniteScrolling({
42
+ dataPages,
43
+ flatData: safeData,
44
+ infiniteScroll: infiniteScrollOpts,
45
+ virtualization: virtualizationOpts,
46
+ parentScrollRef,
47
+ loaderRef,
48
+ });
49
+ // For InfiniteTable, we are ALWAYS in infinite mode, so table data is allRows
50
+ const tableColumns = useMemo(() => {
51
+ return applyFlexSizes(columns !== null && columns !== void 0 ? columns : [], containerWidth, Boolean(virtualizationEnabled));
52
+ }, [columns, containerWidth, virtualizationEnabled]);
53
+ const table = useReactTable({
54
+ data: allRows,
55
+ columns: tableColumns,
56
+ getRowId: (row) => String(row.id),
57
+ getCoreRowModel: getCoreRowModel(),
58
+ manualPagination: true,
59
+ autoResetPageIndex: false,
60
+ meta: {
61
+ hasActions: Boolean(tableActionsDropdown),
62
+ },
63
+ });
64
+ const rows = table.getRowModel().rows;
65
+ return {
66
+ table,
67
+ rows,
68
+ parentScrollRef,
69
+ loaderRef,
70
+ virtualizationEnabled,
71
+ rowVirtualizer,
72
+ hasNextPage,
73
+ isFetchingNextPage: isFetchingMore,
74
+ tableActionsDropdown,
75
+ };
76
+ }
@@ -0,0 +1,2 @@
1
+ import type { PageSizeSelectorProps } from './Pagination.types';
2
+ export declare const PageSizeSelector: ({ id, value, onChange, options, label, className, size, }: PageSizeSelectorProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { SelectWrapper } from '../../SelectWrapper';
3
+ import { cn } from '../../../lib/utils';
4
+ export const PageSizeSelector = ({ id, value, onChange, options = ['5', '10', '20', '50', '100'], label = 'Rows per page:', className, size = 'default', }) => {
5
+ return (_jsxs("div", { className: cn('flex items-center gap-2 text-xs', className), id: `page-size-select-${id}`, children: [_jsx("label", { htmlFor: `page-size-select-${id}`, children: label }), _jsx("div", { className: "w-fit", children: _jsx(SelectWrapper, { size: size, value: value, onChange: onChange, options: options.map((option) => ({
6
+ label: option,
7
+ value: option,
8
+ })), className: "bg-transparent shadow-none border-none gap-1" }) })] }));
9
+ };
@@ -0,0 +1,21 @@
1
+ import type { SelectWrapperProps } from '../../SelectWrapper';
2
+ export interface PaginationControlsProps {
3
+ tableId: string;
4
+ pageIndex: number;
5
+ pageSize: number;
6
+ totalRows: number;
7
+ canNextPage: boolean;
8
+ canPrevPage: boolean;
9
+ onNextPage: () => void;
10
+ onPrevPage: () => void;
11
+ onPageSizeChange: (size: number) => void;
12
+ }
13
+ export type PageSizeSelectorProps = {
14
+ id: string;
15
+ value: string;
16
+ onChange: (val: string) => void;
17
+ options?: string[];
18
+ label?: string;
19
+ className?: SelectWrapperProps['className'];
20
+ size?: SelectWrapperProps['size'];
21
+ };
@@ -0,0 +1,2 @@
1
+ import type { PaginationControlsProps } from './Pagination.types';
2
+ export declare const PaginationFooter: ({ tableId, pageIndex, pageSize, totalRows, canNextPage, canPrevPage, onNextPage, onPrevPage, onPageSizeChange, }: PaginationControlsProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { FaChevronLeft, FaChevronRight } from 'react-icons/fa6';
3
+ import { PageSizeSelector } from './PageSizeSelector';
4
+ import { Button } from '../../Button';
5
+ import { cn } from '../../../lib/utils';
6
+ export const PaginationFooter = ({ tableId, pageIndex, pageSize, totalRows, canNextPage, canPrevPage, onNextPage, onPrevPage, onPageSizeChange, }) => {
7
+ const startRow = totalRows === 0 ? 0 : pageIndex * pageSize + 1;
8
+ const endRow = totalRows === 0 ? 0 : Math.min((pageIndex + 1) * pageSize, totalRows);
9
+ return (_jsxs("div", { className: "flex justify-end items-center gap-6 px-4 py-2 text-xs text-(--uikit-textSecondary) w-full", children: [_jsx(PageSizeSelector, { id: tableId, value: String(pageSize), onChange: (val) => onPageSizeChange(Number(val)), size: "small", label: "Rows per page" }), totalRows > 0 && _jsx("div", { id: `page-indicator-${tableId}`, children: `${startRow} - ${endRow} of ${totalRows}` }), totalRows > 0 && (_jsxs("div", { className: "flex gap-2", id: `pagination-controls-${tableId}`, children: [_jsx(Button, { variant: "ghost", size: "icon", "aria-label": "Previous page", onClick: onPrevPage, disabled: !canPrevPage, className: cn({ '!bg-transparent': !canPrevPage }), children: _jsx(FaChevronLeft, { className: "!w-3 !h-3" }) }), _jsx(Button, { variant: "ghost", size: "icon", "aria-label": "Next page", onClick: onNextPage, disabled: !canNextPage, className: cn({ '!bg-transparent': !canNextPage }), children: _jsx(FaChevronRight, { className: "!w-3 !h-3" }) })] }))] }));
10
+ };
@@ -1,2 +1,3 @@
1
- export * from './PaginationControls';
2
- export * from './PaginationControls.types';
1
+ export * from './PageSizeSelector';
2
+ export * from './PaginationFooter';
3
+ export * from './Pagination.types';
@@ -1,2 +1,3 @@
1
- export * from './PaginationControls';
2
- export * from './PaginationControls.types';
1
+ export * from './PageSizeSelector';
2
+ export * from './PaginationFooter';
3
+ export * from './Pagination.types';