@srcroot/ui 0.0.39 → 0.0.40
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/dist/index.js
CHANGED
|
@@ -372,11 +372,12 @@ async function init(options) {
|
|
|
372
372
|
await initializer.run();
|
|
373
373
|
}
|
|
374
374
|
|
|
375
|
-
// src/cli/
|
|
375
|
+
// src/cli/services/component-adder.ts
|
|
376
376
|
import fs4 from "fs-extra";
|
|
377
377
|
import path4 from "path";
|
|
378
378
|
import ora2 from "ora";
|
|
379
379
|
import prompts2 from "prompts";
|
|
380
|
+
import { execa as execa2 } from "execa";
|
|
380
381
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
381
382
|
|
|
382
383
|
// src/cli/registry.ts
|
|
@@ -755,121 +756,192 @@ var REGISTRY = {
|
|
|
755
756
|
description: "AI chat interface",
|
|
756
757
|
category: "Data Display",
|
|
757
758
|
dependencies: ["button", "input", "scroll-area", "avatar"]
|
|
759
|
+
},
|
|
760
|
+
chart: {
|
|
761
|
+
file: "ui/chart.tsx",
|
|
762
|
+
description: "Charts using Recharts",
|
|
763
|
+
category: "Data Display",
|
|
764
|
+
dependencies: [],
|
|
765
|
+
registryDependencies: ["recharts"]
|
|
758
766
|
}
|
|
759
767
|
};
|
|
760
768
|
|
|
761
|
-
// src/cli/
|
|
769
|
+
// src/cli/services/component-adder.ts
|
|
762
770
|
var __dirname4 = path4.dirname(fileURLToPath3(import.meta.url));
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
771
|
+
var ComponentAdder = class {
|
|
772
|
+
cwd;
|
|
773
|
+
options;
|
|
774
|
+
constructor(cwd, options) {
|
|
775
|
+
this.cwd = cwd;
|
|
776
|
+
this.options = options;
|
|
767
777
|
}
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
choices: Object.keys(REGISTRY).map((name) => ({
|
|
776
|
-
title: name,
|
|
777
|
-
value: name
|
|
778
|
-
}))
|
|
779
|
-
});
|
|
780
|
-
if (!items || items.length === 0) {
|
|
781
|
-
logger.warn("No components selected.");
|
|
782
|
-
process.exit(0);
|
|
778
|
+
async add(components) {
|
|
779
|
+
components = await this.resolveComponents(components);
|
|
780
|
+
const { valid, invalid } = this.validateComponents(components);
|
|
781
|
+
if (invalid.length > 0) {
|
|
782
|
+
logger.error(`Unknown components: ${invalid.join(", ")}`);
|
|
783
|
+
console.log("\nRun '@srcroot/ui list' to see available components.");
|
|
784
|
+
process.exit(1);
|
|
783
785
|
}
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
for (const comp of components) {
|
|
789
|
-
if (comp in REGISTRY) {
|
|
790
|
-
validComponents.push(comp);
|
|
791
|
-
} else {
|
|
792
|
-
invalidComponents.push(comp);
|
|
786
|
+
const { componentDeps, packageDeps } = this.resolveDependencies(valid);
|
|
787
|
+
this.logPlan(componentDeps, packageDeps);
|
|
788
|
+
if (packageDeps.size > 0) {
|
|
789
|
+
await this.installPackages(Array.from(packageDeps));
|
|
793
790
|
}
|
|
791
|
+
await this.copyComponents(Array.from(componentDeps));
|
|
792
|
+
logger.success("\n\u2705 Components added successfully!\n");
|
|
794
793
|
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
794
|
+
async resolveComponents(components) {
|
|
795
|
+
if (this.options.all) {
|
|
796
|
+
return Object.keys(REGISTRY);
|
|
797
|
+
}
|
|
798
|
+
if (components.length === 0) {
|
|
799
|
+
const { items } = await prompts2({
|
|
800
|
+
type: "multiselect",
|
|
801
|
+
name: "items",
|
|
802
|
+
message: "Which components would you like to add?",
|
|
803
|
+
hint: "Space to select. A to toggle all. Enter to submit.",
|
|
804
|
+
instructions: false,
|
|
805
|
+
choices: Object.keys(REGISTRY).map((name) => ({
|
|
806
|
+
title: name,
|
|
807
|
+
value: name
|
|
808
|
+
}))
|
|
809
|
+
});
|
|
810
|
+
if (!items || items.length === 0) {
|
|
811
|
+
logger.warn("No components selected.");
|
|
812
|
+
process.exit(0);
|
|
813
|
+
}
|
|
814
|
+
return items;
|
|
815
|
+
}
|
|
816
|
+
return components;
|
|
799
817
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
818
|
+
validateComponents(components) {
|
|
819
|
+
const valid = [];
|
|
820
|
+
const invalid = [];
|
|
821
|
+
for (const comp of components) {
|
|
822
|
+
if (comp in REGISTRY) {
|
|
823
|
+
valid.push(comp);
|
|
824
|
+
} else {
|
|
825
|
+
invalid.push(comp);
|
|
808
826
|
}
|
|
809
827
|
}
|
|
828
|
+
return { valid, invalid };
|
|
810
829
|
}
|
|
811
|
-
|
|
812
|
-
|
|
830
|
+
resolveDependencies(components) {
|
|
831
|
+
const componentDeps = /* @__PURE__ */ new Set();
|
|
832
|
+
const packageDeps = /* @__PURE__ */ new Set();
|
|
833
|
+
const resolve = (name) => {
|
|
834
|
+
if (componentDeps.has(name)) return;
|
|
835
|
+
componentDeps.add(name);
|
|
836
|
+
const comp = REGISTRY[name];
|
|
837
|
+
if (comp.dependencies) {
|
|
838
|
+
for (const dep of comp.dependencies) {
|
|
839
|
+
resolve(dep);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
if (comp.registryDependencies) {
|
|
843
|
+
for (const pkg of comp.registryDependencies) {
|
|
844
|
+
packageDeps.add(pkg);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
};
|
|
848
|
+
for (const comp of components) {
|
|
849
|
+
resolve(comp);
|
|
850
|
+
}
|
|
851
|
+
return { componentDeps, packageDeps };
|
|
813
852
|
}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
853
|
+
logPlan(componentDeps, packageDeps) {
|
|
854
|
+
const componentsToAdd = Array.from(componentDeps);
|
|
855
|
+
const packagesToInstall = Array.from(packageDeps);
|
|
856
|
+
if (componentsToAdd.length > 10) {
|
|
857
|
+
logger.info(`
|
|
817
858
|
\u{1F4E6} Adding ${componentsToAdd.length} components...
|
|
818
859
|
`);
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
860
|
+
} else {
|
|
861
|
+
logger.info("\n\u{1F4E6} Adding components:\n");
|
|
862
|
+
componentsToAdd.forEach((name) => {
|
|
863
|
+
console.log(` - ${name}`);
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
if (packagesToInstall.length > 0) {
|
|
867
|
+
logger.info("\n\u{1F4E6} Installing dependencies:\n");
|
|
868
|
+
packagesToInstall.forEach((pkg) => {
|
|
869
|
+
console.log(` - ${pkg}`);
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
console.log();
|
|
824
873
|
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
874
|
+
async installPackages(packages) {
|
|
875
|
+
const packageManager = getPackageManager(this.cwd);
|
|
876
|
+
const spinner = ora2("Installing dependencies...").start();
|
|
877
|
+
const installCmd = packageManager === "npm" ? "install" : "add";
|
|
878
|
+
try {
|
|
879
|
+
await execa2(packageManager, [installCmd, ...packages], {
|
|
880
|
+
cwd: this.cwd
|
|
881
|
+
});
|
|
882
|
+
spinner.succeed("Dependencies installed");
|
|
883
|
+
} catch (error) {
|
|
884
|
+
spinner.fail("Failed to install dependencies");
|
|
885
|
+
logger.error(error);
|
|
886
|
+
logger.warn(`
|
|
887
|
+
Please manually install: ${packages.join(" ")}`);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
async copyComponents(components) {
|
|
891
|
+
const spinner = ora2("Adding components...").start();
|
|
892
|
+
const hasSrc = fs4.existsSync(path4.join(this.cwd, "src"));
|
|
893
|
+
const srcPath = hasSrc ? path4.join(this.cwd, "src") : this.cwd;
|
|
894
|
+
const componentsDir = path4.join(srcPath, "components", "ui");
|
|
895
|
+
try {
|
|
896
|
+
await fs4.ensureDir(componentsDir);
|
|
897
|
+
for (const name of components) {
|
|
898
|
+
const comp = REGISTRY[name];
|
|
899
|
+
const fileName = path4.basename(comp.file);
|
|
900
|
+
const targetPath = path4.join(componentsDir, fileName);
|
|
901
|
+
if (fs4.existsSync(targetPath) && !this.options.overwrite) {
|
|
902
|
+
spinner.stop();
|
|
903
|
+
const { overwrite } = await prompts2({
|
|
904
|
+
type: "confirm",
|
|
905
|
+
name: "overwrite",
|
|
906
|
+
message: `${fileName} already exists. Overwrite?`,
|
|
907
|
+
initial: false
|
|
908
|
+
});
|
|
909
|
+
if (!overwrite) {
|
|
910
|
+
spinner.info(`Skipped ${fileName}`);
|
|
911
|
+
spinner.start("Adding components...");
|
|
912
|
+
continue;
|
|
913
|
+
}
|
|
846
914
|
spinner.start("Adding components...");
|
|
915
|
+
}
|
|
916
|
+
const registryPath = path4.resolve(__dirname4, "..", "..", "registry", comp.file);
|
|
917
|
+
if (!fs4.existsSync(registryPath)) {
|
|
918
|
+
spinner.warn(`Registry file not found for ${name}: ${registryPath}`);
|
|
847
919
|
continue;
|
|
848
920
|
}
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
921
|
+
const content = await fs4.readFile(registryPath, "utf-8");
|
|
922
|
+
await fs4.writeFile(targetPath, content);
|
|
923
|
+
if (components.length > 10) {
|
|
924
|
+
spinner.text = `Adding ${fileName}...`;
|
|
925
|
+
} else {
|
|
926
|
+
spinner.succeed(`Added ${fileName}`);
|
|
927
|
+
}
|
|
855
928
|
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
if (componentsToAdd.length > 10) {
|
|
859
|
-
spinner.text = `Adding ${fileName}...`;
|
|
860
|
-
} else {
|
|
861
|
-
spinner.succeed(`Added ${fileName}`);
|
|
929
|
+
if (components.length > 10) {
|
|
930
|
+
spinner.succeed(`Added ${components.length} components`);
|
|
862
931
|
}
|
|
932
|
+
} catch (error) {
|
|
933
|
+
spinner.fail("Failed to add components");
|
|
934
|
+
console.error(error);
|
|
935
|
+
process.exit(1);
|
|
863
936
|
}
|
|
864
|
-
if (componentsToAdd.length > 10) {
|
|
865
|
-
spinner.succeed(`Added ${componentsToAdd.length} components`);
|
|
866
|
-
}
|
|
867
|
-
logger.success("\n\u2705 Components added successfully!\n");
|
|
868
|
-
} catch (error) {
|
|
869
|
-
spinner.fail("Failed to add components");
|
|
870
|
-
console.error(error);
|
|
871
|
-
process.exit(1);
|
|
872
937
|
}
|
|
938
|
+
};
|
|
939
|
+
|
|
940
|
+
// src/cli/commands/add.ts
|
|
941
|
+
async function add(components, options) {
|
|
942
|
+
const cwd = process.cwd();
|
|
943
|
+
const adder = new ComponentAdder(cwd, options);
|
|
944
|
+
await adder.add(components);
|
|
873
945
|
}
|
|
874
946
|
|
|
875
947
|
// src/cli/commands/list.ts
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@ interface GoogleTagManagerProps {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
const GoogleTagManager: FC<GoogleTagManagerProps> = ({ containers }) => {
|
|
14
|
-
const defaultServer = 'https://www.googletagmanager.com
|
|
14
|
+
const defaultServer = 'https://www.googletagmanager.com';
|
|
15
15
|
|
|
16
16
|
// Group containers by tagServer to avoid duplicate script loads
|
|
17
17
|
const scriptsMap = containers.reduce((map, container) => {
|
|
@@ -32,7 +32,7 @@ const GoogleTagManager: FC<GoogleTagManagerProps> = ({ containers }) => {
|
|
|
32
32
|
__html: `
|
|
33
33
|
${ids
|
|
34
34
|
.map(
|
|
35
|
-
id => `(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s);j.async=true;j.src="${server}?"+i;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','${id}');`
|
|
35
|
+
id => `(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s);j.async=true;j.src="${server}/gtm.js?"+i;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','${id}');`
|
|
36
36
|
)
|
|
37
37
|
.join('')}
|
|
38
38
|
`,
|
|
@@ -45,7 +45,7 @@ const GoogleTagManager: FC<GoogleTagManagerProps> = ({ containers }) => {
|
|
|
45
45
|
{scriptElements}
|
|
46
46
|
|
|
47
47
|
<noscript>
|
|
48
|
-
{containers.map(({ gtmId,
|
|
48
|
+
{containers.map(({ gtmId, tagServerUrl = defaultServer }) => (
|
|
49
49
|
<iframe
|
|
50
50
|
key={gtmId}
|
|
51
51
|
src={`${tagServer}/ns.html?id=${gtmId}`}
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { ResponsiveContainer } from "recharts"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
|
|
8
|
+
// Format: { THEME_NAME: CSS_VARIABLE }
|
|
9
|
+
const THEMES = { light: "", dark: ".dark" } as const
|
|
10
|
+
|
|
11
|
+
export type ChartConfig = {
|
|
12
|
+
[k in string]: {
|
|
13
|
+
label?: React.ReactNode
|
|
14
|
+
icon?: React.ComponentType
|
|
15
|
+
} & (
|
|
16
|
+
| { color?: string; theme?: never }
|
|
17
|
+
| { color?: never; theme: Record<keyof typeof THEMES, string> }
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type ChartContextProps = {
|
|
22
|
+
config: ChartConfig
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const ChartContext = React.createContext<ChartContextProps | null>(null)
|
|
26
|
+
|
|
27
|
+
function useChart() {
|
|
28
|
+
const context = React.useContext(ChartContext)
|
|
29
|
+
|
|
30
|
+
if (!context) {
|
|
31
|
+
throw new Error("useChart must be used within a <ChartContainer />")
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return context
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const ChartContainer = React.forwardRef<
|
|
38
|
+
HTMLDivElement,
|
|
39
|
+
React.ComponentProps<"div"> & {
|
|
40
|
+
config: ChartConfig
|
|
41
|
+
children: React.ComponentProps<typeof ResponsiveContainer>["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
|
+
<div
|
|
50
|
+
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
|
+
className
|
|
55
|
+
)}
|
|
56
|
+
{...props}
|
|
57
|
+
>
|
|
58
|
+
<ChartStyle id={chartId} config={config} />
|
|
59
|
+
<ResponsiveContainer>
|
|
60
|
+
{children}
|
|
61
|
+
</ResponsiveContainer>
|
|
62
|
+
</div>
|
|
63
|
+
</ChartContext.Provider>
|
|
64
|
+
)
|
|
65
|
+
})
|
|
66
|
+
ChartContainer.displayName = "ChartContainer"
|
|
67
|
+
|
|
68
|
+
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
|
|
69
|
+
const colorConfig = Object.entries(config).filter(
|
|
70
|
+
([_, config]) => config.theme || config.color
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
if (!colorConfig.length) {
|
|
74
|
+
return null
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<style
|
|
79
|
+
dangerouslySetInnerHTML={{
|
|
80
|
+
__html: Object.entries(THEMES)
|
|
81
|
+
.map(
|
|
82
|
+
([theme, prefix]) => `
|
|
83
|
+
${prefix} [data-chart=${id}] {
|
|
84
|
+
${colorConfig
|
|
85
|
+
.map(([key, itemConfig]) => {
|
|
86
|
+
const color =
|
|
87
|
+
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
|
|
88
|
+
itemConfig.color
|
|
89
|
+
return color ? ` --color-${key}: ${color};` : null
|
|
90
|
+
})
|
|
91
|
+
.join("\n")}
|
|
92
|
+
}
|
|
93
|
+
`
|
|
94
|
+
)
|
|
95
|
+
.join("\n"),
|
|
96
|
+
}}
|
|
97
|
+
/>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const ChartTooltip = ChartTooltipPrimitive
|
|
102
|
+
|
|
103
|
+
const ChartTooltipContent = React.forwardRef<
|
|
104
|
+
HTMLDivElement,
|
|
105
|
+
React.ComponentProps<typeof ChartTooltipPrimitive> &
|
|
106
|
+
React.ComponentProps<"div"> & {
|
|
107
|
+
hideLabel?: boolean
|
|
108
|
+
hideIndicator?: boolean
|
|
109
|
+
indicator?: "line" | "dot" | "dashed"
|
|
110
|
+
nameKey?: string
|
|
111
|
+
labelKey?: string
|
|
112
|
+
}
|
|
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
|
+
ref
|
|
131
|
+
) => {
|
|
132
|
+
const { config } = useChart()
|
|
133
|
+
|
|
134
|
+
const tooltipLabel = React.useMemo(() => {
|
|
135
|
+
if (hideLabel || !payload?.length) {
|
|
136
|
+
return null
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const [item] = payload
|
|
140
|
+
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 || label
|
|
145
|
+
: itemConfig?.label
|
|
146
|
+
|
|
147
|
+
if (labelFormatter) {
|
|
148
|
+
return (
|
|
149
|
+
<div className={cn("font-medium", labelClassName)}>
|
|
150
|
+
{labelFormatter(value, payload)}
|
|
151
|
+
</div>
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return value ? (
|
|
156
|
+
<div className={cn("font-medium", labelClassName)}>{value}</div>
|
|
157
|
+
) : null
|
|
158
|
+
}, [
|
|
159
|
+
label,
|
|
160
|
+
labelFormatter,
|
|
161
|
+
payload,
|
|
162
|
+
hideLabel,
|
|
163
|
+
labelClassName,
|
|
164
|
+
config,
|
|
165
|
+
labelKey,
|
|
166
|
+
])
|
|
167
|
+
|
|
168
|
+
if (!active || !payload?.length) {
|
|
169
|
+
return null
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const nestLabel = payload.length === 1 && indicator !== "dot"
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<div
|
|
176
|
+
ref={ref}
|
|
177
|
+
className={cn(
|
|
178
|
+
"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",
|
|
179
|
+
className
|
|
180
|
+
)}
|
|
181
|
+
>
|
|
182
|
+
{!nestLabel ? tooltipLabel : null}
|
|
183
|
+
<div className="grid gap-1.5">
|
|
184
|
+
{payload.map((item, index) => {
|
|
185
|
+
const key = `${nameKey || item.name || item.dataKey || "value"}`
|
|
186
|
+
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
|
187
|
+
const indicatorColor = color || item.payload.fill || item.color
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<div
|
|
191
|
+
key={item.dataKey}
|
|
192
|
+
className={cn(
|
|
193
|
+
"flex items-center gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
|
|
194
|
+
indicator === "dot" && "items-center"
|
|
195
|
+
)}
|
|
196
|
+
>
|
|
197
|
+
{formatter && item?.value !== undefined && item.name ? (
|
|
198
|
+
formatter(item.value, item.name, item, index, item.payload)
|
|
199
|
+
) : (
|
|
200
|
+
<>
|
|
201
|
+
{itemConfig?.icon ? (
|
|
202
|
+
<itemConfig.icon />
|
|
203
|
+
) : (
|
|
204
|
+
!hideIndicator && (
|
|
205
|
+
<div
|
|
206
|
+
className={cn(
|
|
207
|
+
"shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
|
|
208
|
+
{
|
|
209
|
+
"h-2.5 w-2.5": indicator === "dot",
|
|
210
|
+
"w-1": indicator === "line",
|
|
211
|
+
"w-0 border-[1.5px] border-dashed bg-transparent":
|
|
212
|
+
indicator === "dashed",
|
|
213
|
+
"my-0.5": nestLabel && indicator === "dashed",
|
|
214
|
+
}
|
|
215
|
+
)}
|
|
216
|
+
style={
|
|
217
|
+
{
|
|
218
|
+
"--color-bg": indicatorColor,
|
|
219
|
+
"--color-border": indicatorColor,
|
|
220
|
+
} as React.CSSProperties
|
|
221
|
+
}
|
|
222
|
+
/>
|
|
223
|
+
)
|
|
224
|
+
)}
|
|
225
|
+
<div
|
|
226
|
+
className={cn(
|
|
227
|
+
"flex flex-1 justify-between leading-none",
|
|
228
|
+
nestLabel ? "items-end" : "items-center"
|
|
229
|
+
)}
|
|
230
|
+
>
|
|
231
|
+
<div className="grid gap-1.5">
|
|
232
|
+
{nestLabel ? tooltipLabel : null}
|
|
233
|
+
<span className="text-muted-foreground">
|
|
234
|
+
{itemConfig?.label || item.name}
|
|
235
|
+
</span>
|
|
236
|
+
</div>
|
|
237
|
+
{item.value && (
|
|
238
|
+
<span className="font-mono font-medium text-foreground tabular-nums">
|
|
239
|
+
{item.value.toLocaleString()}
|
|
240
|
+
</span>
|
|
241
|
+
)}
|
|
242
|
+
</div>
|
|
243
|
+
</>
|
|
244
|
+
)}
|
|
245
|
+
</div>
|
|
246
|
+
)
|
|
247
|
+
})}
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
)
|
|
253
|
+
ChartTooltipContent.displayName = "ChartTooltip"
|
|
254
|
+
|
|
255
|
+
const ChartLegend = ChartLegendPrimitive
|
|
256
|
+
|
|
257
|
+
const ChartLegendContent = React.forwardRef<
|
|
258
|
+
HTMLDivElement,
|
|
259
|
+
React.ComponentProps<"div"> &
|
|
260
|
+
Pick<React.ComponentProps<typeof ChartLegendPrimitive>, "payload" | "verticalAlign"> & {
|
|
261
|
+
hideIcon?: boolean
|
|
262
|
+
nameKey?: string
|
|
263
|
+
}
|
|
264
|
+
>(
|
|
265
|
+
(
|
|
266
|
+
{ className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
|
|
267
|
+
ref
|
|
268
|
+
) => {
|
|
269
|
+
const { config } = useChart()
|
|
270
|
+
|
|
271
|
+
if (!payload?.length) {
|
|
272
|
+
return null
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return (
|
|
276
|
+
<div
|
|
277
|
+
ref={ref}
|
|
278
|
+
className={cn(
|
|
279
|
+
"flex items-center justify-center gap-4",
|
|
280
|
+
verticalAlign === "top" ? "pb-3" : "pt-3",
|
|
281
|
+
className
|
|
282
|
+
)}
|
|
283
|
+
>
|
|
284
|
+
{payload.map((item) => {
|
|
285
|
+
const key = `${nameKey || item.dataKey || "value"}`
|
|
286
|
+
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
|
287
|
+
|
|
288
|
+
return (
|
|
289
|
+
<div
|
|
290
|
+
key={item.value}
|
|
291
|
+
className={cn(
|
|
292
|
+
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
|
|
293
|
+
)}
|
|
294
|
+
>
|
|
295
|
+
{itemConfig?.icon && !hideIcon ? (
|
|
296
|
+
<itemConfig.icon />
|
|
297
|
+
) : (
|
|
298
|
+
<div
|
|
299
|
+
className="h-2 w-2 shrink-0 rounded-[2px]"
|
|
300
|
+
style={{
|
|
301
|
+
backgroundColor: item.color,
|
|
302
|
+
}}
|
|
303
|
+
/>
|
|
304
|
+
)}
|
|
305
|
+
{itemConfig?.label}
|
|
306
|
+
</div>
|
|
307
|
+
)
|
|
308
|
+
})}
|
|
309
|
+
</div>
|
|
310
|
+
)
|
|
311
|
+
}
|
|
312
|
+
)
|
|
313
|
+
ChartLegendContent.displayName = "ChartLegend"
|
|
314
|
+
|
|
315
|
+
// Helper to extract item config from a payload.
|
|
316
|
+
function getPayloadConfigFromPayload(
|
|
317
|
+
config: ChartConfig,
|
|
318
|
+
payload: unknown,
|
|
319
|
+
key: string
|
|
320
|
+
) {
|
|
321
|
+
if (typeof payload !== "object" || payload === null) {
|
|
322
|
+
return undefined
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const payloadPayload =
|
|
326
|
+
"payload" in payload &&
|
|
327
|
+
typeof payload.payload === "object" &&
|
|
328
|
+
payload.payload !== null
|
|
329
|
+
? payload.payload
|
|
330
|
+
: undefined
|
|
331
|
+
|
|
332
|
+
let configLabelKey: string = key
|
|
333
|
+
|
|
334
|
+
if (
|
|
335
|
+
key in payload &&
|
|
336
|
+
typeof payload[key as keyof typeof payload] === "string"
|
|
337
|
+
) {
|
|
338
|
+
configLabelKey = payload[key as keyof typeof payload] as string
|
|
339
|
+
} else if (
|
|
340
|
+
payloadPayload &&
|
|
341
|
+
key in payloadPayload &&
|
|
342
|
+
typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
|
|
343
|
+
) {
|
|
344
|
+
configLabelKey = payloadPayload[
|
|
345
|
+
key as keyof typeof payloadPayload
|
|
346
|
+
] as string
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return configLabelKey in config
|
|
350
|
+
? config[configLabelKey]
|
|
351
|
+
: config[key as keyof typeof config]
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Recharts Primitive Imports - We import specific components to avoid large bundle reference but for now let's just use * and assume tree shaking or specific imports in consuming app.
|
|
355
|
+
// However, the above code uses RechartsPrimitive.Tooltip and Legend.
|
|
356
|
+
// Ideally we should import them from recharts.
|
|
357
|
+
|
|
358
|
+
import {
|
|
359
|
+
Legend as ChartLegendPrimitive,
|
|
360
|
+
Tooltip as ChartTooltipPrimitive,
|
|
361
|
+
ResponsiveContainer,
|
|
362
|
+
} from "recharts"
|
|
363
|
+
|
|
364
|
+
export {
|
|
365
|
+
ChartContainer,
|
|
366
|
+
ChartTooltip,
|
|
367
|
+
ChartTooltipContent,
|
|
368
|
+
ChartLegend,
|
|
369
|
+
ChartLegendContent,
|
|
370
|
+
ChartStyle,
|
|
371
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import * as React from "react"
|
|
4
|
-
import {
|
|
4
|
+
import { LuPanelLeft } from "react-icons/lu";
|
|
5
5
|
import { cva, type VariantProps } from "class-variance-authority"
|
|
6
6
|
|
|
7
7
|
import { cn } from "@/lib/utils"
|
|
@@ -236,7 +236,7 @@ const SidebarTrigger = React.forwardRef<
|
|
|
236
236
|
}}
|
|
237
237
|
{...props}
|
|
238
238
|
>
|
|
239
|
-
<
|
|
239
|
+
<LuPanelLeft />
|
|
240
240
|
<span className="sr-only">Toggle Sidebar</span>
|
|
241
241
|
</Button>
|
|
242
242
|
)
|