@eml-payments/ui-kit 1.3.3 → 1.4.4

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,16 @@
1
+ import type { Table } from '@tanstack/react-table';
2
+ interface UsePaginationControllerOptions<T> {
3
+ table: Table<T>;
4
+ paginationMode: 'client' | 'server' | 'none';
5
+ onRefetch?: (pageIndex: number, pageSize: number) => void;
6
+ }
7
+ export declare function usePaginationController<T>({ table, paginationMode, onRefetch }: UsePaginationControllerOptions<T>): {
8
+ pageIndex: number;
9
+ pageSize: number;
10
+ canNextPage: boolean;
11
+ canPrevPage: boolean;
12
+ onNextPage: () => void;
13
+ onPrevPage: () => void;
14
+ setPageSize: (size: number) => void;
15
+ };
16
+ export {};
@@ -0,0 +1,33 @@
1
+ import { useCallback, useEffect } from 'react';
2
+ export function usePaginationController({ table, paginationMode, onRefetch }) {
3
+ const { pageIndex, pageSize } = table.getState().pagination;
4
+ const canNextPage = table.getCanNextPage();
5
+ const canPrevPage = table.getCanPreviousPage();
6
+ const setPageSize = useCallback((size) => {
7
+ table.setPageSize(size);
8
+ }, [table]);
9
+ const onNextPage = useCallback(() => {
10
+ if (canNextPage) {
11
+ table.nextPage();
12
+ }
13
+ }, [canNextPage, table]);
14
+ const onPrevPage = useCallback(() => {
15
+ if (canPrevPage) {
16
+ table.previousPage();
17
+ }
18
+ }, [canPrevPage, table]);
19
+ useEffect(() => {
20
+ if (paginationMode === 'server' && onRefetch) {
21
+ onRefetch(pageIndex, pageSize);
22
+ }
23
+ }, [paginationMode, pageIndex, pageSize, onRefetch]);
24
+ return {
25
+ pageIndex,
26
+ pageSize,
27
+ canNextPage,
28
+ canPrevPage,
29
+ onNextPage,
30
+ onPrevPage,
31
+ setPageSize,
32
+ };
33
+ }
@@ -0,0 +1,4 @@
1
+ import type { StandardTableProps } from '../Table.types';
2
+ export declare function StandardTable<T extends {
3
+ id: string;
4
+ }>(props: Readonly<StandardTableProps<T>>): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,47 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { PaginationFooter } from '../Pagination';
3
+ import { BaseTable } from '../BaseTable/BaseTable';
4
+ import { renderTableBody } from '../body/renderTableBody';
5
+ import { useStandardTableController } from './useStandardTableController';
6
+ import { getTableBodyState, getColSpan } from '../body/utils';
7
+ import { useMemo } from 'react';
8
+ export function StandardTable(props) {
9
+ var _a;
10
+ const { table, height, showHeader, parentScrollRef, pagination, searchBelowMinLength, hasCachedData } = useStandardTableController(props);
11
+ const rows = table.getRowModel().rows;
12
+ const minLen = (_a = props.minSearchLength) !== null && _a !== void 0 ? _a : 3;
13
+ const getNoResultsMessage = () => {
14
+ var _a, _b;
15
+ if (searchBelowMinLength) {
16
+ return `Please enter at least ${minLen} characters to search`;
17
+ }
18
+ if (props.isSearchActive) {
19
+ return (_a = props.noSearchResultsMessage) !== null && _a !== void 0 ? _a : 'No results meet your search criteria';
20
+ }
21
+ return (_b = props.noRowsMessage) !== null && _b !== void 0 ? _b : 'No rows to display';
22
+ };
23
+ const noResultsMessage = getNoResultsMessage();
24
+ const colSpan = getColSpan(table);
25
+ const bodyState = searchBelowMinLength
26
+ ? { type: 'empty', message: noResultsMessage }
27
+ : getTableBodyState({
28
+ isLoading: props.isLoading && !hasCachedData,
29
+ rows,
30
+ noResultsMessage,
31
+ });
32
+ const totalRows = useMemo(() => {
33
+ var _a;
34
+ return props.paginationMode === 'server'
35
+ ? ((_a = props.totalServerRows) !== null && _a !== void 0 ? _a : 0)
36
+ : table.getFilteredRowModel().rows.length;
37
+ }, [props.paginationMode, props.totalServerRows, table]);
38
+ return (_jsx("div", { ref: parentScrollRef, children: _jsx(BaseTable, { table: table, height: height, showHeader: showHeader, className: props.className, isLoading: props.isLoading, renderBody: () => renderTableBody({
39
+ table,
40
+ state: bodyState,
41
+ colSpan,
42
+ onRowClick: props.onRowClick,
43
+ isRowClickable: props.isRowClickable,
44
+ onGroupRowClick: props.onGroupRowClick,
45
+ tableActionsDropdown: props.tableActionsDropdown,
46
+ }), footer: props.paginationMode === 'client' || props.paginationMode === 'server' ? (_jsx(PaginationFooter, { tableId: props.id, pageIndex: pagination.pageIndex, pageSize: pagination.pageSize, totalRows: totalRows, canNextPage: pagination.canNextPage, canPrevPage: pagination.canPrevPage, onNextPage: pagination.onNextPage, onPrevPage: pagination.onPrevPage, onPageSizeChange: pagination.setPageSize })) : null }) }));
47
+ }
@@ -0,0 +1,25 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { StandardTable } from './StandardTable';
3
+ type User = {
4
+ id: string;
5
+ name: string;
6
+ email: string;
7
+ role: string;
8
+ };
9
+ declare const meta: Meta<typeof StandardTable<User>>;
10
+ export default meta;
11
+ type Story = StoryObj<typeof StandardTable<User>>;
12
+ export declare const Basic: Story;
13
+ export declare const EmptyState: Story;
14
+ export declare const Loading: Story;
15
+ export declare const ClientPagination: Story;
16
+ export declare const ServerPagination: Story;
17
+ export declare const RowClick: Story;
18
+ export declare const ControlledSorting: Story;
19
+ export declare const GroupedWithClicks: Story;
20
+ export declare const BasicWithActions: Story;
21
+ export declare const WithSearch: Story;
22
+ export declare const ServerSideSearch: Story;
23
+ export declare const WithQueryParamsSync: Story;
24
+ export declare const ServerPaginationWithQueryParams: Story;
25
+ export declare const TwoTablesWithQueryParams: Story;
@@ -0,0 +1,268 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useCallback, useMemo, useState } from 'react';
3
+ import { StandardTable } from './StandardTable';
4
+ import { SearchInput } from '../../SearchInput';
5
+ import { useLocation } from 'react-router-dom';
6
+ const data = Array.from({ length: 10 }).map((_, i) => ({
7
+ id: `user-${i + 1}`,
8
+ name: `User ${i + 1}`,
9
+ email: `user${i + 1}@example.com`,
10
+ role: i % 2 === 0 ? 'Admin' : 'User',
11
+ }));
12
+ const columns = [
13
+ {
14
+ accessorKey: 'id',
15
+ header: 'ID',
16
+ flex: 1,
17
+ },
18
+ {
19
+ accessorKey: 'name',
20
+ header: 'Name',
21
+ flex: 1,
22
+ },
23
+ {
24
+ accessorKey: 'email',
25
+ header: 'Email',
26
+ flex: 2,
27
+ cell: ({ row }) => (_jsx("a", { href: `mailto:${row.original.email}`, className: "text-blue-600 underline", children: row.original.email })),
28
+ },
29
+ {
30
+ accessorKey: 'role',
31
+ header: 'Role',
32
+ flex: 1,
33
+ cell: ({ row }) => (_jsx("span", { className: row.original.role === 'Admin' ? 'font-bold text-red-500' : undefined, children: row.original.role })),
34
+ },
35
+ ];
36
+ const meta = {
37
+ title: 'UIKit/Table/StandardTable',
38
+ component: StandardTable,
39
+ tags: ['autodocs'],
40
+ };
41
+ export default meta;
42
+ export const Basic = {
43
+ render: () => _jsx(StandardTable, { id: "basic-table", data: data, columns: columns, paginationMode: "client" }),
44
+ };
45
+ export const EmptyState = {
46
+ render: () => (_jsx(StandardTable, { id: "empty-table", data: [], columns: columns, noRowsMessage: "No users found", paginationMode: "client" })),
47
+ };
48
+ export const Loading = {
49
+ render: () => _jsx(StandardTable, { id: "loading-table", data: [], columns: columns, isLoading: true, paginationMode: "client" }),
50
+ };
51
+ export const ClientPagination = {
52
+ render: () => (_jsx(StandardTable, { id: "client-pagination", data: data, columns: columns, paginationMode: "client", rowsPerPage: 5 })),
53
+ };
54
+ function ServerPaginationExample() {
55
+ const [rows, setRows] = useState([]);
56
+ const [isLoading, setIsLoading] = useState(false);
57
+ const fetchData = useCallback(async (pageIndex, pageSize) => {
58
+ setIsLoading(true);
59
+ await new Promise((r) => setTimeout(r, 500));
60
+ const start = pageIndex * pageSize;
61
+ const result = Array.from({ length: pageSize }).map((_, i) => {
62
+ const id = start + i + 1;
63
+ return {
64
+ id: `user-${id}`,
65
+ name: `User ${id}`,
66
+ email: `user${id}@example.com`,
67
+ role: id % 2 === 0 ? 'Admin' : 'User',
68
+ };
69
+ });
70
+ setRows(result);
71
+ setIsLoading(false);
72
+ }, []);
73
+ return (_jsx(StandardTable, { id: "server-pagination", data: rows, columns: columns, paginationMode: "server", totalServerRows: 100, onRefetch: fetchData, isLoading: isLoading }));
74
+ }
75
+ export const ServerPagination = {
76
+ render: () => _jsx(ServerPaginationExample, {}),
77
+ };
78
+ export const RowClick = {
79
+ render: () => (_jsx(StandardTable, { id: "row-click", data: data, columns: columns, isRowClickable: (row) => row.role !== 'Admin', onRowClick: (row) => {
80
+ alert(`Clicked ${row.name}`);
81
+ }, paginationMode: "client" })),
82
+ };
83
+ function ControlledSortingExample() {
84
+ const [sorting, setSorting] = useState([{ id: 'name', desc: true }]);
85
+ return (_jsx(StandardTable, { id: "controlled-sorting", data: data, columns: columns, sorting: sorting, onSortingChange: setSorting, paginationMode: "client" }));
86
+ }
87
+ export const ControlledSorting = {
88
+ render: () => _jsx(ControlledSortingExample, {}),
89
+ };
90
+ const dates = [
91
+ new Date().toISOString(),
92
+ new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
93
+ new Date(Date.now() + 2 * 24 * 60 * 60 * 1000).toISOString(),
94
+ ];
95
+ function getRandomInt(max) {
96
+ if (max <= 0) {
97
+ throw new Error('max must be positive');
98
+ }
99
+ const maxUint32 = 0xffffffff;
100
+ const acceptableRange = Math.floor(maxUint32 / max) * max;
101
+ while (true) {
102
+ const value = crypto.getRandomValues(new Uint32Array(1))[0];
103
+ if (value < acceptableRange) {
104
+ return value % max;
105
+ }
106
+ }
107
+ }
108
+ const groupingData = Array.from({ length: 5 }).map((_, i) => ({
109
+ id: `row-${i + 1}`,
110
+ date: dates[i % dates.length],
111
+ name: `User ${i + 1}`,
112
+ amount: getRandomInt(1000) + 100,
113
+ }));
114
+ const groupingColumns = [
115
+ {
116
+ accessorKey: 'name',
117
+ header: 'Name',
118
+ flex: 1,
119
+ },
120
+ {
121
+ accessorKey: 'amount',
122
+ header: 'Amount',
123
+ flex: 1,
124
+ cell: ({ row }) => (_jsxs("span", { style: { textAlign: 'right', display: 'block' }, children: ["$", row.original.amount.toFixed(2)] })),
125
+ },
126
+ {
127
+ accessorKey: 'date',
128
+ header: 'Last Updated',
129
+ flex: 1,
130
+ enableGrouping: true,
131
+ getGroupingValue: (row) => new Date(row.date).toLocaleDateString('en-GB'),
132
+ meta: {
133
+ bgColor: '#f5f5f5',
134
+ },
135
+ },
136
+ ];
137
+ export const GroupedWithClicks = {
138
+ render: () => (_jsx(StandardTable, { id: "grouped-click-handlers", data: groupingData, columns: groupingColumns, showHeader: false, grouping: ['date'], paginationMode: "client", onRowClick: (row) => {
139
+ alert(`Row clicked: ${row.name} ($${row.amount})`);
140
+ }, onGroupRowClick: ({ value, rows }) => {
141
+ const total = rows.reduce((sum, r) => sum + r.amount, 0);
142
+ alert(`Group ${value}\n` + `Rows: ${rows.length}\n` + `Total amount: $${total}`);
143
+ } })),
144
+ };
145
+ export const BasicWithActions = {
146
+ render: () => (_jsx(StandardTable, { id: "table-with-actions", data: data, columns: columns, paginationMode: "client", tableActionsDropdown: {
147
+ getOptions: (row) => [
148
+ { label: `Edit ${row.name}`, onClick: () => alert(`Edit ${row.name}`) },
149
+ { label: `Delete ${row.name}`, onClick: () => alert(`Delete ${row.name}`) },
150
+ ],
151
+ isDisabled: (row) => row.role === 'Admin',
152
+ } })),
153
+ };
154
+ /**
155
+ * Reusable hook for client-side search with minimum character gating.
156
+ * The table only needs `isSearchActive` — filtering is done externally.
157
+ */
158
+ function useSearch(sourceData, selector, minChars = 3) {
159
+ const [query, setQuery] = useState('');
160
+ const isSearchActive = query.trim().length >= minChars;
161
+ const filteredData = useMemo(() => {
162
+ if (!isSearchActive)
163
+ return sourceData;
164
+ const lowerQuery = query.toLowerCase();
165
+ return sourceData.filter((item) => selector(item).toLowerCase().includes(lowerQuery));
166
+ }, [query, isSearchActive, sourceData, selector]);
167
+ return { query, setQuery, filteredData, isSearchActive };
168
+ }
169
+ /**
170
+ * Client-side search using SearchInput + useSearch.
171
+ * The table receives pre-filtered data and `isSearchActive` for the empty message.
172
+ * No `searchQuery` / `minSearchLength` needed — filtering happens outside the table.
173
+ */
174
+ function WithSearchExample() {
175
+ const { setQuery, filteredData, isSearchActive } = useSearch(data, (user) => [user.name, user.email, user.role].join(' '));
176
+ return (_jsxs("div", { className: "space-y-4", children: [_jsx("div", { style: { width: '300px' }, children: _jsx(SearchInput, { onSearch: (q) => setQuery(q), onClear: () => setQuery(''), placeholder: "Search by name..." }) }), _jsx(StandardTable, { id: "search-table", paginationMode: "client", data: filteredData, columns: columns, isSearchActive: isSearchActive, rowsPerPage: 5 })] }));
177
+ }
178
+ export const WithSearch = {
179
+ render: () => _jsx(WithSearchExample, {}),
180
+ };
181
+ function ServerSideSearchExample() {
182
+ const [rows, setRows] = useState([]);
183
+ const [isLoading, setIsLoading] = useState(false);
184
+ const [query, setQuery] = useState('');
185
+ const [total, setTotal] = useState(0);
186
+ const fetchData = useCallback(async (pageIndex, pageSize) => {
187
+ setIsLoading(true);
188
+ await new Promise((r) => setTimeout(r, 500));
189
+ const filtered = data.filter((user) => [user.name, user.email, user.role].join(' ').toLowerCase().includes(query.toLowerCase()));
190
+ const start = pageIndex * pageSize;
191
+ const paginated = filtered.slice(start, start + pageSize);
192
+ setRows(paginated);
193
+ setTotal(filtered.length);
194
+ setIsLoading(false);
195
+ }, [query]);
196
+ return (_jsxs("div", { className: "space-y-4", children: [_jsx("div", { style: { width: '300px' }, children: _jsx(SearchInput, { onSearch: (q) => setQuery(q), onClear: () => setQuery(''), placeholder: "Search users..." }) }), _jsx(StandardTable, { id: "server-search-table", data: rows, columns: columns, paginationMode: "server", rowsPerPage: 5, totalServerRows: total, onRefetch: fetchData, isLoading: isLoading, isSearchActive: query.trim().length >= 3, noSearchResultsMessage: "No users match your search" })] }));
197
+ }
198
+ export const ServerSideSearch = {
199
+ render: () => _jsx(ServerSideSearchExample, {}),
200
+ };
201
+ const CurrentUrl = () => {
202
+ const location = useLocation();
203
+ return (_jsxs("div", { style: { fontSize: 12, marginBottom: 8 }, children: ["Current URL: ", _jsx("code", { children: location.search })] }));
204
+ };
205
+ export const WithQueryParamsSync = {
206
+ decorators: [
207
+ (Story) => {
208
+ const query = `?clients_pageNo=2&clients_pageSize=5`;
209
+ globalThis.history.replaceState({}, '', query);
210
+ return _jsx(Story, {});
211
+ },
212
+ ],
213
+ render: () => (_jsxs(_Fragment, { children: [_jsx(CurrentUrl, {}), _jsx(StandardTable, { id: "clients", data: data, columns: columns, paginationMode: "client" })] })),
214
+ };
215
+ /** Simulates a server fetch with latency. Used by server-side query-param stories. */
216
+ async function fetchServerData(pageIndex, pageSize) {
217
+ await new Promise((r) => setTimeout(r, 500));
218
+ const start = pageIndex * pageSize;
219
+ return Array.from({ length: pageSize }).map((_, i) => {
220
+ const id = start + i + 1;
221
+ return {
222
+ id: `user-${id}`,
223
+ name: `User ${id}`,
224
+ email: `user${id}@example.com`,
225
+ role: id % 2 === 0 ? 'Admin' : 'User',
226
+ };
227
+ });
228
+ }
229
+ function ServerPaginationWithQueryParamsExample() {
230
+ const [rows, setRows] = useState([]);
231
+ const [isLoading, setIsLoading] = useState(false);
232
+ const fetchData = useCallback(async (pageIndex, pageSize) => {
233
+ setIsLoading(true);
234
+ const results = await fetchServerData(pageIndex, pageSize);
235
+ setRows(results);
236
+ setIsLoading(false);
237
+ }, []);
238
+ return (_jsxs(_Fragment, { children: [_jsx(CurrentUrl, {}), _jsx(StandardTable, { id: "clients", data: rows, columns: columns, paginationMode: "server", totalServerRows: 100, onRefetch: fetchData, isLoading: isLoading })] }));
239
+ }
240
+ export const ServerPaginationWithQueryParams = {
241
+ decorators: [
242
+ (Story) => {
243
+ globalThis.history.replaceState({}, '', '?clients_pageNo=2&clients_pageSize=5');
244
+ return _jsx(Story, {});
245
+ },
246
+ ],
247
+ render: () => _jsx(ServerPaginationWithQueryParamsExample, {}),
248
+ };
249
+ function TwoTablesWithQueryParamsExample() {
250
+ const [usersRows, setUsersRows] = useState([]);
251
+ const [isLoadingUsers, setIsLoadingUsers] = useState(false);
252
+ const fetchUsersData = useCallback(async (pageIndex, pageSize) => {
253
+ setIsLoadingUsers(true);
254
+ const results = await fetchServerData(pageIndex, pageSize);
255
+ setUsersRows(results);
256
+ setIsLoadingUsers(false);
257
+ }, []);
258
+ return (_jsxs(_Fragment, { children: [_jsx(CurrentUrl, {}), _jsxs("div", { style: { display: 'flex', gap: '2rem' }, children: [_jsx(StandardTable, { id: "clients", data: data, columns: columns, paginationMode: "client" }), _jsx(StandardTable, { id: "users", data: usersRows, columns: columns, paginationMode: "server", totalServerRows: 100, onRefetch: fetchUsersData, isLoading: isLoadingUsers })] })] }));
259
+ }
260
+ export const TwoTablesWithQueryParams = {
261
+ decorators: [
262
+ (Story) => {
263
+ globalThis.history.replaceState({}, '', '?clients_pageNo=2&clients_pageSize=5&users_pageNo=3&users_pageSize=10');
264
+ return _jsx(Story, {});
265
+ },
266
+ ],
267
+ render: () => _jsx(TwoTablesWithQueryParamsExample, {}),
268
+ };
@@ -0,0 +1,4 @@
1
+ import type { TableInputProps, StandardPaginationProps } from '../Table.types';
2
+ export type StandardTableProps<T extends {
3
+ id: string;
4
+ }> = TableInputProps<T> & StandardPaginationProps;
@@ -0,0 +1,2 @@
1
+ export { StandardTable } from './StandardTable';
2
+ export type { StandardTableProps } from './StandardTable.types';
@@ -0,0 +1 @@
1
+ export { StandardTable } from './StandardTable';
@@ -0,0 +1,22 @@
1
+ import type { StandardTableProps } from '../Table.types';
2
+ export declare function useStandardTableController<T extends {
3
+ id: string;
4
+ }>(props: StandardTableProps<T>): {
5
+ table: import("@tanstack/table-core").Table<T>;
6
+ height: string | number | undefined;
7
+ showHeader: boolean;
8
+ parentScrollRef: import("react").MutableRefObject<HTMLDivElement | null>;
9
+ setPageIndex: import("react").Dispatch<import("react").SetStateAction<number>>;
10
+ setPageSize: import("react").Dispatch<import("react").SetStateAction<number>>;
11
+ pagination: {
12
+ pageIndex: number;
13
+ pageSize: number;
14
+ canNextPage: boolean;
15
+ canPrevPage: boolean;
16
+ onNextPage: () => void;
17
+ onPrevPage: () => void;
18
+ setPageSize: (size: number) => void;
19
+ };
20
+ searchBelowMinLength: boolean;
21
+ hasCachedData: boolean;
22
+ };
@@ -0,0 +1,100 @@
1
+ import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
2
+ import { useReactTable, getCoreRowModel, getSortedRowModel, getPaginationRowModel, getGroupedRowModel, getExpandedRowModel, } from '@tanstack/react-table';
3
+ import { applyFlexSizes } from '../table.helpers';
4
+ import { useUrlPaginationSync } from '../hooks/useUrlPaginationSync';
5
+ import { usePaginationController } from '../Pagination/usePaginationController';
6
+ export function useStandardTableController(props) {
7
+ const { id, data, columns, rowIdKey = 'id', height, showHeader = true,
8
+ // pagination
9
+ paginationMode = 'client', rowsPerPage = 10, totalServerRows, onRefetch,
10
+ // sorting / grouping
11
+ sorting, onSortingChange, grouping,
12
+ // search
13
+ searchQuery, minSearchLength = 3, tableActionsDropdown, } = props;
14
+ // Keep previous data while loading to prevent UI jumps on page change
15
+ const prevDataRef = useRef([]);
16
+ // Always cache the latest non-empty data
17
+ if (data.length > 0) {
18
+ prevDataRef.current = data;
19
+ }
20
+ // While loading, show cached data; otherwise show current data
21
+ const hasCachedData = Boolean(props.isLoading && prevDataRef.current.length > 0);
22
+ const displayData = hasCachedData ? prevDataRef.current : data;
23
+ // When a searchQuery is provided but below the minimum length, suppress refetch calls
24
+ const searchBelowMinLength = searchQuery !== undefined && searchQuery.length > 0 && searchQuery.length < minSearchLength;
25
+ const gatedOnRefetch = useCallback((pageIndex, pageSize) => {
26
+ if (searchBelowMinLength) {
27
+ return;
28
+ }
29
+ onRefetch === null || onRefetch === void 0 ? void 0 : onRefetch(pageIndex, pageSize);
30
+ }, [onRefetch, searchBelowMinLength]);
31
+ const parentScrollRef = useRef(null);
32
+ const [containerWidth, setContainerWidth] = useState(0);
33
+ useLayoutEffect(() => {
34
+ if (!parentScrollRef.current) {
35
+ return;
36
+ }
37
+ const ro = new ResizeObserver(([entry]) => {
38
+ setContainerWidth(entry.contentRect.width);
39
+ });
40
+ ro.observe(parentScrollRef.current);
41
+ return () => ro.disconnect();
42
+ }, []);
43
+ const { initialPageIndex, initialPageSize, syncToUrl } = useUrlPaginationSync(id, rowsPerPage);
44
+ const [pageIndex, setPageIndex] = useState(initialPageIndex);
45
+ const [pageSize, setPageSize] = useState(initialPageSize);
46
+ const [internalSorting, setInternalSorting] = useState(sorting !== null && sorting !== void 0 ? sorting : []);
47
+ const stableGrouping = useMemo(() => grouping !== null && grouping !== void 0 ? grouping : [], [grouping]);
48
+ const columnVisibility = useMemo(() => Object.fromEntries(stableGrouping.map((colId) => [colId, false])), [stableGrouping]);
49
+ const baseColumns = useMemo(() => applyFlexSizes(columns, containerWidth, false // no virtualization in standard table
50
+ ), [columns, containerWidth]);
51
+ const table = useReactTable({
52
+ data: displayData,
53
+ columns: baseColumns,
54
+ getRowId: (row) => String(row[rowIdKey]),
55
+ state: {
56
+ pagination: { pageIndex, pageSize },
57
+ sorting: sorting !== null && sorting !== void 0 ? sorting : internalSorting,
58
+ grouping: stableGrouping,
59
+ columnVisibility,
60
+ },
61
+ getCoreRowModel: getCoreRowModel(),
62
+ getSortedRowModel: getSortedRowModel(),
63
+ getGroupedRowModel: getGroupedRowModel(),
64
+ getExpandedRowModel: getExpandedRowModel(),
65
+ getPaginationRowModel: paginationMode === 'client' ? getPaginationRowModel() : undefined,
66
+ manualPagination: paginationMode === 'server',
67
+ pageCount: paginationMode === 'server' ? Math.ceil((totalServerRows !== null && totalServerRows !== void 0 ? totalServerRows : 0) / pageSize) : undefined,
68
+ autoResetPageIndex: false,
69
+ onPaginationChange: (updater) => {
70
+ const next = typeof updater === 'function' ? updater({ pageIndex, pageSize }) : updater;
71
+ setPageIndex(next.pageIndex);
72
+ setPageSize(next.pageSize);
73
+ syncToUrl(next.pageIndex, next.pageSize);
74
+ },
75
+ onSortingChange: (updater) => {
76
+ const next = typeof updater === 'function' ? updater(sorting !== null && sorting !== void 0 ? sorting : internalSorting) : updater;
77
+ setInternalSorting(next);
78
+ onSortingChange === null || onSortingChange === void 0 ? void 0 : onSortingChange(next);
79
+ },
80
+ meta: {
81
+ hasActions: Boolean(tableActionsDropdown),
82
+ },
83
+ });
84
+ const pagination = usePaginationController({
85
+ table,
86
+ paginationMode,
87
+ onRefetch: gatedOnRefetch,
88
+ });
89
+ return {
90
+ table,
91
+ height,
92
+ showHeader,
93
+ parentScrollRef,
94
+ setPageIndex,
95
+ setPageSize,
96
+ pagination,
97
+ searchBelowMinLength,
98
+ hasCachedData,
99
+ };
100
+ }
@@ -59,7 +59,7 @@ export function Table(props) {
59
59
  const paddingBottom = Math.max(totalSize - end, 0);
60
60
  const tableRows = table.getRowModel().rows;
61
61
  const isTrulyEmpty = tableRows.length === 0 && !props.isLoading && !hasNextPage;
62
- return (_jsxs("div", { ref: parentScrollRef, style: { height }, className: "relative w-full overflow-auto", children: [props.isLoading && (_jsx("div", { className: classNames('mui-loader', showHeader ? 'mt-4' : 'mt-0') })), _jsx("div", { className: "rounded-t-(--uikit-radius) overflow-y-hidden overflow-x-auto", children: _jsxs("table", { className: classNames('min-w-full text-sm table-fixed bg-[#ffffff]', props.className), role: "table", id: props.id, children: [showHeader && (_jsx("thead", { className: "bg-(--uikit-tertiary)", children: table.getHeaderGroups().map((headerGroup) => (_jsxs("tr", { className: "p-4", children: [headerGroup.headers.map((header) => {
62
+ return (_jsxs("div", { ref: parentScrollRef, style: { height }, className: "relative w-full overflow-auto", children: [props.isLoading && _jsx("div", { className: "mui-loader mt-4" }), _jsx("div", { className: "rounded-t-(--uikit-radius) overflow-y-hidden overflow-x-auto", children: _jsxs("table", { className: classNames('min-w-full text-sm table-fixed bg-[#ffffff]', props.className), role: "table", id: props.id, children: [showHeader && (_jsx("thead", { className: "bg-(--uikit-tertiary)", children: table.getHeaderGroups().map((headerGroup) => (_jsxs("tr", { className: "p-4", children: [headerGroup.headers.map((header) => {
63
63
  return (_jsx("th", { scope: "col", style: { width: header.getSize() }, className: classNames('select-none text-[14px] font-bold ', header.id === 'select' ? 'p-0' : ' p-4 text-left cursor-pointer'), onClick: !(infiniteScroll === null || infiniteScroll === void 0 ? void 0 : infiniteScroll.enabled) && header.column.getCanSort()
64
64
  ? header.column.getToggleSortingHandler()
65
65
  : undefined, children: _jsxs("div", { className: classNames('w-full h-full', header.id === 'select'