azamat-ui-kit-cli 0.2.2

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 (213) hide show
  1. package/README.md +8 -0
  2. package/dist/index.js +432 -0
  3. package/package.json +34 -0
  4. package/vendor/package.json +4 -0
  5. package/vendor/src/components/actions/action-bar.tsx +35 -0
  6. package/vendor/src/components/actions/action-menu.tsx +120 -0
  7. package/vendor/src/components/actions/button-group.tsx +47 -0
  8. package/vendor/src/components/actions/copy-button.tsx +91 -0
  9. package/vendor/src/components/actions/copy-field.tsx +31 -0
  10. package/vendor/src/components/actions/floating-action-button.tsx +33 -0
  11. package/vendor/src/components/actions/index.ts +7 -0
  12. package/vendor/src/components/actions/public.ts +5 -0
  13. package/vendor/src/components/actions/quick-action-grid.tsx +162 -0
  14. package/vendor/src/components/calendar/calendar.tsx +328 -0
  15. package/vendor/src/components/calendar/date-picker.tsx +78 -0
  16. package/vendor/src/components/calendar/date-range-picker.tsx +96 -0
  17. package/vendor/src/components/calendar/date-utils.ts +89 -0
  18. package/vendor/src/components/calendar/index.ts +4 -0
  19. package/vendor/src/components/charts/charts.tsx +275 -0
  20. package/vendor/src/components/charts/horizontal-bar-chart.tsx +46 -0
  21. package/vendor/src/components/charts/index.ts +4 -0
  22. package/vendor/src/components/charts/kpi.tsx +68 -0
  23. package/vendor/src/components/charts/progress-ring.tsx +45 -0
  24. package/vendor/src/components/charts/public.ts +1 -0
  25. package/vendor/src/components/command/command-palette.tsx +375 -0
  26. package/vendor/src/components/command/index.ts +1 -0
  27. package/vendor/src/components/data-table/data-table-actions-column.tsx +58 -0
  28. package/vendor/src/components/data-table/data-table-bulk-actions.tsx +84 -0
  29. package/vendor/src/components/data-table/data-table-column-visibility-menu.tsx +79 -0
  30. package/vendor/src/components/data-table/data-table-pagination.tsx +91 -0
  31. package/vendor/src/components/data-table/data-table-row-actions.tsx +48 -0
  32. package/vendor/src/components/data-table/data-table-select-column.tsx +59 -0
  33. package/vendor/src/components/data-table/data-table-sortable-header.tsx +45 -0
  34. package/vendor/src/components/data-table/data-table-toolbar.tsx +76 -0
  35. package/vendor/src/components/data-table/data-table-view-presets.tsx +128 -0
  36. package/vendor/src/components/data-table/data-table.tsx +507 -0
  37. package/vendor/src/components/data-table/index.ts +12 -0
  38. package/vendor/src/components/data-table/public.ts +10 -0
  39. package/vendor/src/components/data-table/table-export-menu.tsx +56 -0
  40. package/vendor/src/components/data-table/table-import-button.tsx +43 -0
  41. package/vendor/src/components/display/activity-feed.tsx +97 -0
  42. package/vendor/src/components/display/avatar.tsx +131 -0
  43. package/vendor/src/components/display/code-block.tsx +33 -0
  44. package/vendor/src/components/display/data-state.tsx +63 -0
  45. package/vendor/src/components/display/description-list.tsx +119 -0
  46. package/vendor/src/components/display/descriptions.tsx +83 -0
  47. package/vendor/src/components/display/entity-card.tsx +53 -0
  48. package/vendor/src/components/display/file-card.tsx +54 -0
  49. package/vendor/src/components/display/index.ts +30 -0
  50. package/vendor/src/components/display/kanban.tsx +104 -0
  51. package/vendor/src/components/display/keyboard-shortcut.tsx +31 -0
  52. package/vendor/src/components/display/list.tsx +100 -0
  53. package/vendor/src/components/display/metric-grid.tsx +86 -0
  54. package/vendor/src/components/display/progress.tsx +162 -0
  55. package/vendor/src/components/display/property-grid.tsx +54 -0
  56. package/vendor/src/components/display/result.tsx +90 -0
  57. package/vendor/src/components/display/smart-card.tsx +168 -0
  58. package/vendor/src/components/display/statistic.tsx +107 -0
  59. package/vendor/src/components/display/status-legend.tsx +108 -0
  60. package/vendor/src/components/display/tag-list.tsx +52 -0
  61. package/vendor/src/components/display/timeline.tsx +132 -0
  62. package/vendor/src/components/display/tree-view.tsx +116 -0
  63. package/vendor/src/components/feedback/alert.tsx +69 -0
  64. package/vendor/src/components/feedback/empty-state.tsx +56 -0
  65. package/vendor/src/components/feedback/index.ts +5 -0
  66. package/vendor/src/components/feedback/loading-state.tsx +39 -0
  67. package/vendor/src/components/feedback/page-state.tsx +69 -0
  68. package/vendor/src/components/feedback/status-badge.tsx +62 -0
  69. package/vendor/src/components/filters/filter-bar.tsx +89 -0
  70. package/vendor/src/components/filters/filter-chips.tsx +69 -0
  71. package/vendor/src/components/filters/index.ts +2 -0
  72. package/vendor/src/components/form/form-actions.tsx +53 -0
  73. package/vendor/src/components/form/form-async-select.tsx +26 -0
  74. package/vendor/src/components/form/form-date-input.tsx +19 -0
  75. package/vendor/src/components/form/form-date-picker.tsx +54 -0
  76. package/vendor/src/components/form/form-date-range-input.tsx +79 -0
  77. package/vendor/src/components/form/form-date-range-picker.tsx +57 -0
  78. package/vendor/src/components/form/form-field-shell.tsx +191 -0
  79. package/vendor/src/components/form/form-input.tsx +480 -0
  80. package/vendor/src/components/form/form-number-input.tsx +19 -0
  81. package/vendor/src/components/form/form-password-input.tsx +19 -0
  82. package/vendor/src/components/form/form-phone-input.tsx +22 -0
  83. package/vendor/src/components/form/form-search-input.tsx +19 -0
  84. package/vendor/src/components/form/form-section.tsx +29 -0
  85. package/vendor/src/components/form/form-select.tsx +194 -0
  86. package/vendor/src/components/form/form-switch.tsx +145 -0
  87. package/vendor/src/components/form/form-textarea.tsx +103 -0
  88. package/vendor/src/components/form/index.ts +17 -0
  89. package/vendor/src/components/form/public.ts +14 -0
  90. package/vendor/src/components/form/smart-form-shell.tsx +59 -0
  91. package/vendor/src/components/inputs/async-select.tsx +1143 -0
  92. package/vendor/src/components/inputs/clearable-input.tsx +78 -0
  93. package/vendor/src/components/inputs/color-input.tsx +47 -0
  94. package/vendor/src/components/inputs/combobox.tsx +89 -0
  95. package/vendor/src/components/inputs/date-input.tsx +32 -0
  96. package/vendor/src/components/inputs/date-range-input.tsx +67 -0
  97. package/vendor/src/components/inputs/index.ts +19 -0
  98. package/vendor/src/components/inputs/input-chrome.tsx +37 -0
  99. package/vendor/src/components/inputs/input-decorator.tsx +64 -0
  100. package/vendor/src/components/inputs/input-value.ts +42 -0
  101. package/vendor/src/components/inputs/masked-input.tsx +51 -0
  102. package/vendor/src/components/inputs/money-input.tsx +73 -0
  103. package/vendor/src/components/inputs/number-input.tsx +87 -0
  104. package/vendor/src/components/inputs/numeric-value.ts +39 -0
  105. package/vendor/src/components/inputs/otp-input.tsx +102 -0
  106. package/vendor/src/components/inputs/password-input.tsx +85 -0
  107. package/vendor/src/components/inputs/phone-input.tsx +46 -0
  108. package/vendor/src/components/inputs/quantity-input.tsx +116 -0
  109. package/vendor/src/components/inputs/quantity-stepper.tsx +49 -0
  110. package/vendor/src/components/inputs/rating.tsx +98 -0
  111. package/vendor/src/components/inputs/search-input.tsx +26 -0
  112. package/vendor/src/components/inputs/simple-select.tsx +72 -0
  113. package/vendor/src/components/inputs/slider.tsx +149 -0
  114. package/vendor/src/components/inputs/tag-input.tsx +104 -0
  115. package/vendor/src/components/layout/app-header.tsx +46 -0
  116. package/vendor/src/components/layout/app-shell.tsx +243 -0
  117. package/vendor/src/components/layout/app-sidebar.tsx +179 -0
  118. package/vendor/src/components/layout/breadcrumbs.tsx +72 -0
  119. package/vendor/src/components/layout/index.ts +11 -0
  120. package/vendor/src/components/layout/page-container.tsx +30 -0
  121. package/vendor/src/components/layout/page-header.tsx +60 -0
  122. package/vendor/src/components/layout/public.ts +10 -0
  123. package/vendor/src/components/layout/section.tsx +76 -0
  124. package/vendor/src/components/layout/sidebar-nav.tsx +147 -0
  125. package/vendor/src/components/layout/stat-card.tsx +88 -0
  126. package/vendor/src/components/layout/sticky-footer-bar.tsx +23 -0
  127. package/vendor/src/components/layout/workspace-shell.tsx +50 -0
  128. package/vendor/src/components/navigation/anchor-nav.tsx +44 -0
  129. package/vendor/src/components/navigation/index.ts +4 -0
  130. package/vendor/src/components/navigation/page-tabs.tsx +67 -0
  131. package/vendor/src/components/navigation/pagination.tsx +179 -0
  132. package/vendor/src/components/navigation/stepper-tabs.tsx +67 -0
  133. package/vendor/src/components/notifications/index.ts +1 -0
  134. package/vendor/src/components/notifications/toast.tsx +259 -0
  135. package/vendor/src/components/overlay/confirm-dialog.tsx +66 -0
  136. package/vendor/src/components/overlay/dialog-actions.tsx +68 -0
  137. package/vendor/src/components/overlay/index.ts +4 -0
  138. package/vendor/src/components/overlay/modal-shell.tsx +93 -0
  139. package/vendor/src/components/overlay/sheet-shell.tsx +212 -0
  140. package/vendor/src/components/patterns/action-system.tsx +116 -0
  141. package/vendor/src/components/patterns/crud-system.tsx +53 -0
  142. package/vendor/src/components/patterns/data-view.tsx +84 -0
  143. package/vendor/src/components/patterns/entity-details.tsx +66 -0
  144. package/vendor/src/components/patterns/filter-builder.tsx +113 -0
  145. package/vendor/src/components/patterns/form-builder-presets.ts +131 -0
  146. package/vendor/src/components/patterns/form-builder.tsx +334 -0
  147. package/vendor/src/components/patterns/index.ts +12 -0
  148. package/vendor/src/components/patterns/public.ts +4 -0
  149. package/vendor/src/components/patterns/resource-detail-page.tsx +160 -0
  150. package/vendor/src/components/patterns/resource-page.tsx +159 -0
  151. package/vendor/src/components/patterns/resource-system.tsx +61 -0
  152. package/vendor/src/components/patterns/settings-section.tsx +46 -0
  153. package/vendor/src/components/patterns/status-system.tsx +89 -0
  154. package/vendor/src/components/theme-provider.tsx +51 -0
  155. package/vendor/src/components/ui/badge.tsx +52 -0
  156. package/vendor/src/components/ui/button.tsx +61 -0
  157. package/vendor/src/components/ui/card.tsx +103 -0
  158. package/vendor/src/components/ui/checkbox.tsx +82 -0
  159. package/vendor/src/components/ui/collapse.tsx +126 -0
  160. package/vendor/src/components/ui/command.tsx +194 -0
  161. package/vendor/src/components/ui/dialog.tsx +160 -0
  162. package/vendor/src/components/ui/divider.tsx +46 -0
  163. package/vendor/src/components/ui/dropdown-menu.tsx +266 -0
  164. package/vendor/src/components/ui/input-group.tsx +158 -0
  165. package/vendor/src/components/ui/input.tsx +20 -0
  166. package/vendor/src/components/ui/popover.tsx +90 -0
  167. package/vendor/src/components/ui/segmented-control.tsx +78 -0
  168. package/vendor/src/components/ui/select.tsx +201 -0
  169. package/vendor/src/components/ui/skeleton.tsx +75 -0
  170. package/vendor/src/components/ui/spinner.tsx +50 -0
  171. package/vendor/src/components/ui/switch.tsx +71 -0
  172. package/vendor/src/components/ui/table.tsx +114 -0
  173. package/vendor/src/components/ui/tabs.tsx +55 -0
  174. package/vendor/src/components/ui/textarea.tsx +18 -0
  175. package/vendor/src/components/ui/tooltip.tsx +38 -0
  176. package/vendor/src/components/upload/file-upload.tsx +483 -0
  177. package/vendor/src/components/upload/image-upload.tsx +118 -0
  178. package/vendor/src/components/upload/index.ts +2 -0
  179. package/vendor/src/components/wizard/index.ts +2 -0
  180. package/vendor/src/components/wizard/stepper.tsx +53 -0
  181. package/vendor/src/components/wizard/wizard.tsx +60 -0
  182. package/vendor/src/families/card-family.ts +28 -0
  183. package/vendor/src/families/catalog.ts +96 -0
  184. package/vendor/src/families/data-table-family.ts +31 -0
  185. package/vendor/src/families/docs-adoption.ts +103 -0
  186. package/vendor/src/families/docs-groups.ts +209 -0
  187. package/vendor/src/families/docs-queries.ts +84 -0
  188. package/vendor/src/families/docs-routing.ts +89 -0
  189. package/vendor/src/families/form-family.ts +45 -0
  190. package/vendor/src/families/index.ts +17 -0
  191. package/vendor/src/families/input-family.ts +61 -0
  192. package/vendor/src/families/member-metadata.ts +466 -0
  193. package/vendor/src/families/member-queries.ts +28 -0
  194. package/vendor/src/families/member-snippet-queries.ts +54 -0
  195. package/vendor/src/families/member-snippets.ts +673 -0
  196. package/vendor/src/families/migration-map.ts +79 -0
  197. package/vendor/src/families/queries.ts +63 -0
  198. package/vendor/src/families/select-family.ts +33 -0
  199. package/vendor/src/families/views.ts +81 -0
  200. package/vendor/src/hooks/index.ts +6 -0
  201. package/vendor/src/hooks/use-before-unload-when-dirty.ts +21 -0
  202. package/vendor/src/hooks/use-data-table-view-state.ts +122 -0
  203. package/vendor/src/hooks/use-debounce.ts +52 -0
  204. package/vendor/src/hooks/use-disclosure.ts +38 -0
  205. package/vendor/src/hooks/use-is-mobile.ts +28 -0
  206. package/vendor/src/hooks/use-session-storage-state.ts +85 -0
  207. package/vendor/src/index.ts +38 -0
  208. package/vendor/src/lib/utils.ts +6 -0
  209. package/vendor/templates/components/button.tsx +0 -0
  210. package/vendor/templates/components/data-table.tsx +0 -0
  211. package/vendor/templates/components/input.tsx +0 -0
  212. package/vendor/templates/lib/utils.ts +0 -0
  213. package/vendor/templates/styles/globals.css +0 -0
