@codefast/ui 0.3.16-canary.2 → 0.3.16-canary.3

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 (223) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +1 -1
  3. package/dist/components/accordion.mjs +2 -2
  4. package/dist/components/alert-dialog.d.mts +1 -1
  5. package/dist/components/alert-dialog.mjs +4 -4
  6. package/dist/components/alert.d.mts +3 -13
  7. package/dist/components/alert.mjs +3 -23
  8. package/dist/components/badge.d.mts +3 -15
  9. package/dist/components/badge.mjs +2 -44
  10. package/dist/components/breadcrumb.mjs +1 -1
  11. package/dist/components/button-group.d.mts +3 -13
  12. package/dist/components/button-group.mjs +3 -24
  13. package/dist/components/button.d.mts +3 -25
  14. package/dist/components/button.mjs +2 -72
  15. package/dist/components/calendar.mjs +2 -1
  16. package/dist/components/carousel.d.mts +1 -2
  17. package/dist/components/chart.d.mts +2 -4
  18. package/dist/components/checkbox.mjs +2 -2
  19. package/dist/components/context-menu.mjs +2 -2
  20. package/dist/components/dialog.d.mts +1 -1
  21. package/dist/components/dialog.mjs +4 -4
  22. package/dist/components/drawer.d.mts +1 -1
  23. package/dist/components/drawer.mjs +2 -2
  24. package/dist/components/dropdown-menu.mjs +2 -2
  25. package/dist/components/empty.d.mts +3 -13
  26. package/dist/components/empty.mjs +3 -18
  27. package/dist/components/field.d.mts +3 -14
  28. package/dist/components/field.mjs +3 -32
  29. package/dist/components/form.d.mts +2 -4
  30. package/dist/components/hover-card.mjs +1 -1
  31. package/dist/components/input-group.d.mts +4 -31
  32. package/dist/components/input-group.mjs +3 -90
  33. package/dist/components/input-number.mjs +4 -4
  34. package/dist/components/input-otp.mjs +2 -2
  35. package/dist/components/input.mjs +1 -1
  36. package/dist/components/item.d.mts +4 -29
  37. package/dist/components/item.mjs +3 -56
  38. package/dist/components/menubar.mjs +2 -2
  39. package/dist/components/native-select.mjs +1 -1
  40. package/dist/components/navigation-menu.d.mts +1 -6
  41. package/dist/components/navigation-menu.mjs +8 -15
  42. package/dist/components/pagination.d.mts +1 -1
  43. package/dist/components/pagination.mjs +1 -1
  44. package/dist/components/popover.mjs +1 -1
  45. package/dist/components/progress-circle.d.mts +3 -47
  46. package/dist/components/progress-circle.mjs +2 -47
  47. package/dist/components/progress.mjs +1 -1
  48. package/dist/components/radio-group.mjs +1 -1
  49. package/dist/components/radio.mjs +1 -1
  50. package/dist/components/scroll-area.d.mts +3 -19
  51. package/dist/components/scroll-area.mjs +4 -61
  52. package/dist/components/select.d.mts +1 -1
  53. package/dist/components/select.mjs +3 -3
  54. package/dist/components/separator.d.mts +3 -18
  55. package/dist/components/separator.mjs +3 -23
  56. package/dist/components/sheet.d.mts +6 -18
  57. package/dist/components/sheet.mjs +6 -49
  58. package/dist/components/sidebar.d.mts +4 -19
  59. package/dist/components/sidebar.mjs +10 -46
  60. package/dist/components/skeleton.mjs +1 -1
  61. package/dist/components/slider.mjs +1 -1
  62. package/dist/components/spinner.mjs +1 -1
  63. package/dist/components/switch.mjs +2 -2
  64. package/dist/components/table.mjs +1 -1
  65. package/dist/components/tabs.mjs +1 -1
  66. package/dist/components/textarea.mjs +1 -1
  67. package/dist/components/toggle-group.d.mts +3 -2
  68. package/dist/components/toggle-group.mjs +1 -1
  69. package/dist/components/toggle.d.mts +2 -21
  70. package/dist/components/toggle.mjs +2 -39
  71. package/dist/components/tooltip.mjs +1 -1
  72. package/dist/index.d.mts +31 -16
  73. package/dist/index.mjs +30 -15
  74. package/dist/lib/utils.d.mts +1 -12
  75. package/dist/lib/utils.mjs +1 -9
  76. package/dist/primitives/checkbox-group.d.mts +1 -2
  77. package/dist/primitives/input-number.d.mts +1 -2
  78. package/dist/primitives/input.d.mts +1 -2
  79. package/dist/primitives/progress-circle.d.mts +1 -2
  80. package/dist/variants/alert.d.mts +18 -0
  81. package/dist/variants/alert.mjs +25 -0
  82. package/dist/variants/badge.d.mts +20 -0
  83. package/dist/variants/badge.mjs +46 -0
  84. package/dist/variants/button-group.d.mts +18 -0
  85. package/dist/variants/button-group.mjs +26 -0
  86. package/dist/variants/button.d.mts +30 -0
  87. package/dist/variants/button.mjs +76 -0
  88. package/dist/variants/empty.d.mts +18 -0
  89. package/dist/variants/empty.mjs +20 -0
  90. package/dist/variants/field.d.mts +19 -0
  91. package/dist/variants/field.mjs +34 -0
  92. package/dist/variants/input-group.d.mts +43 -0
  93. package/dist/variants/input-group.mjs +93 -0
  94. package/dist/variants/item.d.mts +37 -0
  95. package/dist/variants/item.mjs +60 -0
  96. package/dist/variants/navigation-menu.d.mts +13 -0
  97. package/dist/variants/navigation-menu.mjs +12 -0
  98. package/dist/variants/progress-circle.d.mts +52 -0
  99. package/dist/variants/progress-circle.mjs +49 -0
  100. package/dist/variants/scroll-area.d.mts +24 -0
  101. package/dist/variants/scroll-area.mjs +63 -0
  102. package/dist/variants/separator.d.mts +23 -0
  103. package/dist/variants/separator.mjs +25 -0
  104. package/dist/variants/sheet.d.mts +20 -0
  105. package/dist/variants/sheet.mjs +50 -0
  106. package/dist/variants/sidebar.d.mts +23 -0
  107. package/dist/variants/sidebar.mjs +42 -0
  108. package/dist/variants/toggle.d.mts +23 -0
  109. package/dist/variants/toggle.mjs +43 -0
  110. package/package.json +168 -20
  111. package/src/components/accordion.tsx +156 -0
  112. package/src/components/alert-dialog.tsx +314 -0
  113. package/src/components/alert.tsx +86 -0
  114. package/src/components/aspect-ratio.tsx +28 -0
  115. package/src/components/avatar.tsx +84 -0
  116. package/src/components/badge.tsx +38 -0
  117. package/src/components/breadcrumb.tsx +197 -0
  118. package/src/components/button-group.tsx +107 -0
  119. package/src/components/button.tsx +66 -0
  120. package/src/components/calendar.tsx +277 -0
  121. package/src/components/card.tsx +175 -0
  122. package/src/components/carousel.tsx +367 -0
  123. package/src/components/chart.tsx +587 -0
  124. package/src/components/checkbox-cards.tsx +92 -0
  125. package/src/components/checkbox-group.tsx +83 -0
  126. package/src/components/checkbox.tsx +65 -0
  127. package/src/components/collapsible.tsx +60 -0
  128. package/src/components/command.tsx +311 -0
  129. package/src/components/context-menu.tsx +489 -0
  130. package/src/components/dialog.tsx +295 -0
  131. package/src/components/drawer.tsx +271 -0
  132. package/src/components/dropdown-menu.tsx +498 -0
  133. package/src/components/empty.tsx +169 -0
  134. package/src/components/field.tsx +362 -0
  135. package/src/components/form.tsx +300 -0
  136. package/src/components/hover-card.tsx +116 -0
  137. package/src/components/input-group.tsx +224 -0
  138. package/src/components/input-number.tsx +161 -0
  139. package/src/components/input-otp.tsx +151 -0
  140. package/src/components/input-password.tsx +74 -0
  141. package/src/components/input-search.tsx +98 -0
  142. package/src/components/input.tsx +52 -0
  143. package/src/components/item.tsx +280 -0
  144. package/src/components/kbd.tsx +59 -0
  145. package/src/components/label.tsx +44 -0
  146. package/src/components/menubar.tsx +531 -0
  147. package/src/components/native-select.tsx +96 -0
  148. package/src/components/navigation-menu.tsx +295 -0
  149. package/src/components/pagination.tsx +204 -0
  150. package/src/components/popover.tsx +139 -0
  151. package/src/components/progress-circle.tsx +203 -0
  152. package/src/components/progress.tsx +54 -0
  153. package/src/components/radio-cards.tsx +85 -0
  154. package/src/components/radio-group.tsx +79 -0
  155. package/src/components/radio.tsx +61 -0
  156. package/src/components/resizable.tsx +99 -0
  157. package/src/components/scroll-area.tsx +115 -0
  158. package/src/components/select.tsx +319 -0
  159. package/src/components/separator.tsx +74 -0
  160. package/src/components/sheet.tsx +278 -0
  161. package/src/components/sidebar.tsx +1056 -0
  162. package/src/components/skeleton.tsx +37 -0
  163. package/src/components/slider.tsx +95 -0
  164. package/src/components/sonner.tsx +47 -0
  165. package/src/components/spinner.tsx +75 -0
  166. package/src/components/switch.tsx +66 -0
  167. package/src/components/table.tsx +200 -0
  168. package/src/components/tabs.tsx +128 -0
  169. package/src/components/textarea.tsx +49 -0
  170. package/src/components/toggle-group.tsx +141 -0
  171. package/src/components/toggle.tsx +39 -0
  172. package/src/components/tooltip.tsx +141 -0
  173. package/src/css/amber.css +59 -22
  174. package/src/css/blue.css +59 -22
  175. package/src/css/cyan.css +59 -22
  176. package/src/css/emerald.css +59 -22
  177. package/src/css/fuchsia.css +59 -22
  178. package/src/css/gray.css +59 -22
  179. package/src/css/green.css +59 -22
  180. package/src/css/indigo.css +59 -22
  181. package/src/css/lime.css +59 -22
  182. package/src/css/neutral.css +59 -22
  183. package/src/css/orange.css +59 -22
  184. package/src/css/pink.css +59 -22
  185. package/src/css/preset.css +32 -13
  186. package/src/css/purple.css +59 -22
  187. package/src/css/red.css +59 -22
  188. package/src/css/rose.css +59 -22
  189. package/src/css/sky.css +59 -22
  190. package/src/css/slate.css +59 -22
  191. package/src/css/stone.css +59 -22
  192. package/src/css/teal.css +59 -22
  193. package/src/css/violet.css +59 -22
  194. package/src/css/yellow.css +59 -22
  195. package/src/css/zinc.css +59 -22
  196. package/src/hooks/use-animated-value.ts +97 -0
  197. package/src/hooks/use-copy-to-clipboard.ts +63 -0
  198. package/src/hooks/use-is-mobile.ts +27 -0
  199. package/src/hooks/use-media-query.ts +71 -0
  200. package/src/hooks/use-mutation-observer.ts +54 -0
  201. package/src/hooks/use-pagination.ts +166 -0
  202. package/src/index.ts +720 -0
  203. package/src/lib/utils.ts +5 -0
  204. package/src/primitives/checkbox-group.tsx +360 -0
  205. package/src/primitives/input-number.tsx +1013 -0
  206. package/src/primitives/input.tsx +243 -0
  207. package/src/primitives/progress-circle.tsx +537 -0
  208. package/src/variants/alert.ts +45 -0
  209. package/src/variants/badge.ts +66 -0
  210. package/src/variants/button-group.ts +49 -0
  211. package/src/variants/button.ts +93 -0
  212. package/src/variants/empty.ts +43 -0
  213. package/src/variants/field.ts +50 -0
  214. package/src/variants/input-group.ts +132 -0
  215. package/src/variants/item.ts +90 -0
  216. package/src/variants/navigation-menu.ts +32 -0
  217. package/src/variants/progress-circle.ts +47 -0
  218. package/src/variants/scroll-area.ts +79 -0
  219. package/src/variants/separator.ts +41 -0
  220. package/src/variants/sheet.ts +70 -0
  221. package/src/variants/sidebar.ts +61 -0
  222. package/src/variants/toggle.ts +59 -0
  223. package/dist/node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/clsx.d.mts +0 -6
