@dashforge/tw 0.4.0-beta → 0.6.0-beta

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 (87) hide show
  1. package/CHANGELOG.md +391 -0
  2. package/dist/index.esm.js +2113 -2
  3. package/dist/src/components/Pagination/Pagination.d.ts +32 -0
  4. package/dist/src/components/Pagination/Pagination.d.ts.map +1 -0
  5. package/dist/src/components/Pagination/pagination.helpers.d.ts +23 -0
  6. package/dist/src/components/Pagination/pagination.helpers.d.ts.map +1 -0
  7. package/dist/src/components/Pagination/pagination.types.d.ts +120 -0
  8. package/dist/src/components/Pagination/pagination.types.d.ts.map +1 -0
  9. package/dist/src/components/Pagination/pagination.variants.d.ts +150 -0
  10. package/dist/src/components/Pagination/pagination.variants.d.ts.map +1 -0
  11. package/dist/src/components/Skeleton/Skeleton.d.ts +23 -0
  12. package/dist/src/components/Skeleton/Skeleton.d.ts.map +1 -0
  13. package/dist/src/components/Skeleton/skeleton.types.d.ts +56 -0
  14. package/dist/src/components/Skeleton/skeleton.types.d.ts.map +1 -0
  15. package/dist/src/components/Skeleton/skeleton.variants.d.ts +110 -0
  16. package/dist/src/components/Skeleton/skeleton.variants.d.ts.map +1 -0
  17. package/dist/src/components/Table/Table.d.ts +24 -0
  18. package/dist/src/components/Table/Table.d.ts.map +1 -0
  19. package/dist/src/components/Table/_internal/getNestedValue.d.ts +20 -0
  20. package/dist/src/components/Table/_internal/getNestedValue.d.ts.map +1 -0
  21. package/dist/src/components/Table/_internal/useColumnAutoDetect.d.ts +45 -0
  22. package/dist/src/components/Table/_internal/useColumnAutoDetect.d.ts.map +1 -0
  23. package/dist/src/components/Table/_internal/useControllableState.d.ts +23 -0
  24. package/dist/src/components/Table/_internal/useControllableState.d.ts.map +1 -0
  25. package/dist/src/components/Table/_internal/useDebouncedValue.d.ts +10 -0
  26. package/dist/src/components/Table/_internal/useDebouncedValue.d.ts.map +1 -0
  27. package/dist/src/components/Table/_internal/useTableFilter.d.ts +13 -0
  28. package/dist/src/components/Table/_internal/useTableFilter.d.ts.map +1 -0
  29. package/dist/src/components/Table/_internal/useTableSearch.d.ts +23 -0
  30. package/dist/src/components/Table/_internal/useTableSearch.d.ts.map +1 -0
  31. package/dist/src/components/Table/_internal/useTableSelection.d.ts +25 -0
  32. package/dist/src/components/Table/_internal/useTableSelection.d.ts.map +1 -0
  33. package/dist/src/components/Table/_internal/useTableSort.d.ts +20 -0
  34. package/dist/src/components/Table/_internal/useTableSort.d.ts.map +1 -0
  35. package/dist/src/components/Table/cells/RenderButton.d.ts +13 -0
  36. package/dist/src/components/Table/cells/RenderButton.d.ts.map +1 -0
  37. package/dist/src/components/Table/cells/RenderChip.d.ts +80 -0
  38. package/dist/src/components/Table/cells/RenderChip.d.ts.map +1 -0
  39. package/dist/src/components/Table/cells/RenderText.d.ts +21 -0
  40. package/dist/src/components/Table/cells/RenderText.d.ts.map +1 -0
  41. package/dist/src/components/Table/cells/RenderTwoLine.d.ts +20 -0
  42. package/dist/src/components/Table/cells/RenderTwoLine.d.ts.map +1 -0
  43. package/dist/src/components/Table/cells/RowActionsMenu.d.ts +36 -0
  44. package/dist/src/components/Table/cells/RowActionsMenu.d.ts.map +1 -0
  45. package/dist/src/components/Table/cells/index.d.ts +11 -0
  46. package/dist/src/components/Table/cells/index.d.ts.map +1 -0
  47. package/dist/src/components/Table/table.types.d.ts +304 -0
  48. package/dist/src/components/Table/table.types.d.ts.map +1 -0
  49. package/dist/src/components/Table/table.variants.d.ts +285 -0
  50. package/dist/src/components/Table/table.variants.d.ts.map +1 -0
  51. package/dist/src/index.d.ts +13 -1
  52. package/dist/src/index.d.ts.map +1 -1
  53. package/package.json +1 -1
  54. package/src/components/Pagination/Pagination.test.tsx +314 -0
  55. package/src/components/Pagination/Pagination.tsx +275 -0
  56. package/src/components/Pagination/pagination.helpers.ts +102 -0
  57. package/src/components/Pagination/pagination.types.ts +117 -0
  58. package/src/components/Pagination/pagination.variants.ts +111 -0
  59. package/src/components/Skeleton/Skeleton.test.tsx +127 -0
  60. package/src/components/Skeleton/Skeleton.tsx +49 -0
  61. package/src/components/Skeleton/skeleton.types.ts +59 -0
  62. package/src/components/Skeleton/skeleton.variants.ts +86 -0
  63. package/src/components/Table/Table.test.tsx +603 -0
  64. package/src/components/Table/Table.tsx +816 -0
  65. package/src/components/Table/_internal/getNestedValue.test.ts +50 -0
  66. package/src/components/Table/_internal/getNestedValue.ts +32 -0
  67. package/src/components/Table/_internal/themeIdentity.test.ts +84 -0
  68. package/src/components/Table/_internal/useColumnAutoDetect.test.ts +123 -0
  69. package/src/components/Table/_internal/useColumnAutoDetect.ts +100 -0
  70. package/src/components/Table/_internal/useControllableState.ts +48 -0
  71. package/src/components/Table/_internal/useDebouncedValue.ts +24 -0
  72. package/src/components/Table/_internal/useTableFilter.ts +37 -0
  73. package/src/components/Table/_internal/useTableSearch.test.ts +115 -0
  74. package/src/components/Table/_internal/useTableSearch.ts +66 -0
  75. package/src/components/Table/_internal/useTableSelection.test.ts +100 -0
  76. package/src/components/Table/_internal/useTableSelection.ts +100 -0
  77. package/src/components/Table/_internal/useTableSort.test.ts +150 -0
  78. package/src/components/Table/_internal/useTableSort.ts +141 -0
  79. package/src/components/Table/cells/RenderButton.tsx +23 -0
  80. package/src/components/Table/cells/RenderChip.tsx +103 -0
  81. package/src/components/Table/cells/RenderText.tsx +37 -0
  82. package/src/components/Table/cells/RenderTwoLine.tsx +34 -0
  83. package/src/components/Table/cells/RowActionsMenu.tsx +147 -0
  84. package/src/components/Table/cells/index.ts +14 -0
  85. package/src/components/Table/table.types.ts +337 -0
  86. package/src/components/Table/table.variants.ts +207 -0
  87. package/src/index.ts +60 -1
