@@ -23,7 +23,7 @@ type ChartContextProps = {
23
23
config : ChartConfig ;
24
24
} ;
25
25
26
- const ChartContext = React . createContext < ChartContextProps | null > ( null ) ;
26
+ export const ChartContext = React . createContext < ChartContextProps | null > ( null ) ;
27
27
28
28
function useChart ( ) {
29
29
const context = React . useContext ( ChartContext ) ;
@@ -35,7 +35,7 @@ function useChart() {
35
35
return context ;
36
36
}
37
37
38
- const ChartContainer = React . forwardRef <
38
+ export const ChartContainer = React . forwardRef <
39
39
HTMLDivElement ,
40
40
React . ComponentProps < "div" > & {
41
41
config : ChartConfig ;
@@ -53,7 +53,19 @@ const ChartContainer = React.forwardRef<
53
53
data-chart = { chartId }
54
54
ref = { ref }
55
55
className = { cn (
56
- "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" ,
56
+ "flex aspect-video justify-center text-xs" ,
57
+ "[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground" ,
58
+ "[&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50" ,
59
+ "[&_.recharts-curve.recharts-tooltip-cursor]:stroke-border" ,
60
+ "[&_.recharts-dot[stroke='#fff']]:stroke-transparent" ,
61
+ "[&_.recharts-layer]:outline-none" ,
62
+ "[&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border" ,
63
+ "[&_.recharts-radial-bar-background-sector]:fill-muted" ,
64
+ "[&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted" ,
65
+ "[&_.recharts-reference-line_[stroke='#ccc']]:stroke-border" ,
66
+ "[&_.recharts-sector[stroke='#fff']]:stroke-transparent" ,
67
+ "[&_.recharts-sector]:outline-none" ,
68
+ "[&_.recharts-surface]:outline-none" ,
57
69
className ,
58
70
) }
59
71
{ ...props }
@@ -68,7 +80,10 @@ const ChartContainer = React.forwardRef<
68
80
} ) ;
69
81
ChartContainer . displayName = "Chart" ;
70
82
71
- const ChartStyle = ( { id, config } : { id : string ; config : ChartConfig } ) => {
83
+ export const ChartStyle = ( {
84
+ id,
85
+ config,
86
+ } : { id : string ; config : ChartConfig } ) => {
72
87
const colorConfig = Object . entries ( config ) . filter (
73
88
( [ , config ] ) => config . theme || config . color ,
74
89
) ;
@@ -93,275 +108,10 @@ ${colorConfig
93
108
return color ? ` --color-${ key } : ${ color } ;` : null ;
94
109
} )
95
110
. join ( "\n" ) }
96
- }
97
- ` ,
111
+ }` ,
98
112
)
99
113
. join ( "\n" ) ,
100
114
} }
101
115
/>
102
116
) ;
103
117
} ;
104
-
105
- const ChartTooltip = RechartsPrimitive . Tooltip ;
106
-
107
- const ChartTooltipContent = React . forwardRef <
108
- HTMLDivElement ,
109
- React . ComponentProps < typeof RechartsPrimitive . Tooltip > &
110
- React . ComponentProps < "div" > & {
111
- hideLabel ?: boolean ;
112
- hideIndicator ?: boolean ;
113
- indicator ?: "line" | "dot" | "dashed" ;
114
- nameKey ?: string ;
115
- labelKey ?: string ;
116
- }
117
- > (
118
- (
119
- {
120
- active,
121
- payload,
122
- className,
123
- indicator = "dot" ,
124
- hideLabel = false ,
125
- hideIndicator = false ,
126
- label,
127
- labelFormatter,
128
- labelClassName,
129
- formatter,
130
- color,
131
- nameKey,
132
- labelKey,
133
- } ,
134
- ref ,
135
- ) => {
136
- const { config } = useChart ( ) ;
137
-
138
- const tooltipLabel = React . useMemo ( ( ) => {
139
- if ( hideLabel || ! payload ?. length ) {
140
- return null ;
141
- }
142
-
143
- const [ item ] = payload ;
144
- const key = `${ labelKey || item . dataKey || item . name || "value" } ` ;
145
- const itemConfig = getPayloadConfigFromPayload ( config , item , key ) ;
146
- const value =
147
- ! labelKey && typeof label === "string"
148
- ? config [ label as keyof typeof config ] ?. label || label
149
- : itemConfig ?. label ;
150
-
151
- if ( labelFormatter ) {
152
- return (
153
- < div className = { cn ( "font-medium" , labelClassName ) } >
154
- { labelFormatter ( value , payload ) }
155
- </ div >
156
- ) ;
157
- }
158
-
159
- if ( ! value ) {
160
- return null ;
161
- }
162
-
163
- return < div className = { cn ( "font-medium" , labelClassName ) } > { value } </ div > ;
164
- } , [
165
- label ,
166
- labelFormatter ,
167
- payload ,
168
- hideLabel ,
169
- labelClassName ,
170
- config ,
171
- labelKey ,
172
- ] ) ;
173
-
174
- if ( ! active || ! payload ?. length ) {
175
- return null ;
176
- }
177
-
178
- const nestLabel = payload . length === 1 && indicator !== "dot" ;
179
-
180
- return (
181
- < div
182
- ref = { ref }
183
- className = { cn (
184
- "grid min-w-[8rem] items-start gap-1.5 rounded-sm border border-solid bg-surface-primary px-3 py-2 text-xs shadow-xl" ,
185
- className ,
186
- ) }
187
- >
188
- { ! nestLabel ? tooltipLabel : null }
189
- < div className = "grid gap-1.5" >
190
- { payload . map ( ( item , index ) => {
191
- const key = `${ nameKey || item . name || item . dataKey || "value" } ` ;
192
- const itemConfig = getPayloadConfigFromPayload ( config , item , key ) ;
193
- const indicatorColor = color || item . payload . fill || item . color ;
194
-
195
- return (
196
- < div
197
- key = { item . dataKey }
198
- className = { cn (
199
- "flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground" ,
200
- indicator === "dot" && "items-center" ,
201
- ) }
202
- >
203
- { formatter && item ?. value !== undefined && item . name ? (
204
- formatter ( item . value , item . name , item , index , item . payload )
205
- ) : (
206
- < >
207
- { itemConfig ?. icon ? (
208
- < itemConfig . icon />
209
- ) : (
210
- ! hideIndicator && (
211
- < div
212
- className = { cn (
213
- "shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]" ,
214
- {
215
- "h-2.5 w-2.5" : indicator === "dot" ,
216
- "w-1" : indicator === "line" ,
217
- "w-0 border-[1.5px] border-dashed bg-transparent" :
218
- indicator === "dashed" ,
219
- "my-0.5" : nestLabel && indicator === "dashed" ,
220
- } ,
221
- ) }
222
- style = {
223
- {
224
- "--color-bg" : indicatorColor ,
225
- "--color-border" : indicatorColor ,
226
- } as React . CSSProperties
227
- }
228
- />
229
- )
230
- ) }
231
- < div
232
- className = { cn (
233
- "flex flex-1 justify-between leading-none" ,
234
- nestLabel ? "items-end" : "items-center" ,
235
- ) }
236
- >
237
- < div className = "grid gap-1.5" >
238
- { nestLabel ? tooltipLabel : null }
239
- < span className = "text-muted-foreground" >
240
- { itemConfig ?. label || item . name }
241
- </ span >
242
- </ div >
243
- { item . value && (
244
- < span className = "font-mono font-medium tabular-nums text-foreground" >
245
- { item . value . toLocaleString ( ) }
246
- </ span >
247
- ) }
248
- </ div >
249
- </ >
250
- ) }
251
- </ div >
252
- ) ;
253
- } ) }
254
- </ div >
255
- </ div >
256
- ) ;
257
- } ,
258
- ) ;
259
- ChartTooltipContent . displayName = "ChartTooltip" ;
260
-
261
- const ChartLegend = RechartsPrimitive . Legend ;
262
-
263
- const ChartLegendContent = React . forwardRef <
264
- HTMLDivElement ,
265
- React . ComponentProps < "div" > &
266
- Pick < RechartsPrimitive . LegendProps , "payload" | "verticalAlign" > & {
267
- hideIcon ?: boolean ;
268
- nameKey ?: string ;
269
- }
270
- > (
271
- (
272
- { className, hideIcon = false , payload, verticalAlign = "bottom" , nameKey } ,
273
- ref ,
274
- ) => {
275
- const { config } = useChart ( ) ;
276
-
277
- if ( ! payload ?. length ) {
278
- return null ;
279
- }
280
-
281
- return (
282
- < div
283
- ref = { ref }
284
- className = { cn (
285
- "flex items-center justify-center gap-4" ,
286
- verticalAlign === "top" ? "pb-3" : "pt-3" ,
287
- className ,
288
- ) }
289
- >
290
- { payload . map ( ( item ) => {
291
- const key = `${ nameKey || item . dataKey || "value" } ` ;
292
- const itemConfig = getPayloadConfigFromPayload ( config , item , key ) ;
293
-
294
- return (
295
- < div
296
- key = { item . value }
297
- className = { cn (
298
- "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground" ,
299
- ) }
300
- >
301
- { itemConfig ?. icon && ! hideIcon ? (
302
- < itemConfig . icon />
303
- ) : (
304
- < div
305
- className = "h-2 w-2 shrink-0 rounded-[2px]"
306
- style = { {
307
- backgroundColor : item . color ,
308
- } }
309
- />
310
- ) }
311
- { itemConfig ?. label }
312
- </ div >
313
- ) ;
314
- } ) }
315
- </ div >
316
- ) ;
317
- } ,
318
- ) ;
319
- ChartLegendContent . displayName = "ChartLegend" ;
320
-
321
- // Helper to extract item config from a payload.
322
- function getPayloadConfigFromPayload (
323
- config : ChartConfig ,
324
- payload : unknown ,
325
- key : string ,
326
- ) {
327
- if ( typeof payload !== "object" || payload === null ) {
328
- return undefined ;
329
- }
330
-
331
- const payloadPayload =
332
- "payload" in payload &&
333
- typeof payload . payload === "object" &&
334
- payload . payload !== null
335
- ? payload . payload
336
- : undefined ;
337
-
338
- let configLabelKey : string = key ;
339
-
340
- if (
341
- key in payload &&
342
- typeof payload [ key as keyof typeof payload ] === "string"
343
- ) {
344
- configLabelKey = payload [ key as keyof typeof payload ] as string ;
345
- } else if (
346
- payloadPayload &&
347
- key in payloadPayload &&
348
- typeof payloadPayload [ key as keyof typeof payloadPayload ] === "string"
349
- ) {
350
- configLabelKey = payloadPayload [
351
- key as keyof typeof payloadPayload
352
- ] as string ;
353
- }
354
-
355
- return configLabelKey in config
356
- ? config [ configLabelKey ]
357
- : config [ key as keyof typeof config ] ;
358
- }
359
-
360
- export {
361
- ChartContainer ,
362
- ChartTooltip ,
363
- ChartTooltipContent ,
364
- ChartLegend ,
365
- ChartLegendContent ,
366
- ChartStyle ,
367
- } ;
0 commit comments