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,275 @@
1
+ import * as React from "react"
2
+
3
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
4
+ import { cn } from "@/lib/utils"
5
+
6
+ export type ChartDatum = {
7
+ label: React.ReactNode
8
+ value: number
9
+ description?: React.ReactNode
10
+ color?: string
11
+ }
12
+
13
+ export type ChartSeries = {
14
+ key: string
15
+ label: React.ReactNode
16
+ data: number[]
17
+ color?: string
18
+ }
19
+
20
+ export type ChartAxisLabel = string | number | React.ReactNode
21
+
22
+ export type ChartSize = "sm" | "md" | "lg"
23
+
24
+ const chartHeightBySize: Record<ChartSize, number> = {
25
+ sm: 120,
26
+ md: 180,
27
+ lg: 260,
28
+ }
29
+
30
+ function safeMax(values: number[], fallback = 1) {
31
+ return Math.max(...values, fallback)
32
+ }
33
+
34
+ function normalizeValue(value: number, max: number) {
35
+ if (!Number.isFinite(value) || max <= 0) return 0
36
+ return Math.max(0, Math.min(value / max, 1))
37
+ }
38
+
39
+ function polarToCartesian(cx: number, cy: number, radius: number, angle: number) {
40
+ const radians = ((angle - 90) * Math.PI) / 180
41
+ return {
42
+ x: cx + radius * Math.cos(radians),
43
+ y: cy + radius * Math.sin(radians),
44
+ }
45
+ }
46
+
47
+ function describeArc(cx: number, cy: number, radius: number, startAngle: number, endAngle: number) {
48
+ const start = polarToCartesian(cx, cy, radius, endAngle)
49
+ const end = polarToCartesian(cx, cy, radius, startAngle)
50
+ const largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1"
51
+
52
+ return ["M", start.x, start.y, "A", radius, radius, 0, largeArcFlag, 0, end.x, end.y].join(" ")
53
+ }
54
+
55
+ function buildLinePath(values: number[], width: number, height: number, padding = 12) {
56
+ const max = safeMax(values)
57
+ const usableWidth = Math.max(width - padding * 2, 1)
58
+ const usableHeight = Math.max(height - padding * 2, 1)
59
+
60
+ if (values.length === 0) return ""
61
+
62
+ return values
63
+ .map((value, index) => {
64
+ const x = padding + (values.length === 1 ? usableWidth / 2 : (index / (values.length - 1)) * usableWidth)
65
+ const y = padding + (1 - normalizeValue(value, max)) * usableHeight
66
+ return `${index === 0 ? "M" : "L"} ${x} ${y}`
67
+ })
68
+ .join(" ")
69
+ }
70
+
71
+ function buildAreaPath(values: number[], width: number, height: number, padding = 12) {
72
+ const line = buildLinePath(values, width, height, padding)
73
+ if (!line) return ""
74
+
75
+ const baseline = height - padding
76
+ const lastX = values.length === 1 ? width / 2 : width - padding
77
+ const firstX = values.length === 1 ? width / 2 : padding
78
+
79
+ return `${line} L ${lastX} ${baseline} L ${firstX} ${baseline} Z`
80
+ }
81
+
82
+ export type ChartFrameProps = React.ComponentProps<typeof Card> & {
83
+ title?: React.ReactNode
84
+ description?: React.ReactNode
85
+ action?: React.ReactNode
86
+ }
87
+
88
+ function ChartFrame({ title, description, action, className, children, ...props }: ChartFrameProps) {
89
+ return (
90
+ <Card data-slot="chart-frame" className={className} {...props}>
91
+ {(title || description || action) && (
92
+ <CardHeader className="flex flex-row items-start justify-between gap-3">
93
+ <div className="grid gap-1">
94
+ {title && <CardTitle>{title}</CardTitle>}
95
+ {description && <CardDescription>{description}</CardDescription>}
96
+ </div>
97
+ {action}
98
+ </CardHeader>
99
+ )}
100
+ <CardContent>{children}</CardContent>
101
+ </Card>
102
+ )
103
+ }
104
+
105
+ export type BarChartProps = React.ComponentProps<"div"> & {
106
+ data: ChartDatum[]
107
+ size?: ChartSize
108
+ max?: number
109
+ showLabels?: boolean
110
+ showValues?: boolean
111
+ barClassName?: string
112
+ }
113
+
114
+ function BarChart({ data, size = "md", max, showLabels = true, showValues = true, className, barClassName, ...props }: BarChartProps) {
115
+ const resolvedMax = max ?? safeMax(data.map((item) => item.value))
116
+ const height = chartHeightBySize[size]
117
+
118
+ return (
119
+ <div data-slot="bar-chart" className={cn("grid gap-3", className)} {...props}>
120
+ <div className="flex items-end gap-2" style={{ height }}>
121
+ {data.map((item, index) => {
122
+ const ratio = normalizeValue(item.value, resolvedMax)
123
+ return (
124
+ <div key={index} className="flex min-w-0 flex-1 flex-col items-center gap-2">
125
+ {showValues && <div className="text-xs text-muted-foreground">{item.value}</div>}
126
+ <div className="flex w-full flex-1 items-end rounded-md bg-muted/50">
127
+ <div
128
+ className={cn("w-full rounded-md bg-primary transition-all", barClassName)}
129
+ style={{ height: `${Math.max(ratio * 100, item.value > 0 ? 3 : 0)}%`, background: item.color }}
130
+ />
131
+ </div>
132
+ {showLabels && <div className="max-w-full truncate text-xs text-muted-foreground">{item.label}</div>}
133
+ </div>
134
+ )
135
+ })}
136
+ </div>
137
+ </div>
138
+ )
139
+ }
140
+
141
+ export type LineChartProps = Omit<React.ComponentProps<"svg">, "values"> & {
142
+ values: number[]
143
+ size?: ChartSize
144
+ width?: number
145
+ showArea?: boolean
146
+ stroke?: string
147
+ }
148
+
149
+ function LineChart({ values, size = "md", width = 560, showArea = false, stroke = "var(--primary)", className, ...props }: LineChartProps) {
150
+ const height = chartHeightBySize[size]
151
+ const linePath = buildLinePath(values, width, height)
152
+ const areaPath = buildAreaPath(values, width, height)
153
+
154
+ return (
155
+ <svg data-slot="line-chart" viewBox={`0 0 ${width} ${height}`} className={cn("h-auto w-full overflow-visible", className)} role="img" {...props}>
156
+ {showArea && <path d={areaPath} fill="var(--primary)" opacity="0.12" />}
157
+ <path d={linePath} fill="none" stroke={stroke} strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" />
158
+ {values.map((value, index) => {
159
+ const max = safeMax(values)
160
+ const x = 12 + (values.length === 1 ? (width - 24) / 2 : (index / (values.length - 1)) * (width - 24))
161
+ const y = 12 + (1 - normalizeValue(value, max)) * (height - 24)
162
+ return <circle key={index} cx={x} cy={y} r="3" fill={stroke} />
163
+ })}
164
+ </svg>
165
+ )
166
+ }
167
+
168
+ export type SparklineProps = Omit<LineChartProps, "size" | "showArea"> & {
169
+ values: number[]
170
+ positive?: boolean
171
+ }
172
+
173
+ function Sparkline({ values, positive = true, stroke, className, ...props }: SparklineProps) {
174
+ return (
175
+ <LineChart
176
+ values={values}
177
+ width={180}
178
+ size="sm"
179
+ stroke={stroke ?? (positive ? "var(--primary)" : "var(--destructive)")}
180
+ className={cn("max-w-44", className)}
181
+ aria-label="Sparkline"
182
+ {...props}
183
+ />
184
+ )
185
+ }
186
+
187
+ export type DonutChartProps = React.ComponentProps<"svg"> & {
188
+ data: ChartDatum[]
189
+ size?: number
190
+ strokeWidth?: number
191
+ centerLabel?: React.ReactNode
192
+ centerValue?: React.ReactNode
193
+ }
194
+
195
+ function DonutChart({ data, size = 180, strokeWidth = 18, centerLabel, centerValue, className, ...props }: DonutChartProps) {
196
+ const total = data.reduce((sum, item) => sum + Math.max(item.value, 0), 0)
197
+ const radius = size / 2 - strokeWidth
198
+ const segments = data.map((item, index) => {
199
+ const value = Math.max(item.value, 0)
200
+ const start = data.slice(0, index).reduce((sum, previous) => sum + Math.max(previous.value, 0), 0)
201
+ const end = total > 0 ? start + (value / total) * 360 : start
202
+
203
+ return { item, start, end }
204
+ })
205
+
206
+ return (
207
+ <svg data-slot="donut-chart" viewBox={`0 0 ${size} ${size}`} className={cn("h-auto w-full max-w-48", className)} role="img" {...props}>
208
+ <circle cx={size / 2} cy={size / 2} r={radius} fill="none" stroke="var(--muted)" strokeWidth={strokeWidth} />
209
+ {segments.map(({ item, start, end }, index) => {
210
+ return (
211
+ <path
212
+ key={index}
213
+ d={describeArc(size / 2, size / 2, radius, start, end)}
214
+ fill="none"
215
+ stroke={item.color ?? "var(--primary)"}
216
+ strokeWidth={strokeWidth}
217
+ strokeLinecap="round"
218
+ />
219
+ )
220
+ })}
221
+ {(centerValue || centerLabel) && (
222
+ <text x="50%" y="50%" textAnchor="middle" dominantBaseline="middle" fill="var(--foreground)">
223
+ {centerValue && <tspan x="50%" dy={centerLabel ? "-0.2em" : "0"} className="text-lg font-semibold">{centerValue}</tspan>}
224
+ {centerLabel && <tspan x="50%" dy="1.3em" className="text-xs fill-muted-foreground">{centerLabel}</tspan>}
225
+ </text>
226
+ )}
227
+ </svg>
228
+ )
229
+ }
230
+
231
+ export type ChartLegendProps = React.ComponentProps<"div"> & {
232
+ data: ChartDatum[]
233
+ }
234
+
235
+ function ChartLegend({ data, className, ...props }: ChartLegendProps) {
236
+ return (
237
+ <div data-slot="chart-legend" className={cn("flex flex-wrap gap-3 text-xs text-muted-foreground", className)} {...props}>
238
+ {data.map((item, index) => (
239
+ <div key={index} className="inline-flex items-center gap-1.5">
240
+ <span className="size-2 rounded-full bg-primary" style={{ background: item.color }} />
241
+ <span>{item.label}</span>
242
+ </div>
243
+ ))}
244
+ </div>
245
+ )
246
+ }
247
+
248
+ export type MetricTrendProps = React.ComponentProps<"div"> & {
249
+ label: React.ReactNode
250
+ value: React.ReactNode
251
+ change?: React.ReactNode
252
+ positive?: boolean
253
+ values?: number[]
254
+ }
255
+
256
+ function MetricTrend({ label, value, change, positive = true, values, className, ...props }: MetricTrendProps) {
257
+ return (
258
+ <div data-slot="metric-trend" className={cn("grid gap-3 rounded-lg border bg-card p-4", className)} {...props}>
259
+ <div className="flex items-start justify-between gap-3">
260
+ <div className="grid gap-1">
261
+ <div className="text-sm text-muted-foreground">{label}</div>
262
+ <div className="text-2xl font-semibold tracking-tight">{value}</div>
263
+ </div>
264
+ {change && (
265
+ <div className={cn("rounded-full px-2 py-1 text-xs font-medium", positive ? "bg-primary/10 text-primary" : "bg-destructive/10 text-destructive")}>
266
+ {change}
267
+ </div>
268
+ )}
269
+ </div>
270
+ {values && <Sparkline values={values} positive={positive} />}
271
+ </div>
272
+ )
273
+ }
274
+
275
+ export { BarChart, ChartFrame, ChartLegend, DonutChart, LineChart, MetricTrend, Sparkline }
@@ -0,0 +1,46 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ export type HorizontalBarDatum = {
6
+ key: string
7
+ label: React.ReactNode
8
+ value: number
9
+ description?: React.ReactNode
10
+ color?: string
11
+ }
12
+
13
+ export type HorizontalBarChartProps = React.ComponentProps<"div"> & {
14
+ data: HorizontalBarDatum[]
15
+ max?: number
16
+ showValue?: boolean
17
+ }
18
+
19
+ function HorizontalBarChart({ data, max, showValue = true, className, ...props }: HorizontalBarChartProps) {
20
+ const resolvedMax = max ?? Math.max(...data.map((item) => item.value), 1)
21
+
22
+ return (
23
+ <div data-slot="horizontal-bar-chart" className={cn("grid gap-3", className)} {...props}>
24
+ {data.map((item) => {
25
+ const width = resolvedMax > 0 ? Math.max(0, Math.min((item.value / resolvedMax) * 100, 100)) : 0
26
+
27
+ return (
28
+ <div key={item.key} className="grid gap-1.5">
29
+ <div className="flex items-center justify-between gap-3 text-sm">
30
+ <div className="min-w-0">
31
+ <div className="truncate font-medium text-foreground">{item.label}</div>
32
+ {item.description && <div className="truncate text-xs text-muted-foreground">{item.description}</div>}
33
+ </div>
34
+ {showValue && <div className="text-xs font-medium text-muted-foreground">{item.value}</div>}
35
+ </div>
36
+ <div className="h-2 overflow-hidden rounded-full bg-muted">
37
+ <div className="h-full rounded-full bg-primary" style={{ width: `${width}%`, background: item.color }} />
38
+ </div>
39
+ </div>
40
+ )
41
+ })}
42
+ </div>
43
+ )
44
+ }
45
+
46
+ export { HorizontalBarChart }
@@ -0,0 +1,4 @@
1
+ export * from './charts'
2
+ export * from './kpi'
3
+ export * from './progress-ring'
4
+ export * from './horizontal-bar-chart'
@@ -0,0 +1,68 @@
1
+ import * as React from "react"
2
+
3
+ import { Sparkline } from "@/components/charts/charts"
4
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
5
+ import { cn } from "@/lib/utils"
6
+
7
+ export type KpiTone = "neutral" | "success" | "warning" | "danger"
8
+
9
+ export type KpiCardProps = React.ComponentProps<typeof Card> & {
10
+ label: React.ReactNode
11
+ value: React.ReactNode
12
+ description?: React.ReactNode
13
+ change?: React.ReactNode
14
+ tone?: KpiTone
15
+ values?: number[]
16
+ icon?: React.ReactNode
17
+ }
18
+
19
+ const toneClassName: Record<KpiTone, string> = {
20
+ neutral: "bg-muted text-muted-foreground",
21
+ success: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400",
22
+ warning: "bg-amber-500/10 text-amber-600 dark:text-amber-400",
23
+ danger: "bg-destructive/10 text-destructive",
24
+ }
25
+
26
+ function KpiCard({ label, value, description, change, tone = "neutral", values, icon, className, ...props }: KpiCardProps) {
27
+ return (
28
+ <Card data-slot="kpi-card" className={cn("overflow-hidden", className)} {...props}>
29
+ <CardHeader className="flex flex-row items-start justify-between gap-3 pb-2">
30
+ <CardTitle className="text-sm font-medium text-muted-foreground">{label}</CardTitle>
31
+ {icon && <div className="text-muted-foreground">{icon}</div>}
32
+ </CardHeader>
33
+ <CardContent className="grid gap-3">
34
+ <div className="flex items-end justify-between gap-3">
35
+ <div className="grid gap-1">
36
+ <div className="text-2xl font-semibold tracking-tight text-foreground">{value}</div>
37
+ {description && <div className="text-xs text-muted-foreground">{description}</div>}
38
+ </div>
39
+ {change && <div className={cn("rounded-full px-2 py-1 text-xs font-medium", toneClassName[tone])}>{change}</div>}
40
+ </div>
41
+ {values && <Sparkline values={values} positive={tone !== "danger"} />}
42
+ </CardContent>
43
+ </Card>
44
+ )
45
+ }
46
+
47
+ export type KpiGridProps = React.ComponentProps<"div"> & {
48
+ columns?: 1 | 2 | 3 | 4
49
+ }
50
+
51
+ function KpiGrid({ columns = 4, className, ...props }: KpiGridProps) {
52
+ return (
53
+ <div
54
+ data-slot="kpi-grid"
55
+ className={cn(
56
+ "grid gap-4",
57
+ columns === 1 && "grid-cols-1",
58
+ columns === 2 && "grid-cols-1 sm:grid-cols-2",
59
+ columns === 3 && "grid-cols-1 sm:grid-cols-2 xl:grid-cols-3",
60
+ columns === 4 && "grid-cols-1 sm:grid-cols-2 xl:grid-cols-4",
61
+ className
62
+ )}
63
+ {...props}
64
+ />
65
+ )
66
+ }
67
+
68
+ export { KpiCard, KpiGrid }
@@ -0,0 +1,45 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ export type ProgressRingProps = React.ComponentProps<"svg"> & {
6
+ value: number
7
+ max?: number
8
+ size?: number
9
+ strokeWidth?: number
10
+ label?: React.ReactNode
11
+ description?: React.ReactNode
12
+ }
13
+
14
+ function ProgressRing({ value, max = 100, size = 120, strokeWidth = 10, label, description, className, ...props }: ProgressRingProps) {
15
+ const radius = (size - strokeWidth) / 2
16
+ const circumference = 2 * Math.PI * radius
17
+ const ratio = max > 0 ? Math.min(Math.max(value / max, 0), 1) : 0
18
+ const offset = circumference - ratio * circumference
19
+
20
+ return (
21
+ <svg data-slot="progress-ring" viewBox={`0 0 ${size} ${size}`} className={cn("h-auto w-full max-w-32", className)} role="img" {...props}>
22
+ <circle cx={size / 2} cy={size / 2} r={radius} fill="none" stroke="var(--muted)" strokeWidth={strokeWidth} />
23
+ <circle
24
+ cx={size / 2}
25
+ cy={size / 2}
26
+ r={radius}
27
+ fill="none"
28
+ stroke="var(--primary)"
29
+ strokeWidth={strokeWidth}
30
+ strokeLinecap="round"
31
+ strokeDasharray={circumference}
32
+ strokeDashoffset={offset}
33
+ transform={`rotate(-90 ${size / 2} ${size / 2})`}
34
+ />
35
+ {(label || description) && (
36
+ <text x="50%" y="50%" textAnchor="middle" dominantBaseline="middle" fill="var(--foreground)">
37
+ {label && <tspan x="50%" dy={description ? "-0.15em" : "0"} className="text-base font-semibold">{label}</tspan>}
38
+ {description && <tspan x="50%" dy="1.25em" className="text-xs fill-muted-foreground">{description}</tspan>}
39
+ </text>
40
+ )}
41
+ </svg>
42
+ )
43
+ }
44
+
45
+ export { ProgressRing }
@@ -0,0 +1 @@
1
+ export * from "./charts"