@@ -0,0 +1,285 @@
1
+ import { type VariantProps } from 'tailwind-variants';
2
+ /**
3
+ * `<Table>` variant recipe.
4
+ *
5
+ * Slots:
6
+ * - `root` — outer wrapper
7
+ * - `toolbar` — search + filter chips row above table
8
+ * - `search` — search input
9
+ * - `scroll` — `<div class="overflow-auto">` wrapping the table
10
+ * - `table` — `<table>` element
11
+ * - `thead` — `<thead>` (sticky positioning here)
12
+ * - `tbody` — `<tbody>`
13
+ * - `headerRow` — `<tr>` inside `<thead>`
14
+ * - `headerCell` — `<th>` cell
15
+ * - `headerCellButton` — `<button>` inside sortable `<th>`
16
+ * - `row` — `<tr>` inside `<tbody>` (data row)
17
+ * - `cell` — `<td>` data cell
18
+ * - `selectionCell` — first `<td>` when selection mode active
19
+ * - `expandToggleCell` — second `<td>` when expandable active
20
+ * - `expandedRow` — full-width `<tr>` rendered after an expanded row
21
+ * - `rowActionsCell` — last `<td>` when `rowActions` provided
22
+ * - `emptyState` — placeholder when rows is empty
23
+ * - `loadingOverlay` — skeleton overlay when loading
24
+ * - `bulkActionFooter` — sticky footer with bulk actions
25
+ *
26
+ * Variant axes:
27
+ * - `variant` — visual row-division style:
28
+ * - `plain` — minimal separators
29
+ * - `lines` — Stripe-style thin horizontal dividers (default)
30
+ * - `striped` — alternating row backgrounds (zebra)
31
+ * - `bordered` — full cell borders
32
+ * - `card` — each row rendered as a separate surface card
33
+ * - `size` — `sm` / `md` (default) / `lg`
34
+ * - `density` — `compact` (40px row) / `comfortable` (default 48px) / `spacious` (56px)
35
+ *
36
+ * Smart-default alignment (per column) is applied by the Table
37
+ * component code, NOT here — it depends on the runtime-detected
38
+ * value type, which the recipe doesn't see.
39
+ *
40
+ * **Dashforge identity rule** (memory: `feedback_dashforge_preset_is_identity`):
41
+ * This recipe uses NO `dark:` variants on the neutral palette. The
42
+ * `dashforgePreset()` CSS-variable swap auto-inverts neutral tokens
43
+ * (`bg-neutral-50` is light surface in light mode, dark surface in
44
+ * dark mode — same class, different physical color). Adding a
45
+ * `dark:` variant on neutral palette = DOUBLE inversion = breaks
46
+ * dark mode. The selected-row state uses the canonical LeftNav
47
+ * `itemActive` pattern (`bg-primary-100 text-primary-900 font-medium`),
48
+ * which works in both modes because the primary palette has no
49
+ * inversion (both `bg` and `text` resolve to the same physical
50
+ * colors regardless of mode → guaranteed contrast).
51
+ */
52
+ export declare const tableVariants: import("tailwind-variants").TVReturnType<{
53
+ variant: {
54
+ plain: {};
55
+ lines: {};
56
+ striped: {
57
+ row: string;
58
+ };
59
+ bordered: {
60
+ table: string;
61
+ cell: string;
62
+ headerCell: string;
63
+ };
64
+ card: {
65
+ scroll: string;
66
+ table: string;
67
+ row: string[];
68
+ cell: string;
69
+ headerCell: string;
70
+ };
71
+ };
72
+ size: {
73
+ sm: {
74
+ headerCell: string;
75
+ cell: string;
76
+ search: string;
77
+ };
78
+ md: {
79
+ headerCell: string;
80
+ cell: string;
81
+ search: string;
82
+ };
83
+ lg: {
84
+ headerCell: string;
85
+ cell: string;
86
+ search: string;
87
+ };
88
+ };
89
+ density: {
90
+ compact: {
91
+ headerCell: string;
92
+ cell: string;
93
+ };
94
+ comfortable: {
95
+ headerCell: string;
96
+ cell: string;
97
+ };
98
+ spacious: {
99
+ headerCell: string;
100
+ cell: string;
101
+ };
102
+ };
103
+ stickyHeader: {
104
+ true: {
105
+ thead: string;
106
+ };
107
+ false: {};
108
+ };
109
+ }, {
110
+ root: string;
111
+ toolbar: string;
112
+ search: string[];
113
+ scroll: string;
114
+ table: string;
115
+ thead: string;
116
+ tbody: string;
117
+ headerRow: string;
118
+ headerCell: string[];
119
+ headerCellButton: string[];
120
+ row: string[];
121
+ cell: string[];
122
+ selectionCell: string;
123
+ expandToggleCell: string;
124
+ expandedRow: string;
125
+ rowActionsCell: string[];
126
+ emptyState: string[];
127
+ loadingOverlay: string[];
128
+ bulkActionFooter: string[];
129
+ }, undefined, {
130
+ variant: {
131
+ plain: {};
132
+ lines: {};
133
+ striped: {
134
+ row: string;
135
+ };
136
+ bordered: {
137
+ table: string;
138
+ cell: string;
139
+ headerCell: string;
140
+ };
141
+ card: {
142
+ scroll: string;
143
+ table: string;
144
+ row: string[];
145
+ cell: string;
146
+ headerCell: string;
147
+ };
148
+ };
149
+ size: {
150
+ sm: {
151
+ headerCell: string;
152
+ cell: string;
153
+ search: string;
154
+ };
155
+ md: {
156
+ headerCell: string;
157
+ cell: string;
158
+ search: string;
159
+ };
160
+ lg: {
161
+ headerCell: string;
162
+ cell: string;
163
+ search: string;
164
+ };
165
+ };
166
+ density: {
167
+ compact: {
168
+ headerCell: string;
169
+ cell: string;
170
+ };
171
+ comfortable: {
172
+ headerCell: string;
173
+ cell: string;
174
+ };
175
+ spacious: {
176
+ headerCell: string;
177
+ cell: string;
178
+ };
179
+ };
180
+ stickyHeader: {
181
+ true: {
182
+ thead: string;
183
+ };
184
+ false: {};
185
+ };
186
+ }, {
187
+ root: string;
188
+ toolbar: string;
189
+ search: string[];
190
+ scroll: string;
191
+ table: string;
192
+ thead: string;
193
+ tbody: string;
194
+ headerRow: string;
195
+ headerCell: string[];
196
+ headerCellButton: string[];
197
+ row: string[];
198
+ cell: string[];
199
+ selectionCell: string;
200
+ expandToggleCell: string;
201
+ expandedRow: string;
202
+ rowActionsCell: string[];
203
+ emptyState: string[];
204
+ loadingOverlay: string[];
205
+ bulkActionFooter: string[];
206
+ }, import("tailwind-variants").TVReturnType<{
207
+ variant: {
208
+ plain: {};
209
+ lines: {};
210
+ striped: {
211
+ row: string;
212
+ };
213
+ bordered: {
214
+ table: string;
215
+ cell: string;
216
+ headerCell: string;
217
+ };
218
+ card: {
219
+ scroll: string;
220
+ table: string;
221
+ row: string[];
222
+ cell: string;
223
+ headerCell: string;
224
+ };
225
+ };
226
+ size: {
227
+ sm: {
228
+ headerCell: string;
229
+ cell: string;
230
+ search: string;
231
+ };
232
+ md: {
233
+ headerCell: string;
234
+ cell: string;
235
+ search: string;
236
+ };
237
+ lg: {
238
+ headerCell: string;
239
+ cell: string;
240
+ search: string;
241
+ };
242
+ };
243
+ density: {
244
+ compact: {
245
+ headerCell: string;
246
+ cell: string;
247
+ };
248
+ comfortable: {
249
+ headerCell: string;
250
+ cell: string;
251
+ };
252
+ spacious: {
253
+ headerCell: string;
254
+ cell: string;
255
+ };
256
+ };
257
+ stickyHeader: {
258
+ true: {
259
+ thead: string;
260
+ };
261
+ false: {};
262
+ };
263
+ }, {
264
+ root: string;
265
+ toolbar: string;
266
+ search: string[];
267
+ scroll: string;
268
+ table: string;
269
+ thead: string;
270
+ tbody: string;
271
+ headerRow: string;
272
+ headerCell: string[];
273
+ headerCellButton: string[];
274
+ row: string[];
275
+ cell: string[];
276
+ selectionCell: string;
277
+ expandToggleCell: string;
278
+ expandedRow: string;
279
+ rowActionsCell: string[];
280
+ emptyState: string[];
281
+ loadingOverlay: string[];
282
+ bulkActionFooter: string[];
283
+ }, undefined, unknown, unknown, undefined>>;
284
+ export type TableVariants = VariantProps<typeof tableVariants>;
285
+ //# sourceMappingURL=table.variants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"table.variants.d.ts","sourceRoot":"","sources":["../../../../src/components/Table/table.variants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAM,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2CAwJxB,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,YAAY,CAAC,OAAO,aAAa,CAAC,CAAC"}
@@ -108,6 +108,18 @@ export { popoverVariants } from './components/Popover/popover.variants.js';
108
108
  export { Accordion } from './components/Accordion/Accordion.js';
