@boxcustodia/library 2.0.0-alpha.22 → 2.0.0-alpha.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/calendar/calendar.cjs.js +1 -1
- package/dist/components/calendar/calendar.es.js +43 -44
- package/dist/components/date-picker/date-input.cjs.js +1 -1
- package/dist/components/date-picker/date-input.es.js +160 -140
- package/dist/components/pagination/pagination.cjs.js +1 -1
- package/dist/components/pagination/pagination.es.js +37 -35
- package/dist/components/popover/popover.cjs.js +1 -1
- package/dist/components/popover/popover.es.js +1 -1
- package/dist/components/scroll-area/scroll-area.cjs.js +1 -1
- package/dist/components/scroll-area/scroll-area.es.js +4 -4
- package/dist/components/select/select.cjs.js +1 -1
- package/dist/components/select/select.es.js +94 -90
- package/dist/components/tag/tag.cjs.js +1 -1
- package/dist/components/tag/tag.es.js +37 -18
- package/dist/hooks/use-action/use-action.cjs.js +1 -0
- package/dist/hooks/use-action/use-action.es.js +41 -0
- package/dist/hooks/use-pagination/use-pagination.cjs.js +1 -1
- package/dist/hooks/use-pagination/use-pagination.es.js +77 -32
- package/dist/hooks/use-range-pagination/use-range-pagination.cjs.js +1 -1
- package/dist/hooks/use-range-pagination/use-range-pagination.es.js +8 -5
- package/dist/hooks/use-selection/use-selection.cjs.js +1 -1
- package/dist/hooks/use-selection/use-selection.es.js +95 -33
- package/dist/hooks/use-session-storage/use-session-storage.cjs.js +1 -0
- package/dist/hooks/use-session-storage/use-session-storage.es.js +57 -0
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +61 -63
- package/dist/src/components/select/select.d.ts +9 -2
- package/dist/src/components/tag/tag.d.ts +2 -1
- package/dist/src/hooks/index.d.ts +2 -3
- package/dist/src/hooks/internal/index.d.ts +1 -0
- package/dist/src/hooks/internal/serializer.d.ts +4 -0
- package/dist/src/hooks/use-action/index.d.ts +1 -0
- package/dist/src/hooks/use-action/use-action.d.ts +22 -0
- package/dist/src/hooks/use-local-storage/use-local-storage.d.ts +2 -4
- package/dist/src/hooks/use-pagination/use-pagination.d.ts +47 -32
- package/dist/src/hooks/use-range-pagination/use-range-pagination.d.ts +16 -10
- package/dist/src/hooks/use-selection/use-selection.d.ts +39 -45
- package/dist/src/hooks/use-session-storage/index.d.ts +1 -0
- package/dist/src/hooks/use-session-storage/use-session-storage.d.ts +11 -0
- package/package.json +1 -1
- package/src/components/calendar/calendar.tsx +10 -8
- package/src/components/combobox/combobox.stories.tsx +16 -0
- package/src/components/date-picker/date-input.tsx +23 -2
- package/src/components/form/form.tsx +3 -2
- package/src/components/pagination/pagination.tsx +5 -3
- package/src/components/popover/popover.tsx +1 -1
- package/src/components/scroll-area/scroll-area.tsx +2 -2
- package/src/components/select/select.tsx +14 -3
- package/src/components/tag/tag.stories.tsx +47 -2
- package/src/components/tag/tag.tsx +28 -6
- package/src/hooks/index.ts +2 -3
- package/src/hooks/internal/index.ts +1 -0
- package/src/hooks/internal/serializer.ts +4 -0
- package/src/hooks/use-action/index.ts +1 -0
- package/src/hooks/{use-mutation/use-mutation.stories.tsx → use-action/use-action.stories.tsx} +34 -34
- package/src/hooks/{use-mutation/use-mutation.test.ts → use-action/use-action.test.ts} +53 -53
- package/src/hooks/{use-mutation/use-mutation.ts → use-action/use-action.ts} +20 -20
- package/src/hooks/use-click-outside/use-click-outside.stories.tsx +0 -1
- package/src/hooks/use-clipboard/use-clipboard.stories.tsx +0 -1
- package/src/hooks/use-document-title/use-document-title.stories.tsx +0 -1
- package/src/hooks/use-is-visible/use-is-visible.test.tsx +1 -1
- package/src/hooks/use-local-storage/use-local-storage.stories.tsx +0 -1
- package/src/hooks/use-local-storage/use-local-storage.ts +2 -5
- package/src/hooks/use-media-query/use-media-query.stories.tsx +0 -1
- package/src/hooks/use-pagination/use-pagination.stories.tsx +720 -57
- package/src/hooks/use-pagination/use-pagination.test.tsx +560 -48
- package/src/hooks/use-pagination/use-pagination.ts +266 -0
- package/src/hooks/use-prevent-page-close/use-prevent-page-close.stories.tsx +0 -1
- package/src/hooks/use-range-pagination/use-range-pagination.test.tsx +2 -2
- package/src/hooks/use-range-pagination/use-range-pagination.tsx +24 -21
- package/src/hooks/use-selection/use-selection.stories.tsx +339 -84
- package/src/hooks/use-selection/use-selection.test.tsx +417 -2
- package/src/hooks/use-selection/use-selection.ts +212 -102
- package/src/hooks/use-session-storage/index.ts +1 -0
- package/src/hooks/use-session-storage/use-session-storage.stories.tsx +122 -0
- package/src/hooks/use-session-storage/use-session-storage.test.ts +164 -0
- package/src/hooks/use-session-storage/use-session-storage.ts +115 -0
- package/dist/hooks/use-async/use-async.cjs.js +0 -1
- package/dist/hooks/use-async/use-async.es.js +0 -57
- package/dist/hooks/use-focus-trap/scope-tab.cjs.js +0 -1
- package/dist/hooks/use-focus-trap/scope-tab.es.js +0 -21
- package/dist/hooks/use-focus-trap/tabbable.cjs.js +0 -1
- package/dist/hooks/use-focus-trap/tabbable.es.js +0 -38
- package/dist/hooks/use-focus-trap/use-focus-trap.cjs.js +0 -1
- package/dist/hooks/use-focus-trap/use-focus-trap.es.js +0 -34
- package/dist/hooks/use-mutation/use-mutation.cjs.js +0 -1
- package/dist/hooks/use-mutation/use-mutation.es.js +0 -41
- package/dist/src/hooks/use-async/index.d.ts +0 -1
- package/dist/src/hooks/use-async/use-async.d.ts +0 -21
- package/dist/src/hooks/use-focus-trap/index.d.ts +0 -1
- package/dist/src/hooks/use-focus-trap/scope-tab.d.ts +0 -1
- package/dist/src/hooks/use-focus-trap/tabbable.d.ts +0 -4
- package/dist/src/hooks/use-focus-trap/use-focus-trap.d.ts +0 -1
- package/dist/src/hooks/use-mutation/index.d.ts +0 -1
- package/dist/src/hooks/use-mutation/use-mutation.d.ts +0 -22
- package/dist/src/hooks/use-mutation/use-mutation.test.d.ts +0 -1
- package/src/hooks/use-async/index.ts +0 -1
- package/src/hooks/use-async/use-async.stories.tsx +0 -272
- package/src/hooks/use-async/use-async.test.ts +0 -397
- package/src/hooks/use-async/use-async.ts +0 -135
- package/src/hooks/use-focus-trap/index.ts +0 -1
- package/src/hooks/use-focus-trap/scope-tab.ts +0 -38
- package/src/hooks/use-focus-trap/tabbable.ts +0 -70
- package/src/hooks/use-focus-trap/use-focus-trap.stories.tsx +0 -37
- package/src/hooks/use-focus-trap/use-focus-trap.test.ts +0 -355
- package/src/hooks/use-focus-trap/use-focus-trap.ts +0 -78
- package/src/hooks/use-mutation/index.ts +0 -1
- package/src/hooks/use-pagination/use-pagination.tsx +0 -84
- /package/dist/src/hooks/{use-async/use-async.test.d.ts → use-action/use-action.test.d.ts} +0 -0
- /package/dist/src/hooks/{use-focus-trap/use-focus-trap.test.d.ts → use-session-storage/use-session-storage.test.d.ts} +0 -0
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
import { Meta } from "@storybook/react-vite";
|
|
1
|
+
import type { Meta } from "@storybook/react-vite";
|
|
2
2
|
import { useState } from "react";
|
|
3
|
-
import {
|
|
4
|
-
import { Button } from "../../components";
|
|
3
|
+
import { Button, type ColumnDef, Pagination, Table } from "../../components";
|
|
5
4
|
import { usePagination } from "../use-pagination";
|
|
6
5
|
|
|
7
|
-
const TABLE_PAGE_SIZES = [5, 10, 20, 50, 100];
|
|
8
|
-
/**
|
|
9
|
-
* Hook que facilita el manejo de paginaciones, recibe como param la cantidad de elementos, la cantidad de elementos por página y una función
|
|
10
|
-
* que se ejecuta cuando cambia la pagina
|
|
11
|
-
*/
|
|
12
6
|
const meta: Meta = {
|
|
13
7
|
title: "hooks/usePagination",
|
|
14
8
|
tags: ["autodocs"],
|
|
@@ -16,57 +10,726 @@ const meta: Meta = {
|
|
|
16
10
|
|
|
17
11
|
export default meta;
|
|
18
12
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Story 1 — Default (uncontrolled)
|
|
15
|
+
// Shows all returned values and all 6 action buttons.
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
export const Default = {
|
|
19
|
+
render: () => {
|
|
20
|
+
const {
|
|
21
|
+
page,
|
|
22
|
+
pageSize,
|
|
23
|
+
pageCount,
|
|
24
|
+
isFirstPage,
|
|
25
|
+
isLastPage,
|
|
26
|
+
hasPrevPage,
|
|
27
|
+
hasNextPage,
|
|
28
|
+
range,
|
|
29
|
+
next,
|
|
30
|
+
prev,
|
|
31
|
+
firstPage,
|
|
32
|
+
lastPage,
|
|
33
|
+
goTo,
|
|
34
|
+
setPageSize,
|
|
35
|
+
} = usePagination({ totalItems: 100, defaultPageSize: 10, defaultPage: 1 });
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div className="space-y-4">
|
|
39
|
+
<pre className="rounded-md bg-slate-950 p-4 text-sm text-white font-mono">
|
|
40
|
+
<code className="block">
|
|
41
|
+
page: <span className="text-blue-300">{page}</span>
|
|
42
|
+
</code>
|
|
43
|
+
<code className="block">
|
|
44
|
+
pageSize: <span className="text-blue-300">{pageSize}</span>
|
|
45
|
+
</code>
|
|
46
|
+
<code className="block">
|
|
47
|
+
pageCount: <span className="text-blue-300">{pageCount}</span>
|
|
48
|
+
</code>
|
|
49
|
+
<code className="block">
|
|
50
|
+
isFirstPage:{" "}
|
|
51
|
+
<span className={isFirstPage ? "text-green-400" : "text-red-400"}>
|
|
52
|
+
{String(isFirstPage)}
|
|
53
|
+
</span>
|
|
54
|
+
</code>
|
|
55
|
+
<code className="block">
|
|
56
|
+
isLastPage:{" "}
|
|
57
|
+
<span className={isLastPage ? "text-green-400" : "text-red-400"}>
|
|
58
|
+
{String(isLastPage)}
|
|
59
|
+
</span>
|
|
60
|
+
</code>
|
|
61
|
+
<code className="block">
|
|
62
|
+
hasPrevPage:{" "}
|
|
63
|
+
<span className={hasPrevPage ? "text-green-400" : "text-red-400"}>
|
|
64
|
+
{String(hasPrevPage)}
|
|
65
|
+
</span>
|
|
66
|
+
</code>
|
|
67
|
+
<code className="block">
|
|
68
|
+
hasNextPage:{" "}
|
|
69
|
+
<span className={hasNextPage ? "text-green-400" : "text-red-400"}>
|
|
70
|
+
{String(hasNextPage)}
|
|
71
|
+
</span>
|
|
72
|
+
</code>
|
|
73
|
+
<code className="block">
|
|
74
|
+
range:{" "}
|
|
75
|
+
<span className="text-yellow-300">{`{ start: ${range.start}, end: ${range.end} }`}</span>
|
|
76
|
+
</code>
|
|
77
|
+
</pre>
|
|
78
|
+
<div className="flex flex-wrap gap-2">
|
|
79
|
+
<Button size="sm" onClick={firstPage} disabled={isFirstPage}>
|
|
80
|
+
First
|
|
81
|
+
</Button>
|
|
82
|
+
<Button size="sm" onClick={prev} disabled={!hasPrevPage}>
|
|
83
|
+
Prev
|
|
84
|
+
</Button>
|
|
85
|
+
<Button size="sm" onClick={next} disabled={!hasNextPage}>
|
|
86
|
+
Next
|
|
87
|
+
</Button>
|
|
88
|
+
<Button size="sm" onClick={lastPage} disabled={isLastPage}>
|
|
89
|
+
Last
|
|
90
|
+
</Button>
|
|
91
|
+
<Button size="sm" onClick={() => goTo(5)}>
|
|
92
|
+
Go to 5
|
|
93
|
+
</Button>
|
|
94
|
+
<Button size="sm" onClick={() => setPageSize(20)}>
|
|
95
|
+
Set size 20
|
|
96
|
+
</Button>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Story 2 — Controlled
|
|
105
|
+
// page + onPageChange wired to local useState.
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
export const Controlled = {
|
|
109
|
+
render: () => {
|
|
110
|
+
const [page, setPage] = useState(1);
|
|
111
|
+
const pagination = usePagination({
|
|
112
|
+
totalItems: 50,
|
|
113
|
+
defaultPageSize: 10,
|
|
114
|
+
page,
|
|
115
|
+
onPageChange: setPage,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<div className="space-y-4">
|
|
120
|
+
<p className="text-sm text-muted-foreground">
|
|
121
|
+
Page controlled by parent <code>useState</code>. Current:{" "}
|
|
122
|
+
<strong>{page}</strong>
|
|
123
|
+
</p>
|
|
124
|
+
<pre className="rounded-md bg-slate-950 p-4 text-sm text-white font-mono">
|
|
125
|
+
<code className="block">
|
|
126
|
+
page: <span className="text-blue-300">{pagination.page}</span>
|
|
127
|
+
</code>
|
|
128
|
+
<code className="block">
|
|
129
|
+
pageCount:{" "}
|
|
130
|
+
<span className="text-blue-300">{pagination.pageCount}</span>
|
|
131
|
+
</code>
|
|
132
|
+
<code className="block">
|
|
133
|
+
isFirstPage:{" "}
|
|
134
|
+
<span
|
|
135
|
+
className={
|
|
136
|
+
pagination.isFirstPage ? "text-green-400" : "text-red-400"
|
|
137
|
+
}
|
|
138
|
+
>
|
|
139
|
+
{String(pagination.isFirstPage)}
|
|
140
|
+
</span>
|
|
141
|
+
</code>
|
|
142
|
+
<code className="block">
|
|
143
|
+
isLastPage:{" "}
|
|
144
|
+
<span
|
|
145
|
+
className={
|
|
146
|
+
pagination.isLastPage ? "text-green-400" : "text-red-400"
|
|
147
|
+
}
|
|
148
|
+
>
|
|
149
|
+
{String(pagination.isLastPage)}
|
|
150
|
+
</span>
|
|
151
|
+
</code>
|
|
152
|
+
</pre>
|
|
153
|
+
<div className="flex gap-2">
|
|
154
|
+
<Button
|
|
155
|
+
size="sm"
|
|
156
|
+
onClick={pagination.firstPage}
|
|
157
|
+
disabled={pagination.isFirstPage}
|
|
158
|
+
>
|
|
159
|
+
First
|
|
160
|
+
</Button>
|
|
161
|
+
<Button
|
|
162
|
+
size="sm"
|
|
163
|
+
onClick={pagination.prev}
|
|
164
|
+
disabled={!pagination.hasPrevPage}
|
|
165
|
+
>
|
|
166
|
+
Prev
|
|
167
|
+
</Button>
|
|
168
|
+
<Button
|
|
169
|
+
size="sm"
|
|
170
|
+
onClick={pagination.next}
|
|
171
|
+
disabled={!pagination.hasNextPage}
|
|
172
|
+
>
|
|
173
|
+
Next
|
|
174
|
+
</Button>
|
|
175
|
+
<Button
|
|
176
|
+
size="sm"
|
|
177
|
+
onClick={pagination.lastPage}
|
|
178
|
+
disabled={pagination.isLastPage}
|
|
179
|
+
>
|
|
180
|
+
Last
|
|
181
|
+
</Button>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
);
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
// Story 3 — ControllablePageSize
|
|
190
|
+
// pageSize + onPageSizeChange with a dropdown. Shows auto-reset to page 1.
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
|
|
193
|
+
export const ControllablePageSize = {
|
|
194
|
+
render: () => {
|
|
195
|
+
const [pageSize, setPageSize] = useState(10);
|
|
196
|
+
const [page, setPage] = useState(3);
|
|
197
|
+
// Single fetch-trigger log. onPaginationChange fires ONCE with both final
|
|
198
|
+
// values, even when changing the page size resets the page to 1 — so there
|
|
199
|
+
// is never a duplicate fetch.
|
|
200
|
+
const [fetchLog, setFetchLog] = useState<string[]>([]);
|
|
201
|
+
|
|
202
|
+
const pagination = usePagination({
|
|
23
203
|
totalItems: 100,
|
|
24
|
-
pageSize
|
|
25
|
-
|
|
26
|
-
|
|
204
|
+
pageSize,
|
|
205
|
+
onPageSizeChange: setPageSize,
|
|
206
|
+
page,
|
|
207
|
+
onPageChange: setPage,
|
|
208
|
+
onPaginationChange: ({ page, pageSize }) => {
|
|
209
|
+
setFetchLog((prev) => [
|
|
210
|
+
`fetch → page ${page}, pageSize ${pageSize}`,
|
|
211
|
+
...prev.slice(0, 4),
|
|
212
|
+
]);
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
return (
|
|
217
|
+
<div className="space-y-4">
|
|
218
|
+
<p className="text-sm text-muted-foreground">
|
|
219
|
+
Change page size — page resets to 1 automatically, and{" "}
|
|
220
|
+
<code>onPaginationChange</code> fires exactly once with both final
|
|
221
|
+
values (a single fetch).
|
|
222
|
+
</p>
|
|
223
|
+
<div className="flex items-center gap-2">
|
|
224
|
+
<label htmlFor="page-size" className="text-sm font-medium">
|
|
225
|
+
Page size:
|
|
226
|
+
</label>
|
|
227
|
+
<select
|
|
228
|
+
id="page-size"
|
|
229
|
+
value={pageSize}
|
|
230
|
+
onChange={(e) => pagination.setPageSize(Number(e.target.value))}
|
|
231
|
+
className="border rounded px-2 py-1 text-sm"
|
|
232
|
+
>
|
|
233
|
+
{[5, 10, 20, 50].map((s) => (
|
|
234
|
+
<option key={s} value={s}>
|
|
235
|
+
{s}
|
|
236
|
+
</option>
|
|
237
|
+
))}
|
|
238
|
+
</select>
|
|
239
|
+
</div>
|
|
240
|
+
<pre className="rounded-md bg-slate-950 p-4 text-sm text-white font-mono">
|
|
241
|
+
<code className="block">
|
|
242
|
+
page: <span className="text-blue-300">{pagination.page}</span>
|
|
243
|
+
</code>
|
|
244
|
+
<code className="block">
|
|
245
|
+
pageSize:{" "}
|
|
246
|
+
<span className="text-blue-300">{pagination.pageSize}</span>
|
|
247
|
+
</code>
|
|
248
|
+
<code className="block">
|
|
249
|
+
pageCount:{" "}
|
|
250
|
+
<span className="text-blue-300">{pagination.pageCount}</span>
|
|
251
|
+
</code>
|
|
252
|
+
<code className="block">
|
|
253
|
+
range:{" "}
|
|
254
|
+
<span className="text-yellow-300">{`{ start: ${pagination.range.start}, end: ${pagination.range.end} }`}</span>
|
|
255
|
+
</code>
|
|
256
|
+
</pre>
|
|
257
|
+
<div className="flex gap-2">
|
|
258
|
+
<Button
|
|
259
|
+
size="sm"
|
|
260
|
+
onClick={pagination.prev}
|
|
261
|
+
disabled={!pagination.hasPrevPage}
|
|
262
|
+
>
|
|
263
|
+
Prev
|
|
264
|
+
</Button>
|
|
265
|
+
<Button
|
|
266
|
+
size="sm"
|
|
267
|
+
onClick={pagination.next}
|
|
268
|
+
disabled={!pagination.hasNextPage}
|
|
269
|
+
>
|
|
270
|
+
Next
|
|
271
|
+
</Button>
|
|
272
|
+
</div>
|
|
273
|
+
{fetchLog.length > 0 && (
|
|
274
|
+
<div className="space-y-1">
|
|
275
|
+
<p className="text-xs font-medium text-slate-500">
|
|
276
|
+
onPaginationChange log:
|
|
277
|
+
</p>
|
|
278
|
+
{fetchLog.map((entry, i) => (
|
|
279
|
+
<p key={i} className="font-mono text-xs text-slate-600">
|
|
280
|
+
{entry}
|
|
281
|
+
</p>
|
|
282
|
+
))}
|
|
283
|
+
</div>
|
|
284
|
+
)}
|
|
285
|
+
</div>
|
|
286
|
+
);
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
// ---------------------------------------------------------------------------
|
|
291
|
+
// Story 4 — EdgeCases
|
|
292
|
+
// totalItems=0, last partial page, single-page scenario shown side by side.
|
|
293
|
+
// ---------------------------------------------------------------------------
|
|
294
|
+
|
|
295
|
+
export const EdgeCases = {
|
|
296
|
+
render: () => {
|
|
297
|
+
const empty = usePagination({ totalItems: 0 });
|
|
298
|
+
const partial = usePagination({
|
|
299
|
+
totalItems: 25,
|
|
300
|
+
defaultPageSize: 10,
|
|
301
|
+
defaultPage: 3,
|
|
27
302
|
});
|
|
303
|
+
const single = usePagination({ totalItems: 8, defaultPageSize: 10 });
|
|
304
|
+
|
|
305
|
+
const StateBlock = ({
|
|
306
|
+
label,
|
|
307
|
+
state,
|
|
308
|
+
}: {
|
|
309
|
+
label: string;
|
|
310
|
+
state: ReturnType<typeof usePagination>;
|
|
311
|
+
}) => (
|
|
312
|
+
<div className="flex-1 min-w-48">
|
|
313
|
+
<p className="text-sm font-semibold mb-2">{label}</p>
|
|
314
|
+
<pre className="rounded-md bg-slate-950 p-3 text-xs text-white font-mono">
|
|
315
|
+
<code className="block">page: {state.page}</code>
|
|
316
|
+
<code className="block">pageCount: {state.pageCount}</code>
|
|
317
|
+
<code className="block">pageSize: {state.pageSize}</code>
|
|
318
|
+
<code className="block">isFirst: {String(state.isFirstPage)}</code>
|
|
319
|
+
<code className="block">isLast: {String(state.isLastPage)}</code>
|
|
320
|
+
<code className="block">
|
|
321
|
+
range: {`{${state.range.start}…${state.range.end}}`}
|
|
322
|
+
</code>
|
|
323
|
+
</pre>
|
|
324
|
+
</div>
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
return (
|
|
328
|
+
<div className="flex flex-wrap gap-4">
|
|
329
|
+
<StateBlock label="Empty (totalItems=0)" state={empty} />
|
|
330
|
+
<StateBlock
|
|
331
|
+
label="Last partial page (25 items, page 3 of 3)"
|
|
332
|
+
state={partial}
|
|
333
|
+
/>
|
|
334
|
+
<StateBlock label="Single page (8 items, pageSize=10)" state={single} />
|
|
335
|
+
</div>
|
|
336
|
+
);
|
|
337
|
+
},
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
// ---------------------------------------------------------------------------
|
|
341
|
+
// Story 5 — Integration
|
|
342
|
+
// usePagination is the single source of truth: it drives both the Table slice
|
|
343
|
+
// and the Pagination component (controlled via currentPage / onCurrentPageChange).
|
|
344
|
+
// ---------------------------------------------------------------------------
|
|
345
|
+
|
|
346
|
+
type Employee = {
|
|
347
|
+
id: number;
|
|
348
|
+
name: string;
|
|
349
|
+
department: string;
|
|
350
|
+
role: string;
|
|
351
|
+
status: "Active" | "On leave" | "Inactive";
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
const EMPLOYEES: Employee[] = [
|
|
355
|
+
{
|
|
356
|
+
id: 1,
|
|
357
|
+
name: "Ana García",
|
|
358
|
+
department: "Engineering",
|
|
359
|
+
role: "Senior Frontend",
|
|
360
|
+
status: "Active",
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
id: 2,
|
|
364
|
+
name: "Carlos Méndez",
|
|
365
|
+
department: "Engineering",
|
|
366
|
+
role: "Backend Lead",
|
|
367
|
+
status: "Active",
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
id: 3,
|
|
371
|
+
name: "Sofía Torres",
|
|
372
|
+
department: "Design",
|
|
373
|
+
role: "Product Designer",
|
|
374
|
+
status: "Active",
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
id: 4,
|
|
378
|
+
name: "Matías Romero",
|
|
379
|
+
department: "Engineering",
|
|
380
|
+
role: "DevOps Engineer",
|
|
381
|
+
status: "On leave",
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
id: 5,
|
|
385
|
+
name: "Valentina Cruz",
|
|
386
|
+
department: "Product",
|
|
387
|
+
role: "Product Manager",
|
|
388
|
+
status: "Active",
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
id: 6,
|
|
392
|
+
name: "Ignacio Ruiz",
|
|
393
|
+
department: "Engineering",
|
|
394
|
+
role: "Mobile Engineer",
|
|
395
|
+
status: "Active",
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
id: 7,
|
|
399
|
+
name: "Lucía Fernández",
|
|
400
|
+
department: "Design",
|
|
401
|
+
role: "UX Researcher",
|
|
402
|
+
status: "Active",
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
id: 8,
|
|
406
|
+
name: "Facundo López",
|
|
407
|
+
department: "Engineering",
|
|
408
|
+
role: "Staff Engineer",
|
|
409
|
+
status: "Inactive",
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
id: 9,
|
|
413
|
+
name: "Camila Vargas",
|
|
414
|
+
department: "Marketing",
|
|
415
|
+
role: "Growth Manager",
|
|
416
|
+
status: "Active",
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
id: 10,
|
|
420
|
+
name: "Tomás Jiménez",
|
|
421
|
+
department: "Engineering",
|
|
422
|
+
role: "Frontend Engineer",
|
|
423
|
+
status: "Active",
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
id: 11,
|
|
427
|
+
name: "Martina Sosa",
|
|
428
|
+
department: "Product",
|
|
429
|
+
role: "Product Analyst",
|
|
430
|
+
status: "Active",
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
id: 12,
|
|
434
|
+
name: "Agustín Herrera",
|
|
435
|
+
department: "Engineering",
|
|
436
|
+
role: "Data Engineer",
|
|
437
|
+
status: "On leave",
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
id: 13,
|
|
441
|
+
name: "Florencia Ríos",
|
|
442
|
+
department: "Design",
|
|
443
|
+
role: "Brand Designer",
|
|
444
|
+
status: "Active",
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
id: 14,
|
|
448
|
+
name: "Sebastián Morales",
|
|
449
|
+
department: "Engineering",
|
|
450
|
+
role: "Security Engineer",
|
|
451
|
+
status: "Active",
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
id: 15,
|
|
455
|
+
name: "Julieta Vega",
|
|
456
|
+
department: "Marketing",
|
|
457
|
+
role: "Content Strategist",
|
|
458
|
+
status: "Active",
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
id: 16,
|
|
462
|
+
name: "Rodrigo Blanco",
|
|
463
|
+
department: "Engineering",
|
|
464
|
+
role: "Backend Engineer",
|
|
465
|
+
status: "Active",
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
id: 17,
|
|
469
|
+
name: "Emilia Reyes",
|
|
470
|
+
department: "Product",
|
|
471
|
+
role: "Product Designer",
|
|
472
|
+
status: "Active",
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
id: 18,
|
|
476
|
+
name: "Nicolás Acosta",
|
|
477
|
+
department: "Engineering",
|
|
478
|
+
role: "Frontend Lead",
|
|
479
|
+
status: "Active",
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
id: 19,
|
|
483
|
+
name: "Bianca Molina",
|
|
484
|
+
department: "Marketing",
|
|
485
|
+
role: "SEO Specialist",
|
|
486
|
+
status: "Inactive",
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
id: 20,
|
|
490
|
+
name: "Esteban Peralta",
|
|
491
|
+
department: "Engineering",
|
|
492
|
+
role: "Platform Engineer",
|
|
493
|
+
status: "Active",
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
id: 21,
|
|
497
|
+
name: "Aldana Castillo",
|
|
498
|
+
department: "Design",
|
|
499
|
+
role: "Motion Designer",
|
|
500
|
+
status: "Active",
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
id: 22,
|
|
504
|
+
name: "Bruno Campos",
|
|
505
|
+
department: "Engineering",
|
|
506
|
+
role: "QA Engineer",
|
|
507
|
+
status: "Active",
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
id: 23,
|
|
511
|
+
name: "Xiomara Navarro",
|
|
512
|
+
department: "Product",
|
|
513
|
+
role: "Head of Product",
|
|
514
|
+
status: "Active",
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
id: 24,
|
|
518
|
+
name: "Gonzalo Ibáñez",
|
|
519
|
+
department: "Engineering",
|
|
520
|
+
role: "iOS Engineer",
|
|
521
|
+
status: "On leave",
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
id: 25,
|
|
525
|
+
name: "Renata Suárez",
|
|
526
|
+
department: "Marketing",
|
|
527
|
+
role: "Performance Marketer",
|
|
528
|
+
status: "Active",
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
id: 26,
|
|
532
|
+
name: "Hernán Delgado",
|
|
533
|
+
department: "Engineering",
|
|
534
|
+
role: "Android Engineer",
|
|
535
|
+
status: "Active",
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
id: 27,
|
|
539
|
+
name: "Pilar Guzmán",
|
|
540
|
+
department: "Design",
|
|
541
|
+
role: "Design Systems Lead",
|
|
542
|
+
status: "Active",
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
id: 28,
|
|
546
|
+
name: "Diego Ponce",
|
|
547
|
+
department: "Engineering",
|
|
548
|
+
role: "Tech Lead",
|
|
549
|
+
status: "Active",
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
id: 29,
|
|
553
|
+
name: "Violeta Medina",
|
|
554
|
+
department: "Product",
|
|
555
|
+
role: "Product Strategist",
|
|
556
|
+
status: "Active",
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
id: 30,
|
|
560
|
+
name: "Mauricio Arias",
|
|
561
|
+
department: "Engineering",
|
|
562
|
+
role: "Backend Engineer",
|
|
563
|
+
status: "Active",
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
id: 31,
|
|
567
|
+
name: "Catalina Flores",
|
|
568
|
+
department: "Marketing",
|
|
569
|
+
role: "Brand Manager",
|
|
570
|
+
status: "Active",
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
id: 32,
|
|
574
|
+
name: "Leandro Ortiz",
|
|
575
|
+
department: "Engineering",
|
|
576
|
+
role: "ML Engineer",
|
|
577
|
+
status: "Active",
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
id: 33,
|
|
581
|
+
name: "Inés Carrillo",
|
|
582
|
+
department: "Design",
|
|
583
|
+
role: "UX Designer",
|
|
584
|
+
status: "On leave",
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
id: 34,
|
|
588
|
+
name: "Pablo Montoya",
|
|
589
|
+
department: "Engineering",
|
|
590
|
+
role: "Infrastructure Lead",
|
|
591
|
+
status: "Active",
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
id: 35,
|
|
595
|
+
name: "Verónica Sandoval",
|
|
596
|
+
department: "Product",
|
|
597
|
+
role: "Product Owner",
|
|
598
|
+
status: "Active",
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
id: 36,
|
|
602
|
+
name: "Ramiro Espinoza",
|
|
603
|
+
department: "Engineering",
|
|
604
|
+
role: "Fullstack Engineer",
|
|
605
|
+
status: "Active",
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
id: 37,
|
|
609
|
+
name: "Cecilia Contreras",
|
|
610
|
+
department: "Marketing",
|
|
611
|
+
role: "CRM Manager",
|
|
612
|
+
status: "Active",
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
id: 38,
|
|
616
|
+
name: "Adrián Guerrero",
|
|
617
|
+
department: "Engineering",
|
|
618
|
+
role: "Site Reliability Eng.",
|
|
619
|
+
status: "Inactive",
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
id: 39,
|
|
623
|
+
name: "Gabriela Miranda",
|
|
624
|
+
department: "Design",
|
|
625
|
+
role: "Interaction Designer",
|
|
626
|
+
status: "Active",
|
|
627
|
+
},
|
|
628
|
+
{
|
|
629
|
+
id: 40,
|
|
630
|
+
name: "Claudio Fuentes",
|
|
631
|
+
department: "Engineering",
|
|
632
|
+
role: "API Architect",
|
|
633
|
+
status: "Active",
|
|
634
|
+
},
|
|
635
|
+
{
|
|
636
|
+
id: 41,
|
|
637
|
+
name: "Luciana Rojas",
|
|
638
|
+
department: "Product",
|
|
639
|
+
role: "Data Product Manager",
|
|
640
|
+
status: "Active",
|
|
641
|
+
},
|
|
642
|
+
{
|
|
643
|
+
id: 42,
|
|
644
|
+
name: "Marcos Benítez",
|
|
645
|
+
department: "Engineering",
|
|
646
|
+
role: "Frontend Engineer",
|
|
647
|
+
status: "Active",
|
|
648
|
+
},
|
|
649
|
+
{
|
|
650
|
+
id: 43,
|
|
651
|
+
name: "Daniela Castro",
|
|
652
|
+
department: "Marketing",
|
|
653
|
+
role: "Social Media Lead",
|
|
654
|
+
status: "On leave",
|
|
655
|
+
},
|
|
656
|
+
{
|
|
657
|
+
id: 44,
|
|
658
|
+
name: "Fernando Vera",
|
|
659
|
+
department: "Engineering",
|
|
660
|
+
role: "Platform Architect",
|
|
661
|
+
status: "Active",
|
|
662
|
+
},
|
|
663
|
+
{
|
|
664
|
+
id: 45,
|
|
665
|
+
name: "Antonella Ramos",
|
|
666
|
+
department: "Design",
|
|
667
|
+
role: "Visual Designer",
|
|
668
|
+
status: "Active",
|
|
669
|
+
},
|
|
670
|
+
{
|
|
671
|
+
id: 46,
|
|
672
|
+
name: "Julio Pereira",
|
|
673
|
+
department: "Engineering",
|
|
674
|
+
role: "Security Researcher",
|
|
675
|
+
status: "Active",
|
|
676
|
+
},
|
|
677
|
+
{
|
|
678
|
+
id: 47,
|
|
679
|
+
name: "Belén Alvarado",
|
|
680
|
+
department: "Product",
|
|
681
|
+
role: "Growth Product Manager",
|
|
682
|
+
status: "Active",
|
|
683
|
+
},
|
|
684
|
+
];
|
|
685
|
+
|
|
686
|
+
const STATUS_STYLES: Record<Employee["status"], string> = {
|
|
687
|
+
Active: "bg-green-100 text-green-800",
|
|
688
|
+
"On leave": "bg-yellow-100 text-yellow-800",
|
|
689
|
+
Inactive: "bg-slate-100 text-slate-600",
|
|
690
|
+
};
|
|
691
|
+
|
|
692
|
+
const EMPLOYEE_COLUMNS: ColumnDef<Employee>[] = [
|
|
693
|
+
{ key: "name", header: "Name" },
|
|
694
|
+
{ key: "department", header: "Department" },
|
|
695
|
+
{ key: "role", header: "Role" },
|
|
696
|
+
{
|
|
697
|
+
key: "status",
|
|
698
|
+
header: "Status",
|
|
699
|
+
cell: (row) => (
|
|
700
|
+
<span
|
|
701
|
+
className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${STATUS_STYLES[row.status]}`}
|
|
702
|
+
>
|
|
703
|
+
{row.status}
|
|
704
|
+
</span>
|
|
705
|
+
),
|
|
706
|
+
},
|
|
707
|
+
];
|
|
708
|
+
|
|
709
|
+
export const Integration = {
|
|
710
|
+
render: () => {
|
|
711
|
+
const { page, pageSize, range, goTo, setPageSize } = usePagination({
|
|
712
|
+
totalItems: EMPLOYEES.length,
|
|
713
|
+
defaultPageSize: 10,
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
const pageData = EMPLOYEES.slice(range.start - 1, range.end);
|
|
28
717
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
{JSON.stringify(isLastPage)}
|
|
44
|
-
</span>
|
|
45
|
-
</code>
|
|
46
|
-
<code className="block">
|
|
47
|
-
IsFirstPage:{" "}
|
|
48
|
-
<span className={isFirstPage ? "text-green-500" : "text-red-500"}>
|
|
49
|
-
{JSON.stringify(isFirstPage)}
|
|
50
|
-
</span>
|
|
51
|
-
</code>
|
|
52
|
-
</pre>
|
|
53
|
-
|
|
54
|
-
<div className="flex gap-2">
|
|
55
|
-
<select
|
|
56
|
-
className="border rounded w-24 px-2"
|
|
57
|
-
value={pageSize}
|
|
58
|
-
onChange={(event) => setPageSize(Number(event.target.value))}
|
|
59
|
-
>
|
|
60
|
-
{TABLE_PAGE_SIZES.map((option: number, index) => (
|
|
61
|
-
<option key={index} value={option}>
|
|
62
|
-
{option}
|
|
63
|
-
</option>
|
|
64
|
-
))}
|
|
65
|
-
</select>
|
|
66
|
-
<Button onClick={prev}>Página anterior</Button>
|
|
67
|
-
<Button onClick={next}>Siguiente pagina</Button>
|
|
68
|
-
<Button onClick={() => goTo(10)}>Ir a la pagina 10</Button>
|
|
718
|
+
return (
|
|
719
|
+
<div className="space-y-4">
|
|
720
|
+
<Table
|
|
721
|
+
data={pageData}
|
|
722
|
+
columns={EMPLOYEE_COLUMNS}
|
|
723
|
+
getRowKey={(row) => row.id}
|
|
724
|
+
/>
|
|
725
|
+
<Pagination
|
|
726
|
+
totalItems={EMPLOYEES.length}
|
|
727
|
+
currentPage={page}
|
|
728
|
+
onCurrentPageChange={goTo}
|
|
729
|
+
pageSize={pageSize}
|
|
730
|
+
onPageSizeChange={setPageSize}
|
|
731
|
+
/>
|
|
69
732
|
</div>
|
|
70
|
-
|
|
71
|
-
|
|
733
|
+
);
|
|
734
|
+
},
|
|
72
735
|
};
|