@boxcustodia/library 2.0.0-alpha.13 → 2.0.0-alpha.14

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 (173) hide show
  1. package/dist/index.cjs.js +1 -138
  2. package/dist/index.d.ts +1083 -715
  3. package/dist/index.es.js +7077 -56175
  4. package/dist/theme.css +1 -1
  5. package/package.json +34 -26
  6. package/src/__doc__/Examples.tsx +1 -1
  7. package/src/__doc__/Intro.mdx +3 -3
  8. package/src/__doc__/Tabs.mdx +112 -0
  9. package/src/__doc__/V2.mdx +1246 -0
  10. package/src/components/accordion/accordion.stories.tsx +143 -0
  11. package/src/components/accordion/accordion.tsx +135 -0
  12. package/src/components/accordion/index.ts +1 -0
  13. package/src/components/alert/alert.stories.tsx +24 -4
  14. package/src/components/alert/alert.tsx +17 -9
  15. package/src/components/alert-dialog/alert-dialog.stories.tsx +24 -0
  16. package/src/components/alert-dialog/alert-dialog.test.tsx +1 -1
  17. package/src/components/alert-dialog/alert-dialog.tsx +58 -10
  18. package/src/components/auto-complete/auto-complete.stories.tsx +616 -200
  19. package/src/components/auto-complete/auto-complete.tsx +420 -68
  20. package/src/components/auto-complete/index.ts +0 -1
  21. package/src/components/avatar/avatar.stories.tsx +162 -21
  22. package/src/components/avatar/avatar.tsx +79 -20
  23. package/src/components/button/button.stories.tsx +219 -294
  24. package/src/components/button/button.test.tsx +10 -17
  25. package/src/components/button/button.tsx +78 -19
  26. package/src/components/button/components/base-button.tsx +30 -53
  27. package/src/components/button/index.ts +0 -1
  28. package/src/components/calendar/calendar.stories.tsx +1 -1
  29. package/src/components/calendar/calendar.tsx +4 -4
  30. package/src/components/card/card.stories.tsx +141 -69
  31. package/src/components/card/card.tsx +155 -54
  32. package/src/components/center/center.stories.tsx +22 -39
  33. package/src/components/checkbox/checkbox.stories.tsx +25 -5
  34. package/src/components/checkbox/checkbox.tsx +76 -15
  35. package/src/components/checkbox-group/checkbox-group.stories.tsx +116 -28
  36. package/src/components/checkbox-group/checkbox-group.tsx +84 -3
  37. package/src/components/combobox/combobox.stories.tsx +33 -23
  38. package/src/components/combobox/combobox.tsx +119 -103
  39. package/src/components/date-picker/date-input.stories.tsx +14 -6
  40. package/src/components/date-picker/date-input.tsx +2 -2
  41. package/src/components/date-picker/date-picker.model.ts +13 -4
  42. package/src/components/date-picker/date-picker.stories.tsx +38 -12
  43. package/src/components/date-picker/date-picker.tsx +28 -14
  44. package/src/components/dialog/dialog.stories.tsx +18 -0
  45. package/src/components/dialog/dialog.test.tsx +1 -1
  46. package/src/components/dialog/dialog.tsx +51 -20
  47. package/src/components/divider/divider.stories.tsx +6 -0
  48. package/src/components/dropzone/dropzone.stories.tsx +71 -90
  49. package/src/components/dropzone/dropzone.tsx +383 -105
  50. package/src/components/dropzone/index.ts +0 -1
  51. package/src/components/empty/empty.stories.tsx +165 -0
  52. package/src/components/empty/empty.tsx +156 -0
  53. package/src/components/empty/index.ts +1 -0
  54. package/src/components/field/field.stories.tsx +226 -3
  55. package/src/components/field/field.tsx +77 -42
  56. package/src/components/form/form.stories.tsx +320 -197
  57. package/src/components/form/form.tsx +3 -23
  58. package/src/components/index.ts +2 -6
  59. package/src/components/input/input.stories.tsx +5 -5
  60. package/src/components/input/input.tsx +4 -4
  61. package/src/components/kbd/kbd.stories.tsx +1 -0
  62. package/src/components/label/label.stories.tsx +16 -0
  63. package/src/components/label/label.tsx +13 -2
  64. package/src/components/loader/loader.stories.tsx +7 -5
  65. package/src/components/loader/loader.tsx +8 -3
  66. package/src/components/menu/menu-primitives.tsx +207 -196
  67. package/src/components/menu/menu.stories.tsx +276 -146
  68. package/src/components/menu/menu.tsx +146 -54
  69. package/src/components/number-input/number-input.stories.tsx +27 -4
  70. package/src/components/number-input/number-input.test.tsx +2 -2
  71. package/src/components/number-input/number-input.tsx +25 -29
  72. package/src/components/otp/index.ts +1 -0
  73. package/src/components/otp/otp.stories.tsx +209 -0
  74. package/src/components/otp/otp.tsx +100 -0
  75. package/src/components/pagination/index.ts +1 -0
  76. package/src/components/pagination/pagination.model.ts +2 -0
  77. package/src/components/pagination/pagination.stories.tsx +154 -59
  78. package/src/components/pagination/pagination.test.tsx +122 -57
  79. package/src/components/pagination/pagination.tsx +575 -77
  80. package/src/components/password/password.stories.tsx +18 -3
  81. package/src/components/password/password.tsx +26 -10
  82. package/src/components/popover/popover.stories.tsx +26 -5
  83. package/src/components/popover/popover.tsx +15 -23
  84. package/src/components/progress/progress.stories.tsx +1 -0
  85. package/src/components/radio-group/index.ts +1 -0
  86. package/src/components/radio-group/radio-group.stories.tsx +251 -0
  87. package/src/components/radio-group/radio-group.tsx +212 -0
  88. package/src/components/scroll-area/scroll-area.stories.tsx +1 -0
  89. package/src/components/select/select.stories.tsx +118 -19
  90. package/src/components/select/select.tsx +67 -62
  91. package/src/components/skeleton/skeleton.stories.tsx +1 -0
  92. package/src/components/stack/stack.stories.tsx +179 -89
  93. package/src/components/stack/stack.tsx +2 -2
  94. package/src/components/stepper/index.ts +1 -1
  95. package/src/components/stepper/stepper.stories.tsx +767 -83
  96. package/src/components/stepper/stepper.test.tsx +18 -18
  97. package/src/components/stepper/stepper.tsx +554 -0
  98. package/src/components/switch/switch.stories.tsx +15 -1
  99. package/src/components/switch/switch.tsx +17 -4
  100. package/src/components/table/index.ts +0 -2
  101. package/src/components/table/table.stories.tsx +131 -18
  102. package/src/components/table/table.test.tsx +1 -1
  103. package/src/components/table/table.tsx +183 -77
  104. package/src/components/tabs/tabs.stories.tsx +373 -155
  105. package/src/components/tabs/tabs.test.tsx +12 -12
  106. package/src/components/tabs/tabs.tsx +72 -149
  107. package/src/components/tag/index.ts +0 -1
  108. package/src/components/tag/tag.stories.tsx +155 -120
  109. package/src/components/tag/tag.tsx +47 -95
  110. package/src/components/textarea/textarea.stories.tsx +8 -22
  111. package/src/components/textarea/textarea.tsx +17 -79
  112. package/src/components/timeline/timeline.stories.tsx +323 -42
  113. package/src/components/timeline/timeline.tsx +359 -132
  114. package/src/components/toast/toast.stories.tsx +1 -0
  115. package/src/components/tooltip/tooltip.tsx +11 -9
  116. package/src/components/tree/index.ts +0 -1
  117. package/src/components/tree/tree.stories.tsx +365 -408
  118. package/src/components/tree/tree.test.tsx +163 -0
  119. package/src/components/tree/tree.tsx +212 -36
  120. package/src/hooks/useAsync/__doc__/useAsync.stories.tsx +5 -5
  121. package/src/hooks/useClipboard/__doc__/useClipboard.stories.tsx +1 -3
  122. package/src/hooks/useDebounceCallback/__doc__/useDebouncedCallback.stories.tsx +6 -6
  123. package/src/hooks/useDocumentTitle/__doc__/useDocumentTitle.stories.tsx +1 -1
  124. package/src/hooks/useEventListener/__test__/useEventListener.test.tsx +1 -1
  125. package/src/hooks/useLocalStorage/__doc__/useLocalStorage.stories.tsx +1 -1
  126. package/src/hooks/usePagination/usePagination.tsx +36 -24
  127. package/src/styles/theme.css +1 -1
  128. package/src/utils/form.tsx +67 -37
  129. package/src/utils/index.ts +1 -1
  130. package/src/__doc__/Migration.mdx +0 -451
  131. package/src/components/auto-complete/auto-complete-primitives.tsx +0 -155
  132. package/src/components/background-image/background-image.stories.tsx +0 -21
  133. package/src/components/background-image/background-image.test.tsx +0 -29
  134. package/src/components/background-image/background-image.tsx +0 -23
  135. package/src/components/background-image/index.ts +0 -1
  136. package/src/components/button/button.variants.ts +0 -44
  137. package/src/components/button/components/loader-overlay.tsx +0 -21
  138. package/src/components/button/components/loading-icon.tsx +0 -47
  139. package/src/components/dropzone/upload-primitives.tsx +0 -310
  140. package/src/components/dropzone/use-dropzone.ts +0 -122
  141. package/src/components/empty-state/empty-state.stories.tsx +0 -56
  142. package/src/components/empty-state/empty-state.tsx +0 -39
  143. package/src/components/empty-state/index.ts +0 -1
  144. package/src/components/heading/heading.stories.tsx +0 -74
  145. package/src/components/heading/heading.tsx +0 -28
  146. package/src/components/heading/heading.variants.ts +0 -27
  147. package/src/components/heading/index.ts +0 -1
  148. package/src/components/kbd/kbd.variants.ts +0 -26
  149. package/src/components/menu/util/render-menu-item.tsx +0 -54
  150. package/src/components/multi-select/hooks/use-multi-select.ts +0 -66
  151. package/src/components/multi-select/index.ts +0 -1
  152. package/src/components/multi-select/multi-select.stories.tsx +0 -294
  153. package/src/components/multi-select/multi-select.tsx +0 -300
  154. package/src/components/multi-select/multi-select.variants.ts +0 -22
  155. package/src/components/pagination/components/pagination-option.tsx +0 -27
  156. package/src/components/show/index.ts +0 -1
  157. package/src/components/show/show.stories.tsx +0 -197
  158. package/src/components/show/show.test.tsx +0 -41
  159. package/src/components/show/show.tsx +0 -16
  160. package/src/components/stepper/Stepper.tsx +0 -190
  161. package/src/components/stepper/context/stepper-context.tsx +0 -11
  162. package/src/components/table/table-primitives.tsx +0 -122
  163. package/src/components/table/table.model.ts +0 -20
  164. package/src/components/table-pagination/index.ts +0 -2
  165. package/src/components/table-pagination/table-pagination.model.ts +0 -2
  166. package/src/components/table-pagination/table-pagination.stories.tsx +0 -23
  167. package/src/components/table-pagination/table-pagination.test.tsx +0 -32
  168. package/src/components/table-pagination/table-pagination.tsx +0 -108
  169. package/src/components/tabs/context/tabs-context.tsx +0 -14
  170. package/src/components/tag/tag.variants.ts +0 -31
  171. package/src/components/timeline/timeline-status.ts +0 -5
  172. package/src/components/tree/hooks/use-controllable-tree-state.ts +0 -80
  173. package/src/components/tree/tree-primitives.tsx +0 -126