@@ -0,0 +1,587 @@
1
+ "use client";
2
+
3
+ import type { Scope } from "@radix-ui/react-context";
4
+ import type { ComponentProps, ComponentType, CSSProperties, JSX, ReactNode } from "react";
5
+ import type { NameType, Payload, ValueType } from "recharts/types/component/DefaultTooltipContent";
6
+ import type { TooltipContentProps, TooltipProps } from "recharts";
7
+
8
+ import { cn } from "#/lib/utils";
9
+ import { createContextScope } from "@radix-ui/react-context";
10
+ import { useId, useMemo } from "react";
11
+ import * as RechartsPrimitive from "recharts";
12
+
13
+ /* -----------------------------------------------------------------------------
14
+ * Type Definitions and Utilities
15
+ * -------------------------------------------------------------------------- */
16
+
17
+ type ExtractProps<T> = T extends (props: infer P) => ReactNode ? P : never;
18
+
19
+ type MakeOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
20
+
21
+ /* -----------------------------------------------------------------------------
22
+ * Chart Configuration and Theme Constants
23
+ * --------------------------------------------------------------------------- */
24
+
25
+ const THEMES = { dark: ".dark", light: "" } as const;
26
+
27
+ /**
28
+ * @since 0.3.16-canary.0
29
+ */
30
+ type ChartConfig = Record<
31
+ string,
32
+ {
33
+ label?: ReactNode;
34
+ icon?: ComponentType;
35
+ } & (
36
+ | { color?: never; theme: Record<keyof typeof THEMES, string> }
37
+ | { color?: string; theme?: never }
38
+ )
39
+ >;
40
+
41
+ /* -----------------------------------------------------------------------------
42
+ * Context: ChartProvider
43
+ * --------------------------------------------------------------------------- */
44
+
45
+ const CHART_PROVIDER_NAME = "ChartProvider";
46
+
47
+ type ScopedProps<P> = P & { __scopeChart?: Scope };
48
+
49
+ /**
50
+ * Value provided by Chart Context
51
+ */
52
+ interface ChartContextValue {
53
+ /**
54
+ * Display configuration for the chart
55
+ */
56
+ config: ChartConfig;
57
+ }
58
+
59
+ const [createChartContext, createChartScope] = createContextScope(CHART_PROVIDER_NAME);
60
+
61
+ const [ChartContextProvider, useChartContext] =
62
+ createChartContext<ChartContextValue>(CHART_PROVIDER_NAME);
63
+
64
+ /* -----------------------------------------------------------------------------
65
+ * Component: Chart
66
+ * -------------------------------------------------------------------------- */
67
+
68
+ /**
69
+ * @since 0.3.16-canary.0
70
+ */
71
+ interface ChartContainerProps extends ComponentProps<"div"> {
72
+ children: ComponentProps<typeof RechartsPrimitive.ResponsiveContainer>["children"];
73
+ config: ChartConfig;
74
+ }
75
+
76
+ /**
77
+ * @since 0.3.16-canary.0
78
+ */
79
+ function ChartContainer({
80
+ __scopeChart,
81
+ children,
82
+ className,
83
+ config,
84
+ id,
85
+ ...props
86
+ }: ScopedProps<ChartContainerProps>): JSX.Element {
87
+ const uniqueId = useId();
88
+ const chartId = `chart-${id ?? uniqueId}`;
89
+
90
+ return (
91
+ <ChartContextProvider config={config} scope={__scopeChart}>
92
+ <div
93
+ className={cn(
94
+ "flex aspect-video justify-center text-xs",
95
+ "[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground",
96
+ "[&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50",
97
+ "[&_.recharts-curve.recharts-tooltip-cursor]:stroke-border",
98
+ "[&_.recharts-dot[stroke='#fff']]:stroke-transparent",
99
+ "[&_.recharts-layer]:outline-hidden",
100
+ "[&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border",
101
+ "[&_.recharts-radial-bar-background-sector]:fill-muted",
102
+ "[&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted",
103
+ "[&_.recharts-reference-line_[stroke='#ccc']]:stroke-border",
104
+ "[&_.recharts-sector]:outline-hidden",
105
+ "[&_.recharts-sector[stroke='#fff']]:stroke-transparent",
106
+ "[&_.recharts-surface]:outline-hidden",
107
+ className,
108
+ )}
109
+ data-chart={chartId}
110
+ data-slot="chart"
111
+ {...props}
112
+ >
113
+ <ChartStyle config={config} id={chartId} />
114
+ <RechartsPrimitive.ResponsiveContainer>{children}</RechartsPrimitive.ResponsiveContainer>
115
+ </div>
116
+ </ChartContextProvider>
117
+ );
118
+ }
119
+
120
+ /* -----------------------------------------------------------------------------
121
+ * Component: ChartStyle
122
+ * -------------------------------------------------------------------------- */
123
+
124
+ /**
125
+ * @since 0.3.16-canary.0
126
+ */
127
+ interface ChartStyleProps {
128
+ config: ChartConfig;
129
+ id: string;
130
+ }
131
+
132
+ /**
133
+ * @since 0.3.16-canary.0
134
+ */
135
+ function ChartStyle({ config, id }: ChartStyleProps): ReactNode {
136
+ const colorConfig = Object.entries(config).filter(
137
+ ([, itemConfig]) => itemConfig.theme ?? itemConfig.color,
138
+ );
139
+
140
+ if (colorConfig.length === 0) {
141
+ return null;
142
+ }
143
+
144
+ return (
145
+ <style
146
+ dangerouslySetInnerHTML={{
147
+ __html: generateChartStyles(id, colorConfig),
148
+ }}
149
+ />
150
+ );
151
+ }
152
+
153
+ /* -----------------------------------------------------------------------------
154
+ * Component: ChartTooltip
155
+ * -------------------------------------------------------------------------- */
156
+
157
+ /**
158
+ * @since 0.3.16-canary.0
159
+ */
160
+ type ChartTooltipProps<TValue extends ValueType, TName extends NameType> = TooltipProps<
161
+ TValue,
162
+ TName
163
+ >;
164
+
165
+ /**
166
+ * @since 0.3.16-canary.0
167
+ */
168
+ const ChartTooltip = RechartsPrimitive.Tooltip;
169
+
170
+ /* -----------------------------------------------------------------------------
171
+ * Component: ChartTooltipContent
172
+ * -------------------------------------------------------------------------- */
173
+
174
+ const CHART_TOOLTIP_CONTENT_NAME = "ChartTooltipContent";
175
+
176
+ /**
177
+ * @since 0.3.16-canary.0
178
+ */
179
+ type ChartTooltipContentProps<TValue extends ValueType, TName extends NameType> = Omit<
180
+ MakeOptional<
181
+ TooltipContentProps<TValue, TName>,
182
+ "accessibilityLayer" | "active" | "activeIndex" | "coordinate" | "payload"
183
+ >,
184
+ "payload"
185
+ > & {
186
+ hideIndicator?: boolean;
187
+ hideLabel?: boolean;
188
+ indicator?: "dashed" | "dot" | "line";
189
+ labelKey?: string;
190
+ nameKey?: string;
191
+ color?: string | undefined;
192
+ className?: string | undefined;
193
+ payload?: Array<Payload<TValue, TName>>;
194
+ };
195
+
196
+ /**
197
+ * @since 0.3.16-canary.0
198
+ */
199
+ function ChartTooltipContent<TValue extends ValueType, TName extends NameType>({
200
+ __scopeChart,
201
+ active,
202
+ className,
203
+ color,
204
+ formatter,
205
+ hideIndicator = false,
206
+ hideLabel = false,
207
+ indicator = "dot",
208
+ label,
209
+ labelClassName,
210
+ labelFormatter,
211
+ labelKey,
212
+ nameKey,
213
+ payload = [],
214
+ }: ScopedProps<ChartTooltipContentProps<TValue, TName>>): ReactNode {
215
+ const { config } = useChartContext(CHART_TOOLTIP_CONTENT_NAME, __scopeChart);
216
+
217
+ const tooltipLabel = useMemo((): ReactNode => {
218
+ if (hideLabel || payload.length === 0) {
219
+ return null;
220
+ }
221
+
222
+ const item = payload[0];
223
+ if (item === undefined) {
224
+ return null;
225
+ }
226
+ const key = safeToString(labelKey ?? item.dataKey ?? item.name ?? "value");
227
+ const itemConfig = getPayloadConfigFromPayload(config, item, key);
228
+ const configEntry = typeof label === "string" && label in config ? config[label] : undefined;
229
+ const value =
230
+ !labelKey && typeof label === "string"
231
+ ? configEntry !== undefined
232
+ ? (configEntry.label ?? label)
233
+ : label
234
+ : itemConfig?.label;
235
+
236
+ if (labelFormatter) {
237
+ return (
238
+ <div className={cn("font-medium", labelClassName)}>{labelFormatter(value, payload)}</div>
239
+ );
240
+ }
241
+
242
+ if (!value) {
243
+ return null;
244
+ }
245
+
246
+ return <div className={cn("font-medium", labelClassName)}>{value}</div>;
247
+ }, [label, labelFormatter, payload, hideLabel, labelClassName, config, labelKey]);
248
+
249
+ if (!active || payload.length === 0) {
250
+ return null;
251
+ }
252
+
253
+ const nestLabel = payload.length === 1 && indicator !== "dot";
254
+
255
+ return (
256
+ <div
257
+ className={cn(
258
+ "grid min-w-[8rem] items-start gap-1.5 px-2.5 py-1.5",
259
+ "rounded-lg border border-border/50",
260
+ "bg-background text-xs shadow-xl",
261
+ className,
262
+ )}
263
+ >
264
+ {nestLabel ? null : tooltipLabel}
265
+ <div className="grid gap-1.5">
266
+ {payload.map((item, index) => {
267
+ const key = safeToString(nameKey ?? item.name ?? item.dataKey ?? "value");
268
+ const itemConfig = getPayloadConfigFromPayload(config, item, key);
269
+ const indicatorColor =
270
+ color ??
271
+ (isRecord(item.payload) &&
272
+ "fill" in item.payload &&
273
+ typeof item.payload.fill === "string"
274
+ ? item.payload.fill
275
+ : undefined) ??
276
+ item.color;
277
+
278
+ return (
279
+ <div
280
+ key={key}
281
+ className={cn(
282
+ "flex w-full flex-wrap items-stretch gap-2",
283
+ "[&>svg]:size-2.5 [&>svg]:text-muted-foreground",
284
+ indicator === "dot" && "items-center",
285
+ )}
286
+ >
287
+ {formatter && item.value !== undefined && item.name ? (
288
+ formatter(item.value, item.name, item, index, [item])
289
+ ) : (
290
+ <>
291
+ {itemConfig?.icon ? (
292
+ <itemConfig.icon />
293
+ ) : (
294
+ !hideIndicator && (
295
+ <div
296
+ className={cn(
297
+ "shrink-0",
298
+ "rounded-xs border-(--color-border)",
299
+ "bg-(--color-bg)",
300
+ {
301
+ "h-2.5 w-2.5": indicator === "dot",
302
+ "my-0.5": nestLabel && indicator === "dashed",
303
+ "border-1.5 w-0 border-dashed bg-transparent": indicator === "dashed",
304
+ "w-1": indicator === "line",
305
+ },
306
+ )}
307
+ style={
308
+ {
309
+ "--color-bg": indicatorColor,
310
+ "--color-border": indicatorColor,
311
+ } as CSSProperties
312
+ }
313
+ />
314
+ )
315
+ )}
316
+ <div
317
+ className={cn(
318
+ "flex flex-1 justify-between",
319
+ "leading-none",
320
+ nestLabel ? "items-end" : "items-center",
321
+ )}
322
+ >
323
+ <div className="grid gap-1.5">
324
+ {nestLabel ? tooltipLabel : null}
325
+ <span className="text-muted-foreground">
326
+ {itemConfig?.label ?? item.name}
327
+ </span>
328
+ </div>
329
+ {item.value != null && (
330
+ <span className={cn("font-mono font-medium text-foreground", "tabular-nums")}>
331
+ {typeof item.value === "number"
332
+ ? item.value.toLocaleString()
333
+ : safeToString(item.value)}
334
+ </span>
335
+ )}
336
+ </div>
337
+ </>
338
+ )}
339
+ </div>
340
+ );
341
+ })}
342
+ </div>
343
+ </div>
344
+ );
345
+ }
346
+
347
+ /* -----------------------------------------------------------------------------
348
+ * Component: ChartLegend
349
+ * -------------------------------------------------------------------------- */
350
+
351
+ /**
352
+ * @since 0.3.16-canary.0
353
+ */
354
+ type ChartLegendProps = ComponentProps<typeof RechartsPrimitive.Legend>;
355
+
356
+ /**
357
+ * @since 0.3.16-canary.0
358
+ */
359
+ const ChartLegend: typeof RechartsPrimitive.Legend = RechartsPrimitive.Legend;
360
+
361
+ /* -----------------------------------------------------------------------------
362
+ * Component: ChartLegendContent
363
+ * -------------------------------------------------------------------------- */
364
+
365
+ const CHART_LEGEND_CONTENT_NAME = "ChartLegendContent";
366
+
367
+ /**
368
+ * @since 0.3.16-canary.0
369
+ */
370
+ type ChartLegendContentProps = ComponentProps<"div"> &
371
+ ExtractProps<RechartsPrimitive.LegendProps["content"]> & {
372
+ hideIcon?: boolean;
373
+ nameKey?: string;
374
+ };
375
+
376
+ /**
377
+ * @since 0.3.16-canary.0
378
+ */
379
+ function ChartLegendContent({
380
+ __scopeChart,
381
+ className,
382
+ hideIcon = false,
383
+ nameKey,
384
+ payload,
385
+ verticalAlign = "bottom",
386
+ }: ScopedProps<ChartLegendContentProps>): ReactNode {
387
+ const { config } = useChartContext(CHART_LEGEND_CONTENT_NAME, __scopeChart);
388
+
389
+ if (!payload?.length) {
390
+ return null;
391
+ }
392
+
393
+ return (
394
+ <div
395
+ className={cn(
396
+ "flex items-center justify-center gap-4",
397
+ verticalAlign === "top" ? "pb-3" : "pt-3",
398
+ className,
399
+ )}
400
+ >
401
+ {payload.map((item) => {
402
+ let key = "value";
403
+
404
+ if (nameKey) {
405
+ key = nameKey;
406
+ } else if (item.dataKey != null) {
407
+ key = safeToString(item.dataKey);
408
+ }
409
+
410
+ const itemConfig = getPayloadConfigFromPayload(config, item, key);
411
+
412
+ return (
413
+ <div
414
+ key={nameKey ? safeToString(itemConfig?.color ?? "") : safeToString(item.value ?? "")}
415
+ className={cn(
416
+ "flex items-center gap-1.5",
417
+ "[&>svg]:size-3 [&>svg]:text-muted-foreground",
418
+ )}
419
+ >
420
+ {itemConfig?.icon && !hideIcon ? (
421
+ <itemConfig.icon />
422
+ ) : (
423
+ <div
424
+ className={cn("size-2 shrink-0", "rounded-md")}
425
+ style={{
426
+ backgroundColor: item.color,
427
+ }}
428
+ />
429
+ )}
430
+ {itemConfig?.label}
431
+ </div>
432
+ );
433
+ })}
434
+ </div>
435
+ );
436
+ }
437
+
438
+ /* -----------------------------------------------------------------------------
439
+ * Helpers
440
+ * -------------------------------------------------------------------------- */
441
+
442
+ /**
443
+ * Type guard to check if an unknown value is a record with string keys
444
+ */
445
+ function isRecord(value: unknown): value is Record<string, unknown> {
446
+ return typeof value === "object" && value !== null;
447
+ }
448
+
449
+ /**
450
+ * Safely gets a string value from a record by key
451
+ */
452
+ function getStringValue(record: Record<string, unknown>, key: string): string | undefined {
453
+ if (key in record) {
454
+ const value = record[key];
455
+
456
+ return typeof value === "string" ? value : undefined;
457
+ }
458
+
459
+ return undefined;
460
+ }
461
+
462
+ /**
463
+ * Safely converts a value to string without type coercion
464
+ */
465
+ function safeToString(value: unknown): string {
466
+ if (typeof value === "string") {
467
+ return value;
468
+ }
469
+
470
+ if (typeof value === "number") {
471
+ return value.toString();
472
+ }
473
+
474
+ if (value === null || value === undefined) {
475
+ return "";
476
+ }
477
+
478
+ if (typeof value === "boolean") {
479
+ return value.toString();
480
+ }
481
+
482
+ if (typeof value === "bigint") {
483
+ return value.toString();
484
+ }
485
+
486
+ if (typeof value === "symbol") {
487
+ return value.toString();
488
+ }
489
+
490
+ // For objects, arrays, functions, and other complex types, return empty string to avoid [object Object]
491
+ return "";
492
+ }
493
+
494
+ function getPayloadConfigFromPayload(
495
+ config: ChartConfig,
496
+ payload: unknown,
497
+ key: string,
498
+ ): ChartConfig[string] | undefined {
499
+ if (!isRecord(payload)) {
500
+ return undefined;
501
+ }
502
+
503
+ const payloadPayload = isRecord(payload.payload) ? payload.payload : undefined;
504
+ let configLabelKey: string = key;
505
+
506
+ // Try to get the config key from the payload first
507
+ const payloadValue = getStringValue(payload, key);
508
+
509
+ if (payloadValue) {
510
+ configLabelKey = payloadValue;
511
+ } else if (payloadPayload) {
512
+ // If not found in the payload, try the nested payload
513
+ const nestedValue = getStringValue(payloadPayload, key);
514
+
515
+ if (nestedValue) {
516
+ configLabelKey = nestedValue;
517
+ }
518
+ }
519
+
520
+ return configLabelKey in config ? config[configLabelKey] : config[key];
521
+ }
522
+
523
+ /**
524
+ * Generates CSS custom property for a specific theme and config item
525
+ */
526
+ function generateCssVariable(
527
+ key: string,
528
+ itemConfig: ChartConfig[string],
529
+ theme: string,
530
+ ): null | string {
531
+ const color = itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ?? itemConfig.color;
532
+
533
+ return color ? ` --color-${key}: ${color};` : null;
534
+ }
535
+
536
+ /**
537
+ * Generates CSS rules for a specific theme
538
+ */
539
+ function generateThemeStyles(
540
+ theme: string,
541
+ prefix: string,
542
+ id: string,
543
+ colorConfig: Array<[string, ChartConfig[string]]>,
544
+ ): string {
545
+ const cssVariables = colorConfig
546
+ .map(([key, itemConfig]) => generateCssVariable(key, itemConfig, theme))
547
+ .filter(Boolean)
548
+ .join("\n");
549
+
550
+ return `${prefix} [data-chart=${id}] {\n${cssVariables}\n}`;
551
+ }
552
+
553
+ /**
554
+ * Generates complete CSS styles for all themes
555
+ */
556
+ function generateChartStyles(
557
+ id: string,
558
+ colorConfig: Array<[string, ChartConfig[string]]>,
559
+ ): string {
560
+ return Object.entries(THEMES)
561
+ .map(([theme, prefix]) => generateThemeStyles(theme, prefix, id, colorConfig))
562
+ .join("\n\n");
563
+ }
564
+
565
+ /* -----------------------------------------------------------------------------
566
+ * Exports
567
+ * -------------------------------------------------------------------------- */
568
+
569
+ export type {
570
+ ChartConfig,
571
+ ChartContainerProps,
572
+ ChartLegendContentProps,
573
+ ChartLegendProps,
574
+ ChartStyleProps,
575
+ ChartTooltipContentProps,
576
+ ChartTooltipProps,
577
+ };
578
+
579
+ export {
580
+ ChartContainer,
581
+ ChartLegend,
582
+ ChartLegendContent,
583
+ ChartStyle,
584
+ ChartTooltip,
585
+ ChartTooltipContent,
586
+ createChartScope,
587
+ };
@@ -0,0 +1,92 @@
1
+ "use client";
2
+
3
+ import type { ComponentProps, JSX } from "react";
4
+
5
+ import { cn } from "#/lib/utils";
6
+ import { CheckIcon } from "lucide-react";
7
+
8
+ import { Label } from "#/components/label";
9
+ import * as CheckboxGroupPrimitive from "#/primitives/checkbox-group";
10
+
11
+ /* -----------------------------------------------------------------------------
12
+ * Component: CheckboxCards
13
+ * -------------------------------------------------------------------------- */
14
+
15
+ /**
16
+ * @since 0.3.16-canary.0
17
+ */
18
+ type CheckboxCardsProps = ComponentProps<typeof CheckboxGroupPrimitive.Root>;
19
+
20
+ /**
21
+ * @since 0.3.16-canary.0
22
+ */
23
+ function CheckboxCards(props: CheckboxCardsProps): JSX.Element {
24
+ return <CheckboxGroupPrimitive.Root data-slot="checkbox-cards" {...props} />;
25
+ }
26
+
27
+ /* -----------------------------------------------------------------------------
28
+ * Component: CheckboxCardsItem
29
+ * -------------------------------------------------------------------------- */
30
+
31
+ /**
32
+ * @since 0.3.16-canary.0
33
+ */
34
+ interface CheckboxCardsItemProps extends ComponentProps<typeof CheckboxGroupPrimitive.Item> {
35
+ checkboxClassName?: string;
36
+ }
37
+
38
+ /**
39
+ * @since 0.3.16-canary.0
40
+ */
41
+ function CheckboxCardsItem({
42
+ checkboxClassName,
43
+ children,
44
+ className,
45
+ ...props
46
+ }: CheckboxCardsItemProps): JSX.Element {
47
+ return (
48
+ <Label
49
+ className={cn(
50
+ "flex items-start gap-3",
51
+ "p-3",
52
+ "rounded-lg border border-input",
53
+ "transition",
54
+ "hover:not-has-disabled:not-has-aria-checked:bg-secondary",
55
+ "has-focus-visible:border-ring",
56
+ "has-disabled:opacity-50",
57
+ "has-aria-checked:border-primary has-aria-checked:bg-primary/10",
58
+ className,
59
+ )}
60
+ data-slot="checkbox-card"
61
+ >
62
+ <CheckboxGroupPrimitive.Item
63
+ className={cn(
64
+ "peer flex size-4 shrink-0",
65
+ "rounded-sm border border-input shadow-xs outline-hidden",
66
+ "text-primary-foreground",
67
+ "transition",
68
+ "focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50",
69
+ "aria-checked:border-primary aria-checked:bg-primary",
70
+ "focus-visible:aria-checked:ring-primary/20",
71
+ "dark:bg-input/30",
72
+ "dark:focus-visible:aria-checked:ring-primary/40",
73
+ checkboxClassName,
74
+ )}
75
+ data-slot="checkbox-card-item"
76
+ {...props}
77
+ >
78
+ <CheckboxGroupPrimitive.CheckboxGroupIndicator data-slot="checkbox-card-indicator">
79
+ <CheckIcon className="size-3.5" />
80
+ </CheckboxGroupPrimitive.CheckboxGroupIndicator>
81
+ </CheckboxGroupPrimitive.Item>
82
+ {children}
83
+ </Label>
84
+ );
85
+ }
86
+
87
+ /* -----------------------------------------------------------------------------
88
+ * Exports
89
+ * -------------------------------------------------------------------------- */
90
+
91
+ export { CheckboxCards, CheckboxCardsItem };
92
+ export type { CheckboxCardsItemProps, CheckboxCardsProps };