@gram-ai/elements 1.25.2 → 1.26.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.
Files changed (160) hide show
  1. package/dist/components/Chat/stories/Charts.stories.d.ts +37 -0
  2. package/dist/components/Chat/stories/GenerativeUI.stories.d.ts +17 -0
  3. package/dist/components/ui/button.d.ts +1 -1
  4. package/dist/components/ui/buttonVariants.d.ts +1 -1
  5. package/dist/components/ui/charts.stories.d.ts +43 -0
  6. package/dist/components/ui/generative-ui.stories.d.ts +53 -0
  7. package/dist/elements.cjs +1 -1
  8. package/dist/elements.css +1 -1
  9. package/dist/elements.js +6 -6
  10. package/dist/index-BJnv49-A.js +37057 -0
  11. package/dist/index-BJnv49-A.js.map +1 -0
  12. package/dist/index-BpJstUh1.cjs +280 -0
  13. package/dist/index-BpJstUh1.cjs.map +1 -0
  14. package/dist/index-CUitXazZ.js +30426 -0
  15. package/dist/index-CUitXazZ.js.map +1 -0
  16. package/dist/index-ChW-CSuu.cjs +147 -0
  17. package/dist/index-ChW-CSuu.cjs.map +1 -0
  18. package/dist/plugins/chart/catalog.d.ts +123 -0
  19. package/dist/plugins/chart/index.d.ts +1 -1
  20. package/dist/plugins/chart/ui/area-chart.d.ts +16 -0
  21. package/dist/plugins/chart/ui/bar-chart.d.ts +16 -0
  22. package/dist/plugins/chart/ui/donut-chart.d.ts +17 -0
  23. package/dist/plugins/chart/ui/index.d.ts +7 -0
  24. package/dist/plugins/chart/ui/line-chart.d.ts +17 -0
  25. package/dist/plugins/chart/ui/pie-chart.d.ts +15 -0
  26. package/dist/plugins/chart/ui/radar-chart.d.ts +14 -0
  27. package/dist/plugins/chart/ui/scatter-chart.d.ts +18 -0
  28. package/dist/plugins/components/MacOSWindowFrame.d.ts +13 -0
  29. package/dist/plugins/components/PluginLoadingState.d.ts +1 -1
  30. package/dist/plugins/generative-ui/catalog.d.ts +293 -0
  31. package/dist/plugins/generative-ui/ui/accordion-wrapper.d.ts +18 -0
  32. package/dist/plugins/generative-ui/ui/accordion.d.ts +7 -0
  33. package/dist/plugins/generative-ui/ui/action-button.d.ts +10 -0
  34. package/dist/plugins/generative-ui/ui/alert-wrapper.d.ts +9 -0
  35. package/dist/plugins/generative-ui/ui/alert.d.ts +9 -0
  36. package/dist/plugins/generative-ui/ui/avatar-wrapper.d.ts +9 -0
  37. package/dist/plugins/generative-ui/ui/avatar.d.ts +11 -0
  38. package/dist/plugins/generative-ui/ui/badge.d.ts +12 -0
  39. package/dist/plugins/generative-ui/ui/button-wrapper.d.ts +15 -0
  40. package/dist/plugins/generative-ui/ui/button.d.ts +10 -0
  41. package/dist/plugins/generative-ui/ui/card-wrapper.d.ts +10 -0
  42. package/dist/plugins/generative-ui/ui/card.d.ts +9 -0
  43. package/dist/plugins/generative-ui/ui/checkbox-wrapper.d.ts +10 -0
  44. package/dist/plugins/generative-ui/ui/checkbox.d.ts +4 -0
  45. package/dist/plugins/generative-ui/ui/data-table.d.ts +10 -0
  46. package/dist/plugins/generative-ui/ui/dialog.d.ts +17 -0
  47. package/dist/plugins/generative-ui/ui/dropdown-menu.d.ts +25 -0
  48. package/dist/plugins/generative-ui/ui/grid.d.ts +6 -0
  49. package/dist/plugins/generative-ui/ui/index.d.ts +40 -0
  50. package/dist/plugins/generative-ui/ui/input-wrapper.d.ts +11 -0
  51. package/dist/plugins/generative-ui/ui/input.d.ts +3 -0
  52. package/dist/plugins/generative-ui/ui/label.d.ts +4 -0
  53. package/dist/plugins/generative-ui/ui/list.d.ts +6 -0
  54. package/dist/plugins/generative-ui/ui/metric.d.ts +7 -0
  55. package/dist/plugins/generative-ui/ui/pagination.d.ts +13 -0
  56. package/dist/plugins/generative-ui/ui/popover.d.ts +10 -0
  57. package/dist/plugins/generative-ui/ui/progress.d.ts +10 -0
  58. package/dist/plugins/generative-ui/ui/radio-group.d.ts +5 -0
  59. package/dist/plugins/generative-ui/ui/select-wrapper.d.ts +13 -0
  60. package/dist/plugins/generative-ui/ui/select.d.ts +15 -0
  61. package/dist/plugins/generative-ui/ui/separator.d.ts +4 -0
  62. package/dist/plugins/generative-ui/ui/skeleton-wrapper.d.ts +9 -0
  63. package/dist/plugins/generative-ui/ui/skeleton.d.ts +2 -0
  64. package/dist/plugins/generative-ui/ui/stack.d.ts +8 -0
  65. package/dist/plugins/generative-ui/ui/switch.d.ts +6 -0
  66. package/dist/plugins/generative-ui/ui/table.d.ts +10 -0
  67. package/dist/plugins/generative-ui/ui/tabs-wrapper.d.ts +21 -0
  68. package/dist/plugins/generative-ui/ui/tabs.d.ts +11 -0
  69. package/dist/plugins/generative-ui/ui/text.d.ts +7 -0
  70. package/dist/plugins/generative-ui/ui/textarea.d.ts +3 -0
  71. package/dist/plugins/generative-ui/ui/tooltip.d.ts +7 -0
  72. package/dist/plugins.cjs +1 -1
  73. package/dist/plugins.js +1 -1
  74. package/dist/{profiler-BaG0scxd.js → profiler-D4Tw5ecI.js} +2 -2
  75. package/dist/{profiler-BaG0scxd.js.map → profiler-D4Tw5ecI.js.map} +1 -1
  76. package/dist/{profiler-CuqENACf.cjs → profiler-DCWYDZ1F.cjs} +2 -2
  77. package/dist/{profiler-CuqENACf.cjs.map → profiler-DCWYDZ1F.cjs.map} +1 -1
  78. package/dist/{startRecording-BiLmoqZa.cjs → startRecording-3sTskM3H.cjs} +2 -2
  79. package/dist/{startRecording-BiLmoqZa.cjs.map → startRecording-3sTskM3H.cjs.map} +1 -1
  80. package/dist/{startRecording-86bHmd-l.js → startRecording-BHhcCWQE.js} +2 -2
  81. package/dist/{startRecording-86bHmd-l.js.map → startRecording-BHhcCWQE.js.map} +1 -1
  82. package/package.json +4 -1
  83. package/src/components/Chat/stories/Charts.stories.tsx +260 -0
  84. package/src/components/Chat/stories/ConnectionConfiguration.stories.tsx +6 -6
  85. package/src/components/Chat/stories/GenerativeUI.stories.tsx +113 -0
  86. package/src/components/Replay.stories.tsx +1 -1
  87. package/src/components/Replay.tsx +18 -13
  88. package/src/components/ui/charts.stories.tsx +246 -0
  89. package/src/components/ui/generative-ui.stories.tsx +557 -0
  90. package/src/components/ui/generative-ui.tsx +60 -360
  91. package/src/components/ui/tool-ui.stories.tsx +6 -3
  92. package/src/hooks/useAuth.ts +17 -1
  93. package/src/hooks/useFollowOnSuggestions.ts +6 -1
  94. package/src/plugins/chart/catalog.ts +141 -0
  95. package/src/plugins/chart/component.tsx +79 -125
  96. package/src/plugins/chart/index.ts +141 -89
  97. package/src/plugins/chart/ui/area-chart.tsx +133 -0
  98. package/src/plugins/chart/ui/bar-chart.tsx +137 -0
  99. package/src/plugins/chart/ui/donut-chart.tsx +167 -0
  100. package/src/plugins/chart/ui/index.ts +7 -0
  101. package/src/plugins/chart/ui/line-chart.tsx +135 -0
  102. package/src/plugins/chart/ui/pie-chart.tsx +148 -0
  103. package/src/plugins/chart/ui/radar-chart.tsx +105 -0
  104. package/src/plugins/chart/ui/scatter-chart.tsx +132 -0
  105. package/src/plugins/components/MacOSWindowFrame.tsx +55 -0
  106. package/src/plugins/components/PluginLoadingState.tsx +9 -13
  107. package/src/plugins/generative-ui/catalog.ts +277 -0
  108. package/src/plugins/generative-ui/component.tsx +112 -21
  109. package/src/plugins/generative-ui/index.ts +20 -141
  110. package/src/plugins/generative-ui/ui/accordion-wrapper.tsx +57 -0
  111. package/src/plugins/generative-ui/ui/accordion.tsx +66 -0
  112. package/src/plugins/generative-ui/ui/action-button.tsx +68 -0
  113. package/src/plugins/generative-ui/ui/alert-wrapper.tsx +26 -0
  114. package/src/plugins/generative-ui/ui/alert.tsx +66 -0
  115. package/src/plugins/generative-ui/ui/avatar-wrapper.tsx +22 -0
  116. package/src/plugins/generative-ui/ui/avatar.tsx +109 -0
  117. package/src/plugins/generative-ui/ui/badge.tsx +65 -0
  118. package/src/plugins/generative-ui/ui/button-wrapper.tsx +32 -0
  119. package/src/plugins/generative-ui/ui/button.tsx +65 -0
  120. package/src/plugins/generative-ui/ui/card-wrapper.tsx +36 -0
  121. package/src/plugins/generative-ui/ui/card.tsx +92 -0
  122. package/src/plugins/generative-ui/ui/checkbox-wrapper.tsx +39 -0
  123. package/src/plugins/generative-ui/ui/checkbox.tsx +32 -0
  124. package/src/plugins/generative-ui/ui/data-table.tsx +53 -0
  125. package/src/plugins/generative-ui/ui/dialog.tsx +158 -0
  126. package/src/plugins/generative-ui/ui/dropdown-menu.tsx +257 -0
  127. package/src/plugins/generative-ui/ui/grid.tsx +29 -0
  128. package/src/plugins/generative-ui/ui/index.ts +43 -0
  129. package/src/plugins/generative-ui/ui/input-wrapper.tsx +38 -0
  130. package/src/plugins/generative-ui/ui/input.tsx +21 -0
  131. package/src/plugins/generative-ui/ui/label.tsx +24 -0
  132. package/src/plugins/generative-ui/ui/list.tsx +34 -0
  133. package/src/plugins/generative-ui/ui/metric.tsx +53 -0
  134. package/src/plugins/generative-ui/ui/pagination.tsx +127 -0
  135. package/src/plugins/generative-ui/ui/popover.tsx +89 -0
  136. package/src/plugins/generative-ui/ui/progress.tsx +57 -0
  137. package/src/plugins/generative-ui/ui/radio-group.tsx +45 -0
  138. package/src/plugins/generative-ui/ui/select-wrapper.tsx +41 -0
  139. package/src/plugins/generative-ui/ui/select.tsx +190 -0
  140. package/src/plugins/generative-ui/ui/separator.tsx +28 -0
  141. package/src/plugins/generative-ui/ui/skeleton-wrapper.tsx +30 -0
  142. package/src/plugins/generative-ui/ui/skeleton.tsx +13 -0
  143. package/src/plugins/generative-ui/ui/stack.tsx +54 -0
  144. package/src/plugins/generative-ui/ui/switch.tsx +35 -0
  145. package/src/plugins/generative-ui/ui/table.tsx +116 -0
  146. package/src/plugins/generative-ui/ui/tabs-wrapper.tsx +51 -0
  147. package/src/plugins/generative-ui/ui/tabs.tsx +92 -0
  148. package/src/plugins/generative-ui/ui/text.tsx +33 -0
  149. package/src/plugins/generative-ui/ui/textarea.tsx +18 -0
  150. package/src/plugins/generative-ui/ui/tooltip.tsx +57 -0
  151. package/dist/components/Chat/stories/Plugins.stories.d.ts +0 -12
  152. package/dist/index-B8nSCdu4.cjs +0 -251
  153. package/dist/index-B8nSCdu4.cjs.map +0 -1
  154. package/dist/index-CAtaLV1E.cjs +0 -187
  155. package/dist/index-CAtaLV1E.cjs.map +0 -1
  156. package/dist/index-CJrwma08.js +0 -27232
  157. package/dist/index-CJrwma08.js.map +0 -1
  158. package/dist/index-DLWQ91ow.js +0 -40049
  159. package/dist/index-DLWQ91ow.js.map +0 -1
  160. package/src/components/Chat/stories/Plugins.stories.tsx +0 -158
