@srcroot/ui 0.0.55 → 0.0.58

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 (107) hide show
  1. package/README.md +151 -151
  2. package/dist/index.d.ts +0 -0
  3. package/dist/index.js +120 -93
  4. package/package.json +7 -2
  5. package/src/registry/analytics/google-analytics.tsx +36 -39
  6. package/src/registry/analytics/google-tag-manager.tsx +62 -65
  7. package/src/registry/analytics/meta-pixel.tsx +44 -47
  8. package/src/registry/analytics/microsoft-clarity.tsx +31 -34
  9. package/src/registry/analytics/tiktok-pixel.tsx +34 -37
  10. package/src/registry/lib/utils.ts +0 -0
  11. package/src/registry/themes/v3/blue.css +157 -157
  12. package/src/registry/themes/v3/glass.css +153 -153
  13. package/src/registry/themes/v3/gray.css +157 -157
  14. package/src/registry/themes/v3/green.css +157 -157
  15. package/src/registry/themes/v3/neutral.css +157 -157
  16. package/src/registry/themes/v3/orange.css +157 -157
  17. package/src/registry/themes/v3/rose.css +157 -157
  18. package/src/registry/themes/v3/slate.css +157 -157
  19. package/src/registry/themes/v3/stone.css +157 -157
  20. package/src/registry/themes/v3/violet.css +186 -186
  21. package/src/registry/themes/v3/zinc.css +157 -157
  22. package/src/registry/themes/v4/blue.css +184 -184
  23. package/src/registry/themes/v4/glass.css +180 -180
  24. package/src/registry/themes/v4/gray.css +184 -184
  25. package/src/registry/themes/v4/green.css +184 -184
  26. package/src/registry/themes/v4/neutral.css +184 -184
  27. package/src/registry/themes/v4/orange.css +184 -184
  28. package/src/registry/themes/v4/rose.css +184 -184
  29. package/src/registry/themes/v4/slate.css +184 -184
  30. package/src/registry/themes/v4/stone.css +184 -184
  31. package/src/registry/themes/v4/violet.css +184 -184
  32. package/src/registry/themes/v4/zinc.css +184 -184
  33. package/src/registry/ui/accordion.tsx +164 -165
  34. package/src/registry/ui/alert-dialog.tsx +213 -214
  35. package/src/registry/ui/alert.tsx +73 -76
  36. package/src/registry/ui/aspect-ratio.tsx +44 -47
  37. package/src/registry/ui/avatar.tsx +96 -97
  38. package/src/registry/ui/badge.tsx +52 -55
  39. package/src/registry/ui/breadcrumb.tsx +147 -150
  40. package/src/registry/ui/button-group.tsx +64 -67
  41. package/src/registry/ui/button.tsx +71 -72
  42. package/src/registry/ui/calendar.tsx +514 -515
  43. package/src/registry/ui/card.tsx +88 -91
  44. package/src/registry/ui/carousel.tsx +214 -214
  45. package/src/registry/ui/chart.tsx +373 -373
  46. package/src/registry/ui/chatbot.tsx +86 -13
  47. package/src/registry/ui/checkbox.tsx +93 -94
  48. package/src/registry/ui/collapsible.tsx +107 -108
  49. package/src/registry/ui/combobox.tsx +171 -171
  50. package/src/registry/ui/command.tsx +300 -300
  51. package/src/registry/ui/container.tsx +44 -47
  52. package/src/registry/ui/context-menu.tsx +221 -221
  53. package/src/registry/ui/date-picker.tsx +228 -228
  54. package/src/registry/ui/dialog.tsx +269 -270
  55. package/src/registry/ui/drawer.tsx +10 -4
  56. package/src/registry/ui/dropdown-menu.tsx +529 -530
  57. package/src/registry/ui/empty-state.tsx +0 -2
  58. package/src/registry/ui/file-upload.tsx +0 -0
  59. package/src/registry/ui/floating-dock.tsx +0 -0
  60. package/src/registry/ui/form-field.tsx +91 -94
  61. package/src/registry/ui/google-analytics.tsx +38 -0
  62. package/src/registry/ui/google-tag-manager.tsx +64 -0
  63. package/src/registry/ui/hover-card.tsx +223 -223
  64. package/src/registry/ui/image.tsx +144 -147
  65. package/src/registry/ui/input-group.tsx +82 -85
  66. package/src/registry/ui/input.tsx +125 -125
  67. package/src/registry/ui/kbd.tsx +60 -63
  68. package/src/registry/ui/label.tsx +36 -37
  69. package/src/registry/ui/loading-spinner.tsx +108 -111
  70. package/src/registry/ui/map.tsx +0 -0
  71. package/src/registry/ui/marquee.tsx +2 -0
  72. package/src/registry/ui/menubar.tsx +246 -246
  73. package/src/registry/ui/meta-pixel.tsx +46 -0
  74. package/src/registry/ui/microsoft-clarity.tsx +33 -0
  75. package/src/registry/ui/native-select.tsx +49 -52
  76. package/src/registry/ui/otp-input.tsx +163 -155
  77. package/src/registry/ui/pagination.tsx +149 -152
  78. package/src/registry/ui/patterns.tsx +28 -0
  79. package/src/registry/ui/popover.tsx +226 -227
  80. package/src/registry/ui/progress.tsx +51 -52
  81. package/src/registry/ui/radio.tsx +99 -102
  82. package/src/registry/ui/resizable.tsx +314 -314
  83. package/src/registry/ui/scroll-animation.tsx +45 -0
  84. package/src/registry/ui/scroll-area.tsx +121 -122
  85. package/src/registry/ui/scroll-to-top.tsx +0 -0
  86. package/src/registry/ui/search.tsx +162 -150
  87. package/src/registry/ui/select.tsx +292 -293
  88. package/src/registry/ui/separator.tsx +46 -47
  89. package/src/registry/ui/sheet.tsx +6 -3
  90. package/src/registry/ui/sidebar.tsx +628 -628
  91. package/src/registry/ui/skeleton.tsx +26 -29
  92. package/src/registry/ui/slider.tsx +196 -197
  93. package/src/registry/ui/slot.tsx +69 -72
  94. package/src/registry/ui/star-rating.tsx +146 -134
  95. package/src/registry/ui/switch.tsx +72 -73
  96. package/src/registry/ui/table-of-contents.tsx +96 -96
  97. package/src/registry/ui/table.tsx +138 -139
  98. package/src/registry/ui/tabs.tsx +124 -125
  99. package/src/registry/ui/text.tsx +61 -64
  100. package/src/registry/ui/textarea.tsx +41 -42
  101. package/src/registry/ui/theme-switcher.tsx +66 -66
  102. package/src/registry/ui/tiktok-pixel.tsx +36 -0
  103. package/src/registry/ui/toast.tsx +97 -98
  104. package/src/registry/ui/toggle-group.tsx +129 -129
  105. package/src/registry/ui/toggle.tsx +72 -72
  106. package/src/registry/ui/tooltip.tsx +143 -144
  107. package/src/registry/ui/whatsapp.tsx +0 -0
