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,179 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ export type AppSidebarNavItem = {
6
+ key: string
7
+ label: React.ReactNode
8
+ icon?: React.ReactNode
9
+ href?: string
10
+ active?: boolean
11
+ disabled?: boolean
12
+ badge?: React.ReactNode
13
+ hidden?: boolean
14
+ onSelect?: () => void
15
+ }
16
+
17
+ export type AppSidebarProps = React.ComponentProps<"aside"> & {
18
+ header?: React.ReactNode
19
+ footer?: React.ReactNode
20
+ items?: AppSidebarNavItem[]
21
+ collapsed?: boolean
22
+ onItemSelect?: (item: AppSidebarNavItem) => void
23
+ renderItem?: (item: AppSidebarNavItem, state: { collapsed: boolean }) => React.ReactNode
24
+ renderLink?: (props: React.ComponentProps<"a"> & { item: AppSidebarNavItem; [key: `data-${string}`]: string | boolean | undefined }) => React.ReactNode
25
+ }
26
+
27
+ function AppSidebar({
28
+ className,
29
+ header,
30
+ footer,
31
+ items = [],
32
+ collapsed = false,
33
+ onItemSelect,
34
+ renderItem,
35
+ renderLink,
36
+ children,
37
+ ...props
38
+ }: AppSidebarProps) {
39
+ const visibleItems = items.filter((item) => !item.hidden)
40
+
41
+ return (
42
+ <aside
43
+ data-slot="app-sidebar"
44
+ data-collapsed={collapsed || undefined}
45
+ className={cn("flex h-full min-h-0 flex-col bg-sidebar text-sidebar-foreground", className)}
46
+ {...props}
47
+ >
48
+ {header && <div className="shrink-0 border-b p-3">{header}</div>}
49
+
50
+ <nav data-slot="app-sidebar-nav" className="min-h-0 flex-1 space-y-1 overflow-y-auto p-2">
51
+ {children ??
52
+ visibleItems.map((item) =>
53
+ renderItem ? (
54
+ <React.Fragment key={item.key}>{renderItem(item, { collapsed })}</React.Fragment>
55
+ ) : (
56
+ item.href?.startsWith("/") ? (
57
+ renderLink ? (
58
+ <React.Fragment key={item.key}>
59
+ {renderLink({
60
+ item,
61
+ href: item.href,
62
+ "aria-current": item.active ? "page" : undefined,
63
+ "aria-disabled": item.disabled || undefined,
64
+ "data-active": item.active || undefined,
65
+ "data-disabled": item.disabled || undefined,
66
+ className: cn(
67
+ "flex min-h-9 items-center gap-2 rounded-lg px-2.5 text-sm font-medium outline-none transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 focus-visible:ring-sidebar-ring data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50",
68
+ collapsed && "justify-center px-2"
69
+ ),
70
+ onClick: (event) => {
71
+ if (item.disabled) {
72
+ event.preventDefault()
73
+ return
74
+ }
75
+
76
+ item.onSelect?.()
77
+ onItemSelect?.(item)
78
+ },
79
+ children: (
80
+ <>
81
+ {item.icon && <span className="shrink-0">{item.icon}</span>}
82
+ {!collapsed && <span className="min-w-0 flex-1 truncate">{item.label}</span>}
83
+ {!collapsed && item.badge && <span className="shrink-0">{item.badge}</span>}
84
+ </>
85
+ ),
86
+ })}
87
+ </React.Fragment>
88
+ ) : (
89
+ <a
90
+ key={item.key}
91
+ href={item.href}
92
+ aria-current={item.active ? "page" : undefined}
93
+ aria-disabled={item.disabled || undefined}
94
+ data-active={item.active || undefined}
95
+ data-disabled={item.disabled || undefined}
96
+ className={cn(
97
+ "flex min-h-9 items-center gap-2 rounded-lg px-2.5 text-sm font-medium outline-none transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 focus-visible:ring-sidebar-ring data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50",
98
+ collapsed && "justify-center px-2"
99
+ )}
100
+ onClick={(event) => {
101
+ if (item.disabled) {
102
+ event.preventDefault()
103
+ return
104
+ }
105
+
106
+ item.onSelect?.()
107
+ onItemSelect?.(item)
108
+ }}
109
+ >
110
+ {item.icon && <span className="shrink-0">{item.icon}</span>}
111
+ {!collapsed && <span className="min-w-0 flex-1 truncate">{item.label}</span>}
112
+ {!collapsed && item.badge && <span className="shrink-0">{item.badge}</span>}
113
+ </a>
114
+ )
115
+ ) : item.href ? (
116
+ <button
117
+ key={item.key}
118
+ type="button"
119
+ aria-current={item.active ? "page" : undefined}
120
+ aria-disabled={item.disabled || undefined}
121
+ data-active={item.active || undefined}
122
+ data-disabled={item.disabled || undefined}
123
+ className={cn(
124
+ "flex min-h-9 w-full items-center gap-2 rounded-lg px-2.5 text-sm font-medium outline-none transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 focus-visible:ring-sidebar-ring data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50",
125
+ collapsed && "justify-center px-2"
126
+ )}
127
+ onClick={() => {
128
+ if (item.disabled) return
129
+ const href = item.href
130
+ if (!href) return
131
+
132
+ item.onSelect?.()
133
+ onItemSelect?.(item)
134
+
135
+ if (href.startsWith("http")) {
136
+ window.open(href, "_blank", "noopener,noreferrer")
137
+ return
138
+ }
139
+
140
+ window.location.assign(href)
141
+ }}
142
+ >
143
+ {item.icon && <span className="shrink-0">{item.icon}</span>}
144
+ {!collapsed && <span className="min-w-0 flex-1 truncate">{item.label}</span>}
145
+ {!collapsed && item.badge && <span className="shrink-0">{item.badge}</span>}
146
+ </button>
147
+ ) : (
148
+ <button
149
+ key={item.key}
150
+ type="button"
151
+ disabled={item.disabled}
152
+ aria-current={item.active ? "page" : undefined}
153
+ aria-disabled={item.disabled || undefined}
154
+ data-active={item.active || undefined}
155
+ data-disabled={item.disabled || undefined}
156
+ className={cn(
157
+ "flex min-h-9 items-center gap-2 rounded-lg px-2.5 text-sm font-medium outline-none transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 focus-visible:ring-sidebar-ring data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50",
158
+ collapsed && "justify-center px-2"
159
+ )}
160
+ onClick={() => {
161
+ item.onSelect?.()
162
+ onItemSelect?.(item)
163
+ }}
164
+ >
165
+ {item.icon && <span className="shrink-0">{item.icon}</span>}
166
+ {!collapsed && <span className="min-w-0 flex-1 truncate">{item.label}</span>}
167
+ {!collapsed && item.badge && <span className="shrink-0">{item.badge}</span>}
168
+ </button>
169
+ )
170
+ )
171
+ )}
172
+ </nav>
173
+
174
+ {footer && <div className="shrink-0 border-t p-3">{footer}</div>}
175
+ </aside>
176
+ )
177
+ }
178
+
179
+ export { AppSidebar }
@@ -0,0 +1,72 @@
1
+ import * as React from "react"
2
+ import { ChevronRightIcon } from "lucide-react"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ export type BreadcrumbItem = {
7
+ key: string
8
+ label: React.ReactNode
9
+ href?: string
10
+ current?: boolean
11
+ onSelect?: () => void
12
+ }
13
+
14
+ export type BreadcrumbsProps = React.ComponentProps<"nav"> & {
15
+ items: BreadcrumbItem[]
16
+ separator?: React.ReactNode
17
+ renderLink?: (props: React.ComponentProps<"a"> & { item: BreadcrumbItem; [key: `data-${string}`]: string | boolean | undefined }) => React.ReactNode
18
+ }
19
+
20
+ function Breadcrumbs({
21
+ className,
22
+ items,
23
+ separator = <ChevronRightIcon className="size-3.5" />,
24
+ renderLink,
25
+ ...props
26
+ }: BreadcrumbsProps) {
27
+ return (
28
+ <nav
29
+ data-slot="breadcrumbs"
30
+ aria-label="Breadcrumb"
31
+ className={cn("flex min-w-0 items-center gap-1 text-sm text-muted-foreground", className)}
32
+ {...props}
33
+ >
34
+ {items.map((item, index) => {
35
+ const isLast = index === items.length - 1
36
+ const isCurrent = item.current || isLast
37
+
38
+ return (
39
+ <React.Fragment key={item.key}>
40
+ {index > 0 && <span className="shrink-0 opacity-60">{separator}</span>}
41
+ {item.href && !isCurrent ? (
42
+ renderLink ? renderLink({
43
+ item,
44
+ href: item.href,
45
+ className: "truncate transition-colors hover:text-foreground",
46
+ onClick: () => item.onSelect?.(),
47
+ children: item.label,
48
+ }) : (
49
+ <a
50
+ href={item.href}
51
+ className="truncate transition-colors hover:text-foreground"
52
+ onClick={() => item.onSelect?.()}
53
+ >
54
+ {item.label}
55
+ </a>
56
+ )
57
+ ) : (
58
+ <span
59
+ aria-current={isCurrent ? "page" : undefined}
60
+ className={cn("truncate", isCurrent && "font-medium text-foreground")}
61
+ >
62
+ {item.label}
63
+ </span>
64
+ )}
65
+ </React.Fragment>
66
+ )
67
+ })}
68
+ </nav>
69
+ )
70
+ }
71
+
72
+ export { Breadcrumbs }
@@ -0,0 +1,11 @@
1
+ export * from "./app-shell"
2
+ export * from "./app-header"
3
+ export * from "./app-sidebar"
4
+ export * from "./page-header"
5
+ export * from "./stat-card"
6
+ export * from "./sidebar-nav"
7
+ export * from "./breadcrumbs"
8
+ export * from "./page-container"
9
+ export * from "./section"
10
+ export * from "./sticky-footer-bar"
11
+ export * from "./workspace-shell"
@@ -0,0 +1,30 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ export type PageContainerSize = "default" | "sm" | "md" | "lg" | "xl" | "full"
6
+
7
+ export type PageContainerProps = React.ComponentProps<"div"> & {
8
+ size?: PageContainerSize
9
+ }
10
+
11
+ const sizeClassName: Record<PageContainerSize, string> = {
12
+ sm: "max-w-3xl",
13
+ md: "max-w-5xl",
14
+ lg: "max-w-6xl",
15
+ xl: "max-w-7xl",
16
+ default: "max-w-7xl",
17
+ full: "max-w-none",
18
+ }
19
+
20
+ function PageContainer({ className, size = "default", ...props }: PageContainerProps) {
21
+ return (
22
+ <div
23
+ data-slot="page-container"
24
+ className={cn("mx-auto flex w-full flex-col gap-4", sizeClassName[size], className)}
25
+ {...props}
26
+ />
27
+ )
28
+ }
29
+
30
+ export { PageContainer }
@@ -0,0 +1,60 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ export type PageHeaderProps = React.ComponentProps<"div"> & {
6
+ title?: React.ReactNode
7
+ description?: React.ReactNode
8
+ eyebrow?: React.ReactNode
9
+ breadcrumbs?: React.ReactNode
10
+ actions?: React.ReactNode
11
+ meta?: React.ReactNode
12
+ sticky?: boolean
13
+ }
14
+
15
+ function PageHeader({
16
+ className,
17
+ title,
18
+ description,
19
+ eyebrow,
20
+ breadcrumbs,
21
+ actions,
22
+ meta,
23
+ sticky = false,
24
+ children,
25
+ ...props
26
+ }: PageHeaderProps) {
27
+ return (
28
+ <div
29
+ data-slot="page-header"
30
+ data-sticky={sticky || undefined}
31
+ className={cn(
32
+ "flex flex-col gap-4 border-b border-border/70 pb-5",
33
+ sticky && "sticky top-0 z-30 bg-background/92 pt-4 backdrop-blur supports-[backdrop-filter]:bg-background/78",
34
+ className
35
+ )}
36
+ {...props}
37
+ >
38
+ {breadcrumbs && <div className="text-sm text-muted-foreground/95">{breadcrumbs}</div>}
39
+
40
+ <div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
41
+ <div className="min-w-0 space-y-2">
42
+ {eyebrow && (
43
+ <div className="text-[11px] font-semibold uppercase tracking-[0.24em] text-muted-foreground">
44
+ {eyebrow}
45
+ </div>
46
+ )}
47
+ {title && <h1 className="truncate text-3xl font-semibold tracking-[-0.03em] text-foreground">{title}</h1>}
48
+ {description && <p className="max-w-3xl text-sm leading-7 text-muted-foreground">{description}</p>}
49
+ {meta && <div className="pt-1 text-sm text-muted-foreground">{meta}</div>}
50
+ </div>
51
+
52
+ {actions && <div className="flex shrink-0 flex-wrap items-center gap-2.5">{actions}</div>}
53
+ </div>
54
+
55
+ {children}
56
+ </div>
57
+ )
58
+ }
59
+
60
+ export { PageHeader }
@@ -0,0 +1,10 @@
1
+ export * from "./app-shell"
2
+ export * from "./app-header"
3
+ export * from "./app-sidebar"
4
+ export * from "./page-header"
5
+ export * from "./stat-card"
6
+ export * from "./sidebar-nav"
7
+ export * from "./breadcrumbs"
8
+ export * from "./page-container"
9
+ export * from "./section"
10
+ export * from "./sticky-footer-bar"
@@ -0,0 +1,76 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ export type SectionProps = React.ComponentProps<"section"> & {
6
+ title?: React.ReactNode
7
+ description?: React.ReactNode
8
+ actions?: React.ReactNode
9
+ padded?: boolean
10
+ bordered?: boolean
11
+ }
12
+
13
+ function Section({ title, description, actions, padded = true, bordered = false, className, children, ...props }: SectionProps) {
14
+ return (
15
+ <section data-slot="section" className={cn("grid gap-4", bordered && "rounded-lg border bg-card", padded && bordered && "p-4", className)} {...props}>
16
+ {(title || description || actions) && (
17
+ <div data-slot="section-header" className="flex flex-wrap items-start justify-between gap-3">
18
+ <div className="grid gap-1">
19
+ {title && <h2 className="text-lg font-semibold tracking-tight text-foreground">{title}</h2>}
20
+ {description && <p className="text-sm text-muted-foreground">{description}</p>}
21
+ </div>
22
+ {actions && <div className="flex shrink-0 items-center gap-2">{actions}</div>}
23
+ </div>
24
+ )}
25
+ <div data-slot="section-content">{children}</div>
26
+ </section>
27
+ )
28
+ }
29
+
30
+ export type ToolbarProps = React.ComponentProps<"div"> & {
31
+ title?: React.ReactNode
32
+ description?: React.ReactNode
33
+ filters?: React.ReactNode
34
+ actions?: React.ReactNode
35
+ }
36
+
37
+ function Toolbar({ title, description, filters, actions, className, children, ...props }: ToolbarProps) {
38
+ return (
39
+ <div data-slot="toolbar" className={cn("flex flex-wrap items-center justify-between gap-3", className)} {...props}>
40
+ {(title || description) && (
41
+ <div className="grid gap-0.5">
42
+ {title && <div className="font-medium text-foreground">{title}</div>}
43
+ {description && <div className="text-sm text-muted-foreground">{description}</div>}
44
+ </div>
45
+ )}
46
+ {children}
47
+ {(filters || actions) && (
48
+ <div className="flex flex-wrap items-center gap-2">
49
+ {filters}
50
+ {actions}
51
+ </div>
52
+ )}
53
+ </div>
54
+ )
55
+ }
56
+
57
+ export type SplitLayoutProps = React.ComponentProps<"div"> & {
58
+ aside: React.ReactNode
59
+ asidePosition?: "start" | "end"
60
+ asideClassName?: string
61
+ contentClassName?: string
62
+ }
63
+
64
+ function SplitLayout({ aside, asidePosition = "end", asideClassName, contentClassName, className, children, ...props }: SplitLayoutProps) {
65
+ const asideNode = <aside data-slot="split-layout-aside" className={cn("min-w-0", asideClassName)}>{aside}</aside>
66
+ const contentNode = <main data-slot="split-layout-content" className={cn("min-w-0", contentClassName)}>{children}</main>
67
+
68
+ return (
69
+ <div data-slot="split-layout" className={cn("grid gap-4 lg:grid-cols-[minmax(0,1fr)_320px]", asidePosition === "start" && "lg:grid-cols-[320px_minmax(0,1fr)]", className)} {...props}>
70
+ {asidePosition === "start" ? asideNode : contentNode}
71
+ {asidePosition === "start" ? contentNode : asideNode}
72
+ </div>
73
+ )
74
+ }
75
+
76
+ export { Section, SplitLayout, Toolbar }
@@ -0,0 +1,147 @@
1
+ import * as React from "react"
2
+
3
+ import { Badge } from "@/components/ui/badge"
4
+ import { cn } from "@/lib/utils"
5
+
6
+ export type SidebarNavItem = {
7
+ key: string
8
+ label: React.ReactNode
9
+ icon?: React.ReactNode
10
+ badge?: React.ReactNode
11
+ href?: string
12
+ active?: boolean
13
+ disabled?: boolean
14
+ hidden?: boolean
15
+ onSelect?: () => void
16
+ }
17
+
18
+ export type SidebarNavProps = React.ComponentProps<"nav"> & {
19
+ items: SidebarNavItem[]
20
+ collapsed?: boolean
21
+ itemClassName?: string
22
+ activeItemClassName?: string
23
+ renderItem?: (item: SidebarNavItem, element: React.ReactNode) => React.ReactNode
24
+ renderLink?: (props: React.ComponentProps<"a"> & { item: SidebarNavItem; [key: `data-${string}`]: string | boolean | undefined }) => React.ReactNode
25
+ }
26
+
27
+ function SidebarNav({
28
+ className,
29
+ items,
30
+ collapsed = false,
31
+ itemClassName,
32
+ activeItemClassName,
33
+ renderItem,
34
+ renderLink,
35
+ ...props
36
+ }: SidebarNavProps) {
37
+ const visibleItems = items.filter((item) => !item.hidden)
38
+
39
+ return (
40
+ <nav
41
+ data-slot="sidebar-nav"
42
+ data-collapsed={collapsed || undefined}
43
+ className={cn("grid gap-1", className)}
44
+ {...props}
45
+ >
46
+ {visibleItems.map((item) => {
47
+ const content = (
48
+ <>
49
+ {item.icon && <span className="shrink-0 [&_svg]:size-4">{item.icon}</span>}
50
+ {!collapsed && <span className="min-w-0 flex-1 truncate">{item.label}</span>}
51
+ {!collapsed && item.badge && (
52
+ <Badge variant="secondary" className="ml-auto h-5 shrink-0 px-1.5">
53
+ {item.badge}
54
+ </Badge>
55
+ )}
56
+ </>
57
+ )
58
+
59
+ const commonClassName = cn(
60
+ "group flex min-h-8 items-center gap-2 rounded-lg px-2 text-sm font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground data-[active=true]:bg-muted data-[active=true]:text-foreground data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50",
61
+ collapsed && "justify-center px-0",
62
+ itemClassName,
63
+ item.active && activeItemClassName
64
+ )
65
+
66
+ const isInternalLink = item.href && item.href.startsWith("/")
67
+
68
+ const element = item.href && isInternalLink ? (
69
+ renderLink ? (
70
+ <React.Fragment key={item.key}>
71
+ {renderLink({
72
+ item,
73
+ href: item.href,
74
+ "data-active": item.active || undefined,
75
+ "data-disabled": item.disabled || undefined,
76
+ "aria-current": item.active ? "page" : undefined,
77
+ className: commonClassName,
78
+ onClick: (event) => {
79
+ if (item.disabled) event.preventDefault()
80
+ item.onSelect?.()
81
+ },
82
+ children: content,
83
+ })}
84
+ </React.Fragment>
85
+ ) : (
86
+ <a
87
+ key={item.key}
88
+ href={item.href}
89
+ data-active={item.active || undefined}
90
+ data-disabled={item.disabled || undefined}
91
+ aria-current={item.active ? "page" : undefined}
92
+ className={commonClassName}
93
+ onClick={(event) => {
94
+ if (item.disabled) event.preventDefault()
95
+ item.onSelect?.()
96
+ }}
97
+ >
98
+ {content}
99
+ </a>
100
+ )
101
+ ) : item.href ? (
102
+ <button
103
+ key={item.key}
104
+ type="button"
105
+ data-active={item.active || undefined}
106
+ data-disabled={item.disabled || undefined}
107
+ aria-current={item.active ? "page" : undefined}
108
+ className={cn("w-full text-left", commonClassName)}
109
+ onClick={() => {
110
+ if (item.disabled) return
111
+ const href = item.href
112
+ if (!href) return
113
+ item.onSelect?.()
114
+ if (href.startsWith("http")) {
115
+ window.open(href, "_blank", "noopener,noreferrer")
116
+ return
117
+ }
118
+ window.location.assign(href)
119
+ }}
120
+ >
121
+ {content}
122
+ </button>
123
+ ) : (
124
+ <button
125
+ key={item.key}
126
+ type="button"
127
+ data-active={item.active || undefined}
128
+ data-disabled={item.disabled || undefined}
129
+ disabled={item.disabled}
130
+ className={cn("w-full text-left", commonClassName)}
131
+ onClick={item.onSelect}
132
+ >
133
+ {content}
134
+ </button>
135
+ )
136
+
137
+ return renderItem ? (
138
+ <React.Fragment key={item.key}>{renderItem(item, element)}</React.Fragment>
139
+ ) : (
140
+ element
141
+ )
142
+ })}
143
+ </nav>
144
+ )
145
+ }
146
+
147
+ export { SidebarNav }
@@ -0,0 +1,88 @@
1
+ import * as React from "react"
2
+
3
+ import {
4
+ Card,
5
+ CardAction,
6
+ CardContent,
7
+ CardDescription,
8
+ CardHeader,
9
+ CardTitle,
10
+ } from "@/components/ui/card"
11
+ import { cn } from "@/lib/utils"
12
+
13
+ export type StatCardTrend = {
14
+ value: React.ReactNode
15
+ tone?: "success" | "warning" | "danger" | "muted" | "default"
16
+ }
17
+
18
+ export type StatCardProps = React.ComponentProps<typeof Card> & {
19
+ title?: React.ReactNode
20
+ value?: React.ReactNode
21
+ description?: React.ReactNode
22
+ icon?: React.ReactNode
23
+ action?: React.ReactNode
24
+ trend?: StatCardTrend
25
+ footer?: React.ReactNode
26
+ contentClassName?: string
27
+ }
28
+
29
+ const trendClassName: Record<NonNullable<StatCardTrend["tone"]>, string> = {
30
+ default: "text-foreground",
31
+ success: "text-emerald-600 dark:text-emerald-400",
32
+ warning: "text-amber-600 dark:text-amber-400",
33
+ danger: "text-destructive",
34
+ muted: "text-muted-foreground",
35
+ }
36
+
37
+ function StatCard({
38
+ className,
39
+ title,
40
+ value,
41
+ description,
42
+ icon,
43
+ action,
44
+ trend,
45
+ footer,
46
+ contentClassName,
47
+ ...props
48
+ }: StatCardProps) {
49
+ return (
50
+ <Card data-slot="stat-card" className={cn("min-w-0", className)} {...props}>
51
+ <CardHeader>
52
+ <div className="flex min-w-0 items-start justify-between gap-3">
53
+ <div className="min-w-0 space-y-1">
54
+ {title && <CardDescription className="truncate">{title}</CardDescription>}
55
+ {value && <CardTitle className="truncate text-2xl">{value}</CardTitle>}
56
+ </div>
57
+ {(icon || action) && (
58
+ <CardAction>
59
+ {action ?? (
60
+ <div className="flex size-9 items-center justify-center rounded-lg bg-muted text-muted-foreground">
61
+ {icon}
62
+ </div>
63
+ )}
64
+ </CardAction>
65
+ )}
66
+ </div>
67
+ </CardHeader>
68
+
69
+ {(description || trend || footer) && (
70
+ <CardContent className={cn("space-y-2", contentClassName)}>
71
+ {(description || trend) && (
72
+ <div className="flex flex-wrap items-center gap-2 text-sm">
73
+ {trend && (
74
+ <span className={cn("font-medium", trendClassName[trend.tone ?? "default"])}>
75
+ {trend.value}
76
+ </span>
77
+ )}
78
+ {description && <span className="text-muted-foreground">{description}</span>}
79
+ </div>
80
+ )}
81
+ {footer}
82
+ </CardContent>
83
+ )}
84
+ </Card>
85
+ )
86
+ }
87
+
88
+ export { StatCard }