@@ -1,102 +1,600 @@
1
+ import { useControllableState } from "@radix-ui/react-use-controllable-state";
1
2
  import {
2
3
  ChevronLeft,
3
4
  ChevronRight,
4
5
  ChevronsLeft,
5
6
  ChevronsRight,
7
+ MoreHorizontal,
6
8
  } from "lucide-react";
7
- import { HTMLProps } from "react";
8
- import { DOTS, useRangePagination, useRangePaginationProps } from "../../hooks";
9
- import { cn } from "../../lib";
10
9
  import {
11
- PaginationOption as Option,
12
- PaginationOptionProps,
13
- } from "./components/pagination-option";
10
+ ChangeEvent,
11
+ ComponentProps,
12
+ ComponentType,
13
+ createContext,
14
+ ElementType,
15
+ ReactNode,
16
+ useContext,
17
+ useMemo,
18
+ } from "react";
19
+ import { DOTS, useRangePagination } from "../../hooks";
20
+ import { cn } from "../../lib";
21
+ import { Button } from "../button";
22
+ import { PAGINATION_SIZES, PageSize } from "./pagination.model";
23
+
24
+ type GetPageHref = (state: { page: number; pageSize: number }) => string;
14
25
 
15
- interface Props extends useRangePaginationProps {
16
- optionProps?: PaginationOptionProps;
26
+ type LinkRenderProps = {
27
+ href: string;
28
+ children?: ReactNode;
17
29
  className?: string;
18
- containerProps?: HTMLProps<HTMLDivElement>;
30
+ onClick?: (event: React.MouseEvent) => void;
31
+ };
32
+
33
+ type LinkComponent =
34
+ | ElementType<LinkRenderProps>
35
+ | ComponentType<LinkRenderProps>;
36
+
37
+ interface PaginationTexts {
38
+ goToFirst: string;
39
+ goToPrevious: string;
40
+ goToNext: string;
41
+ goToLast: string;
42
+ goToPage: (page: number) => string;
43
+ total: (n: number) => string;
44
+ pageInfo: (page: number, max: number) => string;
45
+ range: (start: number, end: number, total: number) => string;
46
+ morePages: string;
47
+ }
48
+
49
+ const DEFAULT_TEXTS: PaginationTexts = {
50
+ goToFirst: "Ir a primera página",
51
+ goToPrevious: "Ir a página anterior",
52
+ goToNext: "Ir a próxima página",
53
+ goToLast: "Ir a última página",
54
+ goToPage: (page) => `Ir a la página ${page}`,
55
+ total: (n) => `${n} resultados`,
56
+ pageInfo: (page, max) => `Página ${page} de ${max}`,
57
+ range: (start, end, total) => `${start} - ${end} de ${total}`,
58
+ morePages: "Más páginas",
59
+ };
60
+
61
+ interface PaginationContextValue {
62
+ currentPage: number;
63
+ maxPage: number;
64
+ pageSize: PageSize;
65
+ totalItems: number;
66
+ paginationRange: (string | number)[];
67
+ range: { start: number; end: number };
68
+ isFirstPage: boolean;
69
+ isLastPage: boolean;
70
+ goTo: (page: number) => void;
71
+ next: () => void;
72
+ prev: () => void;
73
+ setPageSize: (size: PageSize) => void;
74
+ getPageHref?: GetPageHref;
75
+ linkComponent: LinkComponent;
76
+ texts: PaginationTexts;
19
77
  }
20
78
 
21
- export const Pagination = ({
22
- optionProps,
79
+ const PaginationContext = createContext<PaginationContextValue | null>(null);
80
+
81
+ function usePaginationContext() {
82
+ const ctx = useContext(PaginationContext);
83
+ if (!ctx) {
84
+ throw new Error(
85
+ "Pagination primitives must be used inside <PaginationRoot>",
86
+ );
87
+ }
88
+ return ctx;
89
+ }
90
+
91
+ interface PaginationRootProps extends Omit<ComponentProps<"nav">, "onChange"> {
92
+ totalItems: number;
93
+ pageSize?: PageSize;
94
+ defaultPageSize?: PageSize;
95
+ onPageSizeChange?: (size: PageSize) => void;
96
+ currentPage?: number;
97
+ defaultCurrentPage?: number;
98
+ onCurrentPageChange?: (page: number) => void;
99
+ siblingCount?: number;
100
+ getPageHref?: GetPageHref;
101
+ linkComponent?: LinkComponent;
102
+ texts?: Partial<PaginationTexts>;
103
+ }
104
+
105
+ export function PaginationRoot({
106
+ totalItems,
107
+ pageSize: pageSizeProp,
108
+ defaultPageSize = PAGINATION_SIZES[0],
109
+ onPageSizeChange,
110
+ currentPage,
111
+ defaultCurrentPage = 1,
112
+ onCurrentPageChange,
113
+ siblingCount = 1,
114
+ getPageHref,
115
+ linkComponent = "a",
116
+ texts: textsProp,
23
117
  className,
24
- containerProps,
25
- ...rangeProps
26
- }: Props) => {
27
- const {
28
- paginationRange,
118
+ children,
119
+ ...props
120
+ }: PaginationRootProps) {
121
+ const [pageSize = defaultPageSize, setPageSize] =
122
+ useControllableState<PageSize>({
123
+ prop: pageSizeProp,
124
+ defaultProp: defaultPageSize,
125
+ onChange: onPageSizeChange,
126
+ });
127
+
128
+ const pagination = useRangePagination({
129
+ totalItems,
130
+ pageSize,
29
131
  currentPage,
30
- goTo,
31
- maxPage,
32
- next,
33
- prev,
34
- isLastPage,
35
- isFirstPage,
36
- } = useRangePagination(rangeProps);
132
+ defaultCurrentPage,
133
+ onCurrentPageChange,
134
+ siblingCount,
135
+ });
136
+
137
+ const texts = useMemo(
138
+ () => ({ ...DEFAULT_TEXTS, ...textsProp }),
139
+ [textsProp],
140
+ );
141
+
142
+ const value = useMemo<PaginationContextValue>(
143
+ () => ({
144
+ ...pagination,
145
+ pageSize,
146
+ totalItems,
147
+ setPageSize,
148
+ getPageHref,
149
+ linkComponent,
150
+ texts,
151
+ }),
152
+ [
153
+ pagination,
154
+ pageSize,
155
+ totalItems,
156
+ setPageSize,
157
+ getPageHref,
158
+ linkComponent,
159
+ texts,
160
+ ],
161
+ );
162
+
163
+ return (
164
+ <PaginationContext.Provider value={value}>
165
+ <nav
166
+ role="navigation"
167
+ aria-label="pagination"
168
+ data-slot="pagination"
169
+ className={cn("flex items-center gap-x-4", className)}
170
+ {...props}
171
+ >
172
+ {children}
173
+ </nav>
174
+ </PaginationContext.Provider>
175
+ );
176
+ }
177
+
178
+ type ButtonOverrides = Omit<
179
+ ComponentProps<typeof Button>,
180
+ "onClick" | "disabled" | "render" | "children" | "nativeButton"
181
+ >;
182
+
183
+ interface NavigableProps {
184
+ page: number;
185
+ isActive?: boolean;
186
+ disabled?: boolean;
187
+ ariaLabel: string;
188
+ dataSlot: string;
189
+ children: ReactNode;
190
+ className?: string;
191
+ buttonProps?: ButtonOverrides;
192
+ }
193
+
194
+ function NavigableButton({
195
+ page,
196
+ isActive,
197
+ disabled,
198
+ ariaLabel,
199
+ dataSlot,
200
+ children,
201
+ className,
202
+ buttonProps,
203
+ }: NavigableProps) {
204
+ const { goTo, getPageHref, pageSize, linkComponent } = usePaginationContext();
205
+ const Link = linkComponent;
206
+ const href = getPageHref?.({ page, pageSize: Number(pageSize) });
207
+ const useLink = Boolean(href) && !disabled;
208
+
209
+ const handleClick = () => {
210
+ if (disabled) return;
211
+ goTo(page);
212
+ };
213
+
214
+ const sharedProps = {
215
+ "data-slot": dataSlot,
216
+ "data-active": isActive ? "true" : "false",
217
+ "aria-current": isActive ? ("page" as const) : undefined,
218
+ "aria-label": ariaLabel,
219
+ className: cn("hover:bg-accent hover:text-accent-foreground", className),
220
+ };
221
+
222
+ if (useLink && href) {
223
+ return (
224
+ <Button
225
+ variant={isActive ? "outline" : "ghost"}
226
+ size="icon"
227
+ nativeButton={false}
228
+ render={
229
+ <Link href={href} onClick={handleClick}>
230
+ {children}
231
+ </Link>
232
+ }
233
+ {...sharedProps}
234
+ {...buttonProps}
235
+ />
236
+ );
237
+ }
238
+
239
+ return (
240
+ <Button
241
+ variant={isActive ? "outline" : "ghost"}
242
+ size="icon"
243
+ disabled={disabled}
244
+ onClick={handleClick}
245
+ {...sharedProps}
246
+ {...buttonProps}
247
+ >
248
+ {children}
249
+ </Button>
250
+ );
251
+ }
252
+
253
+ interface PaginationLinkProps {
254
+ page: number;
255
+ isActive?: boolean;
256
+ className?: string;
257
+ children?: ReactNode;
258
+ buttonProps?: ButtonOverrides;
259
+ }
260
+
261
+ export function PaginationLink({
262
+ page,
263
+ isActive: isActiveProp,
264
+ className,
265
+ children,
266
+ buttonProps,
267
+ }: PaginationLinkProps) {
268
+ const { currentPage, texts } = usePaginationContext();
269
+ const isActive = isActiveProp ?? page === currentPage;
37
270
 
271
+ return (
272
+ <NavigableButton
273
+ page={page}
274
+ isActive={isActive}
275
+ ariaLabel={texts.goToPage(page)}
276
+ dataSlot="pagination-link"
277
+ className={className}
278
+ buttonProps={buttonProps}
279
+ >
280
+ {children ?? page}
281
+ </NavigableButton>
282
+ );
283
+ }
284
+
285
+ interface PaginationNavProps {
286
+ className?: string;
287
+ buttonProps?: ButtonOverrides;
288
+ children?: ReactNode;
289
+ }
290
+
291
+ export function PaginationFirst({
292
+ className,
293
+ buttonProps,
294
+ children,
295
+ }: PaginationNavProps) {
296
+ const { isFirstPage, texts } = usePaginationContext();
297
+ return (
298
+ <NavigableButton
299
+ page={1}
300
+ disabled={isFirstPage}
301
+ ariaLabel={texts.goToFirst}
302
+ dataSlot="pagination-first"
303
+ className={className}
304
+ buttonProps={buttonProps}
305
+ >
306
+ {children ?? <ChevronsLeft data-slot="pagination-icon" />}
307
+ </NavigableButton>
308
+ );
309
+ }
310
+
311
+ export function PaginationPrevious({
312
+ className,
313
+ buttonProps,
314
+ children,
315
+ }: PaginationNavProps) {
316
+ const { currentPage, isFirstPage, texts } = usePaginationContext();
317
+ return (
318
+ <NavigableButton
319
+ page={Math.max(1, currentPage - 1)}
320
+ disabled={isFirstPage}
321
+ ariaLabel={texts.goToPrevious}
322
+ dataSlot="pagination-previous"
323
+ className={className}
324
+ buttonProps={buttonProps}
325
+ >
326
+ {children ?? <ChevronLeft data-slot="pagination-icon" />}
327
+ </NavigableButton>
328
+ );
329
+ }
330
+
331
+ export function PaginationNext({
332
+ className,
333
+ buttonProps,
334
+ children,
335
+ }: PaginationNavProps) {
336
+ const { currentPage, maxPage, isLastPage, texts } = usePaginationContext();
337
+ return (
338
+ <NavigableButton
339
+ page={Math.min(maxPage, currentPage + 1)}
340
+ disabled={isLastPage}
341
+ ariaLabel={texts.goToNext}
342
+ dataSlot="pagination-next"
343
+ className={className}
344
+ buttonProps={buttonProps}
345
+ >
346
+ {children ?? <ChevronRight data-slot="pagination-icon" />}
347
+ </NavigableButton>
348
+ );
349
+ }
350
+
351
+ export function PaginationLast({
352
+ className,
353
+ buttonProps,
354
+ children,
355
+ }: PaginationNavProps) {
356
+ const { maxPage, isLastPage, texts } = usePaginationContext();
357
+ return (
358
+ <NavigableButton
359
+ page={maxPage}
360
+ disabled={isLastPage}
361
+ ariaLabel={texts.goToLast}
362
+ dataSlot="pagination-last"
363
+ className={className}
364
+ buttonProps={buttonProps}
365
+ >
366
+ {children ?? <ChevronsRight data-slot="pagination-icon" />}
367
+ </NavigableButton>
368
+ );
369
+ }
370
+
371
+ export function PaginationEllipsis({
372
+ className,
373
+ ...props
374
+ }: ComponentProps<"span">) {
375
+ const { texts } = usePaginationContext();
376
+ return (
377
+ <span
378
+ aria-hidden
379
+ data-slot="pagination-ellipsis"
380
+ data-dots="true"
381
+ className={cn(
382
+ "flex size-8 items-center justify-center [&_svg:not([class*='size-'])]:size-4",
383
+ className,
384
+ )}
385
+ {...props}
386
+ >
387
+ <MoreHorizontal />
388
+ <span className="sr-only">{texts.morePages}</span>
389
+ </span>
390
+ );
391
+ }
392
+
393
+ interface PaginationPagesProps {
394
+ className?: string;
395
+ linkClassName?: string;
396
+ ellipsisClassName?: string;
397
+ }
398
+
399
+ export function PaginationPages({
400
+ className,
401
+ linkClassName,
402
+ ellipsisClassName,
403
+ }: PaginationPagesProps) {
404
+ const { paginationRange } = usePaginationContext();
38
405
  return (
39
406
  <div
40
- {...containerProps}
407
+ role="list"
408
+ data-slot="pagination-pages"
409
+ className={cn("flex items-center gap-1", className)}
410
+ >
411
+ {paginationRange.map((item, index) =>
412
+ item === DOTS ? (
413
+ <PaginationEllipsis
414
+ key={`dots-${index}`}
415
+ className={ellipsisClassName}
416
+ />
417
+ ) : (
418
+ <PaginationLink
419
+ key={item}
420
+ page={item as number}
421
+ className={linkClassName}
422
+ />
423
+ ),
424
+ )}
425
+ </div>
426
+ );
427
+ }
428
+
429
+ interface PaginationTotalProps {
430
+ className?: string;
431
+ /**
432
+ * Custom content. Receives the total number of items, or pass a static ReactNode.
433
+ * @default `${total} resultados`
434
+ */
435
+ children?: ReactNode | ((total: number) => ReactNode);
436
+ }
437
+
438
+ export function PaginationTotal({
439
+ children,
440
+ className,
441
+ ...props
442
+ }: PaginationTotalProps) {
443
+ const { totalItems, texts } = usePaginationContext();
444
+ const content =
445
+ typeof children === "function"
446
+ ? children(totalItems)
447
+ : (children ?? texts.total(totalItems));
448
+ return (
449
+ <span
450
+ data-slot="pagination-total"
451
+ className={cn("text-sm", className)}
452
+ {...props}
453
+ >
454
+ {content}
455
+ </span>
456
+ );
457
+ }
458
+
459
+ interface PaginationPageInfoProps {
460
+ className?: string;
461
+ /**
462
+ * Custom content. Receives `{ page, max }`, or pass a static ReactNode.
463
+ * @default `Página ${page} de ${max}`
464
+ */
465
+ children?: ReactNode | ((state: { page: number; max: number }) => ReactNode);
466
+ }
467
+
468
+ export function PaginationPageInfo({
469
+ children,
470
+ className,
471
+ ...props
472
+ }: PaginationPageInfoProps) {
473
+ const { currentPage, maxPage, texts } = usePaginationContext();
474
+ const content =
475
+ typeof children === "function"
476
+ ? children({ page: currentPage, max: maxPage })
477
+ : (children ?? texts.pageInfo(currentPage, maxPage));
478
+ return (
479
+ <span
480
+ data-slot="pagination-page-info"
481
+ className={cn("text-sm", className)}
482
+ {...props}
483
+ >
484
+ {content}
485
+ </span>
486
+ );
487
+ }
488
+
489
+ interface PaginationRangeProps {
490
+ className?: string;
491
+ /**
492
+ * Custom content. Receives `{ start, end, total }`, or pass a static ReactNode.
493
+ * @default `${start} - ${end} de ${total}`
494
+ */
495
+ children?:
496
+ | ReactNode
497
+ | ((state: { start: number; end: number; total: number }) => ReactNode);
498
+ }
499
+
500
+ export function PaginationRange({
501
+ children,
502
+ className,
503
+ ...props
504
+ }: PaginationRangeProps) {
505
+ const { range, totalItems, texts } = usePaginationContext();
506
+ const content =
507
+ typeof children === "function"
508
+ ? children({ ...range, total: totalItems })
509
+ : (children ?? texts.range(range.start, range.end, totalItems));
510
+ return (
511
+ <span
512
+ data-slot="pagination-range"
513
+ className={cn("text-sm", className)}
514
+ {...props}
515
+ >
516
+ {content}
517
+ </span>
518
+ );
519
+ }
520
+
521
+ interface PaginationSizeSelectProps
522
+ extends Omit<ComponentProps<"select">, "value" | "onChange"> {
523
+ sizes?: readonly PageSize[] | PageSize[];
524
+ }
525
+
526
+ export function PaginationSizeSelect({
527
+ sizes = PAGINATION_SIZES,
528
+ className,
529
+ ...props
530
+ }: PaginationSizeSelectProps) {
531
+ const { pageSize, setPageSize, goTo } = usePaginationContext();
532
+
533
+ const handleChange = (event: ChangeEvent<HTMLSelectElement>) => {
534
+ const next = Number(event.target.value) as PageSize;
535
+ setPageSize(next);
536
+ goTo(1);
537
+ };
538
+
539
+ return (
540
+ <select
541
+ data-slot="pagination-size-select"
542
+ value={pageSize}
543
+ onChange={handleChange}
41
544
  className={cn(
42
- "flex items-end select-none h-10",
43
- containerProps?.className,
545
+ "border border-input rounded-md w-fit min-w-[3rem] bg-background p-2 ring-offset-background",
546
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
44
547
  className,
45
548
  )}
46
- data-slot="pagination"
549
+ {...props}
47
550
  >
48
- <Option
49
- {...optionProps}
50
- disabled={isFirstPage}
51
- onClick={() => goTo(1)}
52
- className={cn("rounded-l-md", optionProps?.className)}
53
- >
54
- <ChevronsLeft data-slot="pagination-option-icon" />
55
- <span className="sr-only">Ir a primera página</span>
56
- </Option>
57
-
58
- <Option {...optionProps} onClick={prev} disabled={isFirstPage}>
59
- <ChevronLeft data-slot="pagination-option-icon" />
60
- <span className="sr-only">Ir a página anterior</span>
61
- </Option>
62
-
63
- {paginationRange.map((pageNumber) => (
64
- <>
65
- {pageNumber === DOTS ? (
66
- <Option
67
- key={pageNumber}
68
- className={cn("pointer-events-none", optionProps?.className)}
69
- >
70
- {DOTS}
71
- </Option>
72
- ) : (
73
- <Option
74
- {...optionProps}
75
- isActive={pageNumber === currentPage}
76
- onClick={() => goTo(pageNumber as number)}
77
- key={pageNumber}
78
- >
79
- {pageNumber}
80
- <span className="sr-only">Ir a la página {pageNumber}</span>
81
- </Option>
82
- )}
83
- </>
551
+ {sizes.map((size) => (
552
+ <option key={size} value={size} data-slot="pagination-size-option">
553
+ {size}
554
+ </option>
84
555
  ))}
556
+ </select>
557
+ );
558
+ }
85
559
 
86
- <Option {...optionProps} onClick={next} disabled={isLastPage}>
87
- <ChevronRight data-slot="pagination-option-icon" />
88
- <span className="sr-only">Ir a próxima página</span>
89
- </Option>
560
+ interface PaginationProps extends Omit<PaginationRootProps, "children"> {
561
+ sizes?: readonly PageSize[] | PageSize[] | false;
562
+ selectClassName?: string;
563
+ rangeClassName?: string;
564
+ previousClassName?: string;
565
+ nextClassName?: string;
566
+ }
90
567
 
91
- <Option
92
- {...optionProps}
93
- disabled={isLastPage}
94
- onClick={() => goTo(maxPage)}
95
- className={cn("rounded-r-md", optionProps?.className)}
96
- >
97
- <ChevronsRight data-slot="pagination-option-icon" />
98
- <span className="sr-only">Ir a última página</span>
99
- </Option>
100
- </div>
568
+ export function Pagination({
569
+ sizes = PAGINATION_SIZES,
570
+ selectClassName,
571
+ rangeClassName,
572
+ previousClassName,
573
+ nextClassName,
574
+ className,
575
+ ...rootProps
576
+ }: PaginationProps) {
577
+ return (
578
+ <PaginationRoot className={className} {...rootProps}>
579
+ {sizes && (
580
+ <PaginationSizeSelect
581
+ sizes={sizes as PageSize[]}
582
+ className={selectClassName}
583
+ />
584
+ )}
585
+ <PaginationRange className={rangeClassName} />
586
+ <PaginationPrevious className={previousClassName} />
587
+ <PaginationNext className={nextClassName} />
588
+ </PaginationRoot>
101
589
  );
590
+ }
591
+
592
+ export type {
593
+ GetPageHref,
594
+ LinkComponent,
595
+ LinkRenderProps,
596
+ PaginationContextValue,
597
+ PaginationProps,
598
+ PaginationRootProps,
599
+ PaginationTexts,
102
600
  };