@thesage/charts 0.1.0
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.
- package/.turbo/turbo-build.log +23 -0
- package/.turbo/turbo-lint.log +6 -0
- package/dist/index.d.mts +59 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +7 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +37 -0
- package/src/components/chart.tsx +352 -0
- package/src/index.ts +5 -0
- package/src/lib/utils.ts +6 -0
- package/tsconfig.json +30 -0
- package/tsup.config.ts +11 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
> @thesage/charts@0.1.0 build /Users/shalomormsby/Developer/work/ecosystem/packages/charts
|
|
4
|
+
> tsup src/index.ts --format cjs,esm --dts
|
|
5
|
+
|
|
6
|
+
[34mCLI[39m Building entry: src/index.ts
|
|
7
|
+
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
8
|
+
[34mCLI[39m tsup v8.5.1
|
|
9
|
+
[34mCLI[39m Using tsup config: /Users/shalomormsby/Developer/work/ecosystem/packages/charts/tsup.config.ts
|
|
10
|
+
[34mCLI[39m Target: es2020
|
|
11
|
+
[34mCLI[39m Cleaning output folder
|
|
12
|
+
[34mCJS[39m Build start
|
|
13
|
+
[34mESM[39m Build start
|
|
14
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m4.07 KB[39m
|
|
15
|
+
[32mESM[39m [1mdist/index.mjs.map [22m[32m17.06 KB[39m
|
|
16
|
+
[32mESM[39m ⚡️ Build success in 38ms
|
|
17
|
+
[32mCJS[39m [1mdist/index.js [22m[32m4.97 KB[39m
|
|
18
|
+
[32mCJS[39m [1mdist/index.js.map [22m[32m17.21 KB[39m
|
|
19
|
+
[32mCJS[39m ⚡️ Build success in 38ms
|
|
20
|
+
DTS Build start
|
|
21
|
+
DTS ⚡️ Build success in 1352ms
|
|
22
|
+
DTS dist/index.d.ts 2.37 KB
|
|
23
|
+
DTS dist/index.d.mts 2.37 KB
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { ResponsiveContainer, Tooltip, Legend } from 'recharts';
|
|
4
|
+
export * from 'recharts';
|
|
5
|
+
|
|
6
|
+
declare const THEMES: {
|
|
7
|
+
readonly light: {
|
|
8
|
+
readonly '--color-chart-1': "12 76% 61%";
|
|
9
|
+
readonly '--color-chart-2': "173 58% 39%";
|
|
10
|
+
readonly '--color-chart-3': "197 37% 24%";
|
|
11
|
+
readonly '--color-chart-4': "43 74% 66%";
|
|
12
|
+
readonly '--color-chart-5': "27 87% 67%";
|
|
13
|
+
};
|
|
14
|
+
readonly dark: {
|
|
15
|
+
readonly '--color-chart-1': "220 70% 50%";
|
|
16
|
+
readonly '--color-chart-2': "160 60% 45%";
|
|
17
|
+
readonly '--color-chart-3': "30 80% 55%";
|
|
18
|
+
readonly '--color-chart-4': "280 65% 60%";
|
|
19
|
+
readonly '--color-chart-5': "340 75% 55%";
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
type ChartConfig = {
|
|
23
|
+
[k in string]: {
|
|
24
|
+
label?: React.ReactNode;
|
|
25
|
+
icon?: React.ComponentType;
|
|
26
|
+
color?: string;
|
|
27
|
+
theme?: Record<keyof typeof THEMES, string>;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
declare const ChartContainer: React.ForwardRefExoticComponent<Omit<React.ClassAttributes<HTMLDivElement> & React.HTMLAttributes<HTMLDivElement> & {
|
|
31
|
+
config: ChartConfig;
|
|
32
|
+
children: React.ComponentProps<typeof ResponsiveContainer>["children"];
|
|
33
|
+
}, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
34
|
+
declare const ChartStyle: ({ id, config }: {
|
|
35
|
+
id: string;
|
|
36
|
+
config: ChartConfig;
|
|
37
|
+
}) => react_jsx_runtime.JSX.Element | null;
|
|
38
|
+
declare const ChartTooltip: typeof Tooltip;
|
|
39
|
+
declare const ChartTooltipContent: React.ForwardRefExoticComponent<Omit<React.ClassAttributes<HTMLDivElement> & React.HTMLAttributes<HTMLDivElement> & {
|
|
40
|
+
hideLabel?: boolean;
|
|
41
|
+
hideIndicator?: boolean;
|
|
42
|
+
indicator?: "line" | "dot" | "dashed";
|
|
43
|
+
nameKey?: string;
|
|
44
|
+
labelKey?: string;
|
|
45
|
+
active?: boolean;
|
|
46
|
+
payload?: any[];
|
|
47
|
+
label?: any;
|
|
48
|
+
labelFormatter?: (label: any, payload: any[]) => React.ReactNode;
|
|
49
|
+
config?: ChartConfig;
|
|
50
|
+
}, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
51
|
+
declare const ChartLegend: typeof Legend;
|
|
52
|
+
declare const ChartLegendContent: React.ForwardRefExoticComponent<Omit<React.ClassAttributes<HTMLDivElement> & React.HTMLAttributes<HTMLDivElement> & {
|
|
53
|
+
hideIcon?: boolean;
|
|
54
|
+
nameKey?: string;
|
|
55
|
+
verticalAlign?: "top" | "bottom" | "middle";
|
|
56
|
+
payload?: any[];
|
|
57
|
+
}, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
58
|
+
|
|
59
|
+
export { type ChartConfig, ChartContainer, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { ResponsiveContainer, Tooltip, Legend } from 'recharts';
|
|
4
|
+
export * from 'recharts';
|
|
5
|
+
|
|
6
|
+
declare const THEMES: {
|
|
7
|
+
readonly light: {
|
|
8
|
+
readonly '--color-chart-1': "12 76% 61%";
|
|
9
|
+
readonly '--color-chart-2': "173 58% 39%";
|
|
10
|
+
readonly '--color-chart-3': "197 37% 24%";
|
|
11
|
+
readonly '--color-chart-4': "43 74% 66%";
|
|
12
|
+
readonly '--color-chart-5': "27 87% 67%";
|
|
13
|
+
};
|
|
14
|
+
readonly dark: {
|
|
15
|
+
readonly '--color-chart-1': "220 70% 50%";
|
|
16
|
+
readonly '--color-chart-2': "160 60% 45%";
|
|
17
|
+
readonly '--color-chart-3': "30 80% 55%";
|
|
18
|
+
readonly '--color-chart-4': "280 65% 60%";
|
|
19
|
+
readonly '--color-chart-5': "340 75% 55%";
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
type ChartConfig = {
|
|
23
|
+
[k in string]: {
|
|
24
|
+
label?: React.ReactNode;
|
|
25
|
+
icon?: React.ComponentType;
|
|
26
|
+
color?: string;
|
|
27
|
+
theme?: Record<keyof typeof THEMES, string>;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
declare const ChartContainer: React.ForwardRefExoticComponent<Omit<React.ClassAttributes<HTMLDivElement> & React.HTMLAttributes<HTMLDivElement> & {
|
|
31
|
+
config: ChartConfig;
|
|
32
|
+
children: React.ComponentProps<typeof ResponsiveContainer>["children"];
|
|
33
|
+
}, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
34
|
+
declare const ChartStyle: ({ id, config }: {
|
|
35
|
+
id: string;
|
|
36
|
+
config: ChartConfig;
|
|
37
|
+
}) => react_jsx_runtime.JSX.Element | null;
|
|
38
|
+
declare const ChartTooltip: typeof Tooltip;
|
|
39
|
+
declare const ChartTooltipContent: React.ForwardRefExoticComponent<Omit<React.ClassAttributes<HTMLDivElement> & React.HTMLAttributes<HTMLDivElement> & {
|
|
40
|
+
hideLabel?: boolean;
|
|
41
|
+
hideIndicator?: boolean;
|
|
42
|
+
indicator?: "line" | "dot" | "dashed";
|
|
43
|
+
nameKey?: string;
|
|
44
|
+
labelKey?: string;
|
|
45
|
+
active?: boolean;
|
|
46
|
+
payload?: any[];
|
|
47
|
+
label?: any;
|
|
48
|
+
labelFormatter?: (label: any, payload: any[]) => React.ReactNode;
|
|
49
|
+
config?: ChartConfig;
|
|
50
|
+
}, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
51
|
+
declare const ChartLegend: typeof Legend;
|
|
52
|
+
declare const ChartLegendContent: React.ForwardRefExoticComponent<Omit<React.ClassAttributes<HTMLDivElement> & React.HTMLAttributes<HTMLDivElement> & {
|
|
53
|
+
hideIcon?: boolean;
|
|
54
|
+
nameKey?: string;
|
|
55
|
+
verticalAlign?: "top" | "bottom" | "middle";
|
|
56
|
+
payload?: any[];
|
|
57
|
+
}, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
58
|
+
|
|
59
|
+
export { type ChartConfig, ChartContainer, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";var V=Object.create;var y=Object.defineProperty;var q=Object.getOwnPropertyDescriptor;var z=Object.getOwnPropertyNames;var A=Object.getPrototypeOf,F=Object.prototype.hasOwnProperty;var O=(t,e)=>{for(var o in e)y(t,o,{get:e[o],enumerable:!0})},v=(t,e,o,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of z(e))!F.call(t,n)&&n!==o&&y(t,n,{get:()=>e[n],enumerable:!(r=q(e,n))||r.enumerable});return t},x=(t,e,o)=>(v(t,e,"default"),o&&v(o,e,"default")),W=(t,e,o)=>(o=t!=null?V(A(t)):{},v(e||!t||!t.__esModule?y(o,"default",{value:t,enumerable:!0}):o,t)),B=t=>v(y({},"__esModule",{value:!0}),t);var m={};O(m,{ChartContainer:()=>S,ChartLegend:()=>J,ChartLegendContent:()=>H,ChartStyle:()=>_,ChartTooltip:()=>G,ChartTooltipContent:()=>j});module.exports=B(m);var l=W(require("react")),u=require("recharts");var T=require("clsx"),E=require("tailwind-merge");function c(...t){return(0,E.twMerge)((0,T.clsx)(t))}var a=require("react/jsx-runtime");var M=l.createContext(null);function $(){let t=l.useContext(M);if(!t)throw new Error("useChart must be used within a <ChartContainer />");return t}var S=l.forwardRef(({id:t,className:e,children:o,config:r,...n},d)=>{let f=l.useId(),i=`chart-${t||f.replace(/:/g,"")}`;return(0,a.jsx)(M.Provider,{value:{config:r},children:(0,a.jsxs)("div",{"data-chart":i,ref:d,className:c("flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-[var(--color-text-secondary)] [&_.recharts-cartesian-grid_line]:stroke-[var(--color-border)]",e),...n,children:[(0,a.jsx)(_,{id:i,config:r}),(0,a.jsx)(u.ResponsiveContainer,{children:o})]})})});S.displayName="ChartContainer";var _=({id:t,config:e})=>{let o=Object.entries(e).filter(([r,n])=>n.theme||n.color);return o.length?(0,a.jsx)("style",{dangerouslySetInnerHTML:{__html:`
|
|
2
|
+
[data-chart=${t}] {
|
|
3
|
+
${o.map(([r,n])=>{let d=n.theme||n.color;return d?`--color-${r}: ${d};`:null}).join(`
|
|
4
|
+
`)}
|
|
5
|
+
}
|
|
6
|
+
`}}):null},G=u.Tooltip,j=l.forwardRef(({active:t,payload:e,className:o,indicator:r="dot",hideLabel:n=!1,hideIndicator:d=!1,label:f,labelFormatter:i,config:b,nameKey:R,labelKey:g},K)=>{let{config:D}=$(),h=b||D,k=l.useMemo(()=>{if(n||!e||!e.length)return null;let[s]=e,N=`${g||s.dataKey||s.name||"value"}`,w=P(h,s,N),p=!g&&typeof f=="string"?h[f]?.label||f:w?.label;return i?(0,a.jsx)("div",{className:c("font-medium"),children:i(p,e)}):p?(0,a.jsx)("div",{className:c("font-medium"),children:p}):null},[f,i,e,n,g,h]);if(!t||!e?.length)return null;let C=e.length===1&&r!=="dot";return(0,a.jsxs)("div",{ref:K,className:c("grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface)] px-2.5 py-1.5 text-xs shadow-xl transition-all ease-in-out animate-in fade-in-0 zoom-in-95",o),children:[C?null:k,(0,a.jsx)("div",{className:"grid gap-1.5",children:e.map((s,N)=>{let w=`${R||s.name||s.dataKey||"value"}`,p=P(h,s,w),L=I(s.payload.fill||s.color);return(0,a.jsxs)("div",{className:c("flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",r==="dot"&&"items-center"),children:[r&&!d&&(0,a.jsx)("div",{className:c("shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",{"h-2.5 w-2.5":r==="dot","w-1":r==="line","w-0 border-[1.5px] border-dashed bg-transparent":r==="dashed","my-0.5":C&&r==="dashed"}),style:{"--color-bg":L,"--color-border":L}}),(0,a.jsxs)("div",{className:c("flex flex-1 justify-between leading-none",C?"items-end":"items-center"),children:[(0,a.jsxs)("div",{className:"grid gap-1.5",children:[C?k:null,(0,a.jsx)("span",{className:"text-[var(--color-text-secondary)]",children:p?.label||s.name})]}),s.value&&(0,a.jsx)("span",{className:"font-mono font-medium tabular-nums text-[var(--color-text-primary)]",children:s.value.toLocaleString()})]})]},s.dataKey||N)})})]})});j.displayName="ChartTooltipContent";var J=u.Legend,H=l.forwardRef(({className:t,hideIcon:e=!1,payload:o,verticalAlign:r="bottom",nameKey:n},d)=>{let{config:f}=$();return o?.length?(0,a.jsx)("div",{ref:d,className:c("flex items-center justify-center gap-4",r==="top"?"pb-3":"pt-3",t),children:o.map(i=>{let b=`${n||i.dataKey||"value"}`,R=P(f,i,b),g=I(i.color);return(0,a.jsxs)("div",{className:c("flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"),children:[!e&&(0,a.jsx)("div",{className:"h-2 w-2 shrink-0 rounded-[2px]",style:{backgroundColor:g}}),(0,a.jsx)("span",{className:"text-sm text-[var(--color-text-primary)]",children:R?.label||i.value})]},i.value)})}):null});H.displayName="ChartLegendContent";function P(t,e,o){if(typeof e!="object"||e===null)return;let r="payload"in e&&typeof e.payload=="object"&&e.payload!==null?e.payload:void 0,n=o;return o in e&&typeof e[o]=="string"?n=e[o]:r&&o in r&&typeof r[o]=="string"&&(n=r[o]),n in t?t[n]:t[o]}function I(t){return t?(t.startsWith("var(--"),t):"var(--color-primary)"}x(m,require("recharts"),module.exports);0&&(module.exports={ChartContainer,ChartLegend,ChartLegendContent,ChartStyle,ChartTooltip,ChartTooltipContent,...require("recharts")});
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/components/chart.tsx","../src/lib/utils.ts"],"sourcesContent":["export * from './components/chart';\nexport * from 'recharts';\n\n// Override specific recharts exports if we wrapped them, \n// otherwise we just export the primitives and let usage include regular recharts components.\n","'use client';\n\nimport * as React from 'react';\nimport { ResponsiveContainer, Legend, Tooltip } from 'recharts';\nimport { cn } from '../lib/utils';\n\n// Format: { THEME_NAME: { cssVariable: string; value: string } }\nconst THEMES = {\n light: {\n '--color-chart-1': '12 76% 61%',\n '--color-chart-2': '173 58% 39%',\n '--color-chart-3': '197 37% 24%',\n '--color-chart-4': '43 74% 66%',\n '--color-chart-5': '27 87% 67%',\n },\n dark: {\n '--color-chart-1': '220 70% 50%',\n '--color-chart-2': '160 60% 45%',\n '--color-chart-3': '30 80% 55%',\n '--color-chart-4': '280 65% 60%',\n '--color-chart-5': '340 75% 55%',\n },\n} as const;\n\nexport type ChartConfig = {\n [k in string]: {\n label?: React.ReactNode;\n icon?: React.ComponentType;\n color?: string;\n theme?: Record<keyof typeof THEMES, string>;\n }\n};\n\ntype ChartContextProps = {\n config: ChartConfig;\n};\n\nconst ChartContext = React.createContext<ChartContextProps | null>(null);\n\nfunction useChart() {\n const context = React.useContext(ChartContext);\n\n if (!context) {\n throw new Error('useChart must be used within a <ChartContainer />');\n }\n\n return context;\n}\n\nconst ChartContainer = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<'div'> & {\n config: ChartConfig;\n children: React.ComponentProps<typeof ResponsiveContainer>['children'];\n }\n>(({ id, className, children, config, ...props }, ref) => {\n const uniqueId = React.useId();\n const chartId = `chart-${id || uniqueId.replace(/:/g, '')}`;\n\n return (\n <ChartContext.Provider value={{ config }}>\n <div\n data-chart={chartId}\n ref={ref}\n className={cn(\n \"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-[var(--color-text-secondary)] [&_.recharts-cartesian-grid_line]:stroke-[var(--color-border)]\",\n className\n )}\n {...props}\n >\n <ChartStyle id={chartId} config={config} />\n <ResponsiveContainer>\n {children}\n </ResponsiveContainer>\n </div>\n </ChartContext.Provider>\n );\n});\nChartContainer.displayName = 'ChartContainer';\n\nconst ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {\n const colorConfig = Object.entries(config).filter(\n ([_, value]) => value.theme || value.color\n );\n\n if (!colorConfig.length) {\n return null;\n }\n\n return (\n <style dangerouslySetInnerHTML={{\n __html: `\n [data-chart=${id}] {\n ${colorConfig\n .map(([key, item]) => {\n const color = item.theme || item.color;\n return color ? `--color-${key}: ${color};` : null;\n })\n .join('\\n')}\n }\n `\n }} />\n );\n};\n\n// -- Tooltip --\n\nconst ChartTooltip = Tooltip;\n\nconst ChartTooltipContent = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<'div'> & {\n hideLabel?: boolean;\n hideIndicator?: boolean;\n indicator?: 'line' | 'dot' | 'dashed';\n nameKey?: string;\n labelKey?: string;\n active?: boolean;\n payload?: any[];\n label?: any;\n labelFormatter?: (label: any, payload: any[]) => React.ReactNode;\n config?: ChartConfig;\n }\n>(({ active, payload, className, indicator = 'dot', hideLabel = false, hideIndicator = false, label, labelFormatter, config, nameKey, labelKey }, ref) => {\n const { config: configFromContext } = useChart();\n const chartConfig = config || configFromContext;\n\n const tooltipLabel = React.useMemo(() => {\n if (hideLabel || !payload || !payload.length) {\n return null\n }\n\n const [item] = payload\n const key = `${labelKey || item.dataKey || item.name || 'value'}`\n const itemConfig = getPayloadConfigFromPayload(chartConfig, item, key)\n const value =\n !labelKey && typeof label === \"string\"\n ? chartConfig[label as keyof typeof chartConfig]?.label || label\n : itemConfig?.label\n\n if (labelFormatter) {\n return (\n <div className={cn(\"font-medium\")}>\n {labelFormatter(value, payload)}\n </div>\n )\n }\n\n if (!value) {\n return null\n }\n\n return <div className={cn(\"font-medium\")}>{value}</div>\n }, [\n label,\n labelFormatter,\n payload,\n hideLabel,\n labelKey,\n chartConfig,\n ])\n\n if (!active || !payload?.length) {\n return null;\n }\n\n const nestLabel = payload.length === 1 && indicator !== \"dot\"\n\n return (\n <div\n ref={ref}\n className={cn(\n 'grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface)] px-2.5 py-1.5 text-xs shadow-xl transition-all ease-in-out animate-in fade-in-0 zoom-in-95',\n className\n )}\n >\n {!nestLabel ? tooltipLabel : null}\n <div className=\"grid gap-1.5\">\n {payload.map((item, index) => {\n const key = `${nameKey || item.name || item.dataKey || \"value\"}`\n const itemConfig = getPayloadConfigFromPayload(chartConfig, item, key)\n const indicatorColor = colorToCssVariable(\n item.payload.fill || item.color\n )\n\n return (\n <div\n key={item.dataKey || index}\n className={cn(\n \"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground\",\n indicator === \"dot\" && \"items-center\"\n )}\n >\n {indicator && !hideIndicator && (\n <div\n className={cn(\n \"shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]\",\n {\n \"h-2.5 w-2.5\": indicator === \"dot\",\n \"w-1\": indicator === \"line\",\n \"w-0 border-[1.5px] border-dashed bg-transparent\":\n indicator === \"dashed\",\n \"my-0.5\": nestLabel && indicator === \"dashed\",\n }\n )}\n style={\n {\n \"--color-bg\": indicatorColor,\n \"--color-border\": indicatorColor,\n } as React.CSSProperties\n }\n />\n )}\n <div\n className={cn(\n \"flex flex-1 justify-between leading-none\",\n nestLabel ? \"items-end\" : \"items-center\"\n )}\n >\n <div className=\"grid gap-1.5\">\n {nestLabel ? tooltipLabel : null}\n <span className=\"text-[var(--color-text-secondary)]\">\n {itemConfig?.label || item.name}\n </span>\n </div>\n {item.value && (\n <span className=\"font-mono font-medium tabular-nums text-[var(--color-text-primary)]\">\n {item.value.toLocaleString()}\n </span>\n )}\n </div>\n </div>\n )\n })}\n </div>\n </div>\n );\n});\nChartTooltipContent.displayName = 'ChartTooltipContent';\n\n// -- Legend --\n\nconst ChartLegend = Legend;\n\nconst ChartLegendContent = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<'div'> & {\n hideIcon?: boolean;\n nameKey?: string;\n verticalAlign?: 'top' | 'bottom' | 'middle';\n payload?: any[];\n }\n>(({ className, hideIcon = false, payload, verticalAlign = 'bottom', nameKey }, ref) => {\n const { config } = useChart();\n\n if (!payload?.length) {\n return null;\n }\n\n return (\n <div\n ref={ref}\n className={cn(\n 'flex items-center justify-center gap-4',\n verticalAlign === 'top' ? 'pb-3' : 'pt-3',\n className\n )}\n >\n {payload.map((item) => {\n const key = `${nameKey || item.dataKey || \"value\"}`\n const itemConfig = getPayloadConfigFromPayload(config, item, key)\n const indicatorColor = colorToCssVariable(item.color)\n\n return (\n <div\n key={item.value}\n className={cn(\n \"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground\"\n )}\n >\n {!hideIcon && (\n <div\n className=\"h-2 w-2 shrink-0 rounded-[2px]\"\n style={{ backgroundColor: indicatorColor }}\n />\n )}\n <span className=\"text-sm text-[var(--color-text-primary)]\">\n {itemConfig?.label || item.value}\n </span>\n </div>\n )\n })}\n </div>\n );\n});\nChartLegendContent.displayName = 'ChartLegendContent';\n\n// Helper to extract config\nfunction getPayloadConfigFromPayload(\n config: ChartConfig,\n payload: unknown,\n key: string\n) {\n if (typeof payload !== \"object\" || payload === null) {\n return undefined\n }\n\n const payloadPayload =\n \"payload\" in payload &&\n typeof payload.payload === \"object\" &&\n payload.payload !== null\n ? payload.payload\n : undefined\n\n let configLabelKey: string = key\n\n if (\n key in payload &&\n typeof payload[key as keyof typeof payload] === \"string\"\n ) {\n configLabelKey = payload[key as keyof typeof payload] as string\n } else if (\n payloadPayload &&\n key in payloadPayload &&\n typeof payloadPayload[key as keyof typeof payloadPayload] === \"string\"\n ) {\n configLabelKey = payloadPayload[\n key as keyof typeof payloadPayload\n ] as string\n }\n\n return configLabelKey in config\n ? config[configLabelKey]\n : config[key as keyof typeof config]\n}\n\nfunction colorToCssVariable(color: string | undefined): string {\n if (!color) return 'var(--color-primary)';\n // If it's a theme variable like `var(--color-...)` return it\n if (color.startsWith('var(--')) return color;\n // If we wanted to map theme keys to vars, we could do it here\n return color;\n}\n\nexport {\n ChartContainer,\n ChartTooltip,\n ChartTooltipContent,\n ChartLegend,\n ChartLegendContent,\n ChartStyle,\n};\n","import { type ClassValue, clsx } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n"],"mappings":"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","names":["index_exports","__export","ChartContainer","ChartLegend","ChartLegendContent","ChartStyle","ChartTooltip","ChartTooltipContent","__toCommonJS","React","import_recharts","import_clsx","import_tailwind_merge","cn","inputs","import_jsx_runtime","ChartContext","useChart","context","ChartContainer","id","className","children","config","props","ref","uniqueId","chartId","cn","ChartStyle","colorConfig","_","value","key","item","color","ChartTooltip","ChartTooltipContent","active","payload","indicator","hideLabel","hideIndicator","label","labelFormatter","nameKey","labelKey","configFromContext","chartConfig","tooltipLabel","itemConfig","getPayloadConfigFromPayload","nestLabel","index","indicatorColor","colorToCssVariable","ChartLegend","ChartLegendContent","hideIcon","verticalAlign","payloadPayload","configLabelKey","__reExport","index_exports"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import*as l from"react";import{ResponsiveContainer as $,Legend as S,Tooltip as _}from"recharts";import{clsx as E}from"clsx";import{twMerge as M}from"tailwind-merge";function c(...t){return M(E(t))}import{jsx as s,jsxs as u}from"react/jsx-runtime";var w=l.createContext(null);function P(){let t=l.useContext(w);if(!t)throw new Error("useChart must be used within a <ChartContainer />");return t}var j=l.forwardRef(({id:t,className:e,children:r,config:o,...n},d)=>{let f=l.useId(),i=`chart-${t||f.replace(/:/g,"")}`;return s(w.Provider,{value:{config:o},children:u("div",{"data-chart":i,ref:d,className:c("flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-[var(--color-text-secondary)] [&_.recharts-cartesian-grid_line]:stroke-[var(--color-border)]",e),...n,children:[s(H,{id:i,config:o}),s($,{children:r})]})})});j.displayName="ChartContainer";var H=({id:t,config:e})=>{let r=Object.entries(e).filter(([o,n])=>n.theme||n.color);return r.length?s("style",{dangerouslySetInnerHTML:{__html:`
|
|
2
|
+
[data-chart=${t}] {
|
|
3
|
+
${r.map(([o,n])=>{let d=n.theme||n.color;return d?`--color-${o}: ${d};`:null}).join(`
|
|
4
|
+
`)}
|
|
5
|
+
}
|
|
6
|
+
`}}):null},F=_,I=l.forwardRef(({active:t,payload:e,className:r,indicator:o="dot",hideLabel:n=!1,hideIndicator:d=!1,label:f,labelFormatter:i,config:C,nameKey:v,labelKey:g},L)=>{let{config:T}=P(),m=C||T,R=l.useMemo(()=>{if(n||!e||!e.length)return null;let[a]=e,y=`${g||a.dataKey||a.name||"value"}`,x=b(m,a,y),p=!g&&typeof f=="string"?m[f]?.label||f:x?.label;return i?s("div",{className:c("font-medium"),children:i(p,e)}):p?s("div",{className:c("font-medium"),children:p}):null},[f,i,e,n,g,m]);if(!t||!e?.length)return null;let h=e.length===1&&o!=="dot";return u("div",{ref:L,className:c("grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface)] px-2.5 py-1.5 text-xs shadow-xl transition-all ease-in-out animate-in fade-in-0 zoom-in-95",r),children:[h?null:R,s("div",{className:"grid gap-1.5",children:e.map((a,y)=>{let x=`${v||a.name||a.dataKey||"value"}`,p=b(m,a,x),N=k(a.payload.fill||a.color);return u("div",{className:c("flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",o==="dot"&&"items-center"),children:[o&&!d&&s("div",{className:c("shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",{"h-2.5 w-2.5":o==="dot","w-1":o==="line","w-0 border-[1.5px] border-dashed bg-transparent":o==="dashed","my-0.5":h&&o==="dashed"}),style:{"--color-bg":N,"--color-border":N}}),u("div",{className:c("flex flex-1 justify-between leading-none",h?"items-end":"items-center"),children:[u("div",{className:"grid gap-1.5",children:[h?R:null,s("span",{className:"text-[var(--color-text-secondary)]",children:p?.label||a.name})]}),a.value&&s("span",{className:"font-mono font-medium tabular-nums text-[var(--color-text-primary)]",children:a.value.toLocaleString()})]})]},a.dataKey||y)})})]})});I.displayName="ChartTooltipContent";var O=S,K=l.forwardRef(({className:t,hideIcon:e=!1,payload:r,verticalAlign:o="bottom",nameKey:n},d)=>{let{config:f}=P();return r?.length?s("div",{ref:d,className:c("flex items-center justify-center gap-4",o==="top"?"pb-3":"pt-3",t),children:r.map(i=>{let C=`${n||i.dataKey||"value"}`,v=b(f,i,C),g=k(i.color);return u("div",{className:c("flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"),children:[!e&&s("div",{className:"h-2 w-2 shrink-0 rounded-[2px]",style:{backgroundColor:g}}),s("span",{className:"text-sm text-[var(--color-text-primary)]",children:v?.label||i.value})]},i.value)})}):null});K.displayName="ChartLegendContent";function b(t,e,r){if(typeof e!="object"||e===null)return;let o="payload"in e&&typeof e.payload=="object"&&e.payload!==null?e.payload:void 0,n=r;return r in e&&typeof e[r]=="string"?n=e[r]:o&&r in o&&typeof o[r]=="string"&&(n=o[r]),n in t?t[n]:t[r]}function k(t){return t?(t.startsWith("var(--"),t):"var(--color-primary)"}export*from"recharts";export{j as ChartContainer,O as ChartLegend,K as ChartLegendContent,H as ChartStyle,F as ChartTooltip,I as ChartTooltipContent};
|
|
7
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/chart.tsx","../src/lib/utils.ts","../src/index.ts"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { ResponsiveContainer, Legend, Tooltip } from 'recharts';\nimport { cn } from '../lib/utils';\n\n// Format: { THEME_NAME: { cssVariable: string; value: string } }\nconst THEMES = {\n light: {\n '--color-chart-1': '12 76% 61%',\n '--color-chart-2': '173 58% 39%',\n '--color-chart-3': '197 37% 24%',\n '--color-chart-4': '43 74% 66%',\n '--color-chart-5': '27 87% 67%',\n },\n dark: {\n '--color-chart-1': '220 70% 50%',\n '--color-chart-2': '160 60% 45%',\n '--color-chart-3': '30 80% 55%',\n '--color-chart-4': '280 65% 60%',\n '--color-chart-5': '340 75% 55%',\n },\n} as const;\n\nexport type ChartConfig = {\n [k in string]: {\n label?: React.ReactNode;\n icon?: React.ComponentType;\n color?: string;\n theme?: Record<keyof typeof THEMES, string>;\n }\n};\n\ntype ChartContextProps = {\n config: ChartConfig;\n};\n\nconst ChartContext = React.createContext<ChartContextProps | null>(null);\n\nfunction useChart() {\n const context = React.useContext(ChartContext);\n\n if (!context) {\n throw new Error('useChart must be used within a <ChartContainer />');\n }\n\n return context;\n}\n\nconst ChartContainer = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<'div'> & {\n config: ChartConfig;\n children: React.ComponentProps<typeof ResponsiveContainer>['children'];\n }\n>(({ id, className, children, config, ...props }, ref) => {\n const uniqueId = React.useId();\n const chartId = `chart-${id || uniqueId.replace(/:/g, '')}`;\n\n return (\n <ChartContext.Provider value={{ config }}>\n <div\n data-chart={chartId}\n ref={ref}\n className={cn(\n \"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-[var(--color-text-secondary)] [&_.recharts-cartesian-grid_line]:stroke-[var(--color-border)]\",\n className\n )}\n {...props}\n >\n <ChartStyle id={chartId} config={config} />\n <ResponsiveContainer>\n {children}\n </ResponsiveContainer>\n </div>\n </ChartContext.Provider>\n );\n});\nChartContainer.displayName = 'ChartContainer';\n\nconst ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {\n const colorConfig = Object.entries(config).filter(\n ([_, value]) => value.theme || value.color\n );\n\n if (!colorConfig.length) {\n return null;\n }\n\n return (\n <style dangerouslySetInnerHTML={{\n __html: `\n [data-chart=${id}] {\n ${colorConfig\n .map(([key, item]) => {\n const color = item.theme || item.color;\n return color ? `--color-${key}: ${color};` : null;\n })\n .join('\\n')}\n }\n `\n }} />\n );\n};\n\n// -- Tooltip --\n\nconst ChartTooltip = Tooltip;\n\nconst ChartTooltipContent = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<'div'> & {\n hideLabel?: boolean;\n hideIndicator?: boolean;\n indicator?: 'line' | 'dot' | 'dashed';\n nameKey?: string;\n labelKey?: string;\n active?: boolean;\n payload?: any[];\n label?: any;\n labelFormatter?: (label: any, payload: any[]) => React.ReactNode;\n config?: ChartConfig;\n }\n>(({ active, payload, className, indicator = 'dot', hideLabel = false, hideIndicator = false, label, labelFormatter, config, nameKey, labelKey }, ref) => {\n const { config: configFromContext } = useChart();\n const chartConfig = config || configFromContext;\n\n const tooltipLabel = React.useMemo(() => {\n if (hideLabel || !payload || !payload.length) {\n return null\n }\n\n const [item] = payload\n const key = `${labelKey || item.dataKey || item.name || 'value'}`\n const itemConfig = getPayloadConfigFromPayload(chartConfig, item, key)\n const value =\n !labelKey && typeof label === \"string\"\n ? chartConfig[label as keyof typeof chartConfig]?.label || label\n : itemConfig?.label\n\n if (labelFormatter) {\n return (\n <div className={cn(\"font-medium\")}>\n {labelFormatter(value, payload)}\n </div>\n )\n }\n\n if (!value) {\n return null\n }\n\n return <div className={cn(\"font-medium\")}>{value}</div>\n }, [\n label,\n labelFormatter,\n payload,\n hideLabel,\n labelKey,\n chartConfig,\n ])\n\n if (!active || !payload?.length) {\n return null;\n }\n\n const nestLabel = payload.length === 1 && indicator !== \"dot\"\n\n return (\n <div\n ref={ref}\n className={cn(\n 'grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface)] px-2.5 py-1.5 text-xs shadow-xl transition-all ease-in-out animate-in fade-in-0 zoom-in-95',\n className\n )}\n >\n {!nestLabel ? tooltipLabel : null}\n <div className=\"grid gap-1.5\">\n {payload.map((item, index) => {\n const key = `${nameKey || item.name || item.dataKey || \"value\"}`\n const itemConfig = getPayloadConfigFromPayload(chartConfig, item, key)\n const indicatorColor = colorToCssVariable(\n item.payload.fill || item.color\n )\n\n return (\n <div\n key={item.dataKey || index}\n className={cn(\n \"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground\",\n indicator === \"dot\" && \"items-center\"\n )}\n >\n {indicator && !hideIndicator && (\n <div\n className={cn(\n \"shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]\",\n {\n \"h-2.5 w-2.5\": indicator === \"dot\",\n \"w-1\": indicator === \"line\",\n \"w-0 border-[1.5px] border-dashed bg-transparent\":\n indicator === \"dashed\",\n \"my-0.5\": nestLabel && indicator === \"dashed\",\n }\n )}\n style={\n {\n \"--color-bg\": indicatorColor,\n \"--color-border\": indicatorColor,\n } as React.CSSProperties\n }\n />\n )}\n <div\n className={cn(\n \"flex flex-1 justify-between leading-none\",\n nestLabel ? \"items-end\" : \"items-center\"\n )}\n >\n <div className=\"grid gap-1.5\">\n {nestLabel ? tooltipLabel : null}\n <span className=\"text-[var(--color-text-secondary)]\">\n {itemConfig?.label || item.name}\n </span>\n </div>\n {item.value && (\n <span className=\"font-mono font-medium tabular-nums text-[var(--color-text-primary)]\">\n {item.value.toLocaleString()}\n </span>\n )}\n </div>\n </div>\n )\n })}\n </div>\n </div>\n );\n});\nChartTooltipContent.displayName = 'ChartTooltipContent';\n\n// -- Legend --\n\nconst ChartLegend = Legend;\n\nconst ChartLegendContent = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<'div'> & {\n hideIcon?: boolean;\n nameKey?: string;\n verticalAlign?: 'top' | 'bottom' | 'middle';\n payload?: any[];\n }\n>(({ className, hideIcon = false, payload, verticalAlign = 'bottom', nameKey }, ref) => {\n const { config } = useChart();\n\n if (!payload?.length) {\n return null;\n }\n\n return (\n <div\n ref={ref}\n className={cn(\n 'flex items-center justify-center gap-4',\n verticalAlign === 'top' ? 'pb-3' : 'pt-3',\n className\n )}\n >\n {payload.map((item) => {\n const key = `${nameKey || item.dataKey || \"value\"}`\n const itemConfig = getPayloadConfigFromPayload(config, item, key)\n const indicatorColor = colorToCssVariable(item.color)\n\n return (\n <div\n key={item.value}\n className={cn(\n \"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground\"\n )}\n >\n {!hideIcon && (\n <div\n className=\"h-2 w-2 shrink-0 rounded-[2px]\"\n style={{ backgroundColor: indicatorColor }}\n />\n )}\n <span className=\"text-sm text-[var(--color-text-primary)]\">\n {itemConfig?.label || item.value}\n </span>\n </div>\n )\n })}\n </div>\n );\n});\nChartLegendContent.displayName = 'ChartLegendContent';\n\n// Helper to extract config\nfunction getPayloadConfigFromPayload(\n config: ChartConfig,\n payload: unknown,\n key: string\n) {\n if (typeof payload !== \"object\" || payload === null) {\n return undefined\n }\n\n const payloadPayload =\n \"payload\" in payload &&\n typeof payload.payload === \"object\" &&\n payload.payload !== null\n ? payload.payload\n : undefined\n\n let configLabelKey: string = key\n\n if (\n key in payload &&\n typeof payload[key as keyof typeof payload] === \"string\"\n ) {\n configLabelKey = payload[key as keyof typeof payload] as string\n } else if (\n payloadPayload &&\n key in payloadPayload &&\n typeof payloadPayload[key as keyof typeof payloadPayload] === \"string\"\n ) {\n configLabelKey = payloadPayload[\n key as keyof typeof payloadPayload\n ] as string\n }\n\n return configLabelKey in config\n ? config[configLabelKey]\n : config[key as keyof typeof config]\n}\n\nfunction colorToCssVariable(color: string | undefined): string {\n if (!color) return 'var(--color-primary)';\n // If it's a theme variable like `var(--color-...)` return it\n if (color.startsWith('var(--')) return color;\n // If we wanted to map theme keys to vars, we could do it here\n return color;\n}\n\nexport {\n ChartContainer,\n ChartTooltip,\n ChartTooltipContent,\n ChartLegend,\n ChartLegendContent,\n ChartStyle,\n};\n","import { type ClassValue, clsx } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","export * from './components/chart';\nexport * from 'recharts';\n\n// Override specific recharts exports if we wrapped them, \n// otherwise we just export the primitives and let usage include regular recharts components.\n"],"mappings":"AAEA,UAAYA,MAAW,QACvB,OAAS,uBAAAC,EAAqB,UAAAC,EAAQ,WAAAC,MAAe,WCHrD,OAA0B,QAAAC,MAAY,OACtC,OAAS,WAAAC,MAAe,iBAEjB,SAASC,KAAMC,EAAsB,CACxC,OAAOF,EAAQD,EAAKG,CAAM,CAAC,CAC/B,CDwDY,OASI,OAAAC,EATJ,QAAAC,MAAA,oBAxBZ,IAAMC,EAAqB,gBAAwC,IAAI,EAEvE,SAASC,GAAW,CAChB,IAAMC,EAAgB,aAAWF,CAAY,EAE7C,GAAI,CAACE,EACD,MAAM,IAAI,MAAM,mDAAmD,EAGvE,OAAOA,CACX,CAEA,IAAMC,EAAuB,aAM3B,CAAC,CAAE,GAAAC,EAAI,UAAAC,EAAW,SAAAC,EAAU,OAAAC,EAAQ,GAAGC,CAAM,EAAGC,IAAQ,CACtD,IAAMC,EAAiB,QAAM,EACvBC,EAAU,SAASP,GAAMM,EAAS,QAAQ,KAAM,EAAE,CAAC,GAEzD,OACIE,EAACZ,EAAa,SAAb,CAAsB,MAAO,CAAE,OAAAO,CAAO,EACnC,SAAAM,EAAC,OACG,aAAYF,EACZ,IAAKF,EACL,UAAWK,EACP,oLACAT,CACJ,EACC,GAAGG,EAEJ,UAAAI,EAACG,EAAA,CAAW,GAAIJ,EAAS,OAAQJ,EAAQ,EACzCK,EAACI,EAAA,CACI,SAAAV,EACL,GACJ,EACJ,CAER,CAAC,EACDH,EAAe,YAAc,iBAE7B,IAAMY,EAAa,CAAC,CAAE,GAAAX,EAAI,OAAAG,CAAO,IAA2C,CACxE,IAAMU,EAAc,OAAO,QAAQV,CAAM,EAAE,OACvC,CAAC,CAACW,EAAGC,CAAK,IAAMA,EAAM,OAASA,EAAM,KACzC,EAEA,OAAKF,EAAY,OAKbL,EAAC,SAAM,wBAAyB,CAC5B,OAAQ;AAAA,sBACER,CAAE;AAAA,YACZa,EACS,IAAI,CAAC,CAACG,EAAKC,CAAI,IAAM,CAClB,IAAMC,EAAQD,EAAK,OAASA,EAAK,MACjC,OAAOC,EAAQ,WAAWF,CAAG,KAAKE,CAAK,IAAM,IACjD,CAAC,EACA,KAAK;AAAA,CAAI,CAAC;AAAA;AAAA,OAGvB,EAAG,EAfI,IAiBf,EAIMC,EAAeC,EAEfC,EAA4B,aAchC,CAAC,CAAE,OAAAC,EAAQ,QAAAC,EAAS,UAAAtB,EAAW,UAAAuB,EAAY,MAAO,UAAAC,EAAY,GAAO,cAAAC,EAAgB,GAAO,MAAAC,EAAO,eAAAC,EAAgB,OAAAzB,EAAQ,QAAA0B,EAAS,SAAAC,CAAS,EAAGzB,IAAQ,CACtJ,GAAM,CAAE,OAAQ0B,CAAkB,EAAIlC,EAAS,EACzCmC,EAAc7B,GAAU4B,EAExBE,EAAqB,UAAQ,IAAM,CACrC,GAAIR,GAAa,CAACF,GAAW,CAACA,EAAQ,OAClC,OAAO,KAGX,GAAM,CAACN,CAAI,EAAIM,EACTP,EAAM,GAAGc,GAAYb,EAAK,SAAWA,EAAK,MAAQ,OAAO,GACzDiB,EAAaC,EAA4BH,EAAaf,EAAMD,CAAG,EAC/DD,EACF,CAACe,GAAY,OAAOH,GAAU,SACxBK,EAAYL,CAAiC,GAAG,OAASA,EACzDO,GAAY,MAEtB,OAAIN,EAEIpB,EAAC,OAAI,UAAWE,EAAG,aAAa,EAC3B,SAAAkB,EAAeb,EAAOQ,CAAO,EAClC,EAIHR,EAIEP,EAAC,OAAI,UAAWE,EAAG,aAAa,EAAI,SAAAK,EAAM,EAHtC,IAIf,EAAG,CACCY,EACAC,EACAL,EACAE,EACAK,EACAE,CACJ,CAAC,EAED,GAAI,CAACV,GAAU,CAACC,GAAS,OACrB,OAAO,KAGX,IAAMa,EAAYb,EAAQ,SAAW,GAAKC,IAAc,MAExD,OACIf,EAAC,OACG,IAAKJ,EACL,UAAWK,EACP,4MACAT,CACJ,EAEC,UAACmC,EAA2B,KAAfH,EACdzB,EAAC,OAAI,UAAU,eACV,SAAAe,EAAQ,IAAI,CAACN,EAAMoB,IAAU,CAC1B,IAAMrB,EAAM,GAAGa,GAAWZ,EAAK,MAAQA,EAAK,SAAW,OAAO,GACxDiB,EAAaC,EAA4BH,EAAaf,EAAMD,CAAG,EAC/DsB,EAAiBC,EACnBtB,EAAK,QAAQ,MAAQA,EAAK,KAC9B,EAEA,OACIR,EAAC,OAEG,UAAWC,EACP,sGACAc,IAAc,OAAS,cAC3B,EAEC,UAAAA,GAAa,CAACE,GACXlB,EAAC,OACG,UAAWE,EACP,iEACA,CACI,cAAec,IAAc,MAC7B,MAAOA,IAAc,OACrB,kDACIA,IAAc,SAClB,SAAUY,GAAaZ,IAAc,QACzC,CACJ,EACA,MACI,CACI,aAAcc,EACd,iBAAkBA,CACtB,EAER,EAEJ7B,EAAC,OACG,UAAWC,EACP,2CACA0B,EAAY,YAAc,cAC9B,EAEA,UAAA3B,EAAC,OAAI,UAAU,eACV,UAAA2B,EAAYH,EAAe,KAC5BzB,EAAC,QAAK,UAAU,qCACX,SAAA0B,GAAY,OAASjB,EAAK,KAC/B,GACJ,EACCA,EAAK,OACFT,EAAC,QAAK,UAAU,sEACX,SAAAS,EAAK,MAAM,eAAe,EAC/B,GAER,IA3CKA,EAAK,SAAWoB,CA4CzB,CAER,CAAC,EACL,GACJ,CAER,CAAC,EACDhB,EAAoB,YAAc,sBAIlC,IAAMmB,EAAcC,EAEdC,EAA2B,aAQ/B,CAAC,CAAE,UAAAzC,EAAW,SAAA0C,EAAW,GAAO,QAAApB,EAAS,cAAAqB,EAAgB,SAAU,QAAAf,CAAQ,EAAGxB,IAAQ,CACpF,GAAM,CAAE,OAAAF,CAAO,EAAIN,EAAS,EAE5B,OAAK0B,GAAS,OAKVf,EAAC,OACG,IAAKH,EACL,UAAWK,EACP,yCACAkC,IAAkB,MAAQ,OAAS,OACnC3C,CACJ,EAEC,SAAAsB,EAAQ,IAAKN,GAAS,CACnB,IAAMD,EAAM,GAAGa,GAAWZ,EAAK,SAAW,OAAO,GAC3CiB,EAAaC,EAA4BhC,EAAQc,EAAMD,CAAG,EAC1DsB,EAAiBC,EAAmBtB,EAAK,KAAK,EAEpD,OACIR,EAAC,OAEG,UAAWC,EACP,iFACJ,EAEC,WAACiC,GACEnC,EAAC,OACG,UAAU,iCACV,MAAO,CAAE,gBAAiB8B,CAAe,EAC7C,EAEJ9B,EAAC,QAAK,UAAU,2CACX,SAAA0B,GAAY,OAASjB,EAAK,MAC/B,IAbKA,EAAK,KAcd,CAER,CAAC,EACL,EApCO,IAsCf,CAAC,EACDyB,EAAmB,YAAc,qBAGjC,SAASP,EACLhC,EACAoB,EACAP,EACF,CACE,GAAI,OAAOO,GAAY,UAAYA,IAAY,KAC3C,OAGJ,IAAMsB,EACF,YAAatB,GACT,OAAOA,EAAQ,SAAY,UAC3BA,EAAQ,UAAY,KAClBA,EAAQ,QACR,OAENuB,EAAyB9B,EAE7B,OACIA,KAAOO,GACP,OAAOA,EAAQP,CAA2B,GAAM,SAEhD8B,EAAiBvB,EAAQP,CAA2B,EAEpD6B,GACA7B,KAAO6B,GACP,OAAOA,EAAe7B,CAAkC,GAAM,WAE9D8B,EAAiBD,EACb7B,CACJ,GAGG8B,KAAkB3C,EACnBA,EAAO2C,CAAc,EACrB3C,EAAOa,CAA0B,CAC3C,CAEA,SAASuB,EAAmBrB,EAAmC,CAC3D,OAAKA,GAEDA,EAAM,WAAW,QAAQ,EAAUA,GAFpB,sBAKvB,CErVA,WAAc","names":["React","ResponsiveContainer","Legend","Tooltip","clsx","twMerge","cn","inputs","jsx","jsxs","ChartContext","useChart","context","ChartContainer","id","className","children","config","props","ref","uniqueId","chartId","jsx","jsxs","cn","ChartStyle","ResponsiveContainer","colorConfig","_","value","key","item","color","ChartTooltip","Tooltip","ChartTooltipContent","active","payload","indicator","hideLabel","hideIndicator","label","labelFormatter","nameKey","labelKey","configFromContext","chartConfig","tooltipLabel","itemConfig","getPayloadConfigFromPayload","nestLabel","index","indicatorColor","colorToCssVariable","ChartLegend","Legend","ChartLegendContent","hideIcon","verticalAlign","payloadPayload","configLabelKey"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@thesage/charts",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"description": "Chart components for Sage UI",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"module": "./dist/index.mjs",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.mjs",
|
|
15
|
+
"require": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"react": ">=18",
|
|
20
|
+
"recharts": "^2.12.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/react": "^19.0.0",
|
|
24
|
+
"tsup": "^8.0.0",
|
|
25
|
+
"typescript": "^5.0.0",
|
|
26
|
+
"@thesage/config": "0.0.1"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"clsx": "^2.1.1",
|
|
30
|
+
"tailwind-merge": "^3.4.0"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
34
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
35
|
+
"lint": "eslint src/"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { ResponsiveContainer, Legend, Tooltip } from 'recharts';
|
|
5
|
+
import { cn } from '../lib/utils';
|
|
6
|
+
|
|
7
|
+
// Format: { THEME_NAME: { cssVariable: string; value: string } }
|
|
8
|
+
const THEMES = {
|
|
9
|
+
light: {
|
|
10
|
+
'--color-chart-1': '12 76% 61%',
|
|
11
|
+
'--color-chart-2': '173 58% 39%',
|
|
12
|
+
'--color-chart-3': '197 37% 24%',
|
|
13
|
+
'--color-chart-4': '43 74% 66%',
|
|
14
|
+
'--color-chart-5': '27 87% 67%',
|
|
15
|
+
},
|
|
16
|
+
dark: {
|
|
17
|
+
'--color-chart-1': '220 70% 50%',
|
|
18
|
+
'--color-chart-2': '160 60% 45%',
|
|
19
|
+
'--color-chart-3': '30 80% 55%',
|
|
20
|
+
'--color-chart-4': '280 65% 60%',
|
|
21
|
+
'--color-chart-5': '340 75% 55%',
|
|
22
|
+
},
|
|
23
|
+
} as const;
|
|
24
|
+
|
|
25
|
+
export type ChartConfig = {
|
|
26
|
+
[k in string]: {
|
|
27
|
+
label?: React.ReactNode;
|
|
28
|
+
icon?: React.ComponentType;
|
|
29
|
+
color?: string;
|
|
30
|
+
theme?: Record<keyof typeof THEMES, string>;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type ChartContextProps = {
|
|
35
|
+
config: ChartConfig;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const ChartContext = React.createContext<ChartContextProps | null>(null);
|
|
39
|
+
|
|
40
|
+
function useChart() {
|
|
41
|
+
const context = React.useContext(ChartContext);
|
|
42
|
+
|
|
43
|
+
if (!context) {
|
|
44
|
+
throw new Error('useChart must be used within a <ChartContainer />');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return context;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const ChartContainer = React.forwardRef<
|
|
51
|
+
HTMLDivElement,
|
|
52
|
+
React.ComponentProps<'div'> & {
|
|
53
|
+
config: ChartConfig;
|
|
54
|
+
children: React.ComponentProps<typeof ResponsiveContainer>['children'];
|
|
55
|
+
}
|
|
56
|
+
>(({ id, className, children, config, ...props }, ref) => {
|
|
57
|
+
const uniqueId = React.useId();
|
|
58
|
+
const chartId = `chart-${id || uniqueId.replace(/:/g, '')}`;
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<ChartContext.Provider value={{ config }}>
|
|
62
|
+
<div
|
|
63
|
+
data-chart={chartId}
|
|
64
|
+
ref={ref}
|
|
65
|
+
className={cn(
|
|
66
|
+
"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-[var(--color-text-secondary)] [&_.recharts-cartesian-grid_line]:stroke-[var(--color-border)]",
|
|
67
|
+
className
|
|
68
|
+
)}
|
|
69
|
+
{...props}
|
|
70
|
+
>
|
|
71
|
+
<ChartStyle id={chartId} config={config} />
|
|
72
|
+
<ResponsiveContainer>
|
|
73
|
+
{children}
|
|
74
|
+
</ResponsiveContainer>
|
|
75
|
+
</div>
|
|
76
|
+
</ChartContext.Provider>
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
ChartContainer.displayName = 'ChartContainer';
|
|
80
|
+
|
|
81
|
+
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
|
|
82
|
+
const colorConfig = Object.entries(config).filter(
|
|
83
|
+
([_, value]) => value.theme || value.color
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
if (!colorConfig.length) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<style dangerouslySetInnerHTML={{
|
|
92
|
+
__html: `
|
|
93
|
+
[data-chart=${id}] {
|
|
94
|
+
${colorConfig
|
|
95
|
+
.map(([key, item]) => {
|
|
96
|
+
const color = item.theme || item.color;
|
|
97
|
+
return color ? `--color-${key}: ${color};` : null;
|
|
98
|
+
})
|
|
99
|
+
.join('\n')}
|
|
100
|
+
}
|
|
101
|
+
`
|
|
102
|
+
}} />
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// -- Tooltip --
|
|
107
|
+
|
|
108
|
+
const ChartTooltip = Tooltip;
|
|
109
|
+
|
|
110
|
+
const ChartTooltipContent = React.forwardRef<
|
|
111
|
+
HTMLDivElement,
|
|
112
|
+
React.ComponentProps<'div'> & {
|
|
113
|
+
hideLabel?: boolean;
|
|
114
|
+
hideIndicator?: boolean;
|
|
115
|
+
indicator?: 'line' | 'dot' | 'dashed';
|
|
116
|
+
nameKey?: string;
|
|
117
|
+
labelKey?: string;
|
|
118
|
+
active?: boolean;
|
|
119
|
+
payload?: any[];
|
|
120
|
+
label?: any;
|
|
121
|
+
labelFormatter?: (label: any, payload: any[]) => React.ReactNode;
|
|
122
|
+
config?: ChartConfig;
|
|
123
|
+
}
|
|
124
|
+
>(({ active, payload, className, indicator = 'dot', hideLabel = false, hideIndicator = false, label, labelFormatter, config, nameKey, labelKey }, ref) => {
|
|
125
|
+
const { config: configFromContext } = useChart();
|
|
126
|
+
const chartConfig = config || configFromContext;
|
|
127
|
+
|
|
128
|
+
const tooltipLabel = React.useMemo(() => {
|
|
129
|
+
if (hideLabel || !payload || !payload.length) {
|
|
130
|
+
return null
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const [item] = payload
|
|
134
|
+
const key = `${labelKey || item.dataKey || item.name || 'value'}`
|
|
135
|
+
const itemConfig = getPayloadConfigFromPayload(chartConfig, item, key)
|
|
136
|
+
const value =
|
|
137
|
+
!labelKey && typeof label === "string"
|
|
138
|
+
? chartConfig[label as keyof typeof chartConfig]?.label || label
|
|
139
|
+
: itemConfig?.label
|
|
140
|
+
|
|
141
|
+
if (labelFormatter) {
|
|
142
|
+
return (
|
|
143
|
+
<div className={cn("font-medium")}>
|
|
144
|
+
{labelFormatter(value, payload)}
|
|
145
|
+
</div>
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!value) {
|
|
150
|
+
return null
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return <div className={cn("font-medium")}>{value}</div>
|
|
154
|
+
}, [
|
|
155
|
+
label,
|
|
156
|
+
labelFormatter,
|
|
157
|
+
payload,
|
|
158
|
+
hideLabel,
|
|
159
|
+
labelKey,
|
|
160
|
+
chartConfig,
|
|
161
|
+
])
|
|
162
|
+
|
|
163
|
+
if (!active || !payload?.length) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const nestLabel = payload.length === 1 && indicator !== "dot"
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<div
|
|
171
|
+
ref={ref}
|
|
172
|
+
className={cn(
|
|
173
|
+
'grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface)] px-2.5 py-1.5 text-xs shadow-xl transition-all ease-in-out animate-in fade-in-0 zoom-in-95',
|
|
174
|
+
className
|
|
175
|
+
)}
|
|
176
|
+
>
|
|
177
|
+
{!nestLabel ? tooltipLabel : null}
|
|
178
|
+
<div className="grid gap-1.5">
|
|
179
|
+
{payload.map((item, index) => {
|
|
180
|
+
const key = `${nameKey || item.name || item.dataKey || "value"}`
|
|
181
|
+
const itemConfig = getPayloadConfigFromPayload(chartConfig, item, key)
|
|
182
|
+
const indicatorColor = colorToCssVariable(
|
|
183
|
+
item.payload.fill || item.color
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
return (
|
|
187
|
+
<div
|
|
188
|
+
key={item.dataKey || index}
|
|
189
|
+
className={cn(
|
|
190
|
+
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
|
|
191
|
+
indicator === "dot" && "items-center"
|
|
192
|
+
)}
|
|
193
|
+
>
|
|
194
|
+
{indicator && !hideIndicator && (
|
|
195
|
+
<div
|
|
196
|
+
className={cn(
|
|
197
|
+
"shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
|
|
198
|
+
{
|
|
199
|
+
"h-2.5 w-2.5": indicator === "dot",
|
|
200
|
+
"w-1": indicator === "line",
|
|
201
|
+
"w-0 border-[1.5px] border-dashed bg-transparent":
|
|
202
|
+
indicator === "dashed",
|
|
203
|
+
"my-0.5": nestLabel && indicator === "dashed",
|
|
204
|
+
}
|
|
205
|
+
)}
|
|
206
|
+
style={
|
|
207
|
+
{
|
|
208
|
+
"--color-bg": indicatorColor,
|
|
209
|
+
"--color-border": indicatorColor,
|
|
210
|
+
} as React.CSSProperties
|
|
211
|
+
}
|
|
212
|
+
/>
|
|
213
|
+
)}
|
|
214
|
+
<div
|
|
215
|
+
className={cn(
|
|
216
|
+
"flex flex-1 justify-between leading-none",
|
|
217
|
+
nestLabel ? "items-end" : "items-center"
|
|
218
|
+
)}
|
|
219
|
+
>
|
|
220
|
+
<div className="grid gap-1.5">
|
|
221
|
+
{nestLabel ? tooltipLabel : null}
|
|
222
|
+
<span className="text-[var(--color-text-secondary)]">
|
|
223
|
+
{itemConfig?.label || item.name}
|
|
224
|
+
</span>
|
|
225
|
+
</div>
|
|
226
|
+
{item.value && (
|
|
227
|
+
<span className="font-mono font-medium tabular-nums text-[var(--color-text-primary)]">
|
|
228
|
+
{item.value.toLocaleString()}
|
|
229
|
+
</span>
|
|
230
|
+
)}
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
)
|
|
234
|
+
})}
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
);
|
|
238
|
+
});
|
|
239
|
+
ChartTooltipContent.displayName = 'ChartTooltipContent';
|
|
240
|
+
|
|
241
|
+
// -- Legend --
|
|
242
|
+
|
|
243
|
+
const ChartLegend = Legend;
|
|
244
|
+
|
|
245
|
+
const ChartLegendContent = React.forwardRef<
|
|
246
|
+
HTMLDivElement,
|
|
247
|
+
React.ComponentProps<'div'> & {
|
|
248
|
+
hideIcon?: boolean;
|
|
249
|
+
nameKey?: string;
|
|
250
|
+
verticalAlign?: 'top' | 'bottom' | 'middle';
|
|
251
|
+
payload?: any[];
|
|
252
|
+
}
|
|
253
|
+
>(({ className, hideIcon = false, payload, verticalAlign = 'bottom', nameKey }, ref) => {
|
|
254
|
+
const { config } = useChart();
|
|
255
|
+
|
|
256
|
+
if (!payload?.length) {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return (
|
|
261
|
+
<div
|
|
262
|
+
ref={ref}
|
|
263
|
+
className={cn(
|
|
264
|
+
'flex items-center justify-center gap-4',
|
|
265
|
+
verticalAlign === 'top' ? 'pb-3' : 'pt-3',
|
|
266
|
+
className
|
|
267
|
+
)}
|
|
268
|
+
>
|
|
269
|
+
{payload.map((item) => {
|
|
270
|
+
const key = `${nameKey || item.dataKey || "value"}`
|
|
271
|
+
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
|
272
|
+
const indicatorColor = colorToCssVariable(item.color)
|
|
273
|
+
|
|
274
|
+
return (
|
|
275
|
+
<div
|
|
276
|
+
key={item.value}
|
|
277
|
+
className={cn(
|
|
278
|
+
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
|
|
279
|
+
)}
|
|
280
|
+
>
|
|
281
|
+
{!hideIcon && (
|
|
282
|
+
<div
|
|
283
|
+
className="h-2 w-2 shrink-0 rounded-[2px]"
|
|
284
|
+
style={{ backgroundColor: indicatorColor }}
|
|
285
|
+
/>
|
|
286
|
+
)}
|
|
287
|
+
<span className="text-sm text-[var(--color-text-primary)]">
|
|
288
|
+
{itemConfig?.label || item.value}
|
|
289
|
+
</span>
|
|
290
|
+
</div>
|
|
291
|
+
)
|
|
292
|
+
})}
|
|
293
|
+
</div>
|
|
294
|
+
);
|
|
295
|
+
});
|
|
296
|
+
ChartLegendContent.displayName = 'ChartLegendContent';
|
|
297
|
+
|
|
298
|
+
// Helper to extract config
|
|
299
|
+
function getPayloadConfigFromPayload(
|
|
300
|
+
config: ChartConfig,
|
|
301
|
+
payload: unknown,
|
|
302
|
+
key: string
|
|
303
|
+
) {
|
|
304
|
+
if (typeof payload !== "object" || payload === null) {
|
|
305
|
+
return undefined
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const payloadPayload =
|
|
309
|
+
"payload" in payload &&
|
|
310
|
+
typeof payload.payload === "object" &&
|
|
311
|
+
payload.payload !== null
|
|
312
|
+
? payload.payload
|
|
313
|
+
: undefined
|
|
314
|
+
|
|
315
|
+
let configLabelKey: string = key
|
|
316
|
+
|
|
317
|
+
if (
|
|
318
|
+
key in payload &&
|
|
319
|
+
typeof payload[key as keyof typeof payload] === "string"
|
|
320
|
+
) {
|
|
321
|
+
configLabelKey = payload[key as keyof typeof payload] as string
|
|
322
|
+
} else if (
|
|
323
|
+
payloadPayload &&
|
|
324
|
+
key in payloadPayload &&
|
|
325
|
+
typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
|
|
326
|
+
) {
|
|
327
|
+
configLabelKey = payloadPayload[
|
|
328
|
+
key as keyof typeof payloadPayload
|
|
329
|
+
] as string
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return configLabelKey in config
|
|
333
|
+
? config[configLabelKey]
|
|
334
|
+
: config[key as keyof typeof config]
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function colorToCssVariable(color: string | undefined): string {
|
|
338
|
+
if (!color) return 'var(--color-primary)';
|
|
339
|
+
// If it's a theme variable like `var(--color-...)` return it
|
|
340
|
+
if (color.startsWith('var(--')) return color;
|
|
341
|
+
// If we wanted to map theme keys to vars, we could do it here
|
|
342
|
+
return color;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export {
|
|
346
|
+
ChartContainer,
|
|
347
|
+
ChartTooltip,
|
|
348
|
+
ChartTooltipContent,
|
|
349
|
+
ChartLegend,
|
|
350
|
+
ChartLegendContent,
|
|
351
|
+
ChartStyle,
|
|
352
|
+
};
|
package/src/index.ts
ADDED
package/src/lib/utils.ts
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"lib": [
|
|
6
|
+
"ES2020",
|
|
7
|
+
"DOM",
|
|
8
|
+
"DOM.Iterable"
|
|
9
|
+
],
|
|
10
|
+
"jsx": "react-jsx",
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"declaration": true,
|
|
13
|
+
"declarationMap": true,
|
|
14
|
+
"sourceMap": true,
|
|
15
|
+
"outDir": "./dist",
|
|
16
|
+
"rootDir": "./src",
|
|
17
|
+
"strict": true,
|
|
18
|
+
"esModuleInterop": true,
|
|
19
|
+
"skipLibCheck": true,
|
|
20
|
+
"forceConsistentCasingInFileNames": true,
|
|
21
|
+
"resolveJsonModule": true
|
|
22
|
+
},
|
|
23
|
+
"include": [
|
|
24
|
+
"src/**/*"
|
|
25
|
+
],
|
|
26
|
+
"exclude": [
|
|
27
|
+
"node_modules",
|
|
28
|
+
"dist"
|
|
29
|
+
]
|
|
30
|
+
}
|