@@ -0,0 +1,23 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ export type StickyFooterBarProps = React.ComponentProps<"div"> & {
6
+ start?: React.ReactNode
7
+ end?: React.ReactNode
8
+ }
9
+
10
+ function StickyFooterBar({ start, end, className, children, ...props }: StickyFooterBarProps) {
11
+ return (
12
+ <div
13
+ data-slot="sticky-footer-bar"
14
+ className={cn("sticky bottom-0 z-30 flex flex-wrap items-center justify-between gap-3 border-t bg-background px-4 py-3 shadow-lg", className)}
15
+ {...props}
16
+ >
17
+ <div className="min-w-0 flex-1">{start ?? children}</div>
18
+ {end && <div className="flex shrink-0 items-center gap-2">{end}</div>}
19
+ </div>
20
+ )
21
+ }
22
+
23
+ export { StickyFooterBar }
@@ -0,0 +1,50 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ export type WorkspaceShellProps = React.ComponentProps<"div"> & {
6
+ sidebar?: React.ReactNode
7
+ header?: React.ReactNode
8
+ footer?: React.ReactNode
9
+ breadcrumbs?: React.ReactNode
10
+ title?: React.ReactNode
11
+ description?: React.ReactNode
12
+ actions?: React.ReactNode
13
+ aside?: React.ReactNode
14
+ sidebarWidth?: string
15
+ asideWidth?: string
16
+ contentClassName?: string
17
+ mainClassName?: string
18
+ }
19
+
20
+ function WorkspaceShell({ sidebar, header, footer, breadcrumbs, title, description, actions, aside, sidebarWidth = "280px", asideWidth = "360px", contentClassName, mainClassName, className, children, ...props }: WorkspaceShellProps) {
21
+ return (
22
+ <div data-slot="workspace-shell" className={cn("min-h-screen bg-background", className)} {...props}>
23
+ {header}
24
+ <div className="grid min-h-screen" style={{ gridTemplateColumns: sidebar ? `${sidebarWidth} minmax(0, 1fr)` : "minmax(0, 1fr)" }}>
25
+ {sidebar && <aside data-slot="workspace-shell-sidebar" className="border-r bg-card">{sidebar}</aside>}
26
+ <div className={cn("grid min-w-0 grid-rows-[auto_minmax(0,1fr)_auto]", contentClassName)}>
27
+ {(breadcrumbs || title || description || actions) && (
28
+ <div data-slot="workspace-shell-page-header" className="border-b bg-background px-6 py-4">
29
+ {breadcrumbs && <div className="mb-2">{breadcrumbs}</div>}
30
+ <div className="flex flex-wrap items-start justify-between gap-3">
31
+ <div className="grid gap-1">
32
+ {title && <h1 className="text-2xl font-semibold tracking-tight text-foreground">{title}</h1>}
33
+ {description && <p className="text-sm text-muted-foreground">{description}</p>}
34
+ </div>
35
+ {actions && <div className="flex shrink-0 items-center gap-2">{actions}</div>}
36
+ </div>
37
+ </div>
38
+ )}
39
+ <main data-slot="workspace-shell-main" className={cn("grid min-h-0 gap-4 p-6", mainClassName)} style={{ gridTemplateColumns: aside ? `minmax(0, 1fr) ${asideWidth}` : undefined }}>
40
+ <div className="min-w-0">{children}</div>
41
+ {aside && <aside data-slot="workspace-shell-aside" className="min-w-0">{aside}</aside>}
42
+ </main>
43
+ {footer}
44
+ </div>
45
+ </div>
46
+ </div>
47
+ )
48
+ }
49
+
50
+ export { WorkspaceShell }
@@ -0,0 +1,44 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ export type AnchorNavItem = {
6
+ key: string
7
+ label: React.ReactNode
8
+ href: string
9
+ active?: boolean
10
+ disabled?: boolean
11
+ }
12
+
13
+ export type AnchorNavProps = React.ComponentProps<"nav"> & {
14
+ items: AnchorNavItem[]
15
+ orientation?: "vertical" | "horizontal"
16
+ title?: React.ReactNode
17
+ }
18
+
19
+ function AnchorNav({ items, orientation = "vertical", title, className, ...props }: AnchorNavProps) {
20
+ return (
21
+ <nav data-slot="anchor-nav" className={cn("grid gap-2 text-sm", orientation === "horizontal" && "flex flex-wrap", className)} {...props}>
22
+ {title && <div className="text-xs font-medium uppercase tracking-wide text-muted-foreground">{title}</div>}
23
+ <div className={cn("grid gap-1", orientation === "horizontal" && "flex flex-wrap")}>
24
+ {items.map((item) => (
25
+ <a
26
+ key={item.key}
27
+ href={item.disabled ? undefined : item.href}
28
+ aria-current={item.active ? "location" : undefined}
29
+ aria-disabled={item.disabled || undefined}
30
+ className={cn(
31
+ "rounded-md px-2 py-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
32
+ item.active && "bg-muted text-foreground",
33
+ item.disabled && "pointer-events-none opacity-50"
34
+ )}
35
+ >
36
+ {item.label}
37
+ </a>
38
+ ))}
39
+ </div>
40
+ </nav>
41
+ )
42
+ }
43
+
44
+ export { AnchorNav }
@@ -0,0 +1,4 @@
1
+ export * from './pagination'
2
+ export * from './page-tabs'
3
+ export * from './stepper-tabs'
4
+ export * from './anchor-nav'
@@ -0,0 +1,67 @@
1
+ import * as React from "react"
2
+
3
+ import { Badge } from "@/components/ui/badge"
4
+ import { cn } from "@/lib/utils"
5
+
6
+ export type PageTabItem<TValue extends string = string> = {
7
+ value: TValue
8
+ label: React.ReactNode
9
+ icon?: React.ReactNode
10
+ badge?: React.ReactNode
11
+ disabled?: boolean
12
+ hidden?: boolean
13
+ }
14
+
15
+ export type PageTabsProps<TValue extends string = string> = React.ComponentProps<"div"> & {
16
+ value?: TValue
17
+ items: PageTabItem<TValue>[]
18
+ onValueChange?: (value: TValue, item: PageTabItem<TValue>) => void
19
+ variant?: "underline" | "pills" | "cards"
20
+ size?: "sm" | "default"
21
+ }
22
+
23
+ function PageTabs<TValue extends string = string>({
24
+ value,
25
+ items,
26
+ onValueChange,
27
+ variant = "underline",
28
+ size = "default",
29
+ className,
30
+ ...props
31
+ }: PageTabsProps<TValue>) {
32
+ const visibleItems = items.filter((item) => !item.hidden)
33
+
34
+ return (
35
+ <div data-slot="page-tabs" data-variant={variant} className={cn("flex min-w-0 flex-wrap gap-1 border-b", variant !== "underline" && "border-b-0", className)} {...props}>
36
+ {visibleItems.map((item) => {
37
+ const active = item.value === value
38
+ return (
39
+ <button
40
+ key={item.value}
41
+ type="button"
42
+ disabled={item.disabled}
43
+ data-slot="page-tab"
44
+ data-active={active || undefined}
45
+ className={cn(
46
+ "inline-flex min-w-0 items-center gap-2 rounded-md px-3 text-sm font-medium transition-colors disabled:pointer-events-none disabled:opacity-50 [&_svg]:size-4",
47
+ size === "sm" ? "h-8" : "h-10",
48
+ variant === "underline" && "rounded-none border-b-2 border-transparent text-muted-foreground hover:text-foreground",
49
+ variant === "underline" && active && "border-primary text-foreground",
50
+ variant === "pills" && "bg-muted/35 text-muted-foreground hover:bg-muted hover:text-foreground",
51
+ variant === "pills" && active && "bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground",
52
+ variant === "cards" && "border bg-card text-muted-foreground shadow-sm hover:border-primary/40 hover:text-foreground",
53
+ variant === "cards" && active && "border-primary/60 text-foreground"
54
+ )}
55
+ onClick={() => onValueChange?.(item.value, item)}
56
+ >
57
+ {item.icon}
58
+ <span className="truncate">{item.label}</span>
59
+ {item.badge !== undefined && <Badge variant={active && variant === "pills" ? "secondary" : "outline"}>{item.badge}</Badge>}
60
+ </button>
61
+ )
62
+ })}
63
+ </div>
64
+ )
65
+ }
66
+
67
+ export { PageTabs }
@@ -0,0 +1,179 @@
1
+ import * as React from "react"
2
+ import {
3
+ ChevronFirstIcon,
4
+ ChevronLastIcon,
5
+ ChevronLeftIcon,
6
+ ChevronRightIcon,
7
+ MoreHorizontalIcon,
8
+ } from "lucide-react"
9
+
10
+ import { Button } from "@/components/ui/button"
11
+ import { cn } from "@/lib/utils"
12
+
13
+ export type PaginationItem = number | "ellipsis"
14
+
15
+ export type PaginationLabels = {
16
+ first?: string
17
+ previous?: string
18
+ next?: string
19
+ last?: string
20
+ page?: (page: number) => string
21
+ }
22
+
23
+ export type PaginationProps = Omit<React.ComponentProps<"nav">, "onChange"> & {
24
+ page: number
25
+ pageCount: number
26
+ onPageChange: (page: number) => void
27
+ siblingCount?: number
28
+ showEdges?: boolean
29
+ disabled?: boolean
30
+ labels?: PaginationLabels
31
+ }
32
+
33
+ function range(start: number, end: number) {
34
+ const length = Math.max(end - start + 1, 0)
35
+ return Array.from({ length }, (_, index) => index + start)
36
+ }
37
+
38
+ function getPaginationItems(
39
+ page: number,
40
+ pageCount: number,
41
+ siblingCount = 1
42
+ ): PaginationItem[] {
43
+ const totalPageNumbers = siblingCount * 2 + 5
44
+
45
+ if (pageCount <= totalPageNumbers) {
46
+ return range(1, pageCount)
47
+ }
48
+
49
+ const leftSiblingIndex = Math.max(page - siblingCount, 1)
50
+ const rightSiblingIndex = Math.min(page + siblingCount, pageCount)
51
+
52
+ const shouldShowLeftEllipsis = leftSiblingIndex > 2
53
+ const shouldShowRightEllipsis = rightSiblingIndex < pageCount - 1
54
+
55
+ if (!shouldShowLeftEllipsis && shouldShowRightEllipsis) {
56
+ const leftItemCount = siblingCount * 2 + 3
57
+ return [...range(1, leftItemCount), "ellipsis", pageCount]
58
+ }
59
+
60
+ if (shouldShowLeftEllipsis && !shouldShowRightEllipsis) {
61
+ const rightItemCount = siblingCount * 2 + 3
62
+ return [1, "ellipsis", ...range(pageCount - rightItemCount + 1, pageCount)]
63
+ }
64
+
65
+ return [1, "ellipsis", ...range(leftSiblingIndex, rightSiblingIndex), "ellipsis", pageCount]
66
+ }
67
+
68
+ function Pagination({
69
+ className,
70
+ page,
71
+ pageCount,
72
+ onPageChange,
73
+ siblingCount = 1,
74
+ showEdges = true,
75
+ disabled = false,
76
+ labels,
77
+ ...props
78
+ }: PaginationProps) {
79
+ const safePageCount = Math.max(pageCount, 1)
80
+ const currentPage = Math.min(Math.max(page, 1), safePageCount)
81
+ const items = getPaginationItems(currentPage, safePageCount, siblingCount)
82
+
83
+ const goToPage = (nextPage: number) => {
84
+ const clampedPage = Math.min(Math.max(nextPage, 1), safePageCount)
85
+
86
+ if (clampedPage !== currentPage) {
87
+ onPageChange(clampedPage)
88
+ }
89
+ }
90
+
91
+ return (
92
+ <nav
93
+ data-slot="pagination"
94
+ aria-label="Pagination"
95
+ className={cn("flex items-center justify-center gap-1.5 rounded-full border border-border/70 bg-background/88 p-1 shadow-sm backdrop-blur", className)}
96
+ {...props}
97
+ >
98
+ {showEdges && (
99
+ <Button
100
+ type="button"
101
+ variant="outline"
102
+ size="icon-sm"
103
+ disabled={disabled || currentPage <= 1}
104
+ aria-label={labels?.first ?? "First page"}
105
+ onClick={() => goToPage(1)}
106
+ >
107
+ <ChevronFirstIcon />
108
+ </Button>
109
+ )}
110
+
111
+ <Button
112
+ type="button"
113
+ variant="outline"
114
+ size="icon-sm"
115
+ disabled={disabled || currentPage <= 1}
116
+ aria-label={labels?.previous ?? "Previous page"}
117
+ onClick={() => goToPage(currentPage - 1)}
118
+ >
119
+ <ChevronLeftIcon />
120
+ </Button>
121
+
122
+ {items.map((item, index) => {
123
+ if (item === "ellipsis") {
124
+ return (
125
+ <span
126
+ key={`ellipsis-${index}`}
127
+ data-slot="pagination-ellipsis"
128
+ className="flex size-8 items-center justify-center text-muted-foreground"
129
+ >
130
+ <MoreHorizontalIcon className="size-4" />
131
+ <span className="sr-only">More pages</span>
132
+ </span>
133
+ )
134
+ }
135
+
136
+ return (
137
+ <Button
138
+ key={item}
139
+ type="button"
140
+ variant={item === currentPage ? "default" : "outline"}
141
+ size="icon-sm"
142
+ disabled={disabled}
143
+ aria-current={item === currentPage ? "page" : undefined}
144
+ aria-label={labels?.page?.(item) ?? `Page ${item}`}
145
+ onClick={() => goToPage(item)}
146
+ >
147
+ {item}
148
+ </Button>
149
+ )
150
+ })}
151
+
152
+ <Button
153
+ type="button"
154
+ variant="outline"
155
+ size="icon-sm"
156
+ disabled={disabled || currentPage >= safePageCount}
157
+ aria-label={labels?.next ?? "Next page"}
158
+ onClick={() => goToPage(currentPage + 1)}
159
+ >
160
+ <ChevronRightIcon />
161
+ </Button>
162
+
163
+ {showEdges && (
164
+ <Button
165
+ type="button"
166
+ variant="outline"
167
+ size="icon-sm"
168
+ disabled={disabled || currentPage >= safePageCount}
169
+ aria-label={labels?.last ?? "Last page"}
170
+ onClick={() => goToPage(safePageCount)}
171
+ >
172
+ <ChevronLastIcon />
173
+ </Button>
174
+ )}
175
+ </nav>
176
+ )
177
+ }
178
+
179
+ export { Pagination, getPaginationItems }
@@ -0,0 +1,67 @@
1
+ import * as React from "react"
2
+ import { CheckIcon } from "lucide-react"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ export type StepperTabItem<TValue extends string = string> = {
7
+ value: TValue
8
+ label: React.ReactNode
9
+ description?: React.ReactNode
10
+ icon?: React.ReactNode
11
+ completed?: boolean
12
+ disabled?: boolean
13
+ hidden?: boolean
14
+ }
15
+
16
+ export type StepperTabsProps<TValue extends string = string> = React.ComponentProps<"div"> & {
17
+ value?: TValue
18
+ items: StepperTabItem<TValue>[]
19
+ onValueChange?: (value: TValue, item: StepperTabItem<TValue>) => void
20
+ orientation?: "horizontal" | "vertical"
21
+ compact?: boolean
22
+ }
23
+
24
+ function StepperTabs<TValue extends string = string>({
25
+ value,
26
+ items,
27
+ onValueChange,
28
+ orientation = "horizontal",
29
+ compact = false,
30
+ className,
31
+ ...props
32
+ }: StepperTabsProps<TValue>) {
33
+ const visibleItems = items.filter((item) => !item.hidden)
34
+
35
+ return (
36
+ <div data-slot="stepper-tabs" data-orientation={orientation} className={cn(orientation === "vertical" ? "grid gap-2" : "flex flex-wrap gap-2", className)} {...props}>
37
+ {visibleItems.map((item, index) => {
38
+ const active = item.value === value
39
+ const completed = item.completed
40
+ return (
41
+ <button
42
+ key={item.value}
43
+ type="button"
44
+ disabled={item.disabled}
45
+ data-slot="stepper-tab"
46
+ data-active={active || undefined}
47
+ data-completed={completed || undefined}
48
+ className={cn(
49
+ "group flex min-w-0 items-start gap-3 rounded-xl border bg-card text-left shadow-sm transition-colors hover:border-primary/40 disabled:pointer-events-none disabled:opacity-55",
50
+ compact ? "p-3" : "p-4",
51
+ active && "border-primary/60 bg-primary/5"
52
+ )}
53
+ onClick={() => onValueChange?.(item.value, item)}
54
+ >
55
+ <span className={cn("flex shrink-0 items-center justify-center rounded-full border bg-background text-xs font-semibold", compact ? "size-7" : "size-8", completed && "border-primary bg-primary text-primary-foreground", active && !completed && "border-primary text-primary")}>{completed ? <CheckIcon className="size-4" /> : item.icon ?? index + 1}</span>
56
+ <span className="min-w-0 space-y-0.5">
57
+ <span className="block truncate text-sm font-medium text-foreground">{item.label}</span>
58
+ {item.description && <span className="block text-xs leading-5 text-muted-foreground">{item.description}</span>}
59
+ </span>
60
+ </button>
61
+ )
62
+ })}
63
+ </div>
64
+ )
65
+ }
66
+
67
+ export { StepperTabs }
@@ -0,0 +1 @@
1
+ export * from "./toast"