@@ -0,0 +1,137 @@
1
+ 'use client'
2
+
3
+ import { cn } from '@/lib/utils'
4
+ import { FC } from 'react'
5
+ import {
6
+ BarChart as RechartsBarChart,
7
+ Bar,
8
+ XAxis,
9
+ YAxis,
10
+ CartesianGrid,
11
+ Tooltip,
12
+ Legend,
13
+ Cell,
14
+ ResponsiveContainer,
15
+ TooltipProps,
16
+ } from 'recharts'
17
+
18
+ const CustomTooltip = ({ active, payload }: TooltipProps<number, string>) => {
19
+ if (!active || !payload || payload.length === 0) return null
20
+ const value = payload[0]?.value
21
+ return (
22
+ <div className="bg-background text-foreground border-border rounded-md border px-2 py-1 text-xs shadow-sm">
23
+ {typeof value === 'number' ? value.toLocaleString() : value}
24
+ </div>
25
+ )
26
+ }
27
+
28
+ const COLORS = [
29
+ 'var(--chart-1)',
30
+ 'var(--chart-2)',
31
+ 'var(--chart-3)',
32
+ 'var(--chart-4)',
33
+ 'var(--chart-5)',
34
+ ]
35
+
36
+ interface DataPoint {
37
+ label: string
38
+ value: number
39
+ color?: string
40
+ }
41
+
42
+ export interface BarChartProps {
43
+ title?: string
44
+ data: DataPoint[]
45
+ layout?: 'vertical' | 'horizontal'
46
+ showGrid?: boolean
47
+ showLegend?: boolean
48
+ className?: string
49
+ }
50
+
51
+ export const BarChart: FC<BarChartProps> = ({
52
+ title,
53
+ data,
54
+ layout = 'vertical',
55
+ showGrid = true,
56
+ showLegend = false,
57
+ className,
58
+ }) => {
59
+ const isHorizontal = layout === 'horizontal'
60
+
61
+ return (
62
+ <div className={cn('flex flex-col gap-2', className)}>
63
+ {title && (
64
+ <h3 className="text-foreground text-sm font-medium">{title}</h3>
65
+ )}
66
+ <div className="h-[250px] w-full">
67
+ <ResponsiveContainer width="100%" height="100%">
68
+ <RechartsBarChart
69
+ data={data}
70
+ layout={isHorizontal ? 'vertical' : 'horizontal'}
71
+ margin={{ top: 10, right: 10, left: 10, bottom: 10 }}
72
+ >
73
+ {showGrid && (
74
+ <CartesianGrid
75
+ strokeDasharray="3 3"
76
+ className="stroke-muted/30"
77
+ />
78
+ )}
79
+ {isHorizontal ? (
80
+ <>
81
+ <XAxis
82
+ type="number"
83
+ tick={{ fill: 'var(--foreground)', fontSize: 12 }}
84
+ axisLine={{ stroke: 'var(--border)' }}
85
+ tickLine={{ stroke: 'var(--border)' }}
86
+ />
87
+ <YAxis
88
+ dataKey="label"
89
+ type="category"
90
+ width={80}
91
+ tick={{ fill: 'var(--foreground)', fontSize: 12 }}
92
+ axisLine={{ stroke: 'var(--border)' }}
93
+ tickLine={{ stroke: 'var(--border)' }}
94
+ />
95
+ </>
96
+ ) : (
97
+ <>
98
+ <XAxis
99
+ dataKey="label"
100
+ tick={{ fill: 'var(--foreground)', fontSize: 12 }}
101
+ axisLine={{ stroke: 'var(--border)' }}
102
+ tickLine={{ stroke: 'var(--border)' }}
103
+ />
104
+ <YAxis
105
+ tick={{ fill: 'var(--foreground)', fontSize: 12 }}
106
+ axisLine={{ stroke: 'var(--border)' }}
107
+ tickLine={{ stroke: 'var(--border)' }}
108
+ />
109
+ </>
110
+ )}
111
+ <Tooltip content={<CustomTooltip />} />
112
+ {showLegend && (
113
+ <Legend
114
+ wrapperStyle={{ color: 'var(--foreground)' }}
115
+ formatter={(value) => (
116
+ <span style={{ color: 'var(--foreground)' }}>{value}</span>
117
+ )}
118
+ />
119
+ )}
120
+ <Bar
121
+ dataKey="value"
122
+ radius={[4, 4, 0, 0]}
123
+ isAnimationActive={false}
124
+ >
125
+ {data.map((entry, index) => (
126
+ <Cell
127
+ key={`cell-${index}`}
128
+ fill={entry.color || COLORS[index % COLORS.length]}
129
+ />
130
+ ))}
131
+ </Bar>
132
+ </RechartsBarChart>
133
+ </ResponsiveContainer>
134
+ </div>
135
+ </div>
136
+ )
137
+ }
@@ -0,0 +1,167 @@
1
+ 'use client'
2
+
3
+ import { cn } from '@/lib/utils'
4
+ import { FC } from 'react'
5
+ import {
6
+ PieChart as RechartsPieChart,
7
+ Pie,
8
+ Cell,
9
+ Tooltip,
10
+ Legend,
11
+ ResponsiveContainer,
12
+ TooltipProps,
13
+ } from 'recharts'
14
+
15
+ const CustomTooltip = ({ active, payload }: TooltipProps<number, string>) => {
16
+ if (!active || !payload || payload.length === 0) return null
17
+ const entry = payload[0]
18
+ return (
19
+ <div className="bg-background text-foreground border-border rounded-md border px-2 py-1 text-xs shadow-sm">
20
+ <span className="font-medium">
21
+ {typeof entry?.value === 'number'
22
+ ? entry.value.toLocaleString()
23
+ : entry?.value}
24
+ </span>
25
+ </div>
26
+ )
27
+ }
28
+
29
+ const COLORS = [
30
+ 'var(--chart-1)',
31
+ 'var(--chart-2)',
32
+ 'var(--chart-3)',
33
+ 'var(--chart-4)',
34
+ 'var(--chart-5)',
35
+ ]
36
+
37
+ interface DataPoint {
38
+ label: string
39
+ value: number
40
+ color?: string
41
+ }
42
+
43
+ export interface DonutChartProps {
44
+ title?: string
45
+ data: DataPoint[]
46
+ showLabels?: boolean
47
+ showLegend?: boolean
48
+ innerLabel?: string
49
+ innerValue?: string | number
50
+ className?: string
51
+ }
52
+
53
+ export const DonutChart: FC<DonutChartProps> = ({
54
+ title,
55
+ data,
56
+ showLabels = false,
57
+ showLegend = true,
58
+ innerLabel,
59
+ innerValue,
60
+ className,
61
+ }) => {
62
+ // Transform data to use 'name' for Recharts
63
+ const chartData = data.map((d) => ({
64
+ name: d.label,
65
+ value: d.value,
66
+ color: d.color,
67
+ }))
68
+
69
+ return (
70
+ <div className={cn('flex flex-col gap-2', className)}>
71
+ {title && (
72
+ <h3 className="text-foreground text-sm font-medium">{title}</h3>
73
+ )}
74
+ <div className="relative h-[320px] w-full">
75
+ <ResponsiveContainer width="100%" height="100%">
76
+ <RechartsPieChart
77
+ margin={{ top: 20, right: 20, bottom: 20, left: 20 }}
78
+ >
79
+ <Pie
80
+ data={chartData}
81
+ cx="50%"
82
+ cy="45%"
83
+ innerRadius={50}
84
+ outerRadius={80}
85
+ paddingAngle={2}
86
+ dataKey="value"
87
+ label={
88
+ showLabels
89
+ ? ({
90
+ name,
91
+ percent,
92
+ cx,
93
+ cy,
94
+ midAngle,
95
+ outerRadius,
96
+ }: {
97
+ name?: string
98
+ percent?: number
99
+ cx?: number
100
+ cy?: number
101
+ midAngle?: number
102
+ outerRadius?: number
103
+ }) => {
104
+ const RADIAN = Math.PI / 180
105
+ const radius = (outerRadius ?? 80) + 25
106
+ const x =
107
+ (cx ?? 0) +
108
+ radius * Math.cos(-((midAngle ?? 0) * RADIAN))
109
+ const y =
110
+ (cy ?? 0) +
111
+ radius * Math.sin(-((midAngle ?? 0) * RADIAN))
112
+ return (
113
+ <text
114
+ x={x}
115
+ y={y}
116
+ fill="var(--foreground)"
117
+ textAnchor={x > (cx ?? 0) ? 'start' : 'end'}
118
+ dominantBaseline="central"
119
+ fontSize={12}
120
+ >
121
+ {`${name ?? ''} (${((percent ?? 0) * 100).toFixed(0)}%)`}
122
+ </text>
123
+ )
124
+ }
125
+ : undefined
126
+ }
127
+ labelLine={showLabels}
128
+ isAnimationActive={false}
129
+ >
130
+ {chartData.map((entry, index) => (
131
+ <Cell
132
+ key={`cell-${index}`}
133
+ fill={entry.color || COLORS[index % COLORS.length]}
134
+ />
135
+ ))}
136
+ </Pie>
137
+ <Tooltip content={<CustomTooltip />} />
138
+ {showLegend && (
139
+ <Legend
140
+ verticalAlign="bottom"
141
+ wrapperStyle={{ color: 'var(--foreground)', paddingTop: 20 }}
142
+ formatter={(value) => (
143
+ <span style={{ color: 'var(--foreground)' }}>{value}</span>
144
+ )}
145
+ />
146
+ )}
147
+ </RechartsPieChart>
148
+ </ResponsiveContainer>
149
+ {/* Center label - positioned at 45% from top to match pie cy */}
150
+ {(innerLabel !== undefined || innerValue !== undefined) && (
151
+ <div className="pointer-events-none absolute inset-x-0 top-[45%] flex -translate-y-1/2 flex-col items-center">
152
+ {innerValue !== undefined && (
153
+ <span className="text-foreground text-2xl font-bold">
154
+ {innerValue}
155
+ </span>
156
+ )}
157
+ {innerLabel && (
158
+ <span className="text-muted-foreground text-xs">
159
+ {innerLabel}
160
+ </span>
161
+ )}
162
+ </div>
163
+ )}
164
+ </div>
165
+ </div>
166
+ )
167
+ }
@@ -0,0 +1,7 @@
1
+ export { BarChart, type BarChartProps } from './bar-chart'
2
+ export { LineChart, type LineChartProps } from './line-chart'
3
+ export { AreaChart, type AreaChartProps } from './area-chart'
4
+ export { PieChart, type PieChartProps } from './pie-chart'
5
+ export { DonutChart, type DonutChartProps } from './donut-chart'
6
+ export { ScatterChart, type ScatterChartProps } from './scatter-chart'
7
+ export { RadarChart, type RadarChartProps } from './radar-chart'
@@ -0,0 +1,135 @@
1
+ 'use client'
2
+
3
+ import { cn } from '@/lib/utils'
4
+ import { FC, useMemo } from 'react'
5
+ import {
6
+ LineChart as RechartsLineChart,
7
+ Line,
8
+ XAxis,
9
+ YAxis,
10
+ CartesianGrid,
11
+ Tooltip,
12
+ Legend,
13
+ ResponsiveContainer,
14
+ TooltipProps,
15
+ } from 'recharts'
16
+
17
+ const CustomTooltip = ({ active, payload }: TooltipProps<number, string>) => {
18
+ if (!active || !payload || payload.length === 0) return null
19
+ return (
20
+ <div className="bg-background text-foreground border-border rounded-md border px-2 py-1.5 text-xs shadow-sm">
21
+ {payload.map((entry, index) => (
22
+ <div key={index} className="flex items-center gap-2">
23
+ <span
24
+ className="h-2 w-2 rounded-full"
25
+ style={{ backgroundColor: entry.color }}
26
+ />
27
+ <span>{entry.name}:</span>
28
+ <span className="font-medium">
29
+ {typeof entry.value === 'number'
30
+ ? entry.value.toLocaleString()
31
+ : entry.value}
32
+ </span>
33
+ </div>
34
+ ))}
35
+ </div>
36
+ )
37
+ }
38
+
39
+ const COLORS = [
40
+ 'var(--chart-1)',
41
+ 'var(--chart-2)',
42
+ 'var(--chart-3)',
43
+ 'var(--chart-4)',
44
+ 'var(--chart-5)',
45
+ ]
46
+
47
+ interface SeriesDataPoint {
48
+ label: string
49
+ [key: string]: string | number
50
+ }
51
+
52
+ export interface LineChartProps {
53
+ title?: string
54
+ data: SeriesDataPoint[]
55
+ series?: string[]
56
+ showGrid?: boolean
57
+ showLegend?: boolean
58
+ showDots?: boolean
59
+ curved?: boolean
60
+ className?: string
61
+ }
62
+
63
+ export const LineChart: FC<LineChartProps> = ({
64
+ title,
65
+ data,
66
+ series,
67
+ showGrid = true,
68
+ showLegend = true,
69
+ showDots = true,
70
+ curved = true,
71
+ className,
72
+ }) => {
73
+ // Auto-detect series from data keys if not provided
74
+ const seriesKeys = useMemo(() => {
75
+ if (series && series.length > 0) return series
76
+ if (data.length === 0) return []
77
+ const keys = Object.keys(data[0]).filter((k) => k !== 'label')
78
+ return keys
79
+ }, [data, series])
80
+
81
+ return (
82
+ <div className={cn('flex flex-col gap-2', className)}>
83
+ {title && (
84
+ <h3 className="text-foreground text-sm font-medium">{title}</h3>
85
+ )}
86
+ <div className="h-[250px] w-full">
87
+ <ResponsiveContainer width="100%" height="100%">
88
+ <RechartsLineChart
89
+ data={data}
90
+ margin={{ top: 10, right: 10, left: 10, bottom: 10 }}
91
+ >
92
+ {showGrid && (
93
+ <CartesianGrid
94
+ strokeDasharray="3 3"
95
+ className="stroke-muted/30"
96
+ />
97
+ )}
98
+ <XAxis
99
+ dataKey="label"
100
+ tick={{ fill: 'var(--foreground)', fontSize: 12 }}
101
+ axisLine={{ stroke: 'var(--border)' }}
102
+ tickLine={{ stroke: 'var(--border)' }}
103
+ />
104
+ <YAxis
105
+ tick={{ fill: 'var(--foreground)', fontSize: 12 }}
106
+ axisLine={{ stroke: 'var(--border)' }}
107
+ tickLine={{ stroke: 'var(--border)' }}
108
+ />
109
+ <Tooltip content={<CustomTooltip />} />
110
+ {showLegend && seriesKeys.length > 1 && (
111
+ <Legend
112
+ wrapperStyle={{ color: 'var(--foreground)' }}
113
+ formatter={(value) => (
114
+ <span style={{ color: 'var(--foreground)' }}>{value}</span>
115
+ )}
116
+ />
117
+ )}
118
+ {seriesKeys.map((key, index) => (
119
+ <Line
120
+ key={key}
121
+ type={curved ? 'monotone' : 'linear'}
122
+ dataKey={key}
123
+ stroke={COLORS[index % COLORS.length]}
124
+ strokeWidth={2}
125
+ dot={showDots}
126
+ activeDot={{ r: 6 }}
127
+ isAnimationActive={false}
128
+ />
129
+ ))}
130
+ </RechartsLineChart>
131
+ </ResponsiveContainer>
132
+ </div>
133
+ </div>
134
+ )
135
+ }
@@ -0,0 +1,148 @@
1
+ 'use client'
2
+
3
+ import { cn } from '@/lib/utils'
4
+ import { FC } from 'react'
5
+ import {
6
+ PieChart as RechartsPieChart,
7
+ Pie,
8
+ Cell,
9
+ Tooltip,
10
+ Legend,
11
+ ResponsiveContainer,
12
+ TooltipProps,
13
+ } from 'recharts'
14
+
15
+ const CustomTooltip = ({ active, payload }: TooltipProps<number, string>) => {
16
+ if (!active || !payload || payload.length === 0) return null
17
+ const entry = payload[0]
18
+ return (
19
+ <div className="bg-background text-foreground border-border rounded-md border px-2 py-1 text-xs shadow-sm">
20
+ <span className="font-medium">
21
+ {typeof entry?.value === 'number'
22
+ ? entry.value.toLocaleString()
23
+ : entry?.value}
24
+ </span>
25
+ </div>
26
+ )
27
+ }
28
+
29
+ const COLORS = [
30
+ 'var(--chart-1)',
31
+ 'var(--chart-2)',
32
+ 'var(--chart-3)',
33
+ 'var(--chart-4)',
34
+ 'var(--chart-5)',
35
+ ]
36
+
37
+ interface DataPoint {
38
+ label: string
39
+ value: number
40
+ color?: string
41
+ }
42
+
43
+ export interface PieChartProps {
44
+ title?: string
45
+ data: DataPoint[]
46
+ showLabels?: boolean
47
+ showLegend?: boolean
48
+ className?: string
49
+ }
50
+
51
+ export const PieChart: FC<PieChartProps> = ({
52
+ title,
53
+ data,
54
+ showLabels = true,
55
+ showLegend = true,
56
+ className,
57
+ }) => {
58
+ // Transform data to use 'name' for Recharts
59
+ const chartData = data.map((d) => ({
60
+ name: d.label,
61
+ value: d.value,
62
+ color: d.color,
63
+ }))
64
+
65
+ return (
66
+ <div className={cn('flex flex-col gap-2', className)}>
67
+ {title && (
68
+ <h3 className="text-foreground text-sm font-medium">{title}</h3>
69
+ )}
70
+ <div className="h-[320px] w-full">
71
+ <ResponsiveContainer width="100%" height="100%">
72
+ <RechartsPieChart
73
+ margin={{ top: 20, right: 20, bottom: 20, left: 20 }}
74
+ >
75
+ <Pie
76
+ data={chartData}
77
+ cx="50%"
78
+ cy="45%"
79
+ innerRadius={0}
80
+ outerRadius={80}
81
+ paddingAngle={2}
82
+ dataKey="value"
83
+ label={
84
+ showLabels
85
+ ? ({
86
+ name,
87
+ percent,
88
+ cx,
89
+ cy,
90
+ midAngle,
91
+ outerRadius,
92
+ }: {
93
+ name?: string
94
+ percent?: number
95
+ cx?: number
96
+ cy?: number
97
+ midAngle?: number
98
+ outerRadius?: number
99
+ }) => {
100
+ const RADIAN = Math.PI / 180
101
+ const radius = (outerRadius ?? 80) + 25
102
+ const x =
103
+ (cx ?? 0) +
104
+ radius * Math.cos(-((midAngle ?? 0) * RADIAN))
105
+ const y =
106
+ (cy ?? 0) +
107
+ radius * Math.sin(-((midAngle ?? 0) * RADIAN))
108
+ return (
109
+ <text
110
+ x={x}
111
+ y={y}
112
+ fill="var(--foreground)"
113
+ textAnchor={x > (cx ?? 0) ? 'start' : 'end'}
114
+ dominantBaseline="central"
115
+ fontSize={12}
116
+ >
117
+ {`${name ?? ''} (${((percent ?? 0) * 100).toFixed(0)}%)`}
118
+ </text>
119
+ )
120
+ }
121
+ : undefined
122
+ }
123
+ labelLine={showLabels}
124
+ isAnimationActive={false}
125
+ >
126
+ {chartData.map((entry, index) => (
127
+ <Cell
128
+ key={`cell-${index}`}
129
+ fill={entry.color || COLORS[index % COLORS.length]}
130
+ />
131
+ ))}
132
+ </Pie>
133
+ <Tooltip content={<CustomTooltip />} />
134
+ {showLegend && (
135
+ <Legend
136
+ verticalAlign="bottom"
137
+ wrapperStyle={{ color: 'var(--foreground)', paddingTop: 20 }}
138
+ formatter={(value) => (
139
+ <span style={{ color: 'var(--foreground)' }}>{value}</span>
140
+ )}
141
+ />
142
+ )}
143
+ </RechartsPieChart>
144
+ </ResponsiveContainer>
145
+ </div>
146
+ </div>
147
+ )
148
+ }
@@ -0,0 +1,105 @@
1
+ 'use client'
2
+
3
+ import { cn } from '@/lib/utils'
4
+ import { FC } from 'react'
5
+ import {
6
+ RadarChart as RechartsRadarChart,
7
+ Radar,
8
+ PolarGrid,
9
+ PolarAngleAxis,
10
+ PolarRadiusAxis,
11
+ Tooltip,
12
+ Legend,
13
+ ResponsiveContainer,
14
+ TooltipProps,
15
+ } from 'recharts'
16
+
17
+ const CustomTooltip = ({ active, payload }: TooltipProps<number, string>) => {
18
+ if (!active || !payload || payload.length === 0) return null
19
+ const entry = payload[0]
20
+ return (
21
+ <div className="bg-background text-foreground border-border rounded-md border px-2 py-1 text-xs shadow-sm">
22
+ <span className="font-medium">
23
+ {typeof entry?.value === 'number'
24
+ ? entry.value.toLocaleString()
25
+ : entry?.value}
26
+ </span>
27
+ </div>
28
+ )
29
+ }
30
+
31
+ const COLORS = [
32
+ 'var(--chart-1)',
33
+ 'var(--chart-2)',
34
+ 'var(--chart-3)',
35
+ 'var(--chart-4)',
36
+ 'var(--chart-5)',
37
+ ]
38
+
39
+ interface DataPoint {
40
+ label: string
41
+ value: number
42
+ color?: string
43
+ }
44
+
45
+ export interface RadarChartProps {
46
+ title?: string
47
+ data: DataPoint[]
48
+ showLegend?: boolean
49
+ className?: string
50
+ }
51
+
52
+ export const RadarChart: FC<RadarChartProps> = ({
53
+ title,
54
+ data,
55
+ showLegend = false,
56
+ className,
57
+ }) => {
58
+ // Transform data for Recharts (uses 'subject' for labels)
59
+ const chartData = data.map((d) => ({ subject: d.label, value: d.value }))
60
+
61
+ return (
62
+ <div className={cn('flex flex-col gap-2', className)}>
63
+ {title && (
64
+ <h3 className="text-foreground text-sm font-medium">{title}</h3>
65
+ )}
66
+ <div className="h-[250px] w-full">
67
+ <ResponsiveContainer width="100%" height="100%">
68
+ <RechartsRadarChart
69
+ data={chartData}
70
+ cx="50%"
71
+ cy="50%"
72
+ outerRadius="80%"
73
+ >
74
+ <PolarGrid stroke="var(--border)" />
75
+ <PolarAngleAxis
76
+ dataKey="subject"
77
+ tick={{ fill: 'var(--foreground)', fontSize: 12 }}
78
+ />
79
+ <PolarRadiusAxis
80
+ tick={{ fill: 'var(--foreground)', fontSize: 10 }}
81
+ axisLine={{ stroke: 'var(--border)' }}
82
+ />
83
+ <Tooltip content={<CustomTooltip />} />
84
+ {showLegend && (
85
+ <Legend
86
+ wrapperStyle={{ color: 'var(--foreground)' }}
87
+ formatter={(value) => (
88
+ <span style={{ color: 'var(--foreground)' }}>{value}</span>
89
+ )}
90
+ />
91
+ )}
92
+ <Radar
93
+ name="Value"
94
+ dataKey="value"
95
+ stroke={COLORS[0]}
96
+ fill={COLORS[0]}
97
+ fillOpacity={0.3}
98
+ isAnimationActive={false}
99
+ />
100
+ </RechartsRadarChart>
101
+ </ResponsiveContainer>
102
+ </div>
103
+ </div>
104
+ )
105
+ }