@adamosuiteservices/ui 2.13.2 → 2.13.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.
- package/dist/components/ui/slider/slider.d.ts +5 -2
- package/dist/slider.cjs +7 -8
- package/dist/slider.js +192 -178
- package/dist/styles.css +1 -1
- package/docs/AI-GUIDE.md +321 -321
- package/docs/components/layout/sidebar.md +399 -399
- package/docs/components/layout/toaster.md +436 -436
- package/docs/components/ui/accordion-rounded.md +584 -584
- package/docs/components/ui/accordion.md +269 -269
- package/docs/components/ui/calendar.md +1159 -1159
- package/docs/components/ui/card.md +1455 -1455
- package/docs/components/ui/checkbox.md +292 -292
- package/docs/components/ui/collapsible.md +323 -323
- package/docs/components/ui/dialog.md +628 -628
- package/docs/components/ui/field.md +706 -706
- package/docs/components/ui/hover-card.md +446 -446
- package/docs/components/ui/kbd.md +434 -434
- package/docs/components/ui/label.md +359 -359
- package/docs/components/ui/pagination.md +650 -650
- package/docs/components/ui/popover.md +536 -536
- package/docs/components/ui/progress.md +182 -182
- package/docs/components/ui/radio-group.md +311 -311
- package/docs/components/ui/separator.md +214 -214
- package/docs/components/ui/sheet.md +174 -174
- package/docs/components/ui/skeleton.md +140 -140
- package/docs/components/ui/slider.md +460 -341
- package/docs/components/ui/spinner.md +170 -170
- package/docs/components/ui/switch.md +408 -408
- package/docs/components/ui/tabs-underline.md +106 -106
- package/docs/components/ui/tabs.md +122 -122
- package/docs/components/ui/textarea.md +243 -243
- package/docs/components/ui/toggle.md +237 -237
- package/docs/components/ui/tooltip.md +317 -317
- package/docs/components/ui/typography.md +320 -320
- package/package.json +1 -1
|
@@ -1,650 +1,650 @@
|
|
|
1
|
-
# Pagination
|
|
2
|
-
|
|
3
|
-
Controles de navegación para contenido paginado. Incluye Previous/Next, números de página, ellipsis para gaps y estado activo.
|
|
4
|
-
|
|
5
|
-
## Descripción
|
|
6
|
-
|
|
7
|
-
El componente `Pagination` proporciona controles de navegación para contenido paginado.
|
|
8
|
-
|
|
9
|
-
## Importación
|
|
10
|
-
|
|
11
|
-
```typescript
|
|
12
|
-
import {
|
|
13
|
-
Pagination,
|
|
14
|
-
PaginationContent,
|
|
15
|
-
PaginationEllipsis,
|
|
16
|
-
PaginationItem,
|
|
17
|
-
PaginationLink,
|
|
18
|
-
PaginationNext,
|
|
19
|
-
PaginationPrevious,
|
|
20
|
-
} from "@adamosuiteservices/ui/pagination";
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
## Anatomía
|
|
24
|
-
|
|
25
|
-
```tsx
|
|
26
|
-
<Pagination>
|
|
27
|
-
<PaginationContent>
|
|
28
|
-
<PaginationItem>
|
|
29
|
-
<PaginationPrevious href="#" />
|
|
30
|
-
</PaginationItem>
|
|
31
|
-
<PaginationItem>
|
|
32
|
-
<PaginationLink href="#">1</PaginationLink>
|
|
33
|
-
</PaginationItem>
|
|
34
|
-
<PaginationItem>
|
|
35
|
-
<PaginationLink href="#" isActive>
|
|
36
|
-
2
|
|
37
|
-
</PaginationLink>
|
|
38
|
-
</PaginationItem>
|
|
39
|
-
<PaginationItem>
|
|
40
|
-
<PaginationLink href="#">3</PaginationLink>
|
|
41
|
-
</PaginationItem>
|
|
42
|
-
<PaginationItem>
|
|
43
|
-
<PaginationEllipsis />
|
|
44
|
-
</PaginationItem>
|
|
45
|
-
<PaginationItem>
|
|
46
|
-
<PaginationNext href="#" />
|
|
47
|
-
</PaginationItem>
|
|
48
|
-
</PaginationContent>
|
|
49
|
-
</Pagination>
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
**Componentes**: 7 (Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationPrevious, PaginationNext, PaginationEllipsis)
|
|
53
|
-
|
|
54
|
-
## Props Principales
|
|
55
|
-
|
|
56
|
-
### Pagination (Root)
|
|
57
|
-
|
|
58
|
-
| Prop | Tipo | Descripción |
|
|
59
|
-
| ----------- | -------- | ---------------------- |
|
|
60
|
-
| `className` | `string` | Clases CSS adicionales |
|
|
61
|
-
|
|
62
|
-
**Nota**: `<nav>` con `role="navigation"` y `aria-label="pagination"`
|
|
63
|
-
|
|
64
|
-
### PaginationContent
|
|
65
|
-
|
|
66
|
-
| Prop | Tipo | Descripción |
|
|
67
|
-
| ----------- | -------- | ---------------------- |
|
|
68
|
-
| `className` | `string` | Clases CSS adicionales |
|
|
69
|
-
|
|
70
|
-
**Nota**: `<ul>` con flex y gap-1
|
|
71
|
-
|
|
72
|
-
### PaginationItem
|
|
73
|
-
|
|
74
|
-
| Prop | Tipo | Descripción |
|
|
75
|
-
| ----------- | -------- | ---------------------- |
|
|
76
|
-
| `className` | `string` | Clases CSS adicionales |
|
|
77
|
-
|
|
78
|
-
**Nota**: `<li>` wrapper simple
|
|
79
|
-
|
|
80
|
-
### PaginationLink
|
|
81
|
-
|
|
82
|
-
| Prop | Tipo | Default | Descripción |
|
|
83
|
-
| ----------- | ------------- | -------- | ------------------------ |
|
|
84
|
-
| `isActive` | `boolean` | `false` | Marca página como activa |
|
|
85
|
-
| `size` | Button size | `"icon"` | Tamaño del link |
|
|
86
|
-
| `href` | `string` | - | URL destino |
|
|
87
|
-
| `onClick` | `(e) => void` | - | Click handler |
|
|
88
|
-
| `className` | `string` | - | Clases CSS adicionales |
|
|
89
|
-
|
|
90
|
-
**Nota**: `<a>` estilizado como Button
|
|
91
|
-
|
|
92
|
-
### PaginationPrevious
|
|
93
|
-
|
|
94
|
-
| Prop | Tipo | Default | Descripción |
|
|
95
|
-
| ----------- | ------------- | ------------ | ---------------------- |
|
|
96
|
-
| `children` | `ReactNode` | `"Previous"` | Texto del botón |
|
|
97
|
-
| `href` | `string` | - | URL destino |
|
|
98
|
-
| `onClick` | `(e) => void` | - | Click handler |
|
|
99
|
-
| `className` | `string` | - | Clases CSS adicionales |
|
|
100
|
-
|
|
101
|
-
**Nota**: PaginationLink con chevron_left icon (Material Symbol), texto "Previous" oculto en mobile
|
|
102
|
-
|
|
103
|
-
### PaginationNext
|
|
104
|
-
|
|
105
|
-
| Prop | Tipo | Default | Descripción |
|
|
106
|
-
| ----------- | ------------- | -------- | ---------------------- |
|
|
107
|
-
| `children` | `ReactNode` | `"Next"` | Texto del botón |
|
|
108
|
-
| `href` | `string` | - | URL destino |
|
|
109
|
-
| `onClick` | `(e) => void` | - | Click handler |
|
|
110
|
-
| `className` | `string` | - | Clases CSS adicionales |
|
|
111
|
-
|
|
112
|
-
**Nota**: PaginationLink con chevron_right icon (Material Symbol), texto "Next" oculto en mobile
|
|
113
|
-
|
|
114
|
-
### PaginationEllipsis
|
|
115
|
-
|
|
116
|
-
| Prop | Tipo | Descripción |
|
|
117
|
-
| ----------- | -------- | ---------------------- |
|
|
118
|
-
| `className` | `string` | Clases CSS adicionales |
|
|
119
|
-
|
|
120
|
-
**Nota**: `<span>` con more_horiz icon (Material Symbol) y `aria-hidden`
|
|
121
|
-
|
|
122
|
-
## Patrones de Uso
|
|
123
|
-
|
|
124
|
-
### Básico
|
|
125
|
-
|
|
126
|
-
```tsx
|
|
127
|
-
<Pagination>
|
|
128
|
-
<PaginationContent>
|
|
129
|
-
<PaginationItem>
|
|
130
|
-
<PaginationPrevious href="#" />
|
|
131
|
-
</PaginationItem>
|
|
132
|
-
<PaginationItem>
|
|
133
|
-
<PaginationLink href="#">1</PaginationLink>
|
|
134
|
-
</PaginationItem>
|
|
135
|
-
<PaginationItem>
|
|
136
|
-
<PaginationLink href="#" isActive>
|
|
137
|
-
2
|
|
138
|
-
</PaginationLink>
|
|
139
|
-
</PaginationItem>
|
|
140
|
-
<PaginationItem>
|
|
141
|
-
<PaginationLink href="#">3</PaginationLink>
|
|
142
|
-
</PaginationItem>
|
|
143
|
-
<PaginationItem>
|
|
144
|
-
<PaginationEllipsis />
|
|
145
|
-
</PaginationItem>
|
|
146
|
-
<PaginationItem>
|
|
147
|
-
<PaginationNext href="#" />
|
|
148
|
-
</PaginationItem>
|
|
149
|
-
</PaginationContent>
|
|
150
|
-
</Pagination>
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
### Simple (Sin Ellipsis)
|
|
154
|
-
|
|
155
|
-
```tsx
|
|
156
|
-
<Pagination>
|
|
157
|
-
<PaginationContent>
|
|
158
|
-
<PaginationItem>
|
|
159
|
-
<PaginationPrevious href="#" />
|
|
160
|
-
</PaginationItem>
|
|
161
|
-
<PaginationItem>
|
|
162
|
-
<PaginationLink href="#">1</PaginationLink>
|
|
163
|
-
</PaginationItem>
|
|
164
|
-
<PaginationItem>
|
|
165
|
-
<PaginationLink href="#">2</PaginationLink>
|
|
166
|
-
</PaginationItem>
|
|
167
|
-
<PaginationItem>
|
|
168
|
-
<PaginationLink href="#">3</PaginationLink>
|
|
169
|
-
</PaginationItem>
|
|
170
|
-
<PaginationItem>
|
|
171
|
-
<PaginationNext href="#" />
|
|
172
|
-
</PaginationItem>
|
|
173
|
-
</PaginationContent>
|
|
174
|
-
</Pagination>
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
### Con Ellipsis Doble
|
|
178
|
-
|
|
179
|
-
```tsx
|
|
180
|
-
<Pagination>
|
|
181
|
-
<PaginationContent>
|
|
182
|
-
<PaginationItem>
|
|
183
|
-
<PaginationPrevious href="#" />
|
|
184
|
-
</PaginationItem>
|
|
185
|
-
<PaginationItem>
|
|
186
|
-
<PaginationLink href="#">1</PaginationLink>
|
|
187
|
-
</PaginationItem>
|
|
188
|
-
<PaginationItem>
|
|
189
|
-
<PaginationEllipsis />
|
|
190
|
-
</PaginationItem>
|
|
191
|
-
<PaginationItem>
|
|
192
|
-
<PaginationLink href="#">10</PaginationLink>
|
|
193
|
-
</PaginationItem>
|
|
194
|
-
<PaginationItem>
|
|
195
|
-
<PaginationLink href="#" isActive>
|
|
196
|
-
11
|
|
197
|
-
</PaginationLink>
|
|
198
|
-
</PaginationItem>
|
|
199
|
-
<PaginationItem>
|
|
200
|
-
<PaginationLink href="#">12</PaginationLink>
|
|
201
|
-
</PaginationItem>
|
|
202
|
-
<PaginationItem>
|
|
203
|
-
<PaginationEllipsis />
|
|
204
|
-
</PaginationItem>
|
|
205
|
-
<PaginationItem>
|
|
206
|
-
<PaginationLink href="#">50</PaginationLink>
|
|
207
|
-
</PaginationItem>
|
|
208
|
-
<PaginationItem>
|
|
209
|
-
<PaginationNext href="#" />
|
|
210
|
-
</PaginationItem>
|
|
211
|
-
</PaginationContent>
|
|
212
|
-
</Pagination>
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
### Primera Página (Disabled Previous)
|
|
216
|
-
|
|
217
|
-
```tsx
|
|
218
|
-
<Pagination>
|
|
219
|
-
<PaginationContent>
|
|
220
|
-
<PaginationItem>
|
|
221
|
-
<PaginationPrevious href="#" className="pointer-events-none opacity-50" />
|
|
222
|
-
</PaginationItem>
|
|
223
|
-
<PaginationItem>
|
|
224
|
-
<PaginationLink href="#" isActive>
|
|
225
|
-
1
|
|
226
|
-
</PaginationLink>
|
|
227
|
-
</PaginationItem>
|
|
228
|
-
<PaginationItem>
|
|
229
|
-
<PaginationLink href="#">2</PaginationLink>
|
|
230
|
-
</PaginationItem>
|
|
231
|
-
<PaginationItem>
|
|
232
|
-
<PaginationLink href="#">3</PaginationLink>
|
|
233
|
-
</PaginationItem>
|
|
234
|
-
<PaginationItem>
|
|
235
|
-
<PaginationEllipsis />
|
|
236
|
-
</PaginationItem>
|
|
237
|
-
<PaginationItem>
|
|
238
|
-
<PaginationLink href="#">25</PaginationLink>
|
|
239
|
-
</PaginationItem>
|
|
240
|
-
<PaginationItem>
|
|
241
|
-
<PaginationNext href="#" />
|
|
242
|
-
</PaginationItem>
|
|
243
|
-
</PaginationContent>
|
|
244
|
-
</Pagination>
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
### Última Página (Disabled Next)
|
|
248
|
-
|
|
249
|
-
```tsx
|
|
250
|
-
<Pagination>
|
|
251
|
-
<PaginationContent>
|
|
252
|
-
<PaginationItem>
|
|
253
|
-
<PaginationPrevious href="#" />
|
|
254
|
-
</PaginationItem>
|
|
255
|
-
<PaginationItem>
|
|
256
|
-
<PaginationLink href="#">1</PaginationLink>
|
|
257
|
-
</PaginationItem>
|
|
258
|
-
<PaginationItem>
|
|
259
|
-
<PaginationEllipsis />
|
|
260
|
-
</PaginationItem>
|
|
261
|
-
<PaginationItem>
|
|
262
|
-
<PaginationLink href="#">23</PaginationLink>
|
|
263
|
-
</PaginationItem>
|
|
264
|
-
<PaginationItem>
|
|
265
|
-
<PaginationLink href="#">24</PaginationLink>
|
|
266
|
-
</PaginationItem>
|
|
267
|
-
<PaginationItem>
|
|
268
|
-
<PaginationLink href="#" isActive>
|
|
269
|
-
25
|
|
270
|
-
</PaginationLink>
|
|
271
|
-
</PaginationItem>
|
|
272
|
-
<PaginationItem>
|
|
273
|
-
<PaginationNext href="#" className="pointer-events-none opacity-50" />
|
|
274
|
-
</PaginationItem>
|
|
275
|
-
</PaginationContent>
|
|
276
|
-
</Pagination>
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
### Página Única
|
|
280
|
-
|
|
281
|
-
```tsx
|
|
282
|
-
<Pagination>
|
|
283
|
-
<PaginationContent>
|
|
284
|
-
<PaginationItem>
|
|
285
|
-
<PaginationPrevious href="#" className="pointer-events-none opacity-50" />
|
|
286
|
-
</PaginationItem>
|
|
287
|
-
<PaginationItem>
|
|
288
|
-
<PaginationLink href="#" isActive>
|
|
289
|
-
1
|
|
290
|
-
</PaginationLink>
|
|
291
|
-
</PaginationItem>
|
|
292
|
-
<PaginationItem>
|
|
293
|
-
<PaginationNext href="#" className="pointer-events-none opacity-50" />
|
|
294
|
-
</PaginationItem>
|
|
295
|
-
</PaginationContent>
|
|
296
|
-
</Pagination>
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
### Compacto (Solo Previous/Next)
|
|
300
|
-
|
|
301
|
-
```tsx
|
|
302
|
-
<Pagination className="justify-start">
|
|
303
|
-
<PaginationContent>
|
|
304
|
-
<PaginationItem>
|
|
305
|
-
<PaginationPrevious href="#" />
|
|
306
|
-
</PaginationItem>
|
|
307
|
-
<PaginationItem>
|
|
308
|
-
<PaginationNext href="#" />
|
|
309
|
-
</PaginationItem>
|
|
310
|
-
</PaginationContent>
|
|
311
|
-
</Pagination>
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
### Con Información de Página
|
|
315
|
-
|
|
316
|
-
```tsx
|
|
317
|
-
<div className="space-y-4">
|
|
318
|
-
<div className="flex items-center justify-between text-sm text-muted-foreground">
|
|
319
|
-
<span>Showing 1-10 of 250 results</span>
|
|
320
|
-
<span>Page 2 of 25</span>
|
|
321
|
-
</div>
|
|
322
|
-
|
|
323
|
-
<Pagination>
|
|
324
|
-
<PaginationContent>
|
|
325
|
-
<PaginationItem>
|
|
326
|
-
<PaginationPrevious href="#" />
|
|
327
|
-
</PaginationItem>
|
|
328
|
-
<PaginationItem>
|
|
329
|
-
<PaginationLink href="#">1</PaginationLink>
|
|
330
|
-
</PaginationItem>
|
|
331
|
-
<PaginationItem>
|
|
332
|
-
<PaginationLink href="#" isActive>
|
|
333
|
-
2
|
|
334
|
-
</PaginationLink>
|
|
335
|
-
</PaginationItem>
|
|
336
|
-
<PaginationItem>
|
|
337
|
-
<PaginationLink href="#">3</PaginationLink>
|
|
338
|
-
</PaginationItem>
|
|
339
|
-
<PaginationItem>
|
|
340
|
-
<PaginationEllipsis />
|
|
341
|
-
</PaginationItem>
|
|
342
|
-
<PaginationItem>
|
|
343
|
-
<PaginationLink href="#">25</PaginationLink>
|
|
344
|
-
</PaginationItem>
|
|
345
|
-
<PaginationItem>
|
|
346
|
-
<PaginationNext href="#" />
|
|
347
|
-
</PaginationItem>
|
|
348
|
-
</PaginationContent>
|
|
349
|
-
</Pagination>
|
|
350
|
-
</div>
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
### Interactivo (Con Estado)
|
|
354
|
-
|
|
355
|
-
```tsx
|
|
356
|
-
import { useState } from "react";
|
|
357
|
-
|
|
358
|
-
function App() {
|
|
359
|
-
const [currentPage, setCurrentPage] = useState(5);
|
|
360
|
-
const totalPages = 20;
|
|
361
|
-
|
|
362
|
-
const generatePageNumbers = () => {
|
|
363
|
-
const pages = [];
|
|
364
|
-
|
|
365
|
-
// Always show first page
|
|
366
|
-
pages.push(1);
|
|
367
|
-
|
|
368
|
-
// Add ellipsis if there's a gap
|
|
369
|
-
if (currentPage > 4) {
|
|
370
|
-
pages.push(-1); // -1 represents ellipsis
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// Show pages around current page
|
|
374
|
-
const start = Math.max(2, currentPage - 1);
|
|
375
|
-
const end = Math.min(totalPages - 1, currentPage + 1);
|
|
376
|
-
|
|
377
|
-
for (let i = start; i <= end; i++) {
|
|
378
|
-
if (i !== 1 && i !== totalPages) {
|
|
379
|
-
pages.push(i);
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// Add ellipsis if there's a gap
|
|
384
|
-
if (currentPage < totalPages - 3) {
|
|
385
|
-
pages.push(-2); // -2 represents ellipsis
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// Always show last page if more than 1 page
|
|
389
|
-
if (totalPages > 1) {
|
|
390
|
-
pages.push(totalPages);
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
return pages;
|
|
394
|
-
};
|
|
395
|
-
|
|
396
|
-
const pages = generatePageNumbers();
|
|
397
|
-
|
|
398
|
-
const handlePageClick = (page: number) => {
|
|
399
|
-
setCurrentPage(page);
|
|
400
|
-
};
|
|
401
|
-
|
|
402
|
-
const handlePrevious = () => {
|
|
403
|
-
if (currentPage > 1) {
|
|
404
|
-
setCurrentPage(currentPage - 1);
|
|
405
|
-
}
|
|
406
|
-
};
|
|
407
|
-
|
|
408
|
-
const handleNext = () => {
|
|
409
|
-
if (currentPage < totalPages) {
|
|
410
|
-
setCurrentPage(currentPage + 1);
|
|
411
|
-
}
|
|
412
|
-
};
|
|
413
|
-
|
|
414
|
-
return (
|
|
415
|
-
<div className="space-y-4">
|
|
416
|
-
<p className="text-center text-sm text-muted-foreground">
|
|
417
|
-
Page {currentPage} of {totalPages}
|
|
418
|
-
</p>
|
|
419
|
-
|
|
420
|
-
<Pagination>
|
|
421
|
-
<PaginationContent>
|
|
422
|
-
<PaginationItem>
|
|
423
|
-
<PaginationPrevious
|
|
424
|
-
href="#"
|
|
425
|
-
onClick={(e) => {
|
|
426
|
-
e.preventDefault();
|
|
427
|
-
handlePrevious();
|
|
428
|
-
}}
|
|
429
|
-
className={
|
|
430
|
-
currentPage === 1 ? "pointer-events-none opacity-50" : ""
|
|
431
|
-
}
|
|
432
|
-
/>
|
|
433
|
-
</PaginationItem>
|
|
434
|
-
|
|
435
|
-
{pages.map((page, index) => (
|
|
436
|
-
<PaginationItem key={index}>
|
|
437
|
-
{page < 0 ? (
|
|
438
|
-
<PaginationEllipsis />
|
|
439
|
-
) : (
|
|
440
|
-
<PaginationLink
|
|
441
|
-
href="#"
|
|
442
|
-
isActive={page === currentPage}
|
|
443
|
-
onClick={(e) => {
|
|
444
|
-
e.preventDefault();
|
|
445
|
-
handlePageClick(page);
|
|
446
|
-
}}
|
|
447
|
-
>
|
|
448
|
-
{page}
|
|
449
|
-
</PaginationLink>
|
|
450
|
-
)}
|
|
451
|
-
</PaginationItem>
|
|
452
|
-
))}
|
|
453
|
-
|
|
454
|
-
<PaginationItem>
|
|
455
|
-
<PaginationNext
|
|
456
|
-
href="#"
|
|
457
|
-
onClick={(e) => {
|
|
458
|
-
e.preventDefault();
|
|
459
|
-
handleNext();
|
|
460
|
-
}}
|
|
461
|
-
className={
|
|
462
|
-
currentPage === totalPages
|
|
463
|
-
? "pointer-events-none opacity-50"
|
|
464
|
-
: ""
|
|
465
|
-
}
|
|
466
|
-
/>
|
|
467
|
-
</PaginationItem>
|
|
468
|
-
</PaginationContent>
|
|
469
|
-
</Pagination>
|
|
470
|
-
</div>
|
|
471
|
-
);
|
|
472
|
-
}
|
|
473
|
-
```
|
|
474
|
-
|
|
475
|
-
### Tamaños Personalizados
|
|
476
|
-
|
|
477
|
-
```tsx
|
|
478
|
-
{
|
|
479
|
-
/* Small */
|
|
480
|
-
}
|
|
481
|
-
<Pagination>
|
|
482
|
-
<PaginationContent className="gap-0.5">
|
|
483
|
-
<PaginationItem>
|
|
484
|
-
<PaginationPrevious href="#" className="h-8 px-2 text-xs" />
|
|
485
|
-
</PaginationItem>
|
|
486
|
-
<PaginationItem>
|
|
487
|
-
<PaginationLink href="#" className="h-8 w-8 text-xs">
|
|
488
|
-
1
|
|
489
|
-
</PaginationLink>
|
|
490
|
-
</PaginationItem>
|
|
491
|
-
<PaginationItem>
|
|
492
|
-
<PaginationLink href="#" isActive className="h-8 w-8 text-xs">
|
|
493
|
-
2
|
|
494
|
-
</PaginationLink>
|
|
495
|
-
</PaginationItem>
|
|
496
|
-
<PaginationItem>
|
|
497
|
-
<PaginationNext href="#" className="h-8 px-2 text-xs" />
|
|
498
|
-
</PaginationItem>
|
|
499
|
-
</PaginationContent>
|
|
500
|
-
</Pagination>;
|
|
501
|
-
|
|
502
|
-
{
|
|
503
|
-
/* Large */
|
|
504
|
-
}
|
|
505
|
-
<Pagination>
|
|
506
|
-
<PaginationContent className="gap-2">
|
|
507
|
-
<PaginationItem>
|
|
508
|
-
<PaginationPrevious href="#" className="h-12 px-4 text-base" />
|
|
509
|
-
</PaginationItem>
|
|
510
|
-
<PaginationItem>
|
|
511
|
-
<PaginationLink href="#" className="h-12 w-12 text-base">
|
|
512
|
-
1
|
|
513
|
-
</PaginationLink>
|
|
514
|
-
</PaginationItem>
|
|
515
|
-
<PaginationItem>
|
|
516
|
-
<PaginationLink href="#" isActive className="h-12 w-12 text-base">
|
|
517
|
-
2
|
|
518
|
-
</PaginationLink>
|
|
519
|
-
</PaginationItem>
|
|
520
|
-
<PaginationItem>
|
|
521
|
-
<PaginationNext href="#" className="h-12 px-4 text-base" />
|
|
522
|
-
</PaginationItem>
|
|
523
|
-
</PaginationContent>
|
|
524
|
-
</Pagination>;
|
|
525
|
-
```
|
|
526
|
-
|
|
527
|
-
## Casos de Uso Comunes
|
|
528
|
-
|
|
529
|
-
**Tables**: Paginación de tablas con muchas filas
|
|
530
|
-
**Lists**: Listas largas de items (productos, usuarios, posts)
|
|
531
|
-
**Search results**: Resultados de búsqueda paginados
|
|
532
|
-
**Galleries**: Galerías de imágenes o archivos
|
|
533
|
-
**Admin panels**: Dashboards con data paginada
|
|
534
|
-
**API results**: Navegación de resultados de API con offset/limit
|
|
535
|
-
|
|
536
|
-
## Estados y Data Attributes
|
|
537
|
-
|
|
538
|
-
### PaginationLink
|
|
539
|
-
|
|
540
|
-
- **Active**: `isActive` → variant outline, `aria-current="page"`
|
|
541
|
-
- **Inactive**: variant ghost
|
|
542
|
-
|
|
543
|
-
### Disabled (Previous/Next)
|
|
544
|
-
|
|
545
|
-
No hay prop disabled nativa, se simula con:
|
|
546
|
-
|
|
547
|
-
```tsx
|
|
548
|
-
className = "pointer-events-none opacity-50";
|
|
549
|
-
```
|
|
550
|
-
|
|
551
|
-
## Estilos Base
|
|
552
|
-
|
|
553
|
-
### Pagination (Root)
|
|
554
|
-
|
|
555
|
-
- **Layout**: `mx-auto flex w-full justify-center`
|
|
556
|
-
- **Semantic**: `<nav role="navigation" aria-label="pagination">`
|
|
557
|
-
|
|
558
|
-
### PaginationContent
|
|
559
|
-
|
|
560
|
-
- **Layout**: `flex flex-row items-center gap-1`
|
|
561
|
-
|
|
562
|
-
### PaginationLink
|
|
563
|
-
|
|
564
|
-
- **Size default**: `size="icon"` (h-9 w-9)
|
|
565
|
-
- **Variants**: ghost (inactive), outline (active)
|
|
566
|
-
|
|
567
|
-
### PaginationPrevious/Next
|
|
568
|
-
|
|
569
|
-
- **Size**: `size="default"`
|
|
570
|
-
- **Padding**: `px-2.5`
|
|
571
|
-
- **Responsive**: Texto oculto en mobile (`hidden sm:block`)
|
|
572
|
-
- **Gap**: `gap-1` entre icono y texto
|
|
573
|
-
|
|
574
|
-
### PaginationEllipsis
|
|
575
|
-
|
|
576
|
-
- **Size**: `size-9`
|
|
577
|
-
- **Icon**: more_horiz (Material Symbol) con `text-lg`
|
|
578
|
-
- **Layout**: `flex items-center justify-center`
|
|
579
|
-
- **ARIA**: `aria-hidden`
|
|
580
|
-
|
|
581
|
-
## Responsive
|
|
582
|
-
|
|
583
|
-
- **Mobile**: Previous/Next solo muestran icono
|
|
584
|
-
- **Desktop (sm+)**: Previous/Next muestran icono + texto
|
|
585
|
-
|
|
586
|
-
## Accesibilidad
|
|
587
|
-
|
|
588
|
-
- ✅ **Semantic**: `<nav role="navigation" aria-label="pagination">`
|
|
589
|
-
- ✅ **Current page**: `aria-current="page"` en página activa
|
|
590
|
-
- ✅ **Screen readers**: Ellipsis tiene `<span className="sr-only">More pages</span>`
|
|
591
|
-
- ✅ **Keyboard**: Navegación con Tab, activación con Enter/Space
|
|
592
|
-
- ✅ **Labels**: Previous/Next tienen `aria-label` descriptivo
|
|
593
|
-
- ⚠️ **Disabled**: Usa `pointer-events-none opacity-50` + previene click en handler
|
|
594
|
-
|
|
595
|
-
## Notas de Implementación
|
|
596
|
-
|
|
597
|
-
- **Links**: PaginationLink es `<a>` no `<button>`, usa Button styles
|
|
598
|
-
- **Icons**: chevron_left, chevron_right, more_horiz (Material Symbols)
|
|
599
|
-
- **Responsive text**: Previous/Next usan `hidden sm:block` en texto
|
|
600
|
-
- **No state**: Componente stateless, maneja estado externamente
|
|
601
|
-
- **Ellipsis logic**: Representación negativa (-1, -2) común para identificar ellipsis
|
|
602
|
-
- **Disabled**: No hay prop disabled, usa className manual
|
|
603
|
-
- **Centering**: Root tiene `mx-auto justify-center`
|
|
604
|
-
|
|
605
|
-
## Algoritmo de Generación de Páginas
|
|
606
|
-
|
|
607
|
-
Patrón común para mostrar páginas con ellipsis:
|
|
608
|
-
|
|
609
|
-
```tsx
|
|
610
|
-
const generatePageNumbers = (current: number, total: number) => {
|
|
611
|
-
const pages = [];
|
|
612
|
-
|
|
613
|
-
// Siempre primera página
|
|
614
|
-
pages.push(1);
|
|
615
|
-
|
|
616
|
-
// Ellipsis izquierda si hay gap
|
|
617
|
-
if (current > 4) pages.push(-1);
|
|
618
|
-
|
|
619
|
-
// Páginas alrededor de current
|
|
620
|
-
const start = Math.max(2, current - 1);
|
|
621
|
-
const end = Math.min(total - 1, current + 1);
|
|
622
|
-
for (let i = start; i <= end; i++) {
|
|
623
|
-
if (i !== 1 && i !== total) pages.push(i);
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
// Ellipsis derecha si hay gap
|
|
627
|
-
if (current < total - 3) pages.push(-2);
|
|
628
|
-
|
|
629
|
-
// Siempre última página
|
|
630
|
-
if (total > 1) pages.push(total);
|
|
631
|
-
|
|
632
|
-
return pages;
|
|
633
|
-
};
|
|
634
|
-
```
|
|
635
|
-
|
|
636
|
-
## Troubleshooting
|
|
637
|
-
|
|
638
|
-
**Previous/Next no se deshabilitan**: No hay prop disabled, usa `className="pointer-events-none opacity-50"` y previene click en handler
|
|
639
|
-
**Active no se ve**: Verifica `isActive` prop y que solo una página lo tenga
|
|
640
|
-
**Texto Previous/Next no se ve en mobile**: Comportamiento esperado, usa `hidden sm:block`
|
|
641
|
-
**Ellipsis clickeable**: Ellipsis no debe ser clickeable, es `<span>` no link
|
|
642
|
-
**Spacing inconsistente**: PaginationContent tiene `gap-1`, ajusta con className
|
|
643
|
-
**No centrado**: Root tiene `justify-center`, override con `className="justify-start"` si necesario
|
|
644
|
-
**Links no funcionan**: PaginationLink usa `<a>`, asegúrate de pasar `href` o `onClick`
|
|
645
|
-
**Too many pages**: Usa ellipsis y lógica de generación de páginas para mostrar subset
|
|
646
|
-
|
|
647
|
-
## Referencias
|
|
648
|
-
|
|
649
|
-
- **shadcn/ui Pagination**: <https://ui.shadcn.com/docs/components/pagination>
|
|
650
|
-
- **WAI-ARIA Pagination**: <https://www.w3.org/WAI/ARIA/apg/patterns/pagination/>
|
|
1
|
+
# Pagination
|
|
2
|
+
|
|
3
|
+
Controles de navegación para contenido paginado. Incluye Previous/Next, números de página, ellipsis para gaps y estado activo.
|
|
4
|
+
|
|
5
|
+
## Descripción
|
|
6
|
+
|
|
7
|
+
El componente `Pagination` proporciona controles de navegación para contenido paginado.
|
|
8
|
+
|
|
9
|
+
## Importación
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import {
|
|
13
|
+
Pagination,
|
|
14
|
+
PaginationContent,
|
|
15
|
+
PaginationEllipsis,
|
|
16
|
+
PaginationItem,
|
|
17
|
+
PaginationLink,
|
|
18
|
+
PaginationNext,
|
|
19
|
+
PaginationPrevious,
|
|
20
|
+
} from "@adamosuiteservices/ui/pagination";
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Anatomía
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
<Pagination>
|
|
27
|
+
<PaginationContent>
|
|
28
|
+
<PaginationItem>
|
|
29
|
+
<PaginationPrevious href="#" />
|
|
30
|
+
</PaginationItem>
|
|
31
|
+
<PaginationItem>
|
|
32
|
+
<PaginationLink href="#">1</PaginationLink>
|
|
33
|
+
</PaginationItem>
|
|
34
|
+
<PaginationItem>
|
|
35
|
+
<PaginationLink href="#" isActive>
|
|
36
|
+
2
|
|
37
|
+
</PaginationLink>
|
|
38
|
+
</PaginationItem>
|
|
39
|
+
<PaginationItem>
|
|
40
|
+
<PaginationLink href="#">3</PaginationLink>
|
|
41
|
+
</PaginationItem>
|
|
42
|
+
<PaginationItem>
|
|
43
|
+
<PaginationEllipsis />
|
|
44
|
+
</PaginationItem>
|
|
45
|
+
<PaginationItem>
|
|
46
|
+
<PaginationNext href="#" />
|
|
47
|
+
</PaginationItem>
|
|
48
|
+
</PaginationContent>
|
|
49
|
+
</Pagination>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Componentes**: 7 (Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationPrevious, PaginationNext, PaginationEllipsis)
|
|
53
|
+
|
|
54
|
+
## Props Principales
|
|
55
|
+
|
|
56
|
+
### Pagination (Root)
|
|
57
|
+
|
|
58
|
+
| Prop | Tipo | Descripción |
|
|
59
|
+
| ----------- | -------- | ---------------------- |
|
|
60
|
+
| `className` | `string` | Clases CSS adicionales |
|
|
61
|
+
|
|
62
|
+
**Nota**: `<nav>` con `role="navigation"` y `aria-label="pagination"`
|
|
63
|
+
|
|
64
|
+
### PaginationContent
|
|
65
|
+
|
|
66
|
+
| Prop | Tipo | Descripción |
|
|
67
|
+
| ----------- | -------- | ---------------------- |
|
|
68
|
+
| `className` | `string` | Clases CSS adicionales |
|
|
69
|
+
|
|
70
|
+
**Nota**: `<ul>` con flex y gap-1
|
|
71
|
+
|
|
72
|
+
### PaginationItem
|
|
73
|
+
|
|
74
|
+
| Prop | Tipo | Descripción |
|
|
75
|
+
| ----------- | -------- | ---------------------- |
|
|
76
|
+
| `className` | `string` | Clases CSS adicionales |
|
|
77
|
+
|
|
78
|
+
**Nota**: `<li>` wrapper simple
|
|
79
|
+
|
|
80
|
+
### PaginationLink
|
|
81
|
+
|
|
82
|
+
| Prop | Tipo | Default | Descripción |
|
|
83
|
+
| ----------- | ------------- | -------- | ------------------------ |
|
|
84
|
+
| `isActive` | `boolean` | `false` | Marca página como activa |
|
|
85
|
+
| `size` | Button size | `"icon"` | Tamaño del link |
|
|
86
|
+
| `href` | `string` | - | URL destino |
|
|
87
|
+
| `onClick` | `(e) => void` | - | Click handler |
|
|
88
|
+
| `className` | `string` | - | Clases CSS adicionales |
|
|
89
|
+
|
|
90
|
+
**Nota**: `<a>` estilizado como Button
|
|
91
|
+
|
|
92
|
+
### PaginationPrevious
|
|
93
|
+
|
|
94
|
+
| Prop | Tipo | Default | Descripción |
|
|
95
|
+
| ----------- | ------------- | ------------ | ---------------------- |
|
|
96
|
+
| `children` | `ReactNode` | `"Previous"` | Texto del botón |
|
|
97
|
+
| `href` | `string` | - | URL destino |
|
|
98
|
+
| `onClick` | `(e) => void` | - | Click handler |
|
|
99
|
+
| `className` | `string` | - | Clases CSS adicionales |
|
|
100
|
+
|
|
101
|
+
**Nota**: PaginationLink con chevron_left icon (Material Symbol), texto "Previous" oculto en mobile
|
|
102
|
+
|
|
103
|
+
### PaginationNext
|
|
104
|
+
|
|
105
|
+
| Prop | Tipo | Default | Descripción |
|
|
106
|
+
| ----------- | ------------- | -------- | ---------------------- |
|
|
107
|
+
| `children` | `ReactNode` | `"Next"` | Texto del botón |
|
|
108
|
+
| `href` | `string` | - | URL destino |
|
|
109
|
+
| `onClick` | `(e) => void` | - | Click handler |
|
|
110
|
+
| `className` | `string` | - | Clases CSS adicionales |
|
|
111
|
+
|
|
112
|
+
**Nota**: PaginationLink con chevron_right icon (Material Symbol), texto "Next" oculto en mobile
|
|
113
|
+
|
|
114
|
+
### PaginationEllipsis
|
|
115
|
+
|
|
116
|
+
| Prop | Tipo | Descripción |
|
|
117
|
+
| ----------- | -------- | ---------------------- |
|
|
118
|
+
| `className` | `string` | Clases CSS adicionales |
|
|
119
|
+
|
|
120
|
+
**Nota**: `<span>` con more_horiz icon (Material Symbol) y `aria-hidden`
|
|
121
|
+
|
|
122
|
+
## Patrones de Uso
|
|
123
|
+
|
|
124
|
+
### Básico
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
<Pagination>
|
|
128
|
+
<PaginationContent>
|
|
129
|
+
<PaginationItem>
|
|
130
|
+
<PaginationPrevious href="#" />
|
|
131
|
+
</PaginationItem>
|
|
132
|
+
<PaginationItem>
|
|
133
|
+
<PaginationLink href="#">1</PaginationLink>
|
|
134
|
+
</PaginationItem>
|
|
135
|
+
<PaginationItem>
|
|
136
|
+
<PaginationLink href="#" isActive>
|
|
137
|
+
2
|
|
138
|
+
</PaginationLink>
|
|
139
|
+
</PaginationItem>
|
|
140
|
+
<PaginationItem>
|
|
141
|
+
<PaginationLink href="#">3</PaginationLink>
|
|
142
|
+
</PaginationItem>
|
|
143
|
+
<PaginationItem>
|
|
144
|
+
<PaginationEllipsis />
|
|
145
|
+
</PaginationItem>
|
|
146
|
+
<PaginationItem>
|
|
147
|
+
<PaginationNext href="#" />
|
|
148
|
+
</PaginationItem>
|
|
149
|
+
</PaginationContent>
|
|
150
|
+
</Pagination>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Simple (Sin Ellipsis)
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
<Pagination>
|
|
157
|
+
<PaginationContent>
|
|
158
|
+
<PaginationItem>
|
|
159
|
+
<PaginationPrevious href="#" />
|
|
160
|
+
</PaginationItem>
|
|
161
|
+
<PaginationItem>
|
|
162
|
+
<PaginationLink href="#">1</PaginationLink>
|
|
163
|
+
</PaginationItem>
|
|
164
|
+
<PaginationItem>
|
|
165
|
+
<PaginationLink href="#">2</PaginationLink>
|
|
166
|
+
</PaginationItem>
|
|
167
|
+
<PaginationItem>
|
|
168
|
+
<PaginationLink href="#">3</PaginationLink>
|
|
169
|
+
</PaginationItem>
|
|
170
|
+
<PaginationItem>
|
|
171
|
+
<PaginationNext href="#" />
|
|
172
|
+
</PaginationItem>
|
|
173
|
+
</PaginationContent>
|
|
174
|
+
</Pagination>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Con Ellipsis Doble
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
<Pagination>
|
|
181
|
+
<PaginationContent>
|
|
182
|
+
<PaginationItem>
|
|
183
|
+
<PaginationPrevious href="#" />
|
|
184
|
+
</PaginationItem>
|
|
185
|
+
<PaginationItem>
|
|
186
|
+
<PaginationLink href="#">1</PaginationLink>
|
|
187
|
+
</PaginationItem>
|
|
188
|
+
<PaginationItem>
|
|
189
|
+
<PaginationEllipsis />
|
|
190
|
+
</PaginationItem>
|
|
191
|
+
<PaginationItem>
|
|
192
|
+
<PaginationLink href="#">10</PaginationLink>
|
|
193
|
+
</PaginationItem>
|
|
194
|
+
<PaginationItem>
|
|
195
|
+
<PaginationLink href="#" isActive>
|
|
196
|
+
11
|
|
197
|
+
</PaginationLink>
|
|
198
|
+
</PaginationItem>
|
|
199
|
+
<PaginationItem>
|
|
200
|
+
<PaginationLink href="#">12</PaginationLink>
|
|
201
|
+
</PaginationItem>
|
|
202
|
+
<PaginationItem>
|
|
203
|
+
<PaginationEllipsis />
|
|
204
|
+
</PaginationItem>
|
|
205
|
+
<PaginationItem>
|
|
206
|
+
<PaginationLink href="#">50</PaginationLink>
|
|
207
|
+
</PaginationItem>
|
|
208
|
+
<PaginationItem>
|
|
209
|
+
<PaginationNext href="#" />
|
|
210
|
+
</PaginationItem>
|
|
211
|
+
</PaginationContent>
|
|
212
|
+
</Pagination>
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Primera Página (Disabled Previous)
|
|
216
|
+
|
|
217
|
+
```tsx
|
|
218
|
+
<Pagination>
|
|
219
|
+
<PaginationContent>
|
|
220
|
+
<PaginationItem>
|
|
221
|
+
<PaginationPrevious href="#" className="pointer-events-none opacity-50" />
|
|
222
|
+
</PaginationItem>
|
|
223
|
+
<PaginationItem>
|
|
224
|
+
<PaginationLink href="#" isActive>
|
|
225
|
+
1
|
|
226
|
+
</PaginationLink>
|
|
227
|
+
</PaginationItem>
|
|
228
|
+
<PaginationItem>
|
|
229
|
+
<PaginationLink href="#">2</PaginationLink>
|
|
230
|
+
</PaginationItem>
|
|
231
|
+
<PaginationItem>
|
|
232
|
+
<PaginationLink href="#">3</PaginationLink>
|
|
233
|
+
</PaginationItem>
|
|
234
|
+
<PaginationItem>
|
|
235
|
+
<PaginationEllipsis />
|
|
236
|
+
</PaginationItem>
|
|
237
|
+
<PaginationItem>
|
|
238
|
+
<PaginationLink href="#">25</PaginationLink>
|
|
239
|
+
</PaginationItem>
|
|
240
|
+
<PaginationItem>
|
|
241
|
+
<PaginationNext href="#" />
|
|
242
|
+
</PaginationItem>
|
|
243
|
+
</PaginationContent>
|
|
244
|
+
</Pagination>
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Última Página (Disabled Next)
|
|
248
|
+
|
|
249
|
+
```tsx
|
|
250
|
+
<Pagination>
|
|
251
|
+
<PaginationContent>
|
|
252
|
+
<PaginationItem>
|
|
253
|
+
<PaginationPrevious href="#" />
|
|
254
|
+
</PaginationItem>
|
|
255
|
+
<PaginationItem>
|
|
256
|
+
<PaginationLink href="#">1</PaginationLink>
|
|
257
|
+
</PaginationItem>
|
|
258
|
+
<PaginationItem>
|
|
259
|
+
<PaginationEllipsis />
|
|
260
|
+
</PaginationItem>
|
|
261
|
+
<PaginationItem>
|
|
262
|
+
<PaginationLink href="#">23</PaginationLink>
|
|
263
|
+
</PaginationItem>
|
|
264
|
+
<PaginationItem>
|
|
265
|
+
<PaginationLink href="#">24</PaginationLink>
|
|
266
|
+
</PaginationItem>
|
|
267
|
+
<PaginationItem>
|
|
268
|
+
<PaginationLink href="#" isActive>
|
|
269
|
+
25
|
|
270
|
+
</PaginationLink>
|
|
271
|
+
</PaginationItem>
|
|
272
|
+
<PaginationItem>
|
|
273
|
+
<PaginationNext href="#" className="pointer-events-none opacity-50" />
|
|
274
|
+
</PaginationItem>
|
|
275
|
+
</PaginationContent>
|
|
276
|
+
</Pagination>
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Página Única
|
|
280
|
+
|
|
281
|
+
```tsx
|
|
282
|
+
<Pagination>
|
|
283
|
+
<PaginationContent>
|
|
284
|
+
<PaginationItem>
|
|
285
|
+
<PaginationPrevious href="#" className="pointer-events-none opacity-50" />
|
|
286
|
+
</PaginationItem>
|
|
287
|
+
<PaginationItem>
|
|
288
|
+
<PaginationLink href="#" isActive>
|
|
289
|
+
1
|
|
290
|
+
</PaginationLink>
|
|
291
|
+
</PaginationItem>
|
|
292
|
+
<PaginationItem>
|
|
293
|
+
<PaginationNext href="#" className="pointer-events-none opacity-50" />
|
|
294
|
+
</PaginationItem>
|
|
295
|
+
</PaginationContent>
|
|
296
|
+
</Pagination>
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Compacto (Solo Previous/Next)
|
|
300
|
+
|
|
301
|
+
```tsx
|
|
302
|
+
<Pagination className="justify-start">
|
|
303
|
+
<PaginationContent>
|
|
304
|
+
<PaginationItem>
|
|
305
|
+
<PaginationPrevious href="#" />
|
|
306
|
+
</PaginationItem>
|
|
307
|
+
<PaginationItem>
|
|
308
|
+
<PaginationNext href="#" />
|
|
309
|
+
</PaginationItem>
|
|
310
|
+
</PaginationContent>
|
|
311
|
+
</Pagination>
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Con Información de Página
|
|
315
|
+
|
|
316
|
+
```tsx
|
|
317
|
+
<div className="space-y-4">
|
|
318
|
+
<div className="flex items-center justify-between text-sm text-muted-foreground">
|
|
319
|
+
<span>Showing 1-10 of 250 results</span>
|
|
320
|
+
<span>Page 2 of 25</span>
|
|
321
|
+
</div>
|
|
322
|
+
|
|
323
|
+
<Pagination>
|
|
324
|
+
<PaginationContent>
|
|
325
|
+
<PaginationItem>
|
|
326
|
+
<PaginationPrevious href="#" />
|
|
327
|
+
</PaginationItem>
|
|
328
|
+
<PaginationItem>
|
|
329
|
+
<PaginationLink href="#">1</PaginationLink>
|
|
330
|
+
</PaginationItem>
|
|
331
|
+
<PaginationItem>
|
|
332
|
+
<PaginationLink href="#" isActive>
|
|
333
|
+
2
|
|
334
|
+
</PaginationLink>
|
|
335
|
+
</PaginationItem>
|
|
336
|
+
<PaginationItem>
|
|
337
|
+
<PaginationLink href="#">3</PaginationLink>
|
|
338
|
+
</PaginationItem>
|
|
339
|
+
<PaginationItem>
|
|
340
|
+
<PaginationEllipsis />
|
|
341
|
+
</PaginationItem>
|
|
342
|
+
<PaginationItem>
|
|
343
|
+
<PaginationLink href="#">25</PaginationLink>
|
|
344
|
+
</PaginationItem>
|
|
345
|
+
<PaginationItem>
|
|
346
|
+
<PaginationNext href="#" />
|
|
347
|
+
</PaginationItem>
|
|
348
|
+
</PaginationContent>
|
|
349
|
+
</Pagination>
|
|
350
|
+
</div>
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Interactivo (Con Estado)
|
|
354
|
+
|
|
355
|
+
```tsx
|
|
356
|
+
import { useState } from "react";
|
|
357
|
+
|
|
358
|
+
function App() {
|
|
359
|
+
const [currentPage, setCurrentPage] = useState(5);
|
|
360
|
+
const totalPages = 20;
|
|
361
|
+
|
|
362
|
+
const generatePageNumbers = () => {
|
|
363
|
+
const pages = [];
|
|
364
|
+
|
|
365
|
+
// Always show first page
|
|
366
|
+
pages.push(1);
|
|
367
|
+
|
|
368
|
+
// Add ellipsis if there's a gap
|
|
369
|
+
if (currentPage > 4) {
|
|
370
|
+
pages.push(-1); // -1 represents ellipsis
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Show pages around current page
|
|
374
|
+
const start = Math.max(2, currentPage - 1);
|
|
375
|
+
const end = Math.min(totalPages - 1, currentPage + 1);
|
|
376
|
+
|
|
377
|
+
for (let i = start; i <= end; i++) {
|
|
378
|
+
if (i !== 1 && i !== totalPages) {
|
|
379
|
+
pages.push(i);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Add ellipsis if there's a gap
|
|
384
|
+
if (currentPage < totalPages - 3) {
|
|
385
|
+
pages.push(-2); // -2 represents ellipsis
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Always show last page if more than 1 page
|
|
389
|
+
if (totalPages > 1) {
|
|
390
|
+
pages.push(totalPages);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return pages;
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
const pages = generatePageNumbers();
|
|
397
|
+
|
|
398
|
+
const handlePageClick = (page: number) => {
|
|
399
|
+
setCurrentPage(page);
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
const handlePrevious = () => {
|
|
403
|
+
if (currentPage > 1) {
|
|
404
|
+
setCurrentPage(currentPage - 1);
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
const handleNext = () => {
|
|
409
|
+
if (currentPage < totalPages) {
|
|
410
|
+
setCurrentPage(currentPage + 1);
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
return (
|
|
415
|
+
<div className="space-y-4">
|
|
416
|
+
<p className="text-center text-sm text-muted-foreground">
|
|
417
|
+
Page {currentPage} of {totalPages}
|
|
418
|
+
</p>
|
|
419
|
+
|
|
420
|
+
<Pagination>
|
|
421
|
+
<PaginationContent>
|
|
422
|
+
<PaginationItem>
|
|
423
|
+
<PaginationPrevious
|
|
424
|
+
href="#"
|
|
425
|
+
onClick={(e) => {
|
|
426
|
+
e.preventDefault();
|
|
427
|
+
handlePrevious();
|
|
428
|
+
}}
|
|
429
|
+
className={
|
|
430
|
+
currentPage === 1 ? "pointer-events-none opacity-50" : ""
|
|
431
|
+
}
|
|
432
|
+
/>
|
|
433
|
+
</PaginationItem>
|
|
434
|
+
|
|
435
|
+
{pages.map((page, index) => (
|
|
436
|
+
<PaginationItem key={index}>
|
|
437
|
+
{page < 0 ? (
|
|
438
|
+
<PaginationEllipsis />
|
|
439
|
+
) : (
|
|
440
|
+
<PaginationLink
|
|
441
|
+
href="#"
|
|
442
|
+
isActive={page === currentPage}
|
|
443
|
+
onClick={(e) => {
|
|
444
|
+
e.preventDefault();
|
|
445
|
+
handlePageClick(page);
|
|
446
|
+
}}
|
|
447
|
+
>
|
|
448
|
+
{page}
|
|
449
|
+
</PaginationLink>
|
|
450
|
+
)}
|
|
451
|
+
</PaginationItem>
|
|
452
|
+
))}
|
|
453
|
+
|
|
454
|
+
<PaginationItem>
|
|
455
|
+
<PaginationNext
|
|
456
|
+
href="#"
|
|
457
|
+
onClick={(e) => {
|
|
458
|
+
e.preventDefault();
|
|
459
|
+
handleNext();
|
|
460
|
+
}}
|
|
461
|
+
className={
|
|
462
|
+
currentPage === totalPages
|
|
463
|
+
? "pointer-events-none opacity-50"
|
|
464
|
+
: ""
|
|
465
|
+
}
|
|
466
|
+
/>
|
|
467
|
+
</PaginationItem>
|
|
468
|
+
</PaginationContent>
|
|
469
|
+
</Pagination>
|
|
470
|
+
</div>
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### Tamaños Personalizados
|
|
476
|
+
|
|
477
|
+
```tsx
|
|
478
|
+
{
|
|
479
|
+
/* Small */
|
|
480
|
+
}
|
|
481
|
+
<Pagination>
|
|
482
|
+
<PaginationContent className="gap-0.5">
|
|
483
|
+
<PaginationItem>
|
|
484
|
+
<PaginationPrevious href="#" className="h-8 px-2 text-xs" />
|
|
485
|
+
</PaginationItem>
|
|
486
|
+
<PaginationItem>
|
|
487
|
+
<PaginationLink href="#" className="h-8 w-8 text-xs">
|
|
488
|
+
1
|
|
489
|
+
</PaginationLink>
|
|
490
|
+
</PaginationItem>
|
|
491
|
+
<PaginationItem>
|
|
492
|
+
<PaginationLink href="#" isActive className="h-8 w-8 text-xs">
|
|
493
|
+
2
|
|
494
|
+
</PaginationLink>
|
|
495
|
+
</PaginationItem>
|
|
496
|
+
<PaginationItem>
|
|
497
|
+
<PaginationNext href="#" className="h-8 px-2 text-xs" />
|
|
498
|
+
</PaginationItem>
|
|
499
|
+
</PaginationContent>
|
|
500
|
+
</Pagination>;
|
|
501
|
+
|
|
502
|
+
{
|
|
503
|
+
/* Large */
|
|
504
|
+
}
|
|
505
|
+
<Pagination>
|
|
506
|
+
<PaginationContent className="gap-2">
|
|
507
|
+
<PaginationItem>
|
|
508
|
+
<PaginationPrevious href="#" className="h-12 px-4 text-base" />
|
|
509
|
+
</PaginationItem>
|
|
510
|
+
<PaginationItem>
|
|
511
|
+
<PaginationLink href="#" className="h-12 w-12 text-base">
|
|
512
|
+
1
|
|
513
|
+
</PaginationLink>
|
|
514
|
+
</PaginationItem>
|
|
515
|
+
<PaginationItem>
|
|
516
|
+
<PaginationLink href="#" isActive className="h-12 w-12 text-base">
|
|
517
|
+
2
|
|
518
|
+
</PaginationLink>
|
|
519
|
+
</PaginationItem>
|
|
520
|
+
<PaginationItem>
|
|
521
|
+
<PaginationNext href="#" className="h-12 px-4 text-base" />
|
|
522
|
+
</PaginationItem>
|
|
523
|
+
</PaginationContent>
|
|
524
|
+
</Pagination>;
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
## Casos de Uso Comunes
|
|
528
|
+
|
|
529
|
+
**Tables**: Paginación de tablas con muchas filas
|
|
530
|
+
**Lists**: Listas largas de items (productos, usuarios, posts)
|
|
531
|
+
**Search results**: Resultados de búsqueda paginados
|
|
532
|
+
**Galleries**: Galerías de imágenes o archivos
|
|
533
|
+
**Admin panels**: Dashboards con data paginada
|
|
534
|
+
**API results**: Navegación de resultados de API con offset/limit
|
|
535
|
+
|
|
536
|
+
## Estados y Data Attributes
|
|
537
|
+
|
|
538
|
+
### PaginationLink
|
|
539
|
+
|
|
540
|
+
- **Active**: `isActive` → variant outline, `aria-current="page"`
|
|
541
|
+
- **Inactive**: variant ghost
|
|
542
|
+
|
|
543
|
+
### Disabled (Previous/Next)
|
|
544
|
+
|
|
545
|
+
No hay prop disabled nativa, se simula con:
|
|
546
|
+
|
|
547
|
+
```tsx
|
|
548
|
+
className = "pointer-events-none opacity-50";
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
## Estilos Base
|
|
552
|
+
|
|
553
|
+
### Pagination (Root)
|
|
554
|
+
|
|
555
|
+
- **Layout**: `mx-auto flex w-full justify-center`
|
|
556
|
+
- **Semantic**: `<nav role="navigation" aria-label="pagination">`
|
|
557
|
+
|
|
558
|
+
### PaginationContent
|
|
559
|
+
|
|
560
|
+
- **Layout**: `flex flex-row items-center gap-1`
|
|
561
|
+
|
|
562
|
+
### PaginationLink
|
|
563
|
+
|
|
564
|
+
- **Size default**: `size="icon"` (h-9 w-9)
|
|
565
|
+
- **Variants**: ghost (inactive), outline (active)
|
|
566
|
+
|
|
567
|
+
### PaginationPrevious/Next
|
|
568
|
+
|
|
569
|
+
- **Size**: `size="default"`
|
|
570
|
+
- **Padding**: `px-2.5`
|
|
571
|
+
- **Responsive**: Texto oculto en mobile (`hidden sm:block`)
|
|
572
|
+
- **Gap**: `gap-1` entre icono y texto
|
|
573
|
+
|
|
574
|
+
### PaginationEllipsis
|
|
575
|
+
|
|
576
|
+
- **Size**: `size-9`
|
|
577
|
+
- **Icon**: more_horiz (Material Symbol) con `text-lg`
|
|
578
|
+
- **Layout**: `flex items-center justify-center`
|
|
579
|
+
- **ARIA**: `aria-hidden`
|
|
580
|
+
|
|
581
|
+
## Responsive
|
|
582
|
+
|
|
583
|
+
- **Mobile**: Previous/Next solo muestran icono
|
|
584
|
+
- **Desktop (sm+)**: Previous/Next muestran icono + texto
|
|
585
|
+
|
|
586
|
+
## Accesibilidad
|
|
587
|
+
|
|
588
|
+
- ✅ **Semantic**: `<nav role="navigation" aria-label="pagination">`
|
|
589
|
+
- ✅ **Current page**: `aria-current="page"` en página activa
|
|
590
|
+
- ✅ **Screen readers**: Ellipsis tiene `<span className="sr-only">More pages</span>`
|
|
591
|
+
- ✅ **Keyboard**: Navegación con Tab, activación con Enter/Space
|
|
592
|
+
- ✅ **Labels**: Previous/Next tienen `aria-label` descriptivo
|
|
593
|
+
- ⚠️ **Disabled**: Usa `pointer-events-none opacity-50` + previene click en handler
|
|
594
|
+
|
|
595
|
+
## Notas de Implementación
|
|
596
|
+
|
|
597
|
+
- **Links**: PaginationLink es `<a>` no `<button>`, usa Button styles
|
|
598
|
+
- **Icons**: chevron_left, chevron_right, more_horiz (Material Symbols)
|
|
599
|
+
- **Responsive text**: Previous/Next usan `hidden sm:block` en texto
|
|
600
|
+
- **No state**: Componente stateless, maneja estado externamente
|
|
601
|
+
- **Ellipsis logic**: Representación negativa (-1, -2) común para identificar ellipsis
|
|
602
|
+
- **Disabled**: No hay prop disabled, usa className manual
|
|
603
|
+
- **Centering**: Root tiene `mx-auto justify-center`
|
|
604
|
+
|
|
605
|
+
## Algoritmo de Generación de Páginas
|
|
606
|
+
|
|
607
|
+
Patrón común para mostrar páginas con ellipsis:
|
|
608
|
+
|
|
609
|
+
```tsx
|
|
610
|
+
const generatePageNumbers = (current: number, total: number) => {
|
|
611
|
+
const pages = [];
|
|
612
|
+
|
|
613
|
+
// Siempre primera página
|
|
614
|
+
pages.push(1);
|
|
615
|
+
|
|
616
|
+
// Ellipsis izquierda si hay gap
|
|
617
|
+
if (current > 4) pages.push(-1);
|
|
618
|
+
|
|
619
|
+
// Páginas alrededor de current
|
|
620
|
+
const start = Math.max(2, current - 1);
|
|
621
|
+
const end = Math.min(total - 1, current + 1);
|
|
622
|
+
for (let i = start; i <= end; i++) {
|
|
623
|
+
if (i !== 1 && i !== total) pages.push(i);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// Ellipsis derecha si hay gap
|
|
627
|
+
if (current < total - 3) pages.push(-2);
|
|
628
|
+
|
|
629
|
+
// Siempre última página
|
|
630
|
+
if (total > 1) pages.push(total);
|
|
631
|
+
|
|
632
|
+
return pages;
|
|
633
|
+
};
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
## Troubleshooting
|
|
637
|
+
|
|
638
|
+
**Previous/Next no se deshabilitan**: No hay prop disabled, usa `className="pointer-events-none opacity-50"` y previene click en handler
|
|
639
|
+
**Active no se ve**: Verifica `isActive` prop y que solo una página lo tenga
|
|
640
|
+
**Texto Previous/Next no se ve en mobile**: Comportamiento esperado, usa `hidden sm:block`
|
|
641
|
+
**Ellipsis clickeable**: Ellipsis no debe ser clickeable, es `<span>` no link
|
|
642
|
+
**Spacing inconsistente**: PaginationContent tiene `gap-1`, ajusta con className
|
|
643
|
+
**No centrado**: Root tiene `justify-center`, override con `className="justify-start"` si necesario
|
|
644
|
+
**Links no funcionan**: PaginationLink usa `<a>`, asegúrate de pasar `href` o `onClick`
|
|
645
|
+
**Too many pages**: Usa ellipsis y lógica de generación de páginas para mostrar subset
|
|
646
|
+
|
|
647
|
+
## Referencias
|
|
648
|
+
|
|
649
|
+
- **shadcn/ui Pagination**: <https://ui.shadcn.com/docs/components/pagination>
|
|
650
|
+
- **WAI-ARIA Pagination**: <https://www.w3.org/WAI/ARIA/apg/patterns/pagination/>
|