109
109
  export type { AccordionProps, AccordionSlotProps, AccordionItem, } from './components/Accordion/accordion.types.js';
110
110
  export { accordionVariants } from './components/Accordion/accordion.variants.js';
111
+ export { Skeleton } from './components/Skeleton/Skeleton.js';
112
+ export type { SkeletonProps, SkeletonSlotProps, } from './components/Skeleton/skeleton.types.js';
113
+ export { skeletonVariants } from './components/Skeleton/skeleton.variants.js';
114
+ export { Pagination } from './components/Pagination/Pagination.js';
115
+ export type { PaginationProps, PaginationSlotProps, PaginationLabels, } from './components/Pagination/pagination.types.js';
116
+ export { paginationVariants } from './components/Pagination/pagination.variants.js';
117
+ export { Table } from './components/Table/Table.js';
118
+ export type { TableProps, TableColumn, TableColumnInferredType, TableCellContext, TableSortDirection, TableSortItem, TableSortModel, TableFilterItem, TableFilterModel, TableRowSelectionMode, TableExpandableConfig, TableLabels, TableSlotProps, NestedKeyOf, } from './components/Table/table.types.js';
119
+ export { tableVariants } from './components/Table/table.variants.js';
120
+ export { RenderText, RenderTwoLine, RenderChip, RenderButton, RowActionsMenu, } from './components/Table/cells/index.js';
121
+ export type { RenderTextProps, RenderTwoLineProps, RenderChipProps, RenderButtonProps, RowActionsMenuProps, TableRowAction, } from './components/Table/cells/index.js';
122
+ export { getNestedValue } from './components/Table/_internal/getNestedValue.js';
111
123
  export { useAccessState } from './hooks/useAccessState.js';
