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

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 (174) hide show
  1. package/dist/index.cjs.js +1 -138
  2. package/dist/index.d.ts +1083 -717
  3. package/dist/index.es.js +7059 -56179
  4. package/dist/theme.css +1 -1
  5. package/package.json +34 -26
  6. package/src/__doc__/Changelog.mdx +6 -6
  7. package/src/__doc__/Examples.tsx +1 -1
  8. package/src/__doc__/Intro.mdx +3 -3
  9. package/src/__doc__/Tabs.mdx +112 -0
  10. package/src/__doc__/V2.mdx +1245 -0
  11. package/src/components/accordion/accordion.stories.tsx +143 -0
  12. package/src/components/accordion/accordion.tsx +135 -0
  13. package/src/components/accordion/index.ts +1 -0
  14. package/src/components/alert/alert.stories.tsx +24 -4
  15. package/src/components/alert/alert.tsx +17 -9
  16. package/src/components/alert-dialog/alert-dialog.stories.tsx +24 -0
  17. package/src/components/alert-dialog/alert-dialog.test.tsx +1 -1
  18. package/src/components/alert-dialog/alert-dialog.tsx +58 -10
  19. package/src/components/auto-complete/auto-complete.stories.tsx +615 -200
  20. package/src/components/auto-complete/auto-complete.tsx +420 -68
  21. package/src/components/auto-complete/index.ts +0 -1
  22. package/src/components/avatar/avatar.stories.tsx +162 -21
  23. package/src/components/avatar/avatar.tsx +79 -20
  24. package/src/components/button/button.stories.tsx +236 -294
  25. package/src/components/button/button.test.tsx +10 -17
  26. package/src/components/button/button.tsx +53 -18
  27. package/src/components/button/components/base-button.tsx +25 -53
  28. package/src/components/button/index.ts +0 -1
  29. package/src/components/calendar/calendar.stories.tsx +1 -1
  30. package/src/components/calendar/calendar.tsx +4 -4
  31. package/src/components/card/card.stories.tsx +140 -69
  32. package/src/components/card/card.tsx +155 -54
  33. package/src/components/center/center.stories.tsx +22 -39
  34. package/src/components/checkbox/checkbox.stories.tsx +25 -5
  35. package/src/components/checkbox/checkbox.tsx +76 -15
  36. package/src/components/checkbox-group/checkbox-group.stories.tsx +116 -28
  37. package/src/components/checkbox-group/checkbox-group.tsx +84 -3
  38. package/src/components/combobox/combobox.stories.tsx +33 -23
  39. package/src/components/combobox/combobox.tsx +120 -104
  40. package/src/components/date-picker/date-input.stories.tsx +14 -6
  41. package/src/components/date-picker/date-input.tsx +3 -3
  42. package/src/components/date-picker/date-picker.model.ts +13 -4
  43. package/src/components/date-picker/date-picker.stories.tsx +38 -12
  44. package/src/components/date-picker/date-picker.tsx +29 -15
  45. package/src/components/dialog/dialog.stories.tsx +18 -0
  46. package/src/components/dialog/dialog.test.tsx +1 -1
  47. package/src/components/dialog/dialog.tsx +51 -20
  48. package/src/components/divider/divider.stories.tsx +6 -0
  49. package/src/components/dropzone/dropzone.stories.tsx +70 -90
  50. package/src/components/dropzone/dropzone.tsx +383 -105
  51. package/src/components/dropzone/index.ts +0 -1
  52. package/src/components/empty/empty.stories.tsx +164 -0
  53. package/src/components/empty/empty.tsx +156 -0
  54. package/src/components/empty/index.ts +1 -0
  55. package/src/components/field/field.stories.tsx +226 -3
  56. package/src/components/field/field.tsx +77 -42
  57. package/src/components/form/form.stories.tsx +320 -197
  58. package/src/components/form/form.tsx +3 -23
  59. package/src/components/index.ts +2 -6
  60. package/src/components/input/input.stories.tsx +5 -5
  61. package/src/components/input/input.tsx +5 -5
  62. package/src/components/kbd/kbd.stories.tsx +1 -0
  63. package/src/components/label/label.stories.tsx +16 -0
  64. package/src/components/label/label.tsx +13 -2
  65. package/src/components/loader/loader.stories.tsx +7 -5
  66. package/src/components/loader/loader.tsx +8 -3
  67. package/src/components/menu/menu-primitives.tsx +207 -196
  68. package/src/components/menu/menu.stories.tsx +275 -146
  69. package/src/components/menu/menu.tsx +146 -54
  70. package/src/components/number-input/number-input.stories.tsx +27 -4
  71. package/src/components/number-input/number-input.test.tsx +2 -2
  72. package/src/components/number-input/number-input.tsx +29 -33
  73. package/src/components/otp/index.ts +1 -0
  74. package/src/components/otp/otp.stories.tsx +209 -0
  75. package/src/components/otp/otp.tsx +100 -0
  76. package/src/components/pagination/index.ts +1 -0
  77. package/src/components/pagination/pagination.model.ts +2 -0
  78. package/src/components/pagination/pagination.stories.tsx +153 -59
  79. package/src/components/pagination/pagination.test.tsx +122 -57
  80. package/src/components/pagination/pagination.tsx +575 -77
  81. package/src/components/password/password.stories.tsx +18 -3
  82. package/src/components/password/password.tsx +26 -10
  83. package/src/components/popover/popover.stories.tsx +26 -5
  84. package/src/components/popover/popover.tsx +15 -23
  85. package/src/components/progress/progress.stories.tsx +1 -0
  86. package/src/components/radio-group/index.ts +1 -0
  87. package/src/components/radio-group/radio-group.stories.tsx +251 -0
  88. package/src/components/radio-group/radio-group.tsx +212 -0
  89. package/src/components/scroll-area/scroll-area.stories.tsx +1 -0
  90. package/src/components/select/select.stories.tsx +118 -19
  91. package/src/components/select/select.tsx +67 -62
  92. package/src/components/skeleton/skeleton.stories.tsx +1 -0
  93. package/src/components/stack/stack.stories.tsx +179 -89
  94. package/src/components/stack/stack.tsx +2 -2
  95. package/src/components/stepper/index.ts +1 -1
  96. package/src/components/stepper/stepper.stories.tsx +766 -83
  97. package/src/components/stepper/stepper.test.tsx +18 -18
  98. package/src/components/stepper/stepper.tsx +554 -0
  99. package/src/components/switch/switch.stories.tsx +15 -1
  100. package/src/components/switch/switch.tsx +17 -4
  101. package/src/components/table/index.ts +0 -2
  102. package/src/components/table/table.stories.tsx +131 -18
  103. package/src/components/table/table.test.tsx +1 -1
  104. package/src/components/table/table.tsx +183 -77
  105. package/src/components/tabs/tabs.stories.tsx +372 -155
  106. package/src/components/tabs/tabs.test.tsx +12 -12
  107. package/src/components/tabs/tabs.tsx +72 -149
  108. package/src/components/tag/index.ts +0 -1
  109. package/src/components/tag/tag.stories.tsx +147 -120
  110. package/src/components/tag/tag.tsx +47 -95
  111. package/src/components/textarea/textarea.stories.tsx +8 -22
  112. package/src/components/textarea/textarea.tsx +17 -79
  113. package/src/components/timeline/timeline.stories.tsx +322 -42
  114. package/src/components/timeline/timeline.tsx +359 -132
  115. package/src/components/toast/toast.stories.tsx +1 -0
  116. package/src/components/tooltip/tooltip.tsx +11 -9
  117. package/src/components/tree/index.ts +0 -1
  118. package/src/components/tree/tree.stories.tsx +364 -408
  119. package/src/components/tree/tree.test.tsx +163 -0
  120. package/src/components/tree/tree.tsx +212 -36
  121. package/src/hooks/useAsync/__doc__/useAsync.stories.tsx +5 -5
  122. package/src/hooks/useClipboard/__doc__/useClipboard.stories.tsx +1 -3
  123. package/src/hooks/useDebounceCallback/__doc__/useDebouncedCallback.stories.tsx +6 -6
  124. package/src/hooks/useDocumentTitle/__doc__/useDocumentTitle.stories.tsx +1 -1
  125. package/src/hooks/useEventListener/__test__/useEventListener.test.tsx +1 -1
  126. package/src/hooks/useLocalStorage/__doc__/useLocalStorage.stories.tsx +1 -1
  127. package/src/hooks/usePagination/usePagination.tsx +36 -24
  128. package/src/styles/theme.css +1 -1
  129. package/src/utils/form.tsx +69 -37
  130. package/src/utils/index.ts +1 -1
  131. package/src/__doc__/Migration.mdx +0 -451
  132. package/src/components/auto-complete/auto-complete-primitives.tsx +0 -155
  133. package/src/components/background-image/background-image.stories.tsx +0 -21
  134. package/src/components/background-image/background-image.test.tsx +0 -29
  135. package/src/components/background-image/background-image.tsx +0 -23
  136. package/src/components/background-image/index.ts +0 -1
  137. package/src/components/button/button.variants.ts +0 -44
  138. package/src/components/button/components/loader-overlay.tsx +0 -21
  139. package/src/components/button/components/loading-icon.tsx +0 -47
  140. package/src/components/dropzone/upload-primitives.tsx +0 -310
  141. package/src/components/dropzone/use-dropzone.ts +0 -122
  142. package/src/components/empty-state/empty-state.stories.tsx +0 -56
  143. package/src/components/empty-state/empty-state.tsx +0 -39
  144. package/src/components/empty-state/index.ts +0 -1
  145. package/src/components/heading/heading.stories.tsx +0 -74
  146. package/src/components/heading/heading.tsx +0 -28
  147. package/src/components/heading/heading.variants.ts +0 -27
  148. package/src/components/heading/index.ts +0 -1
  149. package/src/components/kbd/kbd.variants.ts +0 -26
  150. package/src/components/menu/util/render-menu-item.tsx +0 -54
  151. package/src/components/multi-select/hooks/use-multi-select.ts +0 -66
  152. package/src/components/multi-select/index.ts +0 -1
  153. package/src/components/multi-select/multi-select.stories.tsx +0 -294
  154. package/src/components/multi-select/multi-select.tsx +0 -300
  155. package/src/components/multi-select/multi-select.variants.ts +0 -22
  156. package/src/components/pagination/components/pagination-option.tsx +0 -27
  157. package/src/components/show/index.ts +0 -1
  158. package/src/components/show/show.stories.tsx +0 -197
  159. package/src/components/show/show.test.tsx +0 -41
  160. package/src/components/show/show.tsx +0 -16
  161. package/src/components/stepper/Stepper.tsx +0 -190
  162. package/src/components/stepper/context/stepper-context.tsx +0 -11
  163. package/src/components/table/table-primitives.tsx +0 -122
  164. package/src/components/table/table.model.ts +0 -20
  165. package/src/components/table-pagination/index.ts +0 -2
  166. package/src/components/table-pagination/table-pagination.model.ts +0 -2
  167. package/src/components/table-pagination/table-pagination.stories.tsx +0 -23
  168. package/src/components/table-pagination/table-pagination.test.tsx +0 -32
  169. package/src/components/table-pagination/table-pagination.tsx +0 -108
  170. package/src/components/tabs/context/tabs-context.tsx +0 -14
  171. package/src/components/tag/tag.variants.ts +0 -31
  172. package/src/components/timeline/timeline-status.ts +0 -5
  173. package/src/components/tree/hooks/use-controllable-tree-state.ts +0 -80
  174. 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
  };