@alpic-ai/ui 1.153.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/accordion-card.d.mts +4 -25
- package/dist/components/accordion.d.mts +4 -18
- package/dist/components/alert.d.mts +5 -24
- package/dist/components/area-chart.d.mts +1 -22
- package/dist/components/area-chart.mjs +20 -109
- package/dist/components/attachment-tile.d.mts +1 -8
- package/dist/components/avatar.d.mts +4 -27
- package/dist/components/badge.d.mts +1 -8
- package/dist/components/bar-chart.d.mts +1 -21
- package/dist/components/bar-chart.mjs +10 -68
- package/dist/components/bar-list.d.mts +1 -13
- package/dist/components/breadcrumb.d.mts +8 -35
- package/dist/components/button.d.mts +1 -14
- package/dist/components/card.d.mts +7 -30
- package/dist/components/chart-card.d.mts +1 -12
- package/dist/components/chart-container.d.mts +1 -6
- package/dist/components/chart-legend.d.mts +1 -8
- package/dist/components/chart-primitives.d.mts +69 -0
- package/dist/components/chart-primitives.mjs +134 -0
- package/dist/components/chart-tooltip.d.mts +1 -11
- package/dist/components/checkbox.d.mts +1 -5
- package/dist/components/collapsible.d.mts +3 -10
- package/dist/components/combobox.d.mts +9 -45
- package/dist/components/command.d.mts +8 -32
- package/dist/components/copyable.d.mts +2 -9
- package/dist/components/description-list.d.mts +4 -17
- package/dist/components/dialog.d.mts +10 -42
- package/dist/components/donut-chart.d.mts +1 -16
- package/dist/components/dropdown-menu.d.mts +13 -55
- package/dist/components/field.d.mts +14 -0
- package/dist/components/field.mjs +41 -0
- package/dist/components/form.d.mts +15 -115
- package/dist/components/github-button.d.mts +1 -6
- package/dist/components/heatmap-chart.d.mts +1 -23
- package/dist/components/input-group.d.mts +3 -15
- package/dist/components/input.d.mts +1 -14
- package/dist/components/input.mjs +25 -49
- package/dist/components/label.d.mts +1 -5
- package/dist/components/line-chart.d.mts +1 -21
- package/dist/components/line-chart.mjs +20 -102
- package/dist/components/page-loader.d.mts +1 -3
- package/dist/components/pagination.d.mts +1 -8
- package/dist/components/popover.d.mts +4 -16
- package/dist/components/radio-group.d.mts +2 -9
- package/dist/components/scroll-area.d.mts +2 -11
- package/dist/components/select.d.mts +8 -35
- package/dist/components/separator.d.mts +1 -7
- package/dist/components/sheet.d.mts +9 -35
- package/dist/components/shimmer-text.d.mts +1 -5
- package/dist/components/sidebar.d.mts +22 -113
- package/dist/components/skeleton.d.mts +1 -6
- package/dist/components/sonner.d.mts +1 -8
- package/dist/components/spinner.d.mts +2 -14
- package/dist/components/stat.d.mts +1 -10
- package/dist/components/status-dot.d.mts +1 -7
- package/dist/components/switch.d.mts +1 -5
- package/dist/components/table.d.mts +8 -35
- package/dist/components/tabs.d.mts +7 -36
- package/dist/components/tag.d.mts +3 -20
- package/dist/components/task-progress.d.mts +1 -7
- package/dist/components/textarea.d.mts +1 -11
- package/dist/components/textarea.mjs +17 -41
- package/dist/components/toggle-group.d.mts +2 -15
- package/dist/components/tooltip-icon-button.d.mts +0 -1
- package/dist/components/tooltip.d.mts +4 -17
- package/dist/components/typography.d.mts +4 -17
- package/dist/components/wizard.d.mts +1 -7
- package/dist/hooks/use-copy-to-clipboard.d.mts +1 -3
- package/dist/lib/chart-palette.mjs +39 -21
- package/dist/lib/chart.d.mts +6 -1
- package/dist/lib/cn.d.mts +0 -1
- package/package.json +19 -19
- 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
|
@@ -6,9 +6,6 @@ import {
|
|
|
6
6
|
CartesianGrid,
|
|
7
7
|
LabelList,
|
|
8
8
|
AreaChart as RechartsAreaChart,
|
|
9
|
-
ReferenceArea,
|
|
10
|
-
ReferenceDot,
|
|
11
|
-
ReferenceLine,
|
|
12
9
|
ResponsiveContainer,
|
|
13
10
|
Tooltip,
|
|
14
11
|
XAxis,
|
|
@@ -20,6 +17,15 @@ import type { ChartPaletteName } from "../lib/chart-palette";
|
|
|
20
17
|
import { cn } from "../lib/cn";
|
|
21
18
|
import { useChartContext } from "./chart-container";
|
|
22
19
|
import { ChartLegend } from "./chart-legend";
|
|
20
|
+
import {
|
|
21
|
+
computeNumericMax,
|
|
22
|
+
makeActiveDot,
|
|
23
|
+
makeChartAxis,
|
|
24
|
+
makeLastValueLabel,
|
|
25
|
+
makeLegendItems,
|
|
26
|
+
renderMarkers,
|
|
27
|
+
renderReferenceLine,
|
|
28
|
+
} from "./chart-primitives";
|
|
23
29
|
import { ChartTooltipContent } from "./chart-tooltip";
|
|
24
30
|
import { Skeleton } from "./skeleton";
|
|
25
31
|
|
|
@@ -87,71 +93,14 @@ function AreaChart({
|
|
|
87
93
|
const lead = resolved[0];
|
|
88
94
|
const withTotal = stacked && rendered.length > 1;
|
|
89
95
|
|
|
90
|
-
|
|
91
|
-
// single series, or the reference band stops short of the chart top.
|
|
92
|
-
const numericMax = React.useMemo(() => {
|
|
93
|
-
let max = 0;
|
|
94
|
-
for (const row of data) {
|
|
95
|
-
let rowTotal = 0;
|
|
96
|
-
for (const entry of series) {
|
|
97
|
-
const value = Number(row[entry.key]);
|
|
98
|
-
if (Number.isFinite(value)) {
|
|
99
|
-
rowTotal += value;
|
|
100
|
-
if (!stacked && value > max) {
|
|
101
|
-
max = value;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
if (stacked && rowTotal > max) {
|
|
106
|
-
max = rowTotal;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return max;
|
|
110
|
-
}, [data, series, stacked]);
|
|
96
|
+
const numericMax = React.useMemo(() => computeNumericMax(data, series, stacked), [data, series, stacked]);
|
|
111
97
|
|
|
112
98
|
const curveType = CURVE_TYPE[curve];
|
|
113
99
|
const margin = { top: markers?.length ? 18 : 8, right: lastValueLabel ? 56 : 8, bottom: 2, left: 0 };
|
|
114
100
|
|
|
115
|
-
const axis =
|
|
116
|
-
stroke: theme.border,
|
|
117
|
-
tick: { fill: theme.axisForeground, fontSize: 10, fontFamily: theme.fontMono },
|
|
118
|
-
tickLine: false as const,
|
|
119
|
-
axisLine: { stroke: theme.border, strokeOpacity: 0.6 },
|
|
120
|
-
};
|
|
101
|
+
const axis = makeChartAxis(theme);
|
|
121
102
|
|
|
122
|
-
const legendItems = resolved
|
|
123
|
-
|
|
124
|
-
const activeDotFor = (entry: (typeof rendered)[number]) =>
|
|
125
|
-
valueFlags
|
|
126
|
-
? (dotProps: { cx?: number; cy?: number; value?: number | string }) => {
|
|
127
|
-
if (dotProps.cx == null || dotProps.cy == null) {
|
|
128
|
-
return <g />;
|
|
129
|
-
}
|
|
130
|
-
return (
|
|
131
|
-
<g>
|
|
132
|
-
<circle
|
|
133
|
-
cx={dotProps.cx}
|
|
134
|
-
cy={dotProps.cy}
|
|
135
|
-
r={3.5}
|
|
136
|
-
fill={entry.color}
|
|
137
|
-
stroke={theme.card}
|
|
138
|
-
strokeWidth={2}
|
|
139
|
-
/>
|
|
140
|
-
<text
|
|
141
|
-
x={dotProps.cx}
|
|
142
|
-
y={dotProps.cy - 8}
|
|
143
|
-
textAnchor="middle"
|
|
144
|
-
fill={entry.color}
|
|
145
|
-
fontFamily={theme.fontMono}
|
|
146
|
-
fontSize={10}
|
|
147
|
-
style={{ fontVariantNumeric: "tabular-nums" }}
|
|
148
|
-
>
|
|
149
|
-
{valueFormatter(Number(dotProps.value ?? 0))}
|
|
150
|
-
</text>
|
|
151
|
-
</g>
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
: { r: 3.5, fill: entry.color, stroke: theme.card, strokeWidth: 2 };
|
|
103
|
+
const legendItems = makeLegendItems(resolved);
|
|
155
104
|
|
|
156
105
|
const fillFor = (entry: (typeof rendered)[number], slot: number) => {
|
|
157
106
|
if (!filled) {
|
|
@@ -163,33 +112,6 @@ function AreaChart({
|
|
|
163
112
|
return `url(#area-${reactId}-${slot})`;
|
|
164
113
|
};
|
|
165
114
|
|
|
166
|
-
const renderLastLabel =
|
|
167
|
-
(color: string) =>
|
|
168
|
-
(props: {
|
|
169
|
-
x?: string | number;
|
|
170
|
-
y?: string | number;
|
|
171
|
-
value?: string | number | boolean | Array<string | number | boolean> | null;
|
|
172
|
-
index?: number;
|
|
173
|
-
}) => {
|
|
174
|
-
if (props.index !== data.length - 1 || props.x == null || props.y == null) {
|
|
175
|
-
return null;
|
|
176
|
-
}
|
|
177
|
-
return (
|
|
178
|
-
<text
|
|
179
|
-
x={Number(props.x) + 6}
|
|
180
|
-
y={Number(props.y)}
|
|
181
|
-
dy={3}
|
|
182
|
-
fill={color}
|
|
183
|
-
fontFamily={theme.fontMono}
|
|
184
|
-
fontSize={10}
|
|
185
|
-
textAnchor="start"
|
|
186
|
-
style={{ fontVariantNumeric: "tabular-nums" }}
|
|
187
|
-
>
|
|
188
|
-
{valueFormatter(Number(props.value ?? 0))}
|
|
189
|
-
</text>
|
|
190
|
-
);
|
|
191
|
-
};
|
|
192
|
-
|
|
193
115
|
const isEmpty = data.length === 0 || rendered.length === 0;
|
|
194
116
|
|
|
195
117
|
return (
|
|
@@ -264,34 +186,7 @@ function AreaChart({
|
|
|
264
186
|
/>
|
|
265
187
|
}
|
|
266
188
|
/>
|
|
267
|
-
{referenceLine
|
|
268
|
-
<ReferenceArea
|
|
269
|
-
y1={referenceLine.y}
|
|
270
|
-
y2={numericMax}
|
|
271
|
-
fill={theme.warning}
|
|
272
|
-
fillOpacity={0.06}
|
|
273
|
-
ifOverflow="extendDomain"
|
|
274
|
-
/>
|
|
275
|
-
)}
|
|
276
|
-
{referenceLine && (
|
|
277
|
-
<ReferenceLine
|
|
278
|
-
y={referenceLine.y}
|
|
279
|
-
stroke={theme.warning}
|
|
280
|
-
strokeDasharray="4 4"
|
|
281
|
-
strokeOpacity={0.6}
|
|
282
|
-
label={
|
|
283
|
-
referenceLine.label
|
|
284
|
-
? {
|
|
285
|
-
value: referenceLine.label,
|
|
286
|
-
fill: theme.warning,
|
|
287
|
-
fontSize: 9,
|
|
288
|
-
fontFamily: theme.fontMono,
|
|
289
|
-
position: "insideBottomRight",
|
|
290
|
-
}
|
|
291
|
-
: undefined
|
|
292
|
-
}
|
|
293
|
-
/>
|
|
294
|
-
)}
|
|
189
|
+
{renderReferenceLine({ referenceLine, numericMax, theme })}
|
|
295
190
|
|
|
296
191
|
{rendered.map((entry, slot) => (
|
|
297
192
|
<Area
|
|
@@ -305,37 +200,21 @@ function AreaChart({
|
|
|
305
200
|
strokeDasharray={entry.dashed ? "5 3" : undefined}
|
|
306
201
|
fill={fillFor(entry, slot)}
|
|
307
202
|
dot={false}
|
|
308
|
-
activeDot={
|
|
203
|
+
activeDot={makeActiveDot(entry, { valueFlags, theme, valueFormatter })}
|
|
309
204
|
isAnimationActive={false}
|
|
310
205
|
animationDuration={650}
|
|
311
206
|
animationEasing="ease-out"
|
|
312
207
|
>
|
|
313
|
-
{lastValueLabel &&
|
|
208
|
+
{lastValueLabel && (
|
|
209
|
+
<LabelList
|
|
210
|
+
dataKey={entry.key}
|
|
211
|
+
content={makeLastValueLabel(entry.color, { dataLength: data.length, theme, valueFormatter })}
|
|
212
|
+
/>
|
|
213
|
+
)}
|
|
314
214
|
</Area>
|
|
315
215
|
))}
|
|
316
216
|
|
|
317
|
-
{markers
|
|
318
|
-
<ReferenceDot
|
|
319
|
-
key={`${marker.x}-${marker.y}`}
|
|
320
|
-
x={marker.x}
|
|
321
|
-
y={marker.y}
|
|
322
|
-
r={3.5}
|
|
323
|
-
fill={marker.color ?? theme.foreground}
|
|
324
|
-
stroke={theme.card}
|
|
325
|
-
strokeWidth={2}
|
|
326
|
-
label={
|
|
327
|
-
marker.label
|
|
328
|
-
? {
|
|
329
|
-
value: marker.label,
|
|
330
|
-
fill: marker.color ?? theme.foreground,
|
|
331
|
-
fontSize: 9,
|
|
332
|
-
fontFamily: theme.fontMono,
|
|
333
|
-
position: "top",
|
|
334
|
-
}
|
|
335
|
-
: undefined
|
|
336
|
-
}
|
|
337
|
-
/>
|
|
338
|
-
))}
|
|
217
|
+
{renderMarkers(markers, theme)}
|
|
339
218
|
</RechartsAreaChart>
|
|
340
219
|
</ResponsiveContainer>
|
|
341
220
|
)}
|
|
@@ -7,9 +7,6 @@ import {
|
|
|
7
7
|
Cell,
|
|
8
8
|
LabelList,
|
|
9
9
|
BarChart as RechartsBarChart,
|
|
10
|
-
ReferenceArea,
|
|
11
|
-
ReferenceDot,
|
|
12
|
-
ReferenceLine,
|
|
13
10
|
ResponsiveContainer,
|
|
14
11
|
Tooltip,
|
|
15
12
|
XAxis,
|
|
@@ -22,6 +19,13 @@ import { cn } from "../lib/cn";
|
|
|
22
19
|
import type { ChartMarker } from "./area-chart";
|
|
23
20
|
import { useChartContext } from "./chart-container";
|
|
24
21
|
import { ChartLegend } from "./chart-legend";
|
|
22
|
+
import {
|
|
23
|
+
computeNumericMax,
|
|
24
|
+
makeChartAxis,
|
|
25
|
+
makeLegendItems,
|
|
26
|
+
renderMarkers,
|
|
27
|
+
renderReferenceLine,
|
|
28
|
+
} from "./chart-primitives";
|
|
25
29
|
import { ChartTooltipContent } from "./chart-tooltip";
|
|
26
30
|
import { Skeleton } from "./skeleton";
|
|
27
31
|
|
|
@@ -78,27 +82,7 @@ function BarChart({
|
|
|
78
82
|
const lead = resolved[0];
|
|
79
83
|
const withTotal = stacked && rendered.length > 1;
|
|
80
84
|
|
|
81
|
-
|
|
82
|
-
// series, so the explicit YAxis domain must not clip a tall stack short.
|
|
83
|
-
const numericMax = React.useMemo(() => {
|
|
84
|
-
let max = 0;
|
|
85
|
-
for (const row of data) {
|
|
86
|
-
let rowTotal = 0;
|
|
87
|
-
for (const entry of series) {
|
|
88
|
-
const value = Number(row[entry.key]);
|
|
89
|
-
if (Number.isFinite(value)) {
|
|
90
|
-
rowTotal += value;
|
|
91
|
-
if (!stacked && value > max) {
|
|
92
|
-
max = value;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
if (stacked && rowTotal > max) {
|
|
97
|
-
max = rowTotal;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return max;
|
|
101
|
-
}, [data, series, stacked]);
|
|
85
|
+
const numericMax = React.useMemo(() => computeNumericMax(data, series, stacked), [data, series, stacked]);
|
|
102
86
|
|
|
103
87
|
const topSlotByRow = React.useMemo(() => {
|
|
104
88
|
if (!stacked) {
|
|
@@ -117,14 +101,9 @@ function BarChart({
|
|
|
117
101
|
|
|
118
102
|
const margin = { top: markers?.length || valueLabels ? 18 : 8, right: 8, bottom: 2, left: 0 };
|
|
119
103
|
|
|
120
|
-
const axis =
|
|
121
|
-
stroke: theme.border,
|
|
122
|
-
tick: { fill: theme.axisForeground, fontSize: 10, fontFamily: theme.fontMono },
|
|
123
|
-
tickLine: false as const,
|
|
124
|
-
axisLine: { stroke: theme.border, strokeOpacity: 0.6 },
|
|
125
|
-
};
|
|
104
|
+
const axis = makeChartAxis(theme);
|
|
126
105
|
|
|
127
|
-
const legendItems = resolved
|
|
106
|
+
const legendItems = makeLegendItems(resolved);
|
|
128
107
|
|
|
129
108
|
const radiusFor = (slot: number): [number, number, number, number] => {
|
|
130
109
|
if (!stacked || slot === rendered.length - 1) {
|
|
@@ -251,34 +230,7 @@ function BarChart({
|
|
|
251
230
|
/>
|
|
252
231
|
}
|
|
253
232
|
/>
|
|
254
|
-
{referenceLine
|
|
255
|
-
<ReferenceArea
|
|
256
|
-
y1={referenceLine.y}
|
|
257
|
-
y2={numericMax}
|
|
258
|
-
fill={theme.warning}
|
|
259
|
-
fillOpacity={0.06}
|
|
260
|
-
ifOverflow="extendDomain"
|
|
261
|
-
/>
|
|
262
|
-
)}
|
|
263
|
-
{referenceLine && (
|
|
264
|
-
<ReferenceLine
|
|
265
|
-
y={referenceLine.y}
|
|
266
|
-
stroke={theme.warning}
|
|
267
|
-
strokeDasharray="4 4"
|
|
268
|
-
strokeOpacity={0.6}
|
|
269
|
-
label={
|
|
270
|
-
referenceLine.label
|
|
271
|
-
? {
|
|
272
|
-
value: referenceLine.label,
|
|
273
|
-
fill: theme.warning,
|
|
274
|
-
fontSize: 9,
|
|
275
|
-
fontFamily: theme.fontMono,
|
|
276
|
-
position: "insideBottomRight",
|
|
277
|
-
}
|
|
278
|
-
: undefined
|
|
279
|
-
}
|
|
280
|
-
/>
|
|
281
|
-
)}
|
|
233
|
+
{renderReferenceLine({ referenceLine, numericMax, theme })}
|
|
282
234
|
|
|
283
235
|
{rendered.map((entry, slot) => (
|
|
284
236
|
<Bar
|
|
@@ -320,28 +272,7 @@ function BarChart({
|
|
|
320
272
|
</Bar>
|
|
321
273
|
))}
|
|
322
274
|
|
|
323
|
-
{markers
|
|
324
|
-
<ReferenceDot
|
|
325
|
-
key={`${marker.x}-${marker.y}`}
|
|
326
|
-
x={marker.x}
|
|
327
|
-
y={marker.y}
|
|
328
|
-
r={3.5}
|
|
329
|
-
fill={marker.color ?? theme.foreground}
|
|
330
|
-
stroke={theme.card}
|
|
331
|
-
strokeWidth={2}
|
|
332
|
-
label={
|
|
333
|
-
marker.label
|
|
334
|
-
? {
|
|
335
|
-
value: marker.label,
|
|
336
|
-
fill: marker.color ?? theme.foreground,
|
|
337
|
-
fontSize: 9,
|
|
338
|
-
fontFamily: theme.fontMono,
|
|
339
|
-
position: "top",
|
|
340
|
-
}
|
|
341
|
-
: undefined
|
|
342
|
-
}
|
|
343
|
-
/>
|
|
344
|
-
))}
|
|
275
|
+
{renderMarkers(markers, theme)}
|
|
345
276
|
</RechartsBarChart>
|
|
346
277
|
</ResponsiveContainer>
|
|
347
278
|
)}
|
|
@@ -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
|
|