create-bluecopa-react-app 1.0.40 → 1.0.42

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 (102) hide show
  1. package/README.md +16 -14
  2. package/package.json +1 -1
  3. package/templates/latest/.claude/settings.local.json +56 -0
  4. package/templates/latest/.env.example +8 -0
  5. package/templates/latest/Agent.md +598 -775
  6. package/templates/latest/CLAUDE.md +759 -0
  7. package/templates/latest/README.md +17 -8
  8. package/templates/latest/app/app.css +292 -85
  9. package/templates/latest/app/app.tsx +48 -39
  10. package/templates/latest/app/components/bluecopa-logo.tsx +20 -0
  11. package/templates/latest/app/components/charts/bar-chart.tsx +132 -0
  12. package/templates/latest/app/components/charts/base-chart.tsx +149 -0
  13. package/templates/latest/app/components/charts/chart-provider.tsx +71 -0
  14. package/templates/latest/app/components/charts/chart-theme.ts +262 -0
  15. package/templates/latest/app/components/charts/chart-utils.ts +142 -0
  16. package/templates/latest/app/components/charts/donut-chart.tsx +110 -0
  17. package/templates/latest/app/components/charts/index.ts +5 -0
  18. package/templates/latest/app/components/layouts/app-layout.tsx +22 -0
  19. package/templates/latest/app/components/layouts/app-sidebar.tsx +88 -0
  20. package/templates/latest/app/components/layouts/nav-main.tsx +50 -0
  21. package/templates/latest/app/components/layouts/nav-user.tsx +38 -0
  22. package/templates/latest/app/components/layouts/site-header.tsx +93 -0
  23. package/templates/latest/app/components/loading-screen.tsx +41 -0
  24. package/templates/latest/app/components/ui/ag-grid-table.tsx +79 -0
  25. package/templates/latest/app/components/ui/button.tsx +23 -23
  26. package/templates/latest/app/components/ui/card.tsx +20 -20
  27. package/templates/latest/app/components/ui/dropdown-menu.tsx +54 -49
  28. package/templates/latest/app/components/ui/input.tsx +8 -8
  29. package/templates/latest/app/components/ui/label.tsx +8 -8
  30. package/templates/latest/app/components/ui/separator.tsx +7 -7
  31. package/templates/latest/app/components/ui/sheet.tsx +43 -32
  32. package/templates/latest/app/components/ui/sidebar.tsx +240 -235
  33. package/templates/latest/app/components/ui/skeleton.tsx +4 -4
  34. package/templates/latest/app/components/ui/sonner.tsx +6 -9
  35. package/templates/latest/app/components/ui/tabs.tsx +15 -15
  36. package/templates/latest/app/components/ui/tooltip.tsx +18 -12
  37. package/templates/latest/app/constants/index.ts +31 -0
  38. package/templates/latest/app/contexts/app-context.tsx +201 -0
  39. package/templates/latest/app/hooks/use-mobile.ts +13 -12
  40. package/templates/latest/app/main.tsx +1 -1
  41. package/templates/latest/app/pages/dashboard.tsx +246 -0
  42. package/templates/latest/app/pages/payments.tsx +182 -0
  43. package/templates/latest/app/pages/settings.tsx +128 -0
  44. package/templates/latest/app/routes/index.tsx +19 -0
  45. package/templates/latest/app/single-spa.tsx +68 -86
  46. package/templates/latest/app/types/index.ts +37 -0
  47. package/templates/latest/app/utils/ag-grid-datasource.ts +63 -0
  48. package/templates/latest/app/utils/ag-grid-license.ts +12 -0
  49. package/templates/latest/app/utils/ag-grid-theme.ts +9 -0
  50. package/templates/latest/app/utils/component-style.ts +7 -0
  51. package/templates/latest/app/utils/style-drivers.ts +24 -0
  52. package/templates/latest/app/utils/utils.ts +10 -0
  53. package/templates/latest/components.json +3 -3
  54. package/templates/latest/index.html +30 -2
  55. package/templates/latest/package-lock.json +30 -416
  56. package/templates/latest/package.json +8 -18
  57. package/templates/latest/preview/index.html +125 -285
  58. package/templates/latest/public/favicon.svg +1 -0
  59. package/templates/latest/vite.config.ts +2 -8
  60. package/templates/latest/app/components/app-sidebar.tsx +0 -182
  61. package/templates/latest/app/components/chart-area-interactive.tsx +0 -290
  62. package/templates/latest/app/components/data-table.tsx +0 -807
  63. package/templates/latest/app/components/nav-documents.tsx +0 -92
  64. package/templates/latest/app/components/nav-main.tsx +0 -40
  65. package/templates/latest/app/components/nav-secondary.tsx +0 -42
  66. package/templates/latest/app/components/nav-user.tsx +0 -111
  67. package/templates/latest/app/components/section-cards.tsx +0 -102
  68. package/templates/latest/app/components/site-header.tsx +0 -28
  69. package/templates/latest/app/components/ui/avatar.tsx +0 -53
  70. package/templates/latest/app/components/ui/badge.tsx +0 -46
  71. package/templates/latest/app/components/ui/breadcrumb.tsx +0 -109
  72. package/templates/latest/app/components/ui/chart.tsx +0 -352
  73. package/templates/latest/app/components/ui/checkbox.tsx +0 -30
  74. package/templates/latest/app/components/ui/drawer.tsx +0 -139
  75. package/templates/latest/app/components/ui/select.tsx +0 -183
  76. package/templates/latest/app/components/ui/table.tsx +0 -117
  77. package/templates/latest/app/components/ui/toggle-group.tsx +0 -73
  78. package/templates/latest/app/components/ui/toggle.tsx +0 -47
  79. package/templates/latest/app/data/data.json +0 -614
  80. package/templates/latest/app/data/mock-payments.json +0 -122
  81. package/templates/latest/app/data/mock-transactions.json +0 -128
  82. package/templates/latest/app/hooks/use-bluecopa-user.ts +0 -37
  83. package/templates/latest/app/lib/utils.ts +0 -6
  84. package/templates/latest/app/routes/apitest.tsx +0 -2118
  85. package/templates/latest/app/routes/comments.tsx +0 -588
  86. package/templates/latest/app/routes/dashboard.tsx +0 -36
  87. package/templates/latest/app/routes/payments.tsx +0 -342
  88. package/templates/latest/app/routes/statements.tsx +0 -493
  89. package/templates/latest/app/routes/websocket.tsx +0 -450
  90. package/templates/latest/app/routes.tsx +0 -22
  91. package/templates/latest/dist/assets/__federation_expose_App-OFfdinOR.js +0 -97
  92. package/templates/latest/dist/assets/__federation_fn_import-CzfA7kmP.js +0 -438
  93. package/templates/latest/dist/assets/__federation_shared_react-Bp6HVBS4.js +0 -16
  94. package/templates/latest/dist/assets/__federation_shared_react-dom-BCcRGiYp.js +0 -17
  95. package/templates/latest/dist/assets/client-CkHcT_xc.js +0 -76035
  96. package/templates/latest/dist/assets/index-B3cD3sP_.js +0 -60
  97. package/templates/latest/dist/assets/index-BzNimew1.js +0 -69
  98. package/templates/latest/dist/assets/index-DMFtQdNS.js +0 -412
  99. package/templates/latest/dist/assets/remoteEntry.css +0 -3996
  100. package/templates/latest/dist/assets/remoteEntry.js +0 -88
  101. package/templates/latest/dist/favicon.ico +0 -0
  102. package/templates/latest/dist/index.html +0 -19
