huggingface/ai-deadlines
781
1import * as React from "react"2import * as RechartsPrimitive from "recharts"3 4import { cn } from "@/lib/utils"5 6// Format: { THEME_NAME: CSS_SELECTOR }7const THEMES = { light: "", dark: ".dark" } as const8 9export type ChartConfig = {10 [k in string]: {11 label?: React.ReactNode12 icon?: React.ComponentType13 } & (14 | { color?: string; theme?: never }15 | { color?: never; theme: Record<keyof typeof THEMES, string> }16 )17}18 19type ChartContextProps = {20 config: ChartConfig21}22 23const ChartContext = React.createContext<ChartContextProps | null>(null)24 25function useChart() {26 const context = React.useContext(ChartContext)27 28 if (!context) {29 throw new Error("useChart must be used within a <ChartContainer />")30 }31 32 return context33}34 35const ChartContainer = React.forwardRef<36 HTMLDivElement,37 React.ComponentProps<"div"> & {38 config: ChartConfig39 children: React.ComponentProps<40 typeof RechartsPrimitive.ResponsiveContainer41 >["children"]42 }43>(({ id, className, children, config, ...props }, ref) => {44 const uniqueId = React.useId()45 const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`46 47 return (48 <ChartContext.Provider value={{ config }}>49 <div50 data-chart={chartId}51 ref={ref}52 className={cn(53 "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",54 className55 )}56 {...props}57 >58 <ChartStyle id={chartId} config={config} />59 <RechartsPrimitive.ResponsiveContainer>60 {children}61 </RechartsPrimitive.ResponsiveContainer>62 </div>63 </ChartContext.Provider>64 )65})66ChartContainer.displayName = "Chart"67 68const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {69 const colorConfig = Object.entries(config).filter(70 ([_, config]) => config.theme || config.color71 )72 73 if (!colorConfig.length) {74 return null75 }76 77 return (78 <style79 dangerouslySetInnerHTML={{80 __html: Object.entries(THEMES)81 .map(82 ([theme, prefix]) => `83${prefix} [data-chart=${id}] {84${colorConfig85 .map(([key, itemConfig]) => {86 const color =87 itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||88 itemConfig.color89 return color ? ` --color-${key}: ${color};` : null90 })91 .join("\n")}92}93`94 )95 .join("\n"),96 }}97 />98 )99}100 101const ChartTooltip = RechartsPrimitive.Tooltip102 103const ChartTooltipContent = React.forwardRef<104 HTMLDivElement,105 React.ComponentProps<typeof RechartsPrimitive.Tooltip> &106 React.ComponentProps<"div"> & {107 hideLabel?: boolean108 hideIndicator?: boolean109 indicator?: "line" | "dot" | "dashed"110 nameKey?: string111 labelKey?: string112 }113>(114 (115 {116 active,117 payload,118 className,119 indicator = "dot",120 hideLabel = false,121 hideIndicator = false,122 label,123 labelFormatter,124 labelClassName,125 formatter,126 color,127 nameKey,128 labelKey,129 },130 ref131 ) => {132 const { config } = useChart()133 134 const tooltipLabel = React.useMemo(() => {135 if (hideLabel || !payload?.length) {136 return null137 }138 139 const [item] = payload140 const key = `${labelKey || item.dataKey || item.name || "value"}`141 const itemConfig = getPayloadConfigFromPayload(config, item, key)142 const value =143 !labelKey && typeof label === "string"144 ? config[label as keyof typeof config]?.label || label145 : itemConfig?.label146 147 if (labelFormatter) {148 return (149 <div className={cn("font-medium", labelClassName)}>150 {labelFormatter(value, payload)}151 </div>152 )153 }154 155 if (!value) {156 return null157 }158 159 return <div className={cn("font-medium", labelClassName)}>{value}</div>160 }, [161 label,162 labelFormatter,163 payload,164 hideLabel,165 labelClassName,166 config,167 labelKey,168 ])169 170 if (!active || !payload?.length) {171 return null172 }173 174 const nestLabel = payload.length === 1 && indicator !== "dot"175 176 return (177 <div178 ref={ref}179 className={cn(180 "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",181 className182 )}183 >184 {!nestLabel ? tooltipLabel : null}185 <div className="grid gap-1.5">186 {payload.map((item, index) => {187 const key = `${nameKey || item.name || item.dataKey || "value"}`188 const itemConfig = getPayloadConfigFromPayload(config, item, key)189 const indicatorColor = color || item.payload.fill || item.color190 191 return (192 <div193 key={item.dataKey}194 className={cn(195 "flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",196 indicator === "dot" && "items-center"197 )}198 >199 {formatter && item?.value !== undefined && item.name ? (200 formatter(item.value, item.name, item, index, item.payload)201 ) : (202 <>203 {itemConfig?.icon ? (204 <itemConfig.icon />205 ) : (206 !hideIndicator && (207 <div208 className={cn(209 "shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",210 {211 "h-2.5 w-2.5": indicator === "dot",212 "w-1": indicator === "line",213 "w-0 border-[1.5px] border-dashed bg-transparent":214 indicator === "dashed",215 "my-0.5": nestLabel && indicator === "dashed",216 }217 )}218 style={219 {220 "--color-bg": indicatorColor,221 "--color-border": indicatorColor,222 } as React.CSSProperties223 }224 />225 )226 )}227 <div228 className={cn(229 "flex flex-1 justify-between leading-none",230 nestLabel ? "items-end" : "items-center"231 )}232 >233 <div className="grid gap-1.5">234 {nestLabel ? tooltipLabel : null}235 <span className="text-muted-foreground">236 {itemConfig?.label || item.name}237 </span>238 </div>239 {item.value && (240 <span className="font-mono font-medium tabular-nums text-foreground">241 {item.value.toLocaleString()}242 </span>243 )}244 </div>245 </>246 )}247 </div>248 )249 })}250 </div>251 </div>252 )253 }254)255ChartTooltipContent.displayName = "ChartTooltip"256 257const ChartLegend = RechartsPrimitive.Legend258 259const ChartLegendContent = React.forwardRef<260 HTMLDivElement,261 React.ComponentProps<"div"> &262 Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {263 hideIcon?: boolean264 nameKey?: string265 }266>(267 (268 { className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },269 ref270 ) => {271 const { config } = useChart()272 273 if (!payload?.length) {274 return null275 }276 277 return (278 <div279 ref={ref}280 className={cn(281 "flex items-center justify-center gap-4",282 verticalAlign === "top" ? "pb-3" : "pt-3",283 className284 )}285 >286 {payload.map((item) => {287 const key = `${nameKey || item.dataKey || "value"}`288 const itemConfig = getPayloadConfigFromPayload(config, item, key)289 290 return (291 <div292 key={item.value}293 className={cn(294 "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"295 )}296 >297 {itemConfig?.icon && !hideIcon ? (298 <itemConfig.icon />299 ) : (300 <div301 className="h-2 w-2 shrink-0 rounded-[2px]"302 style={{303 backgroundColor: item.color,304 }}305 />306 )}307 {itemConfig?.label}308 </div>309 )310 })}311 </div>312 )313 }314)315ChartLegendContent.displayName = "ChartLegend"316 317// Helper to extract item config from a payload.318function getPayloadConfigFromPayload(319 config: ChartConfig,320 payload: unknown,321 key: string322) {323 if (typeof payload !== "object" || payload === null) {324 return undefined325 }326 327 const payloadPayload =328 "payload" in payload &&329 typeof payload.payload === "object" &&330 payload.payload !== null331 ? payload.payload332 : undefined333 334 let configLabelKey: string = key335 336 if (337 key in payload &&338 typeof payload[key as keyof typeof payload] === "string"339 ) {340 configLabelKey = payload[key as keyof typeof payload] as string341 } else if (342 payloadPayload &&343 key in payloadPayload &&344 typeof payloadPayload[key as keyof typeof payloadPayload] === "string"345 ) {346 configLabelKey = payloadPayload[347 key as keyof typeof payloadPayload348 ] as string349 }350 351 return configLabelKey in config352 ? config[configLabelKey]353 : config[key as keyof typeof config]354}355 356export {357 ChartContainer,358 ChartTooltip,359 ChartTooltipContent,360 ChartLegend,361 ChartLegendContent,362 ChartStyle,363}364 