112
124
  export { cn } from './utils/cn.js';
113
125
  export { tv } from 'tailwind-variants';
@@ -115,5 +127,5 @@ export type { VariantProps } from 'tailwind-variants';
115
127
  /**
116
128
  * Package version (synced with `package.json` at publish time).
117
129
  */
118
- export declare const VERSION = "0.4.0-beta";
130
+ export declare const VERSION = "0.6.0-beta";
119
131
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dashforge/tw",
3
- "version": "0.4.0-beta",
3
+ "version": "0.6.0-beta",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.esm.js",
@@ -0,0 +1,314 @@
1
+ // @vitest-environment jsdom
2
+ import * as React from 'react';
3
+ import { describe, it, expect, afterEach } from 'vitest';
4
+ import { render, screen, fireEvent, cleanup } from '@testing-library/react';
5
+ import { Pagination } from './Pagination.js';
6
+ import { computePageRange } from './pagination.helpers.js';
7
+
8
+ void React;
9
+ afterEach(() => cleanup());
10
+
11
+ describe('computePageRange — helper', () => {
12
+ it('returns empty for totalPages=0', () => {
13
+ expect(computePageRange(1, 0, 1, 1)).toEqual([]);
14
+ });
15
+
16
+ it('returns [1] for totalPages=1', () => {
17
+ expect(computePageRange(1, 1, 1, 1)).toEqual([1]);
18
+ });
19
+
20
+ it('lists all pages when totalPages <= window size', () => {
21
+ expect(computePageRange(1, 5, 1, 1)).toEqual([1, 2, 3, 4, 5]);
22
+ });
23
+
24
+ it('inserts ellipsis on the right when current is near start', () => {
25
+ const r = computePageRange(2, 20, 1, 1);
26
+ expect(r).toContain(1);
27
+ expect(r).toContain(2);
28
+ expect(r).toContain(3);
29
+ expect(r).toContain('ellipsis');
30
+ expect(r).toContain(20);
31
+ expect(r).not.toContain(10);
32
+ });
33
+
34
+ it('inserts ellipsis on both sides when current is in the middle', () => {
35
+ const r = computePageRange(10, 20, 1, 1);
36
+ expect(r.filter((x) => x === 'ellipsis').length).toBeGreaterThanOrEqual(1);
37
+ expect(r).toContain(1);
38
+ expect(r).toContain(9);
39
+ expect(r).toContain(10);
40
+ expect(r).toContain(11);
41
+ expect(r).toContain(20);
42
+ });
43
+
44
+ it('inserts ellipsis on the left when current is near end', () => {
45
+ const r = computePageRange(19, 20, 1, 1);
46
+ expect(r).toContain(1);
47
+ expect(r).toContain('ellipsis');
48
+ expect(r).toContain(18);
49
+ expect(r).toContain(19);
50
+ expect(r).toContain(20);
51
+ });
52
+
53
+ it('respects siblingCount=2', () => {
54
+ const r = computePageRange(10, 20, 2, 1);
55
+ expect(r).toContain(8);
56
+ expect(r).toContain(9);
57
+ expect(r).toContain(10);
58
+ expect(r).toContain(11);
59
+ expect(r).toContain(12);
60
+ });
61
+
62
+ it('respects boundaryCount=2', () => {
63
+ const r = computePageRange(10, 20, 1, 2);
64
+ expect(r).toContain(1);
65
+ expect(r).toContain(2);
66
+ expect(r).toContain(19);
67
+ expect(r).toContain(20);
68
+ });
69
+ });
70
+
71
+ describe('Pagination — rendering (default variant)', () => {
72
+ it('renders nav landmark with aria-label', () => {
73
+ render(<Pagination page={1} pageSize={10} totalCount={50} onPageChange={() => {}} />);
74
+ expect(screen.getByRole('navigation', { name: 'Pagination' })).not.toBeNull();
75
+ });
76
+
77
+ it('renders the "Showing X-Y of Z" summary', () => {
78
+ render(<Pagination page={1} pageSize={10} totalCount={50} onPageChange={() => {}} />);
79
+ expect(screen.getByText(/Showing 1-10 of 50/)).not.toBeNull();
80
+ });
81
+
82
+ it('renders correct summary on last page (partial fill)', () => {
83
+ render(<Pagination page={5} pageSize={10} totalCount={47} onPageChange={() => {}} />);
84
+ expect(screen.getByText(/Showing 41-47 of 47/)).not.toBeNull();
85
+ });
86
+
87
+ it('marks active page with aria-current=page', () => {
88
+ render(<Pagination page={3} pageSize={10} totalCount={50} onPageChange={() => {}} />);
89
+ const active = screen.getByRole('button', { name: 'Page 3' });
90
+ expect(active.getAttribute('aria-current')).toBe('page');
91
+ });
92
+
93
+ it('disables Prev + First on page 1', () => {
94
+ render(<Pagination page={1} pageSize={10} totalCount={50} onPageChange={() => {}} />);
95
+ expect((screen.getByRole('button', { name: 'Previous' }) as HTMLButtonElement).disabled).toBe(true);
96
+ expect((screen.getByRole('button', { name: 'First' }) as HTMLButtonElement).disabled).toBe(true);
97
+ });
98
+
99
+ it('disables Next + Last on last page', () => {
100
+ render(<Pagination page={5} pageSize={10} totalCount={50} onPageChange={() => {}} />);
101
+ expect((screen.getByRole('button', { name: 'Next' }) as HTMLButtonElement).disabled).toBe(true);
102
+ expect((screen.getByRole('button', { name: 'Last' }) as HTMLButtonElement).disabled).toBe(true);
103
+ });
104
+ });
105
+
106
+ describe('Pagination — interactions', () => {
107
+ it('emits onPageChange when a page button is clicked', () => {
108
+ let last = 0;
109
+ render(
110
+ <Pagination
111
+ page={1}
112
+ pageSize={10}
113
+ totalCount={50}
114
+ onPageChange={(p) => (last = p)}
115
+ />,
116
+ );
117
+ fireEvent.click(screen.getByRole('button', { name: 'Page 3' }));
118
+ expect(last).toBe(3);
119
+ });
120
+
121
+ it('Prev decrements page', () => {
122
+ let last = 0;
123
+ render(
124
+ <Pagination
125
+ page={3}
126
+ pageSize={10}
127
+ totalCount={50}
128
+ onPageChange={(p) => (last = p)}
129
+ />,
130
+ );
131
+ fireEvent.click(screen.getByRole('button', { name: 'Previous' }));
132
+ expect(last).toBe(2);
133
+ });
134
+
135
+ it('Next increments page', () => {
136
+ let last = 0;
137
+ render(
138
+ <Pagination
139
+ page={3}
140
+ pageSize={10}
141
+ totalCount={50}
142
+ onPageChange={(p) => (last = p)}
143
+ />,
144
+ );
145
+ fireEvent.click(screen.getByRole('button', { name: 'Next' }));
146
+ expect(last).toBe(4);
147
+ });
148
+
149
+ it('First/Last jump to extremes', () => {
150
+ let last = 0;
151
+ const { rerender } = render(
152
+ <Pagination
153
+ page={3}
154
+ pageSize={10}
155
+ totalCount={50}
156
+ onPageChange={(p) => (last = p)}
157
+ />,
158
+ );
159
+ fireEvent.click(screen.getByRole('button', { name: 'First' }));
160
+ expect(last).toBe(1);
161
+ rerender(
162
+ <Pagination
163
+ page={3}
164
+ pageSize={10}
165
+ totalCount={50}
166
+ onPageChange={(p) => (last = p)}
167
+ />,
168
+ );
169
+ fireEvent.click(screen.getByRole('button', { name: 'Last' }));
170
+ expect(last).toBe(5);
171
+ });
172
+
173
+ it('emits onPageSizeChange when selector changes', () => {
174
+ let lastSize = 0;
175
+ render(
176
+ <Pagination
177
+ page={1}
178
+ pageSize={10}
179
+ totalCount={100}
180
+ onPageChange={() => {}}
181
+ onPageSizeChange={(s) => (lastSize = s)}
182
+ />,
183
+ );
184
+ const selector = screen.getByLabelText('per page') as HTMLSelectElement;
185
+ fireEvent.change(selector, { target: { value: '50' } });
186
+ expect(lastSize).toBe(50);
187
+ });
188
+
189
+ it('hides page-size selector if onPageSizeChange not provided', () => {
190
+ render(<Pagination page={1} pageSize={10} totalCount={50} onPageChange={() => {}} />);
191
+ expect(screen.queryByLabelText('per page')).toBeNull();
192
+ });
193
+ });
194
+
195
+ describe('Pagination — variants', () => {
196
+ it('compact variant hides summary + selector', () => {
197
+ render(
198
+ <Pagination
199
+ variant="compact"
200
+ page={1}
201
+ pageSize={10}
202
+ totalCount={50}
203
+ onPageChange={() => {}}
204
+ onPageSizeChange={() => {}}
205
+ />,
206
+ );
207
+ expect(screen.queryByText(/Showing/)).toBeNull();
208
+ expect(screen.queryByLabelText('per page')).toBeNull();
209
+ });
210
+
211
+ it('minimal variant shows "Page X of Y" + only Prev/Next', () => {
212
+ render(
213
+ <Pagination
214
+ variant="minimal"
215
+ page={3}
216
+ pageSize={10}
217
+ totalCount={50}
218
+ onPageChange={() => {}}
219
+ />,
220
+ );
221
+ expect(screen.getByText(/Page 3 of 5/)).not.toBeNull();
222
+ // Page-number buttons should NOT exist
223
+ expect(screen.queryByRole('button', { name: 'Page 1' })).toBeNull();
224
+ // Prev / Next remain
225
+ expect(screen.queryByRole('button', { name: 'Previous' })).not.toBeNull();
226
+ expect(screen.queryByRole('button', { name: 'Next' })).not.toBeNull();
227
+ });
228
+ });
229
+
230
+ describe('Pagination — i18n', () => {
231
+ it('honors custom labels', () => {
232
+ render(
233
+ <Pagination
234
+ page={1}
235
+ pageSize={10}
236
+ totalCount={50}
237
+ onPageChange={() => {}}
238
+ labels={{ showing: 'Mostro', of: 'di', prev: 'Indietro', next: 'Avanti' }}
239
+ />,
240
+ );
241
+ expect(screen.getByText(/Mostro 1-10 di 50/)).not.toBeNull();
242
+ expect(screen.queryByRole('button', { name: 'Indietro' })).not.toBeNull();
243
+ expect(screen.queryByRole('button', { name: 'Avanti' })).not.toBeNull();
244
+ });
245
+ });
246
+
247
+ describe('Pagination — disabled', () => {
248
+ it('disables all buttons when disabled=true', () => {
249
+ render(
250
+ <Pagination
251
+ page={3}
252
+ pageSize={10}
253
+ totalCount={50}
254
+ onPageChange={() => {}}
255
+ disabled
256
+ />,
257
+ );
258
+ expect((screen.getByRole('button', { name: 'Previous' }) as HTMLButtonElement).disabled).toBe(true);
259
+ expect((screen.getByRole('button', { name: 'Next' }) as HTMLButtonElement).disabled).toBe(true);
260
+ expect((screen.getByRole('button', { name: 'Page 2' }) as HTMLButtonElement).disabled).toBe(true);
261
+ });
262
+ });
263
+
264
+ describe('Pagination — sx + slotProps', () => {
265
+ it('applies sx to root', () => {
266
+ const { container } = render(
267
+ <Pagination
268
+ page={1}
269
+ pageSize={10}
270
+ totalCount={50}
271
+ onPageChange={() => {}}
272
+ sx="my-custom-root"
273
+ />,
274
+ );
275
+ expect(container.querySelector('nav')!.className).toContain('my-custom-root');
276
+ });
277
+
278
+ it('applies slotProps.pageButton.className', () => {
279
+ render(
280
+ <Pagination
281
+ page={1}
282
+ pageSize={10}
283
+ totalCount={50}
284
+ onPageChange={() => {}}
285
+ slotProps={{ pageButton: { className: 'slot-page' } }}
286
+ />,
287
+ );
288
+ expect(screen.getByRole('button', { name: 'Page 2' }).className).toContain('slot-page');
289
+ });
290
+ });
291
+
292
+ describe('Pagination — edge cases', () => {
293
+ it('renders correctly with totalCount=0', () => {
294
+ render(<Pagination page={1} pageSize={10} totalCount={0} onPageChange={() => {}} />);
295
+ expect(screen.getByText(/Showing 0-0 of 0/)).not.toBeNull();
296
+ expect((screen.getByRole('button', { name: 'Previous' }) as HTMLButtonElement).disabled).toBe(true);
297
+ expect((screen.getByRole('button', { name: 'Next' }) as HTMLButtonElement).disabled).toBe(true);
298
+ });
299
+
300
+ it('clamps page > totalPages internally without crashing', () => {
301
+ let last = 0;
302
+ render(
303
+ <Pagination
304
+ page={99}
305
+ pageSize={10}
306
+ totalCount={50}
307
+ onPageChange={(p) => (last = p)}
308
+ />,
309
+ );
310
+ // Component computes safePage=5 (last valid). User clicks Prev → goes to 4.
311
+ fireEvent.click(screen.getByRole('button', { name: 'Previous' }));
312
+ expect(last).toBe(4);
313
+ });
314
+ });