@@ -0,0 +1,132 @@
1
+ import React, { useMemo } from "react";
2
+ import type { EChartsOption } from "echarts";
3
+ import { BaseChart, type BaseChartRef } from "./base-chart";
4
+ import { chartTooltip, chartLegend, chartGrid, chartCategoryAxis, chartValueAxis, chartTitle, chartAnimation, chartColor } from "./chart-utils";
5
+ import { cn } from "~/utils/utils";
6
+
7
+ export interface BarChartSeries {
8
+ name: string;
9
+ data: number[];
10
+ color?: string;
11
+ }
12
+
13
+ export interface BarChartData {
14
+ categories: string[];
15
+ series: BarChartSeries[];
16
+ }
17
+
18
+ export interface BarChartProps {
19
+ data: BarChartData;
20
+ title?: string;
21
+ subtitle?: string;
22
+ height?: string | number;
23
+ className?: string;
24
+ horizontal?: boolean;
25
+ stacked?: boolean;
26
+ showLegend?: boolean;
27
+ showValues?: boolean;
28
+ loading?: boolean;
29
+ empty?: boolean;
30
+ emptyText?: string;
31
+ onBarClick?: (params: { name: string; seriesName: string; value: number }) => void;
32
+ formatValue?: (value: number) => string;
33
+ ariaLabel?: string;
34
+ }
35
+
36
+ export const BarChart = React.forwardRef<BaseChartRef, BarChartProps>(
37
+ function BarChart(
38
+ { data, title, subtitle, height = "100%", className, horizontal = false, stacked = false, showLegend, showValues = false, loading = false, empty = false, emptyText, onBarClick, formatValue, ariaLabel },
39
+ ref
40
+ ) {
41
+ const option = useMemo<EChartsOption>(() => {
42
+ const { categories, series } = data;
43
+ const shouldShowLegend = showLegend ?? series.length > 1;
44
+
45
+ const chartSeries = series.map((s, idx) => ({
46
+ type: "bar" as const,
47
+ name: s.name,
48
+ data: s.data,
49
+ itemStyle: {
50
+ color: s.color ?? chartColor(idx),
51
+ borderRadius: horizontal ? [0, 4, 4, 0] : [4, 4, 0, 0],
52
+ },
53
+ emphasis: {
54
+ focus: "series" as const,
55
+ blurScope: "coordinateSystem" as const,
56
+ itemStyle: { shadowBlur: 10, shadowOffsetY: 2, shadowColor: "rgba(0, 0, 0, 0.18)", borderWidth: 1, borderColor: "rgba(255, 255, 255, 0.4)" },
57
+ },
58
+ stack: stacked ? "value" : undefined,
59
+ label: showValues
60
+ ? { show: true, position: (horizontal ? "right" : "top") as any, formatter: formatValue ? (p: any) => formatValue(p.value) : undefined, fontSize: 11, fontWeight: 500, distance: 4 }
61
+ : undefined,
62
+ }));
63
+
64
+ const baseOption: Record<string, any> = {
65
+ ...(title && chartTitle(title, { subtitle })),
66
+ ...chartTooltip({
67
+ trigger: "axis",
68
+ formatter: (params: any) => {
69
+ if (!Array.isArray(params)) params = [params];
70
+ let html = `<strong>${params[0]?.name}</strong><br/>`;
71
+ params.forEach((p: any) => {
72
+ const val = formatValue ? formatValue(p.value) : p.value;
73
+ html += `<span style="display:inline-block;margin-right:8px;"><span style="display:inline-block;width:8px;height:8px;border-radius:2px;background:${p.color};margin-right:6px;"></span>${p.seriesName}: ${val}</span><br/>`;
74
+ });
75
+ return html;
76
+ },
77
+ }),
78
+ ...(shouldShowLegend && chartLegend({ position: "bottom" })),
79
+ ...chartGrid(),
80
+ ...chartAnimation({ delay: (idx: number) => idx * 60 }),
81
+ };
82
+
83
+ if (horizontal) {
84
+ baseOption.xAxis = chartValueAxis();
85
+ baseOption.yAxis = chartCategoryAxis(categories);
86
+ } else {
87
+ baseOption.xAxis = chartCategoryAxis(categories);
88
+ baseOption.yAxis = chartValueAxis();
89
+ }
90
+
91
+ baseOption.series = chartSeries;
92
+ return baseOption;
93
+ }, [data, title, subtitle, horizontal, stacked, showLegend, showValues, formatValue]);
94
+
95
+ const onEvents = useMemo(() => {
96
+ if (!onBarClick) return undefined;
97
+ return {
98
+ click: (params: any) => {
99
+ if (params.componentSubType === "bar") {
100
+ onBarClick({ name: params.name, seriesName: params.seriesName, value: params.value });
101
+ }
102
+ },
103
+ };
104
+ }, [onBarClick]);
105
+
106
+ const dataSummary = useMemo(() => {
107
+ const { categories, series } = data;
108
+ return series.map((s) => {
109
+ const points = s.data.map((val, i) => `${categories[i]}: ${formatValue ? formatValue(val) : val}`).join(", ");
110
+ return `${s.name}: ${points}`;
111
+ }).join(". ");
112
+ }, [data, formatValue]);
113
+
114
+ return (
115
+ <BaseChart
116
+ ref={ref}
117
+ option={option}
118
+ height={height}
119
+ className={cn("copa:bar-chart", className)}
120
+ loading={loading}
121
+ empty={empty}
122
+ emptyText={emptyText}
123
+ onEvents={onEvents}
124
+ ariaLabel={ariaLabel || `Bar chart${title ? `: ${title}` : ""}`}
125
+ dataSummary={dataSummary}
126
+ />
127
+ );
128
+ }
129
+ );
130
+
131
+ BarChart.displayName = "BarChart";
132
+ export default BarChart;
@@ -0,0 +1,149 @@
1
+ /**
2
+ * BaseChart
3
+ *
4
+ * Thin wrapper around echarts-for-react that uses the BlueCopa ECharts theme,
5
+ * handles responsive resizing, and provides loading & empty states.
6
+ */
7
+
8
+ import React, { useRef, useEffect, useMemo, forwardRef, useImperativeHandle } from "react";
9
+ import ReactECharts from "echarts-for-react";
10
+ import type { EChartsOption, ECharts } from "echarts";
11
+ import { useChartContext } from "./chart-provider";
12
+ import { chartAnimation } from "./chart-utils";
13
+ import { cn } from "~/utils/utils";
14
+
15
+ export interface BaseChartProps {
16
+ option: EChartsOption | Record<string, any>;
17
+ height?: string | number;
18
+ width?: string | number;
19
+ className?: string;
20
+ style?: React.CSSProperties;
21
+ loading?: boolean;
22
+ empty?: boolean;
23
+ emptyText?: string;
24
+ onEvents?: Record<string, (params: any) => void>;
25
+ group?: string;
26
+ noAnimation?: boolean;
27
+ renderer?: "canvas" | "svg";
28
+ onChartReady?: (instance: ECharts) => void;
29
+ ariaLabel?: string;
30
+ dataSummary?: string;
31
+ }
32
+
33
+ export interface BaseChartRef {
34
+ getInstance: () => ECharts | undefined;
35
+ resize: () => void;
36
+ }
37
+
38
+ function ChartLoading() {
39
+ return (
40
+ <div className="copa:absolute copa:inset-0 copa:flex copa:items-center copa:justify-center copa:bg-background/60 copa:backdrop-blur-sm copa:z-10 copa:rounded-lg">
41
+ <div className="copa:flex copa:flex-col copa:items-center copa:gap-3">
42
+ <div className="copa:h-8 copa:w-8 copa:rounded-full copa:border-2 copa:border-muted copa:border-t-primary copa:animate-spin" />
43
+ <span className="copa:text-xs copa:text-muted-foreground">Loading chart…</span>
44
+ </div>
45
+ </div>
46
+ );
47
+ }
48
+
49
+ function ChartEmpty({ text = "No data available" }: { text?: string }) {
50
+ return (
51
+ <div className="copa:absolute copa:inset-0 copa:flex copa:items-center copa:justify-center copa:z-10">
52
+ <div className="copa:flex copa:flex-col copa:items-center copa:gap-2 copa:text-muted-foreground">
53
+ <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" className="copa:opacity-40">
54
+ <path d="M3 3v18h18" />
55
+ <path d="M7 16l4-8 4 4 4-6" />
56
+ </svg>
57
+ <span className="copa:text-sm">{text}</span>
58
+ </div>
59
+ </div>
60
+ );
61
+ }
62
+
63
+ export const BaseChart = forwardRef<BaseChartRef, BaseChartProps>(
64
+ function BaseChart(
65
+ {
66
+ option,
67
+ height = "100%",
68
+ width = "100%",
69
+ className,
70
+ style,
71
+ loading = false,
72
+ empty = false,
73
+ emptyText,
74
+ onEvents,
75
+ group,
76
+ noAnimation = false,
77
+ renderer = "canvas",
78
+ onChartReady,
79
+ ariaLabel,
80
+ dataSummary,
81
+ },
82
+ ref
83
+ ) {
84
+ const chartRef = useRef<ReactECharts>(null);
85
+ const { themeName, version } = useChartContext();
86
+
87
+ const mergedOption = useMemo(() => {
88
+ const anim = noAnimation ? { animation: false } : chartAnimation();
89
+ return { ...anim, ...option };
90
+ }, [option, noAnimation]);
91
+
92
+ useImperativeHandle(ref, () => ({
93
+ getInstance: () => chartRef.current?.getEchartsInstance(),
94
+ resize: () => chartRef.current?.getEchartsInstance()?.resize(),
95
+ }));
96
+
97
+ useEffect(() => {
98
+ if (group) {
99
+ const instance = chartRef.current?.getEchartsInstance();
100
+ if (instance) instance.group = group;
101
+ }
102
+ }, [group]);
103
+
104
+ useEffect(() => {
105
+ if (onChartReady) {
106
+ const instance = chartRef.current?.getEchartsInstance();
107
+ if (instance) onChartReady(instance);
108
+ }
109
+ }, [onChartReady]);
110
+
111
+ const key = `chart-${version}`;
112
+
113
+ return (
114
+ <div
115
+ className={cn("copa:relative", className)}
116
+ style={{
117
+ height: typeof height === "number" ? `${height}px` : height,
118
+ width: typeof width === "number" ? `${width}px` : width,
119
+ minHeight: 200,
120
+ ...style,
121
+ }}
122
+ role="img"
123
+ aria-label={ariaLabel}
124
+ >
125
+ {dataSummary && (
126
+ <div className="copa:sr-only" role="document" aria-label="Chart data">
127
+ {dataSummary}
128
+ </div>
129
+ )}
130
+ {loading && <ChartLoading />}
131
+ {empty && <ChartEmpty text={emptyText} />}
132
+
133
+ <ReactECharts
134
+ key={key}
135
+ ref={chartRef}
136
+ option={mergedOption}
137
+ theme={themeName}
138
+ style={{ height: "100%", width: "100%" }}
139
+ opts={{ renderer }}
140
+ onEvents={onEvents}
141
+ notMerge={true}
142
+ lazyUpdate={true}
143
+ />
144
+ </div>
145
+ );
146
+ }
147
+ );
148
+
149
+ export default BaseChart;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * ChartProvider
3
+ *
4
+ * React context that bridges the CSS design token system to ECharts.
5
+ * Re-registers the ECharts theme when the document theme changes.
6
+ */
7
+
8
+ import React, { createContext, useContext, useEffect, useState, useCallback, useRef } from "react";
9
+ import { registerChartTheme, resolveChartTokens, CHART_THEME_NAME, type ChartTokens } from "./chart-theme";
10
+
11
+ interface ChartContextValue {
12
+ themeName: string;
13
+ tokens: ChartTokens;
14
+ version: number;
15
+ }
16
+
17
+ const ChartContext = createContext<ChartContextValue | null>(null);
18
+
19
+ export function useChartContext(): ChartContextValue {
20
+ const ctx = useContext(ChartContext);
21
+ if (!ctx) {
22
+ throw new Error("useChartContext must be used inside a <ChartProvider>.");
23
+ }
24
+ return ctx;
25
+ }
26
+
27
+ interface ChartProviderProps {
28
+ children: React.ReactNode;
29
+ }
30
+
31
+ export function ChartProvider({ children }: ChartProviderProps) {
32
+ const [version, setVersion] = useState(0);
33
+ const [chartTokens, setChartTokens] = useState<ChartTokens>(() =>
34
+ typeof window !== "undefined" ? resolveChartTokens() : ({} as ChartTokens)
35
+ );
36
+ const observerRef = useRef<MutationObserver | null>(null);
37
+
38
+ const refresh = useCallback(() => {
39
+ registerChartTheme();
40
+ setChartTokens(resolveChartTokens());
41
+ setVersion((v) => v + 1);
42
+ }, []);
43
+
44
+ useEffect(() => {
45
+ refresh();
46
+
47
+ const target = document.documentElement;
48
+ observerRef.current = new MutationObserver((mutations) => {
49
+ for (const m of mutations) {
50
+ if (m.type === "attributes" && (m.attributeName === "class" || m.attributeName === "data-theme" || m.attributeName === "style")) {
51
+ refresh();
52
+ break;
53
+ }
54
+ }
55
+ });
56
+
57
+ observerRef.current.observe(target, { attributes: true, attributeFilter: ["class", "data-theme", "style"] });
58
+
59
+ return () => {
60
+ observerRef.current?.disconnect();
61
+ };
62
+ }, [refresh]);
63
+
64
+ return (
65
+ <ChartContext.Provider value={{ themeName: CHART_THEME_NAME, tokens: chartTokens, version }}>
66
+ {children}
67
+ </ChartContext.Provider>
68
+ );
69
+ }
70
+
71
+ export default ChartProvider;
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Chart Theme Bridge
3
+ * Reads CSS design tokens from .mfe-root and maps them to ECharts theme.
4
+ */
5
+
6
+ import * as echarts from "echarts";
7
+
8
+ // ---------------------------------------------------------------------------
9
+ // Helpers
10
+ // ---------------------------------------------------------------------------
11
+
12
+ /** Resolve a CSS custom property — prefers .mfe-root, falls back to :root */
13
+ function getCSSVar(name: string): string {
14
+ const mfeRoot = document.querySelector(".mfe-root") as HTMLElement | null;
15
+ const target = mfeRoot ?? document.documentElement;
16
+ return getComputedStyle(target).getPropertyValue(name).trim();
17
+ }
18
+
19
+ function clamp01(x: number): number {
20
+ return Math.max(0, Math.min(1, x));
21
+ }
22
+
23
+ function linearToSrgb(c: number): number {
24
+ return c <= 0.0031308 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
25
+ }
26
+
27
+ function oklabToLinearSrgb(L: number, a: number, b: number): [number, number, number] {
28
+ const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
29
+ const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
30
+ const s_ = L - 0.0894841775 * a - 1.2914855480 * b;
31
+ const l = l_ * l_ * l_;
32
+ const m = m_ * m_ * m_;
33
+ const s = s_ * s_ * s_;
34
+ return [
35
+ +4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
36
+ -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
37
+ -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s,
38
+ ];
39
+ }
40
+
41
+ function oklchToHex(L: number, C: number, H: number): string {
42
+ const hRad = (H * Math.PI) / 180;
43
+ const a = C * Math.cos(hRad);
44
+ const b = C * Math.sin(hRad);
45
+ const [lr, lg, lb] = oklabToLinearSrgb(L, a, b);
46
+ const r = Math.round(clamp01(linearToSrgb(lr)) * 255);
47
+ const g = Math.round(clamp01(linearToSrgb(lg)) * 255);
48
+ const bv = Math.round(clamp01(linearToSrgb(lb)) * 255);
49
+ return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${bv.toString(16).padStart(2, "0")}`;
50
+ }
51
+
52
+ function parseRgbToHex(str: string): string | null {
53
+ const m = str.match(/rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)/);
54
+ if (!m) return null;
55
+ const r = Math.round(parseFloat(m[1]));
56
+ const g = Math.round(parseFloat(m[2]));
57
+ const b = Math.round(parseFloat(m[3]));
58
+ return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
59
+ }
60
+
61
+ function resolveColor(cssValue: string): string {
62
+ if (!cssValue) return "#888888";
63
+ const v = cssValue.trim();
64
+
65
+ if (/^#[0-9a-fA-F]{6}$/.test(v)) return v;
66
+ if (/^#[0-9a-fA-F]{3}$/.test(v)) {
67
+ const [, r, g, b] = v.split("");
68
+ return `#${r}${r}${g}${g}${b}${b}`;
69
+ }
70
+
71
+ const oklchMatch = v.match(/oklch\(\s*([\d.]+)\s+([\d.]+)\s+([\d.]+)/);
72
+ if (oklchMatch) {
73
+ return oklchToHex(parseFloat(oklchMatch[1]), parseFloat(oklchMatch[2]), parseFloat(oklchMatch[3]));
74
+ }
75
+
76
+ const rgbResult = parseRgbToHex(v);
77
+ if (rgbResult) return rgbResult;
78
+
79
+ return "#888888";
80
+ }
81
+
82
+ function hexToRgb(hex: string): [number, number, number] {
83
+ const h = hex.replace("#", "");
84
+ return [parseInt(h.substring(0, 2), 16), parseInt(h.substring(2, 4), 16), parseInt(h.substring(4, 6), 16)];
85
+ }
86
+
87
+ function rgbToHex(r: number, g: number, b: number): string {
88
+ return "#" + [r, g, b].map((c) => Math.max(0, Math.min(255, Math.round(c))).toString(16).padStart(2, "0")).join("");
89
+ }
90
+
91
+ function tint(hex: string, factor: number): string {
92
+ const [r, g, b] = hexToRgb(hex);
93
+ return rgbToHex(r + (255 - r) * factor, g + (255 - g) * factor, b + (255 - b) * factor);
94
+ }
95
+
96
+ function shade(hex: string, factor: number): string {
97
+ const [r, g, b] = hexToRgb(hex);
98
+ return rgbToHex(r * (1 - factor), g * (1 - factor), b * (1 - factor));
99
+ }
100
+
101
+ // ---------------------------------------------------------------------------
102
+ // Fallback colors (hex equivalents of Dream Light OKLCH chart palette)
103
+ // ---------------------------------------------------------------------------
104
+
105
+ const CHART_FALLBACK_COLORS = [
106
+ "#2563eb", // --chart-1: oklch(0.55 0.15 255) — Blue
107
+ "#0d9488", // --chart-2: oklch(0.62 0.12 175) — Teal
108
+ "#ca8a04", // --chart-3: oklch(0.70 0.13 80) — Gold
109
+ "#7c3aed", // --chart-4: oklch(0.55 0.14 295) — Purple
110
+ "#dc2626", // --chart-5: oklch(0.62 0.12 15) — Red
111
+ ];
112
+
113
+ function resolveChartColor(cssVar: string, fallback: string): string {
114
+ const raw = getCSSVar(cssVar);
115
+ if (!raw) return fallback;
116
+ const resolved = resolveColor(raw);
117
+ return resolved === "#888888" ? fallback : resolved;
118
+ }
119
+
120
+ // ---------------------------------------------------------------------------
121
+ // Token Resolution
122
+ // ---------------------------------------------------------------------------
123
+
124
+ export interface ChartTokens {
125
+ colors: string[];
126
+ fontFamily: string;
127
+ fontSize: number;
128
+ textColor: string;
129
+ textColorSecondary: string;
130
+ backgroundColor: string;
131
+ borderColor: string;
132
+ successColor: string;
133
+ warningColor: string;
134
+ errorColor: string;
135
+ infoColor: string;
136
+ containerPadding: number;
137
+ animationDuration: number;
138
+ animationEasing: string;
139
+ }
140
+
141
+ export function resolveChartTokens(): ChartTokens {
142
+ const base5 = [
143
+ resolveChartColor("--chart-1", CHART_FALLBACK_COLORS[0]),
144
+ resolveChartColor("--chart-2", CHART_FALLBACK_COLORS[1]),
145
+ resolveChartColor("--chart-3", CHART_FALLBACK_COLORS[2]),
146
+ resolveChartColor("--chart-4", CHART_FALLBACK_COLORS[3]),
147
+ resolveChartColor("--chart-5", CHART_FALLBACK_COLORS[4]),
148
+ ];
149
+
150
+ const extended = [
151
+ ...base5,
152
+ tint(base5[0], 0.35),
153
+ shade(base5[1], 0.25),
154
+ tint(base5[2], 0.4),
155
+ shade(base5[3], 0.2),
156
+ tint(base5[4], 0.3),
157
+ ];
158
+
159
+ const fontFamily = getCSSVar("--font-sans") || "Satoshi, system-ui, sans-serif";
160
+
161
+ return {
162
+ colors: extended,
163
+ fontFamily,
164
+ fontSize: 12,
165
+ textColor: resolveChartColor("--foreground", "#1a1a1a"),
166
+ textColorSecondary: resolveChartColor("--muted-foreground", "#737373"),
167
+ backgroundColor: resolveChartColor("--background", "#f8f8fa"),
168
+ borderColor: resolveChartColor("--border", "#e5e5e5"),
169
+ successColor: resolveChartColor("--status-success", "#22c55e"),
170
+ warningColor: resolveChartColor("--status-warning", "#f59e0b"),
171
+ errorColor: resolveChartColor("--status-error", "#ef4444"),
172
+ infoColor: resolveChartColor("--status-info", "#3b82f6"),
173
+ containerPadding: 16,
174
+ animationDuration: 800,
175
+ animationEasing: "cubicInOut",
176
+ };
177
+ }
178
+
179
+ // ---------------------------------------------------------------------------
180
+ // ECharts Theme
181
+ // ---------------------------------------------------------------------------
182
+
183
+ export function buildEChartsTheme(tokens: ChartTokens): Record<string, any> {
184
+ const { colors, fontFamily, fontSize, textColor, textColorSecondary, backgroundColor, borderColor } = tokens;
185
+
186
+ return {
187
+ color: colors,
188
+ backgroundColor: "transparent",
189
+ textStyle: { fontFamily, fontSize, color: textColor },
190
+ title: {
191
+ textStyle: { fontFamily, fontSize: 16, fontWeight: 600, color: textColor },
192
+ subtextStyle: { fontFamily, fontSize: 12, color: textColorSecondary },
193
+ top: 0, left: 0, padding: [0, 0, 16, 0],
194
+ },
195
+ legend: {
196
+ textStyle: { fontFamily, fontSize: 12, color: textColorSecondary },
197
+ icon: "roundRect", itemWidth: 12, itemHeight: 12, itemGap: 16, bottom: 0,
198
+ },
199
+ tooltip: {
200
+ backgroundColor, borderColor, borderWidth: 1, borderRadius: 8, padding: [8, 12],
201
+ textStyle: { fontFamily, fontSize: 12, color: textColor },
202
+ extraCssText: "box-shadow: 0 4px 12px rgba(0,0,0,0.08); backdrop-filter: blur(8px);",
203
+ },
204
+ grid: { top: 48, right: 16, bottom: 48, left: 16, containLabel: true },
205
+ categoryAxis: {
206
+ axisLine: { show: true, lineStyle: { color: borderColor } },
207
+ axisTick: { show: false },
208
+ axisLabel: { fontFamily, fontSize: 11, color: textColorSecondary },
209
+ splitLine: { show: false },
210
+ },
211
+ valueAxis: {
212
+ axisLine: { show: false }, axisTick: { show: false },
213
+ axisLabel: { fontFamily, fontSize: 11, color: textColorSecondary },
214
+ splitLine: { show: true, lineStyle: { color: borderColor, type: "dashed", opacity: 0.6 } },
215
+ },
216
+ bar: {
217
+ barMaxWidth: 32,
218
+ itemStyle: { borderRadius: [4, 4, 0, 0] },
219
+ emphasis: {
220
+ focus: "series", blurScope: "coordinateSystem",
221
+ itemStyle: { shadowBlur: 12, shadowOffsetY: 3, shadowColor: "rgba(0,0,0,0.2)", borderWidth: 1, borderColor: "rgba(255,255,255,0.4)" },
222
+ },
223
+ },
224
+ line: {
225
+ smooth: true, symbol: "circle", symbolSize: 6, lineStyle: { width: 2.5 },
226
+ emphasis: {
227
+ focus: "series", blurScope: "coordinateSystem", lineStyle: { width: 4 },
228
+ itemStyle: { borderWidth: 3, borderColor: "#fff", shadowBlur: 8, shadowColor: "rgba(0,0,0,0.18)" },
229
+ },
230
+ },
231
+ pie: {
232
+ radius: ["0%", "70%"],
233
+ itemStyle: { borderColor: backgroundColor, borderWidth: 3, borderRadius: 4 },
234
+ label: { fontFamily, fontSize: 12, color: textColor },
235
+ emphasis: {
236
+ focus: "self",
237
+ itemStyle: { shadowBlur: 16, shadowOffsetY: 4, shadowColor: "rgba(0,0,0,0.25)", borderWidth: 3, borderColor: "rgba(255,255,255,0.6)" },
238
+ },
239
+ },
240
+ };
241
+ }
242
+
243
+ // ---------------------------------------------------------------------------
244
+ // Theme Registration
245
+ // ---------------------------------------------------------------------------
246
+
247
+ const THEME_NAME = "bluecopa";
248
+ let _registered = false;
249
+
250
+ export function registerChartTheme(): string {
251
+ const tokens = resolveChartTokens();
252
+ const theme = buildEChartsTheme(tokens);
253
+ echarts.registerTheme(THEME_NAME, theme);
254
+ _registered = true;
255
+ return THEME_NAME;
256
+ }
257
+
258
+ export function isChartThemeRegistered(): boolean {
259
+ return _registered;
260
+ }
261
+
262
+ export const CHART_THEME_NAME = THEME_NAME;