@@ -1,373 +1,373 @@
1
- "use client"
2
-
3
- import * as React from "react"
4
- import {
5
- Legend as ChartLegendPrimitive,
6
- ResponsiveContainer,
7
- Tooltip as ChartTooltipPrimitive,
8
- } from "recharts"
9
-
10
- import { cn } from "@/lib/utils"
11
-
12
- // Format: { THEME_NAME: CSS_VARIABLE }
13
- const THEMES = { light: "", dark: ".dark" } as const
14
-
15
- export type ChartConfig = {
16
- [k in string]: {
17
- label?: React.ReactNode
18
- icon?: React.ComponentType
19
- } & (
20
- | { color?: string; theme?: never }
21
- | { color?: never; theme: Record<keyof typeof THEMES, string> }
22
- )
23
- }
24
-
25
- type ChartContextProps = {
26
- config: ChartConfig
27
- }
28
-
29
- const ChartContext = React.createContext<ChartContextProps | null>(null)
30
-
31
- function useChart() {
32
- const context = React.useContext(ChartContext)
33
-
34
- if (!context) {
35
- throw new Error("useChart must be used within a <ChartContainer />")
36
- }
37
-
38
- return context
39
- }
40
-
41
- const ChartContainer = React.forwardRef<
42
- HTMLDivElement,
43
- React.ComponentProps<"div"> & {
44
- config: ChartConfig
45
- children: React.ComponentProps<typeof ResponsiveContainer>["children"]
46
- }
47
- >(({ id, className, children, config, ...props }, ref) => {
48
- const uniqueId = React.useId()
49
- const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
50
-
51
- return (
52
- <ChartContext.Provider value={{ config }}>
53
- <div
54
- data-chart={chartId}
55
- ref={ref}
56
- className={cn(
57
- "flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
58
- className
59
- )}
60
- {...props}
61
- >
62
- <ChartStyle id={chartId} config={config} />
63
- <ResponsiveContainer>
64
- {children}
65
- </ResponsiveContainer>
66
- </div>
67
- </ChartContext.Provider>
68
- )
69
- })
70
- ChartContainer.displayName = "ChartContainer"
71
-
72
- const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
73
- const colorConfig = Object.entries(config).filter(
74
- ([_, config]) => config.theme || config.color
75
- )
76
-
77
- if (!colorConfig.length) {
78
- return null
79
- }
80
-
81
- return (
82
- <style
83
- dangerouslySetInnerHTML={{
84
- __html: Object.entries(THEMES)
85
- .map(
86
- ([theme, prefix]) => `
87
- ${prefix} [data-chart=${id}] {
88
- ${colorConfig
89
- .map(([key, itemConfig]) => {
90
- const color =
91
- itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
92
- itemConfig.color
93
- return color ? ` --color-${key}: ${color};` : null
94
- })
95
- .join("\n")}
96
- }
97
- `
98
- )
99
- .join("\n"),
100
- }}
101
- />
102
- )
103
- }
104
-
105
- const ChartTooltip = ChartTooltipPrimitive
106
-
107
- const ChartTooltipContent = React.forwardRef<
108
- HTMLDivElement,
109
- Omit<React.ComponentProps<typeof ChartTooltipPrimitive>, "payload"> &
110
- React.ComponentProps<"div"> & {
111
- hideLabel?: boolean
112
- hideIndicator?: boolean
113
- indicator?: "line" | "dot" | "dashed"
114
- nameKey?: string
115
- labelKey?: string
116
- payload?: any[]
117
- label?: any
118
- labelFormatter?: any
119
- labelClassName?: string
120
- formatter?: any
121
- color?: string
122
- }
123
- >(
124
- (
125
- {
126
- active,
127
- payload,
128
- className,
129
- indicator = "dot",
130
- hideLabel = false,
131
- hideIndicator = false,
132
- label,
133
- labelFormatter,
134
- labelClassName,
135
- formatter,
136
- color,
137
- nameKey,
138
- labelKey,
139
- },
140
- ref
141
- ) => {
142
- const { config } = useChart()
143
-
144
- const tooltipLabel = React.useMemo(() => {
145
- if (hideLabel || !payload?.length) {
146
- return null
147
- }
148
-
149
- const [item] = payload
150
- const key = `${labelKey || item.dataKey || item.name || "value"}`
151
- const itemConfig = getPayloadConfigFromPayload(config, item, key)
152
- const value =
153
- !labelKey && typeof label === "string"
154
- ? config[label as keyof typeof config]?.label || label
155
- : itemConfig?.label
156
-
157
- if (labelFormatter) {
158
- return (
159
- <div className={cn("font-medium", labelClassName)}>
160
- {labelFormatter(value, payload)}
161
- </div>
162
- )
163
- }
164
-
165
- return value ? (
166
- <div className={cn("font-medium", labelClassName)}>{value}</div>
167
- ) : null
168
- }, [
169
- label,
170
- labelFormatter,
171
- payload,
172
- hideLabel,
173
- labelClassName,
174
- config,
175
- labelKey,
176
- ])
177
-
178
- if (!active || !payload?.length) {
179
- return null
180
- }
181
-
182
- const nestLabel = payload.length === 1 && indicator !== "dot"
183
-
184
- return (
185
- <div
186
- ref={ref}
187
- className={cn(
188
- "grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
189
- className
190
- )}
191
- >
192
- {!nestLabel ? tooltipLabel : null}
193
- <div className="grid gap-1.5">
194
- {payload.map((item: any, index: number) => {
195
- const key = `${nameKey || item.name || item.dataKey || "value"}`
196
- const itemConfig = getPayloadConfigFromPayload(config, item, key)
197
- const indicatorColor = color || item.payload.fill || item.color
198
-
199
- return (
200
- <div
201
- key={item.dataKey || item.name || index}
202
- className={cn(
203
- "flex items-center gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
204
- indicator === "dot" && "items-center"
205
- )}
206
- >
207
- {formatter && item?.value !== undefined && item.name ? (
208
- formatter(item.value, item.name, item, index, item.payload)
209
- ) : (
210
- <>
211
- {itemConfig?.icon ? (
212
- <itemConfig.icon />
213
- ) : (
214
- !hideIndicator && (
215
- <div
216
- className={cn(
217
- "shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
218
- {
219
- "h-2.5 w-2.5": indicator === "dot",
220
- "w-1": indicator === "line",
221
- "w-0 border-[1.5px] border-dashed bg-transparent":
222
- indicator === "dashed",
223
- "my-0.5": nestLabel && indicator === "dashed",
224
- }
225
- )}
226
- style={
227
- {
228
- "--color-bg": indicatorColor,
229
- "--color-border": indicatorColor,
230
- } as React.CSSProperties
231
- }
232
- />
233
- )
234
- )}
235
- <div
236
- className={cn(
237
- "flex flex-1 justify-between leading-none",
238
- nestLabel ? "items-end" : "items-center"
239
- )}
240
- >
241
- <div className="grid gap-1.5">
242
- {nestLabel ? tooltipLabel : null}
243
- <span className="text-muted-foreground">
244
- {itemConfig?.label || item.name}
245
- </span>
246
- </div>
247
- {item.value && (
248
- <span className="font-mono font-medium text-foreground tabular-nums">
249
- {item.value.toLocaleString()}
250
- </span>
251
- )}
252
- </div>
253
- </>
254
- )}
255
- </div>
256
- )
257
- })}
258
- </div>
259
- </div>
260
- )
261
- }
262
- )
263
- ChartTooltipContent.displayName = "ChartTooltip"
264
-
265
- const ChartLegend = ChartLegendPrimitive
266
-
267
- const ChartLegendContent = React.forwardRef<
268
- HTMLDivElement,
269
- React.ComponentProps<"div"> &
270
- {
271
- payload?: any[]
272
- verticalAlign?: "top" | "middle" | "bottom"
273
- hideIcon?: boolean
274
- nameKey?: string
275
- }
276
- >(
277
- (
278
- { className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
279
- ref
280
- ) => {
281
- const { config } = useChart()
282
-
283
- if (!payload?.length) {
284
- return null
285
- }
286
-
287
- return (
288
- <div
289
- ref={ref}
290
- className={cn(
291
- "flex items-center justify-center gap-4",
292
- verticalAlign === "top" ? "pb-3" : "pt-3",
293
- className
294
- )}
295
- >
296
- {(payload as any[]).map((item) => {
297
- const key = `${nameKey || item.dataKey || "value"}`
298
- const itemConfig = getPayloadConfigFromPayload(config, item, key)
299
-
300
- return (
301
- <div
302
- key={item.value}
303
- className={cn(
304
- "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
305
- )}
306
- >
307
- {itemConfig?.icon && !hideIcon ? (
308
- <itemConfig.icon />
309
- ) : (
310
- <div
311
- className="h-2 w-2 shrink-0 rounded-[2px]"
312
- style={{
313
- backgroundColor: item.color,
314
- }}
315
- />
316
- )}
317
- {itemConfig?.label}
318
- </div>
319
- )
320
- })}
321
- </div>
322
- )
323
- }
324
- )
325
- ChartLegendContent.displayName = "ChartLegend"
326
-
327
- // Helper to extract item config from a payload.
328
- function getPayloadConfigFromPayload(
329
- config: ChartConfig,
330
- payload: unknown,
331
- key: string
332
- ) {
333
- if (typeof payload !== "object" || payload === null) {
334
- return undefined
335
- }
336
-
337
- const payloadPayload =
338
- "payload" in payload &&
339
- typeof payload.payload === "object" &&
340
- payload.payload !== null
341
- ? payload.payload
342
- : undefined
343
-
344
- let configLabelKey: string = key
345
-
346
- if (
347
- key in payload &&
348
- typeof payload[key as keyof typeof payload] === "string"
349
- ) {
350
- configLabelKey = payload[key as keyof typeof payload] as string
351
- } else if (
352
- payloadPayload &&
353
- key in payloadPayload &&
354
- typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
355
- ) {
356
- configLabelKey = payloadPayload[
357
- key as keyof typeof payloadPayload
358
- ] as string
359
- }
360
-
361
- return configLabelKey in config
362
- ? config[configLabelKey]
363
- : config[key as keyof typeof config]
364
- }
365
-
366
- export {
367
- ChartContainer,
368
- ChartTooltip,
369
- ChartTooltipContent,
370
- ChartLegend,
371
- ChartLegendContent,
372
- ChartStyle,
373
- }
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import {
5
+ Legend as ChartLegendPrimitive,
6
+ ResponsiveContainer,
7
+ Tooltip as ChartTooltipPrimitive,
8
+ } from "recharts"
9
+
10
+ import { cn } from "@/lib/utils"
11
+
12
+ // Format: { THEME_NAME: CSS_VARIABLE }
13
+ const THEMES = { light: "", dark: ".dark" } as const
14
+
15
+ export type ChartConfig = {
16
+ [k in string]: {
17
+ label?: React.ReactNode
18
+ icon?: React.ComponentType
19
+ } & (
20
+ | { color?: string; theme?: never }
21
+ | { color?: never; theme: Record<keyof typeof THEMES, string> }
22
+ )
23
+ }
24
+
25
+ type ChartContextProps = {
26
+ config: ChartConfig
27
+ }
28
+
29
+ const ChartContext = React.createContext<ChartContextProps | null>(null)
30
+
31
+ function useChart() {
32
+ const context = React.useContext(ChartContext)
33
+
34
+ if (!context) {
35
+ throw new Error("useChart must be used within a <ChartContainer />")
36
+ }
37
+
38
+ return context
39
+ }
40
+
41
+ const ChartContainer = React.forwardRef<
42
+ HTMLDivElement,
43
+ React.ComponentProps<"div"> & {
44
+ config: ChartConfig
45
+ children: React.ComponentProps<typeof ResponsiveContainer>["children"]
46
+ }
47
+ >(({ id, className, children, config, ...props }, ref) => {
48
+ const uniqueId = React.useId()
49
+ const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
50
+
51
+ return (
52
+ <ChartContext.Provider value={{ config }}>
53
+ <div
54
+ data-chart={chartId}
55
+ ref={ref}
56
+ className={cn(
57
+ "flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
58
+ className
59
+ )}
60
+ {...props}
61
+ >
62
+ <ChartStyle id={chartId} config={config} />
63
+ <ResponsiveContainer>
64
+ {children}
65
+ </ResponsiveContainer>
66
+ </div>
67
+ </ChartContext.Provider>
68
+ )
69
+ })
70
+ ChartContainer.displayName = "ChartContainer"
71
+
72
+ const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
73
+ const colorConfig = Object.entries(config).filter(
74
+ ([_, config]) => config.theme || config.color
75
+ )
76
+
77
+ if (!colorConfig.length) {
78
+ return null
79
+ }
80
+
81
+ return (
82
+ <style
83
+ dangerouslySetInnerHTML={{
84
+ __html: Object.entries(THEMES)
85
+ .map(
86
+ ([theme, prefix]) => `
87
+ ${prefix} [data-chart=${id}] {
88
+ ${colorConfig
89
+ .map(([key, itemConfig]) => {
90
+ const color =
91
+ itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
92
+ itemConfig.color
93
+ return color ? ` --color-${key}: ${color};` : null
94
+ })
95
+ .join("\n")}
96
+ }
97
+ `
98
+ )
99
+ .join("\n"),
100
+ }}
101
+ />
102
+ )
103
+ }
104
+
105
+ const ChartTooltip = ChartTooltipPrimitive
106
+
107
+ const ChartTooltipContent = React.forwardRef<
108
+ HTMLDivElement,
109
+ Omit<React.ComponentProps<typeof ChartTooltipPrimitive>, "payload"> &
110
+ React.ComponentProps<"div"> & {
111
+ hideLabel?: boolean
112
+ hideIndicator?: boolean
113
+ indicator?: "line" | "dot" | "dashed"
114
+ nameKey?: string
115
+ labelKey?: string
116
+ payload?: any[]
117
+ label?: any
118
+ labelFormatter?: any
119
+ labelClassName?: string
120
+ formatter?: any
121
+ color?: string
122
+ }
123
+ >(
124
+ (
125
+ {
126
+ active,
127
+ payload,
128
+ className,
129
+ indicator = "dot",
130
+ hideLabel = false,
131
+ hideIndicator = false,
132
+ label,
133
+ labelFormatter,
134
+ labelClassName,
135
+ formatter,
136
+ color,
137
+ nameKey,
138
+ labelKey,
139
+ },
140
+ ref
141
+ ) => {
142
+ const { config } = useChart()
143
+
144
+ const tooltipLabel = React.useMemo(() => {
145
+ if (hideLabel || !payload?.length) {
146
+ return null
147
+ }
148
+
149
+ const [item] = payload
150
+ const key = `${labelKey || item.dataKey || item.name || "value"}`
151
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
152
+ const value =
153
+ !labelKey && typeof label === "string"
154
+ ? config[label as keyof typeof config]?.label || label
155
+ : itemConfig?.label
156
+
157
+ if (labelFormatter) {
158
+ return (
159
+ <div className={cn("font-medium", labelClassName)}>
160
+ {labelFormatter(value, payload)}
161
+ </div>
162
+ )
163
+ }
164
+
165
+ return value ? (
166
+ <div className={cn("font-medium", labelClassName)}>{value}</div>
167
+ ) : null
168
+ }, [
169
+ label,
170
+ labelFormatter,
171
+ payload,
172
+ hideLabel,
173
+ labelClassName,
174
+ config,
175
+ labelKey,
176
+ ])
177
+
178
+ if (!active || !payload?.length) {
179
+ return null
180
+ }
181
+
182
+ const nestLabel = payload.length === 1 && indicator !== "dot"
183
+
184
+ return (
185
+ <div
186
+ ref={ref}
187
+ className={cn(
188
+ "grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
189
+ className
190
+ )}
191
+ >
192
+ {!nestLabel ? tooltipLabel : null}
193
+ <div className="grid gap-1.5">
194
+ {payload.map((item: any, index: number) => {
195
+ const key = `${nameKey || item.name || item.dataKey || "value"}`
196
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
197
+ const indicatorColor = color || item.payload.fill || item.color
198
+
199
+ return (
200
+ <div
201
+ key={item.dataKey || item.name || index}
202
+ className={cn(
203
+ "flex items-center gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
204
+ indicator === "dot" && "items-center"
205
+ )}
206
+ >
207
+ {formatter && item?.value !== undefined && item.name ? (
208
+ formatter(item.value, item.name, item, index, item.payload)
209
+ ) : (
210
+ <>
211
+ {itemConfig?.icon ? (
212
+ <itemConfig.icon />
213
+ ) : (
214
+ !hideIndicator && (
215
+ <div
216
+ className={cn(
217
+ "shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
218
+ {
219
+ "h-2.5 w-2.5": indicator === "dot",
220
+ "w-1": indicator === "line",
221
+ "w-0 border-[1.5px] border-dashed bg-transparent":
222
+ indicator === "dashed",
223
+ "my-0.5": nestLabel && indicator === "dashed",
224
+ }
225
+ )}
226
+ style={
227
+ {
228
+ "--color-bg": indicatorColor,
229
+ "--color-border": indicatorColor,
230
+ } as React.CSSProperties
231
+ }
232
+ />
233
+ )
234
+ )}
235
+ <div
236
+ className={cn(
237
+ "flex flex-1 justify-between leading-none",
238
+ nestLabel ? "items-end" : "items-center"
239
+ )}
240
+ >
241
+ <div className="grid gap-1.5">
242
+ {nestLabel ? tooltipLabel : null}
243
+ <span className="text-muted-foreground">
244
+ {itemConfig?.label || item.name}
245
+ </span>
246
+ </div>
247
+ {item.value && (
248
+ <span className="font-mono font-medium text-foreground tabular-nums">
249
+ {item.value.toLocaleString()}
250
+ </span>
251
+ )}
252
+ </div>
253
+ </>
254
+ )}
255
+ </div>
256
+ )
257
+ })}
258
+ </div>
259
+ </div>
260
+ )
261
+ }
262
+ )
263
+ ChartTooltipContent.displayName = "ChartTooltip"
264
+
265
+ const ChartLegend = ChartLegendPrimitive
266
+
267
+ const ChartLegendContent = React.forwardRef<
268
+ HTMLDivElement,
269
+ React.ComponentProps<"div"> &
270
+ {
271
+ payload?: any[]
272
+ verticalAlign?: "top" | "middle" | "bottom"
273
+ hideIcon?: boolean
274
+ nameKey?: string
275
+ }
276
+ >(
277
+ (
278
+ { className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
279
+ ref
280
+ ) => {
281
+ const { config } = useChart()
282
+
283
+ if (!payload?.length) {
284
+ return null
285
+ }
286
+
287
+ return (
288
+ <div
289
+ ref={ref}
290
+ className={cn(
291
+ "flex items-center justify-center gap-4",
292
+ verticalAlign === "top" ? "pb-3" : "pt-3",
293
+ className
294
+ )}
295
+ >
296
+ {(payload as any[]).map((item) => {
297
+ const key = `${nameKey || item.dataKey || "value"}`
298
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
299
+
300
+ return (
301
+ <div
302
+ key={item.value}
303
+ className={cn(
304
+ "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
305
+ )}
306
+ >
307
+ {itemConfig?.icon && !hideIcon ? (
308
+ <itemConfig.icon />
309
+ ) : (
310
+ <div
311
+ className="h-2 w-2 shrink-0 rounded-[2px]"
312
+ style={{
313
+ backgroundColor: item.color,
314
+ }}
315
+ />
316
+ )}
317
+ {itemConfig?.label}
318
+ </div>
319
+ )
320
+ })}
321
+ </div>
322
+ )
323
+ }
324
+ )
325
+ ChartLegendContent.displayName = "ChartLegend"
326
+
327
+ // Helper to extract item config from a payload.
328
+ function getPayloadConfigFromPayload(
329
+ config: ChartConfig,
330
+ payload: unknown,
331
+ key: string
332
+ ) {
333
+ if (typeof payload !== "object" || payload === null) {
334
+ return undefined
335
+ }
336
+
337
+ const payloadPayload =
338
+ "payload" in payload &&
339
+ typeof payload.payload === "object" &&
340
+ payload.payload !== null
341
+ ? payload.payload
342
+ : undefined
343
+
344
+ let configLabelKey: string = key
345
+
346
+ if (
347
+ key in payload &&
348
+ typeof payload[key as keyof typeof payload] === "string"
349
+ ) {
350
+ configLabelKey = payload[key as keyof typeof payload] as string
351
+ } else if (
352
+ payloadPayload &&
353
+ key in payloadPayload &&
354
+ typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
355
+ ) {
356
+ configLabelKey = payloadPayload[
357
+ key as keyof typeof payloadPayload
358
+ ] as string
359
+ }
360
+
361
+ return configLabelKey in config
362
+ ? config[configLabelKey]
363
+ : config[key as keyof typeof config]
364
+ }
365
+
366
+ export {
367
+ ChartContainer,
368
+ ChartTooltip,
369
+ ChartTooltipContent,
370
+ ChartLegend,
371
+ ChartLegendContent,
372
+ ChartStyle,
373
+ }