@alpic-ai/ui 1.154.0 → 1.154.1
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/components/area-chart.mjs +20 -109
- package/dist/components/bar-chart.mjs +10 -68
- package/dist/components/chart-primitives.d.mts +69 -0
- package/dist/components/chart-primitives.mjs +134 -0
- package/dist/components/field.d.mts +14 -0
- package/dist/components/field.mjs +41 -0
- package/dist/components/input.mjs +25 -49
- package/dist/components/line-chart.mjs +20 -102
- package/dist/components/textarea.mjs +17 -41
- package/dist/lib/chart.d.mts +5 -1
- package/package.json +1 -1
- package/src/components/area-chart.tsx +21 -142
- package/src/components/bar-chart.tsx +12 -81
- package/src/components/chart-primitives.tsx +188 -0
- package/src/components/field.tsx +57 -0
- package/src/components/input.tsx +3 -33
- package/src/components/line-chart.tsx +21 -133
- package/src/components/textarea.tsx +3 -33
- package/src/stories/area-chart.stories.tsx +8 -6
- package/src/stories/bar-chart.stories.tsx +8 -6
- package/src/stories/donut-chart.stories.tsx +8 -6
- package/src/stories/line-chart.stories.tsx +8 -6
- package/src/stories/stat.stories.tsx +8 -6
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { ReferenceArea, ReferenceDot, ReferenceLine } from "recharts";
|
|
4
|
+
import type { ChartTheme } from "../hooks/use-chart-theme";
|
|
5
|
+
import type { ChartSeries, ResolvedSeries } from "../lib/chart";
|
|
6
|
+
import type { ChartMarker } from "./area-chart";
|
|
7
|
+
|
|
8
|
+
export type ReferenceLineConfig = { y: number; label?: string; band?: boolean };
|
|
9
|
+
|
|
10
|
+
type ChartData = ReadonlyArray<Record<string, string | number | null | undefined>>;
|
|
11
|
+
|
|
12
|
+
export function makeChartAxis(theme: ChartTheme) {
|
|
13
|
+
return {
|
|
14
|
+
stroke: theme.border,
|
|
15
|
+
tick: { fill: theme.axisForeground, fontSize: 10, fontFamily: theme.fontMono },
|
|
16
|
+
tickLine: false as const,
|
|
17
|
+
axisLine: { stroke: theme.border, strokeOpacity: 0.6 },
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function makeLegendItems(resolved: ResolvedSeries[]) {
|
|
22
|
+
return resolved.map((entry) => ({ name: entry.name, color: entry.color, dashed: entry.dashed }));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Stacked series must reach the stack height (sum per x-point), not the tallest
|
|
27
|
+
* single series, or reference bands and explicit Y domains clip a tall stack short.
|
|
28
|
+
*/
|
|
29
|
+
export function computeNumericMax(data: ChartData, series: ChartSeries[], stacked: boolean) {
|
|
30
|
+
let max = 0;
|
|
31
|
+
for (const row of data) {
|
|
32
|
+
let rowTotal = 0;
|
|
33
|
+
for (const entry of series) {
|
|
34
|
+
const value = Number(row[entry.key]);
|
|
35
|
+
if (Number.isFinite(value)) {
|
|
36
|
+
rowTotal += value;
|
|
37
|
+
if (!stacked && value > max) {
|
|
38
|
+
max = value;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (stacked && rowTotal > max) {
|
|
43
|
+
max = rowTotal;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return max;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function makeActiveDot(
|
|
50
|
+
entry: ResolvedSeries,
|
|
51
|
+
{
|
|
52
|
+
valueFlags,
|
|
53
|
+
theme,
|
|
54
|
+
valueFormatter,
|
|
55
|
+
}: { valueFlags: boolean; theme: ChartTheme; valueFormatter: (value: number) => string },
|
|
56
|
+
) {
|
|
57
|
+
if (!valueFlags) {
|
|
58
|
+
return { r: 3.5, fill: entry.color, stroke: theme.card, strokeWidth: 2 };
|
|
59
|
+
}
|
|
60
|
+
return (dotProps: { cx?: number; cy?: number; value?: number | string }) => {
|
|
61
|
+
if (dotProps.cx == null || dotProps.cy == null) {
|
|
62
|
+
return <g />;
|
|
63
|
+
}
|
|
64
|
+
return (
|
|
65
|
+
<g>
|
|
66
|
+
<circle cx={dotProps.cx} cy={dotProps.cy} r={3.5} fill={entry.color} stroke={theme.card} strokeWidth={2} />
|
|
67
|
+
<text
|
|
68
|
+
x={dotProps.cx}
|
|
69
|
+
y={dotProps.cy - 8}
|
|
70
|
+
textAnchor="middle"
|
|
71
|
+
fill={entry.color}
|
|
72
|
+
fontFamily={theme.fontMono}
|
|
73
|
+
fontSize={10}
|
|
74
|
+
style={{ fontVariantNumeric: "tabular-nums" }}
|
|
75
|
+
>
|
|
76
|
+
{valueFormatter(Number(dotProps.value ?? 0))}
|
|
77
|
+
</text>
|
|
78
|
+
</g>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function makeLastValueLabel(
|
|
84
|
+
color: string,
|
|
85
|
+
{
|
|
86
|
+
dataLength,
|
|
87
|
+
theme,
|
|
88
|
+
valueFormatter,
|
|
89
|
+
}: { dataLength: number; theme: ChartTheme; valueFormatter: (value: number) => string },
|
|
90
|
+
) {
|
|
91
|
+
return (props: {
|
|
92
|
+
x?: string | number;
|
|
93
|
+
y?: string | number;
|
|
94
|
+
value?: string | number | boolean | Array<string | number | boolean> | null;
|
|
95
|
+
index?: number;
|
|
96
|
+
}) => {
|
|
97
|
+
if (props.index !== dataLength - 1 || props.x == null || props.y == null) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
return (
|
|
101
|
+
<text
|
|
102
|
+
x={Number(props.x) + 6}
|
|
103
|
+
y={Number(props.y)}
|
|
104
|
+
dy={3}
|
|
105
|
+
fill={color}
|
|
106
|
+
fontFamily={theme.fontMono}
|
|
107
|
+
fontSize={10}
|
|
108
|
+
textAnchor="start"
|
|
109
|
+
style={{ fontVariantNumeric: "tabular-nums" }}
|
|
110
|
+
>
|
|
111
|
+
{valueFormatter(Number(props.value ?? 0))}
|
|
112
|
+
</text>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Returns recharts elements (not a wrapper component) so recharts still detects
|
|
119
|
+
* them as direct children when spread into a chart via `{renderReferenceLine(...)}`.
|
|
120
|
+
*/
|
|
121
|
+
export function renderReferenceLine({
|
|
122
|
+
referenceLine,
|
|
123
|
+
numericMax,
|
|
124
|
+
theme,
|
|
125
|
+
}: {
|
|
126
|
+
referenceLine: ReferenceLineConfig | undefined;
|
|
127
|
+
numericMax: number;
|
|
128
|
+
theme: ChartTheme;
|
|
129
|
+
}) {
|
|
130
|
+
if (!referenceLine) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
return [
|
|
134
|
+
referenceLine.band ? (
|
|
135
|
+
<ReferenceArea
|
|
136
|
+
key="reference-band"
|
|
137
|
+
y1={referenceLine.y}
|
|
138
|
+
y2={numericMax}
|
|
139
|
+
fill={theme.warning}
|
|
140
|
+
fillOpacity={0.06}
|
|
141
|
+
ifOverflow="extendDomain"
|
|
142
|
+
/>
|
|
143
|
+
) : null,
|
|
144
|
+
<ReferenceLine
|
|
145
|
+
key="reference-line"
|
|
146
|
+
y={referenceLine.y}
|
|
147
|
+
stroke={theme.warning}
|
|
148
|
+
strokeDasharray="4 4"
|
|
149
|
+
strokeOpacity={0.6}
|
|
150
|
+
label={
|
|
151
|
+
referenceLine.label
|
|
152
|
+
? {
|
|
153
|
+
value: referenceLine.label,
|
|
154
|
+
fill: theme.warning,
|
|
155
|
+
fontSize: 9,
|
|
156
|
+
fontFamily: theme.fontMono,
|
|
157
|
+
position: "insideBottomRight",
|
|
158
|
+
}
|
|
159
|
+
: undefined
|
|
160
|
+
}
|
|
161
|
+
/>,
|
|
162
|
+
];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function renderMarkers(markers: ChartMarker[] | undefined, theme: ChartTheme) {
|
|
166
|
+
return markers?.map((marker) => (
|
|
167
|
+
<ReferenceDot
|
|
168
|
+
key={`${marker.x}-${marker.y}`}
|
|
169
|
+
x={marker.x}
|
|
170
|
+
y={marker.y}
|
|
171
|
+
r={3.5}
|
|
172
|
+
fill={marker.color ?? theme.foreground}
|
|
173
|
+
stroke={theme.card}
|
|
174
|
+
strokeWidth={2}
|
|
175
|
+
label={
|
|
176
|
+
marker.label
|
|
177
|
+
? {
|
|
178
|
+
value: marker.label,
|
|
179
|
+
fill: marker.color ?? theme.foreground,
|
|
180
|
+
fontSize: 9,
|
|
181
|
+
fontFamily: theme.fontMono,
|
|
182
|
+
position: "top",
|
|
183
|
+
}
|
|
184
|
+
: undefined
|
|
185
|
+
}
|
|
186
|
+
/>
|
|
187
|
+
));
|
|
188
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Info } from "lucide-react";
|
|
4
|
+
import type * as React from "react";
|
|
5
|
+
|
|
6
|
+
import { cn } from "../lib/cn";
|
|
7
|
+
import { Label } from "./label";
|
|
8
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip";
|
|
9
|
+
|
|
10
|
+
interface FieldProps {
|
|
11
|
+
fieldId: string;
|
|
12
|
+
label?: string;
|
|
13
|
+
required?: boolean;
|
|
14
|
+
hint?: string;
|
|
15
|
+
error?: string;
|
|
16
|
+
tooltip?: string;
|
|
17
|
+
children: React.ReactNode;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function Field({ fieldId, label, required, hint, error, tooltip, children }: FieldProps) {
|
|
21
|
+
return (
|
|
22
|
+
<div className="flex flex-col gap-1.5">
|
|
23
|
+
{label && (
|
|
24
|
+
<div className="flex items-center gap-0.5">
|
|
25
|
+
<Label htmlFor={fieldId} className="type-text-sm font-medium text-muted-foreground">
|
|
26
|
+
{label}
|
|
27
|
+
</Label>
|
|
28
|
+
{required && (
|
|
29
|
+
<span aria-hidden className="type-text-sm font-medium text-required">
|
|
30
|
+
*
|
|
31
|
+
</span>
|
|
32
|
+
)}
|
|
33
|
+
{tooltip && (
|
|
34
|
+
<Tooltip>
|
|
35
|
+
<TooltipTrigger asChild>
|
|
36
|
+
<Info className="size-4 text-muted-foreground" />
|
|
37
|
+
</TooltipTrigger>
|
|
38
|
+
<TooltipContent>{tooltip}</TooltipContent>
|
|
39
|
+
</Tooltip>
|
|
40
|
+
)}
|
|
41
|
+
</div>
|
|
42
|
+
)}
|
|
43
|
+
{children}
|
|
44
|
+
{(hint || error) && (
|
|
45
|
+
<p
|
|
46
|
+
id={fieldId ? `${fieldId}-description` : undefined}
|
|
47
|
+
className={cn("type-text-sm", error ? "text-destructive" : "text-subtle-foreground")}
|
|
48
|
+
>
|
|
49
|
+
{error ?? hint}
|
|
50
|
+
</p>
|
|
51
|
+
)}
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type { FieldProps };
|
|
57
|
+
export { Field };
|
package/src/components/input.tsx
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { Info } from "lucide-react";
|
|
4
3
|
import * as React from "react";
|
|
5
4
|
|
|
6
5
|
import { cn } from "../lib/cn";
|
|
7
|
-
import {
|
|
8
|
-
import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip";
|
|
6
|
+
import { Field } from "./field";
|
|
9
7
|
|
|
10
8
|
const inputSizeStyles = {
|
|
11
9
|
sm: {
|
|
@@ -119,37 +117,9 @@ function Input({
|
|
|
119
117
|
);
|
|
120
118
|
|
|
121
119
|
return (
|
|
122
|
-
<
|
|
123
|
-
{label && (
|
|
124
|
-
<div className="flex items-center gap-0.5">
|
|
125
|
-
<Label htmlFor={fieldId} className="type-text-sm font-medium text-muted-foreground">
|
|
126
|
-
{label}
|
|
127
|
-
</Label>
|
|
128
|
-
{required && (
|
|
129
|
-
<span aria-hidden className="type-text-sm font-medium text-required">
|
|
130
|
-
*
|
|
131
|
-
</span>
|
|
132
|
-
)}
|
|
133
|
-
{tooltip && (
|
|
134
|
-
<Tooltip>
|
|
135
|
-
<TooltipTrigger asChild>
|
|
136
|
-
<Info className="size-4 text-muted-foreground" />
|
|
137
|
-
</TooltipTrigger>
|
|
138
|
-
<TooltipContent>{tooltip}</TooltipContent>
|
|
139
|
-
</Tooltip>
|
|
140
|
-
)}
|
|
141
|
-
</div>
|
|
142
|
-
)}
|
|
120
|
+
<Field fieldId={fieldId} label={label} required={required} hint={hint} error={error} tooltip={tooltip}>
|
|
143
121
|
{wrappedInput}
|
|
144
|
-
|
|
145
|
-
<p
|
|
146
|
-
id={fieldId ? `${fieldId}-description` : undefined}
|
|
147
|
-
className={cn("type-text-sm", error ? "text-destructive" : "text-subtle-foreground")}
|
|
148
|
-
>
|
|
149
|
-
{error ?? hint}
|
|
150
|
-
</p>
|
|
151
|
-
)}
|
|
152
|
-
</div>
|
|
122
|
+
</Field>
|
|
153
123
|
);
|
|
154
124
|
}
|
|
155
125
|
|
|
@@ -6,9 +6,6 @@ import {
|
|
|
6
6
|
LabelList,
|
|
7
7
|
Line,
|
|
8
8
|
LineChart as RechartsLineChart,
|
|
9
|
-
ReferenceArea,
|
|
10
|
-
ReferenceDot,
|
|
11
|
-
ReferenceLine,
|
|
12
9
|
ResponsiveContainer,
|
|
13
10
|
Tooltip,
|
|
14
11
|
XAxis,
|
|
@@ -21,6 +18,15 @@ import { cn } from "../lib/cn";
|
|
|
21
18
|
import type { ChartMarker } from "./area-chart";
|
|
22
19
|
import { useChartContext } from "./chart-container";
|
|
23
20
|
import { ChartLegend } from "./chart-legend";
|
|
21
|
+
import {
|
|
22
|
+
computeNumericMax,
|
|
23
|
+
makeActiveDot,
|
|
24
|
+
makeChartAxis,
|
|
25
|
+
makeLastValueLabel,
|
|
26
|
+
makeLegendItems,
|
|
27
|
+
renderMarkers,
|
|
28
|
+
renderReferenceLine,
|
|
29
|
+
} from "./chart-primitives";
|
|
24
30
|
import { ChartTooltipContent } from "./chart-tooltip";
|
|
25
31
|
import { Skeleton } from "./skeleton";
|
|
26
32
|
|
|
@@ -71,89 +77,14 @@ function LineChart({
|
|
|
71
77
|
|
|
72
78
|
const resolved = resolveSeries(series, paletteColors, theme);
|
|
73
79
|
|
|
74
|
-
const numericMax = React.useMemo(() =>
|
|
75
|
-
let max = 0;
|
|
76
|
-
for (const row of data) {
|
|
77
|
-
for (const entry of series) {
|
|
78
|
-
const value = Number(row[entry.key]);
|
|
79
|
-
if (Number.isFinite(value) && value > max) {
|
|
80
|
-
max = value;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
return max;
|
|
85
|
-
}, [data, series]);
|
|
80
|
+
const numericMax = React.useMemo(() => computeNumericMax(data, series, false), [data, series]);
|
|
86
81
|
|
|
87
82
|
const curveType = CURVE_TYPE[curve];
|
|
88
83
|
const margin = { top: markers?.length ? 18 : 8, right: lastValueLabel ? 56 : 8, bottom: 2, left: 0 };
|
|
89
84
|
|
|
90
|
-
const axis =
|
|
91
|
-
stroke: theme.border,
|
|
92
|
-
tick: { fill: theme.axisForeground, fontSize: 10, fontFamily: theme.fontMono },
|
|
93
|
-
tickLine: false as const,
|
|
94
|
-
axisLine: { stroke: theme.border, strokeOpacity: 0.6 },
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const legendItems = resolved.map((entry) => ({ name: entry.name, color: entry.color, dashed: entry.dashed }));
|
|
85
|
+
const axis = makeChartAxis(theme);
|
|
98
86
|
|
|
99
|
-
const
|
|
100
|
-
valueFlags
|
|
101
|
-
? (dotProps: { cx?: number; cy?: number; value?: number | string }) => {
|
|
102
|
-
if (dotProps.cx == null || dotProps.cy == null) {
|
|
103
|
-
return <g />;
|
|
104
|
-
}
|
|
105
|
-
return (
|
|
106
|
-
<g>
|
|
107
|
-
<circle
|
|
108
|
-
cx={dotProps.cx}
|
|
109
|
-
cy={dotProps.cy}
|
|
110
|
-
r={3.5}
|
|
111
|
-
fill={entry.color}
|
|
112
|
-
stroke={theme.card}
|
|
113
|
-
strokeWidth={2}
|
|
114
|
-
/>
|
|
115
|
-
<text
|
|
116
|
-
x={dotProps.cx}
|
|
117
|
-
y={dotProps.cy - 8}
|
|
118
|
-
textAnchor="middle"
|
|
119
|
-
fill={entry.color}
|
|
120
|
-
fontFamily={theme.fontMono}
|
|
121
|
-
fontSize={10}
|
|
122
|
-
style={{ fontVariantNumeric: "tabular-nums" }}
|
|
123
|
-
>
|
|
124
|
-
{valueFormatter(Number(dotProps.value ?? 0))}
|
|
125
|
-
</text>
|
|
126
|
-
</g>
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
: { r: 3.5, fill: entry.color, stroke: theme.card, strokeWidth: 2 };
|
|
130
|
-
|
|
131
|
-
const renderLastLabel =
|
|
132
|
-
(color: string) =>
|
|
133
|
-
(props: {
|
|
134
|
-
x?: string | number;
|
|
135
|
-
y?: string | number;
|
|
136
|
-
value?: string | number | boolean | Array<string | number | boolean> | null;
|
|
137
|
-
index?: number;
|
|
138
|
-
}) => {
|
|
139
|
-
if (props.index !== data.length - 1 || props.x == null || props.y == null) {
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
return (
|
|
143
|
-
<text
|
|
144
|
-
x={Number(props.x) + 6}
|
|
145
|
-
y={Number(props.y)}
|
|
146
|
-
dy={3}
|
|
147
|
-
fill={color}
|
|
148
|
-
fontFamily={theme.fontMono}
|
|
149
|
-
fontSize={10}
|
|
150
|
-
textAnchor="start"
|
|
151
|
-
style={{ fontVariantNumeric: "tabular-nums" }}
|
|
152
|
-
>
|
|
153
|
-
{valueFormatter(Number(props.value ?? 0))}
|
|
154
|
-
</text>
|
|
155
|
-
);
|
|
156
|
-
};
|
|
87
|
+
const legendItems = makeLegendItems(resolved);
|
|
157
88
|
|
|
158
89
|
const isEmpty = data.length === 0 || resolved.length === 0;
|
|
159
90
|
|
|
@@ -196,34 +127,7 @@ function LineChart({
|
|
|
196
127
|
cursor={{ stroke: theme.axisForeground, strokeWidth: 1, strokeDasharray: "3 3" }}
|
|
197
128
|
content={<ChartTooltipContent valueFormatter={valueFormatter} labelFormatter={labelFormatter} />}
|
|
198
129
|
/>
|
|
199
|
-
{referenceLine
|
|
200
|
-
<ReferenceArea
|
|
201
|
-
y1={referenceLine.y}
|
|
202
|
-
y2={numericMax}
|
|
203
|
-
fill={theme.warning}
|
|
204
|
-
fillOpacity={0.06}
|
|
205
|
-
ifOverflow="extendDomain"
|
|
206
|
-
/>
|
|
207
|
-
)}
|
|
208
|
-
{referenceLine && (
|
|
209
|
-
<ReferenceLine
|
|
210
|
-
y={referenceLine.y}
|
|
211
|
-
stroke={theme.warning}
|
|
212
|
-
strokeDasharray="4 4"
|
|
213
|
-
strokeOpacity={0.6}
|
|
214
|
-
label={
|
|
215
|
-
referenceLine.label
|
|
216
|
-
? {
|
|
217
|
-
value: referenceLine.label,
|
|
218
|
-
fill: theme.warning,
|
|
219
|
-
fontSize: 9,
|
|
220
|
-
fontFamily: theme.fontMono,
|
|
221
|
-
position: "insideBottomRight",
|
|
222
|
-
}
|
|
223
|
-
: undefined
|
|
224
|
-
}
|
|
225
|
-
/>
|
|
226
|
-
)}
|
|
130
|
+
{renderReferenceLine({ referenceLine, numericMax, theme })}
|
|
227
131
|
|
|
228
132
|
{resolved.map((entry) => (
|
|
229
133
|
<Line
|
|
@@ -235,37 +139,21 @@ function LineChart({
|
|
|
235
139
|
strokeWidth={entry.dashed ? 2 : 1.8}
|
|
236
140
|
strokeDasharray={entry.dashed ? "5 3" : undefined}
|
|
237
141
|
dot={dots ? { r: 2.5, fill: entry.color, strokeWidth: 0 } : false}
|
|
238
|
-
activeDot={
|
|
142
|
+
activeDot={makeActiveDot(entry, { valueFlags, theme, valueFormatter })}
|
|
239
143
|
isAnimationActive={false}
|
|
240
144
|
animationDuration={650}
|
|
241
145
|
animationEasing="ease-out"
|
|
242
146
|
>
|
|
243
|
-
{lastValueLabel &&
|
|
147
|
+
{lastValueLabel && (
|
|
148
|
+
<LabelList
|
|
149
|
+
dataKey={entry.key}
|
|
150
|
+
content={makeLastValueLabel(entry.color, { dataLength: data.length, theme, valueFormatter })}
|
|
151
|
+
/>
|
|
152
|
+
)}
|
|
244
153
|
</Line>
|
|
245
154
|
))}
|
|
246
155
|
|
|
247
|
-
{markers
|
|
248
|
-
<ReferenceDot
|
|
249
|
-
key={`${marker.x}-${marker.y}`}
|
|
250
|
-
x={marker.x}
|
|
251
|
-
y={marker.y}
|
|
252
|
-
r={3.5}
|
|
253
|
-
fill={marker.color ?? theme.foreground}
|
|
254
|
-
stroke={theme.card}
|
|
255
|
-
strokeWidth={2}
|
|
256
|
-
label={
|
|
257
|
-
marker.label
|
|
258
|
-
? {
|
|
259
|
-
value: marker.label,
|
|
260
|
-
fill: marker.color ?? theme.foreground,
|
|
261
|
-
fontSize: 9,
|
|
262
|
-
fontFamily: theme.fontMono,
|
|
263
|
-
position: "top",
|
|
264
|
-
}
|
|
265
|
-
: undefined
|
|
266
|
-
}
|
|
267
|
-
/>
|
|
268
|
-
))}
|
|
156
|
+
{renderMarkers(markers, theme)}
|
|
269
157
|
</RechartsLineChart>
|
|
270
158
|
</ResponsiveContainer>
|
|
271
159
|
)}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { Info } from "lucide-react";
|
|
4
3
|
import * as React from "react";
|
|
5
4
|
|
|
6
5
|
import { cn } from "../lib/cn";
|
|
7
|
-
import {
|
|
8
|
-
import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip";
|
|
6
|
+
import { Field } from "./field";
|
|
9
7
|
|
|
10
8
|
interface TextareaProps extends React.ComponentProps<"textarea"> {
|
|
11
9
|
label?: string;
|
|
@@ -43,37 +41,9 @@ function Textarea({ className, id, label, required, hint, error, tooltip, ...pro
|
|
|
43
41
|
);
|
|
44
42
|
|
|
45
43
|
return (
|
|
46
|
-
<
|
|
47
|
-
{label && (
|
|
48
|
-
<div className="flex items-center gap-0.5">
|
|
49
|
-
<Label htmlFor={fieldId} className="type-text-sm font-medium text-muted-foreground">
|
|
50
|
-
{label}
|
|
51
|
-
</Label>
|
|
52
|
-
{required && (
|
|
53
|
-
<span aria-hidden className="type-text-sm font-medium text-required">
|
|
54
|
-
*
|
|
55
|
-
</span>
|
|
56
|
-
)}
|
|
57
|
-
{tooltip && (
|
|
58
|
-
<Tooltip>
|
|
59
|
-
<TooltipTrigger asChild>
|
|
60
|
-
<Info className="size-4 text-muted-foreground" />
|
|
61
|
-
</TooltipTrigger>
|
|
62
|
-
<TooltipContent>{tooltip}</TooltipContent>
|
|
63
|
-
</Tooltip>
|
|
64
|
-
)}
|
|
65
|
-
</div>
|
|
66
|
-
)}
|
|
44
|
+
<Field fieldId={fieldId} label={label} required={required} hint={hint} error={error} tooltip={tooltip}>
|
|
67
45
|
{textarea}
|
|
68
|
-
|
|
69
|
-
<p
|
|
70
|
-
id={fieldId ? `${fieldId}-description` : undefined}
|
|
71
|
-
className={cn("type-text-sm", error ? "text-destructive" : "text-subtle-foreground")}
|
|
72
|
-
>
|
|
73
|
-
{error ?? hint}
|
|
74
|
-
</p>
|
|
75
|
-
)}
|
|
76
|
-
</div>
|
|
46
|
+
</Field>
|
|
77
47
|
);
|
|
78
48
|
}
|
|
79
49
|
|
|
@@ -6,12 +6,14 @@ import { Stat } from "../components/stat";
|
|
|
6
6
|
|
|
7
7
|
export default { title: "Charts/Area Chart" };
|
|
8
8
|
|
|
9
|
-
const mulberry32 = (seed: number) =>
|
|
10
|
-
seed
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
const mulberry32 = (seed: number) => {
|
|
10
|
+
let state = seed | 0;
|
|
11
|
+
return () => {
|
|
12
|
+
state = (state + 0x6d2b79f5) | 0;
|
|
13
|
+
let hash = Math.imul(state ^ (state >>> 15), 1 | state);
|
|
14
|
+
hash = (hash + Math.imul(hash ^ (hash >>> 7), 61 | hash)) ^ hash;
|
|
15
|
+
return ((hash ^ (hash >>> 14)) >>> 0) / 4294967296;
|
|
16
|
+
};
|
|
15
17
|
};
|
|
16
18
|
|
|
17
19
|
const CLIENTS = ["ChatGPT", "Claude Code", "Claude", "Anthropic", "Goose", "VS Code"];
|
|
@@ -6,12 +6,14 @@ import { Stat } from "../components/stat";
|
|
|
6
6
|
|
|
7
7
|
export default { title: "Charts/Bar Chart" };
|
|
8
8
|
|
|
9
|
-
const mulberry32 = (seed: number) =>
|
|
10
|
-
seed
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
const mulberry32 = (seed: number) => {
|
|
10
|
+
let state = seed | 0;
|
|
11
|
+
return () => {
|
|
12
|
+
state = (state + 0x6d2b79f5) | 0;
|
|
13
|
+
let hash = Math.imul(state ^ (state >>> 15), 1 | state);
|
|
14
|
+
hash = (hash + Math.imul(hash ^ (hash >>> 7), 61 | hash)) ^ hash;
|
|
15
|
+
return ((hash ^ (hash >>> 14)) >>> 0) / 4294967296;
|
|
16
|
+
};
|
|
15
17
|
};
|
|
16
18
|
|
|
17
19
|
const CLIENTS = ["ChatGPT", "Claude Code", "Claude", "Anthropic", "Goose", "VS Code"];
|
|
@@ -6,12 +6,14 @@ import { Stat } from "../components/stat";
|
|
|
6
6
|
|
|
7
7
|
export default { title: "Charts/Donut Chart" };
|
|
8
8
|
|
|
9
|
-
const mulberry32 = (seed: number) =>
|
|
10
|
-
seed
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
const mulberry32 = (seed: number) => {
|
|
10
|
+
let state = seed | 0;
|
|
11
|
+
return () => {
|
|
12
|
+
state = (state + 0x6d2b79f5) | 0;
|
|
13
|
+
let hash = Math.imul(state ^ (state >>> 15), 1 | state);
|
|
14
|
+
hash = (hash + Math.imul(hash ^ (hash >>> 7), 61 | hash)) ^ hash;
|
|
15
|
+
return ((hash ^ (hash >>> 14)) >>> 0) / 4294967296;
|
|
16
|
+
};
|
|
15
17
|
};
|
|
16
18
|
|
|
17
19
|
const CLIENTS = ["ChatGPT", "Claude Code", "Claude", "Anthropic", "Goose", "VS Code"];
|
|
@@ -6,12 +6,14 @@ import { Stat } from "../components/stat";
|
|
|
6
6
|
|
|
7
7
|
export default { title: "Charts/Line Chart" };
|
|
8
8
|
|
|
9
|
-
const mulberry32 = (seed: number) =>
|
|
10
|
-
seed
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
const mulberry32 = (seed: number) => {
|
|
10
|
+
let state = seed | 0;
|
|
11
|
+
return () => {
|
|
12
|
+
state = (state + 0x6d2b79f5) | 0;
|
|
13
|
+
let hash = Math.imul(state ^ (state >>> 15), 1 | state);
|
|
14
|
+
hash = (hash + Math.imul(hash ^ (hash >>> 7), 61 | hash)) ^ hash;
|
|
15
|
+
return ((hash ^ (hash >>> 14)) >>> 0) / 4294967296;
|
|
16
|
+
};
|
|
15
17
|
};
|
|
16
18
|
|
|
17
19
|
const hourLabel = (index: number, length: number) =>
|
|
@@ -5,12 +5,14 @@ import { Stat } from "../components/stat";
|
|
|
5
5
|
|
|
6
6
|
export default { title: "Charts/Stat" };
|
|
7
7
|
|
|
8
|
-
const mulberry32 = (seed: number) =>
|
|
9
|
-
seed
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
const mulberry32 = (seed: number) => {
|
|
9
|
+
let state = seed | 0;
|
|
10
|
+
return () => {
|
|
11
|
+
state = (state + 0x6d2b79f5) | 0;
|
|
12
|
+
let hash = Math.imul(state ^ (state >>> 15), 1 | state);
|
|
13
|
+
hash = (hash + Math.imul(hash ^ (hash >>> 7), 61 | hash)) ^ hash;
|
|
14
|
+
return ((hash ^ (hash >>> 14)) >>> 0) / 4294967296;
|
|
15
|
+
};
|
|
14
16
|
};
|
|
15
17
|
|
|
16
18
|
const spark = (seed: number, length = 24) => {
|