aplance-ui 1.1.1 → 1.2.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/api.d.ts +2 -0
- package/dist/api.js +4 -0
- package/dist/api.js.map +1 -1
- package/dist/index.js +18 -1
- package/dist/index.js.map +1 -1
- package/package.json +63 -63
- package/registry/components/primitives/infinite-scroll/infinite-scroll-context.ts +32 -0
- package/registry/components/primitives/infinite-scroll/infinite-scroll.module.css +118 -0
- package/registry/components/primitives/infinite-scroll/infinite-scroll.tsx +386 -0
- package/registry/components/primitives/slider/slider-context.ts +50 -0
- package/registry/components/primitives/slider/slider.module.css +300 -0
- package/registry/components/primitives/slider/slider.tsx +1174 -0
- package/registry/components/primitives/slider/types.ts +140 -0
- package/registry/components/primitives/slider/utils.ts +88 -0
- package/registry/components/ui/breadcrumbs/breadcrumbs.module.css +144 -0
- package/registry/components/ui/breadcrumbs/breadcrumbs.tsx +236 -0
- package/registry/components/ui/chart/chart.module.css +86 -70
- package/registry/components/ui/chart/chart.tsx +117 -31
- package/registry/components/ui/input-fields/media-input/media-input.module.css +12 -0
- package/registry/components/ui/input-fields/media-input/media-input.tsx +10 -9
- package/registry/components/ui/input-fields/media-input/useMediaUpload.tsx +19 -5
- package/registry/components/ui/input-fields/media-input/utils.ts +13 -5
- package/registry/registry.json +64 -1
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/* ── Container card ───────────────────────────────────────────── */
|
|
2
|
-
.card {
|
|
3
|
-
width: 100%;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
border
|
|
7
|
-
|
|
8
|
-
display: flex;
|
|
9
|
-
flex-direction: column;
|
|
10
|
-
gap: var(--spacing-
|
|
11
|
-
|
|
2
|
+
.card {
|
|
3
|
+
width: 100%;
|
|
4
|
+
min-width: 0;
|
|
5
|
+
background-color: var(--color-background);
|
|
6
|
+
border: var(--border-1) solid var(--color-border);
|
|
7
|
+
border-radius: var(--radius-lg);
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-direction: column;
|
|
10
|
+
gap: var(--spacing-6);
|
|
11
|
+
padding: var(--spacing-6);
|
|
12
|
+
}
|
|
12
13
|
|
|
13
14
|
.header {
|
|
14
15
|
display: flex;
|
|
@@ -29,18 +30,19 @@
|
|
|
29
30
|
color: var(--color-muted-foreground);
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
.canvas {
|
|
33
|
-
width: 100%;
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
.canvas {
|
|
34
|
+
width: 100%;
|
|
35
|
+
min-width: 0;
|
|
36
|
+
position: relative;
|
|
37
|
+
}
|
|
36
38
|
|
|
37
|
-
.plot {
|
|
38
|
-
width: 100%;
|
|
39
|
-
min-width: 0;
|
|
40
|
-
display: flex;
|
|
41
|
-
flex-direction: column;
|
|
42
|
-
gap: var(--spacing-
|
|
43
|
-
}
|
|
39
|
+
.plot {
|
|
40
|
+
width: 100%;
|
|
41
|
+
min-width: 0;
|
|
42
|
+
display: flex;
|
|
43
|
+
flex-direction: column;
|
|
44
|
+
gap: var(--spacing-4);
|
|
45
|
+
}
|
|
44
46
|
|
|
45
47
|
.plot :global(.recharts-cartesian-axis-tick-value) {
|
|
46
48
|
font-variant-numeric: tabular-nums;
|
|
@@ -70,10 +72,11 @@
|
|
|
70
72
|
/* Match the page surface so series colors (designed against the page bg)
|
|
71
73
|
stay readable inside the tooltip. Border defines the floating edge
|
|
72
74
|
without relying on shadow alone. */
|
|
73
|
-
.tooltip {
|
|
74
|
-
min-width: 10rem;
|
|
75
|
-
|
|
76
|
-
color: var(--color-
|
|
75
|
+
.tooltip {
|
|
76
|
+
min-width: 10rem;
|
|
77
|
+
max-width: min(18rem, calc(100vw - var(--spacing-8)));
|
|
78
|
+
background-color: var(--color-background);
|
|
79
|
+
color: var(--color-foreground);
|
|
77
80
|
border: var(--border-1) solid var(--color-border);
|
|
78
81
|
border-radius: var(--radius-md);
|
|
79
82
|
padding: var(--spacing-4) var(--spacing-5);
|
|
@@ -86,55 +89,68 @@
|
|
|
86
89
|
margin-bottom: var(--spacing-2);
|
|
87
90
|
}
|
|
88
91
|
|
|
89
|
-
.tooltip_row {
|
|
90
|
-
display: flex;
|
|
91
|
-
align-items: center;
|
|
92
|
-
gap: var(--spacing-3);
|
|
93
|
-
font-size: var(--font-size-xs);
|
|
92
|
+
.tooltip_row {
|
|
93
|
+
display: flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
gap: var(--spacing-3);
|
|
96
|
+
font-size: var(--font-size-xs);
|
|
97
|
+
min-width: 0;
|
|
94
98
|
|
|
95
99
|
&:not(:last-child) {
|
|
96
100
|
margin-bottom: var(--spacing-2);
|
|
97
101
|
}
|
|
98
102
|
}
|
|
99
103
|
|
|
100
|
-
.tooltip_dot {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
font-
|
|
112
|
-
|
|
104
|
+
.tooltip_dot {
|
|
105
|
+
display: block;
|
|
106
|
+
width: 8px;
|
|
107
|
+
height: 8px;
|
|
108
|
+
border-radius: var(--radius-full);
|
|
109
|
+
flex-shrink: 0;
|
|
110
|
+
box-shadow: 0 0 0 1px color-mix(in srgb, var(--color-background) 84%, transparent);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.tooltip_value {
|
|
114
|
+
margin-left: auto;
|
|
115
|
+
font-weight: var(--font-weight-semibold);
|
|
116
|
+
padding-left: var(--spacing-3);
|
|
117
|
+
font-variant-numeric: tabular-nums;
|
|
118
|
+
white-space: nowrap;
|
|
119
|
+
}
|
|
113
120
|
|
|
114
121
|
/* ── Legend (shared across all chart types) ──────────────────── */
|
|
115
|
-
.legend {
|
|
116
|
-
display: flex;
|
|
117
|
-
flex-wrap: wrap;
|
|
118
|
-
gap: var(--spacing-3) var(--spacing-
|
|
119
|
-
justify-content: center;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
122
|
+
.legend {
|
|
123
|
+
display: flex;
|
|
124
|
+
flex-wrap: wrap;
|
|
125
|
+
gap: var(--spacing-3) var(--spacing-5);
|
|
126
|
+
justify-content: center;
|
|
127
|
+
min-width: 0;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.legend_item {
|
|
131
|
+
display: inline-flex;
|
|
132
|
+
align-items: center;
|
|
133
|
+
gap: var(--spacing-2);
|
|
134
|
+
min-width: 0;
|
|
135
|
+
font-size: var(--font-size-xs);
|
|
136
|
+
font-weight: var(--font-weight-medium);
|
|
137
|
+
color: var(--color-muted-foreground);
|
|
138
|
+
line-height: 1.2;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.legend_dot {
|
|
142
|
+
display: block;
|
|
143
|
+
width: 8px;
|
|
144
|
+
height: 8px;
|
|
145
|
+
border-radius: var(--radius-full);
|
|
146
|
+
flex-shrink: 0;
|
|
147
|
+
box-shadow: 0 0 0 1px color-mix(in srgb, var(--color-background) 84%, transparent);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.legend_item > span:last-child {
|
|
151
|
+
overflow: hidden;
|
|
152
|
+
text-overflow: ellipsis;
|
|
153
|
+
}
|
|
138
154
|
|
|
139
155
|
.center_overlay {
|
|
140
156
|
position: absolute;
|
|
@@ -151,10 +167,10 @@
|
|
|
151
167
|
}
|
|
152
168
|
|
|
153
169
|
@media (width <= 600px) {
|
|
154
|
-
.card {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
170
|
+
.card {
|
|
171
|
+
gap: var(--spacing-5);
|
|
172
|
+
padding: var(--spacing-5);
|
|
173
|
+
}
|
|
158
174
|
|
|
159
175
|
.tooltip {
|
|
160
176
|
min-width: 8rem;
|
|
@@ -77,9 +77,7 @@ export type {
|
|
|
77
77
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
78
78
|
|
|
79
79
|
const DEFAULT_CHART_HEIGHT = 260;
|
|
80
|
-
|
|
81
|
-
// so the visible whitespace on both sides feels balanced.
|
|
82
|
-
const CARTESIAN_MARGIN = { top: 10, right: 10, bottom: 10, left: -8 } as const;
|
|
80
|
+
const CARTESIAN_MARGIN = { top: 8, right: 12, bottom: 4, left: 0 } as const;
|
|
83
81
|
const YAXIS_WIDTH = 56;
|
|
84
82
|
const YAXIS_WIDTH_HORIZONTAL = 80;
|
|
85
83
|
const BAR_TOP_RADIUS: [number, number, number, number] = [6, 6, 0, 0];
|
|
@@ -114,6 +112,8 @@ type ChartTooltipProps = {
|
|
|
114
112
|
// a single dataKey across all sectors), read the row label from the original
|
|
115
113
|
// datum at this key so the tooltip shows "Aarav" instead of the dataKey.
|
|
116
114
|
nameKey?: string;
|
|
115
|
+
/** Read a per-datum color from tooltip payload before falling back to series color. */
|
|
116
|
+
colorKey?: string;
|
|
117
117
|
};
|
|
118
118
|
|
|
119
119
|
type CartesianRenderOptions = {
|
|
@@ -160,6 +160,37 @@ type LegendItem = { key: string; label: string; color: string };
|
|
|
160
160
|
const toLegendItems = (series: PreparedSeries[]): LegendItem[] =>
|
|
161
161
|
series.map(({ key, label, color }) => ({ key, label, color }));
|
|
162
162
|
|
|
163
|
+
const toCategoryLegendItems = ({
|
|
164
|
+
data,
|
|
165
|
+
categoryKey,
|
|
166
|
+
colors,
|
|
167
|
+
fallbackColor,
|
|
168
|
+
}: {
|
|
169
|
+
data: ChartDatum[];
|
|
170
|
+
categoryKey: string;
|
|
171
|
+
colors?: Record<string, string>;
|
|
172
|
+
fallbackColor: string;
|
|
173
|
+
}): LegendItem[] => {
|
|
174
|
+
const seen = new Set<string>();
|
|
175
|
+
const items: LegendItem[] = [];
|
|
176
|
+
|
|
177
|
+
for (const entry of data) {
|
|
178
|
+
const rawLabel = entry[categoryKey];
|
|
179
|
+
const label = String(rawLabel ?? "Unknown");
|
|
180
|
+
const lookup = toLookupKey(rawLabel);
|
|
181
|
+
if (seen.has(lookup)) continue;
|
|
182
|
+
|
|
183
|
+
seen.add(lookup);
|
|
184
|
+
items.push({
|
|
185
|
+
key: lookup || label,
|
|
186
|
+
label,
|
|
187
|
+
color: resolveColorToken(colors?.[lookup], fallbackColor),
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return items;
|
|
192
|
+
};
|
|
193
|
+
|
|
163
194
|
// ─── Option resolution ────────────────────────────────────────────────────────
|
|
164
195
|
|
|
165
196
|
const resolveCartesianOptions = (opts: CartesianRenderOptions) => ({
|
|
@@ -311,6 +342,11 @@ const formatTooltipValue = (value: TooltipValue): string => {
|
|
|
311
342
|
return String(value);
|
|
312
343
|
};
|
|
313
344
|
|
|
345
|
+
const toFiniteNumber = (value: unknown, fallback = 0) => {
|
|
346
|
+
const number = typeof value === "number" ? value : Number(value);
|
|
347
|
+
return Number.isFinite(number) ? number : fallback;
|
|
348
|
+
};
|
|
349
|
+
|
|
314
350
|
const formatTooltipEntryValue = (
|
|
315
351
|
value: TooltipValue,
|
|
316
352
|
valueFormatter: TooltipValueFormatter | undefined,
|
|
@@ -331,6 +367,25 @@ const formatTooltipLabel = (
|
|
|
331
367
|
return String(label);
|
|
332
368
|
};
|
|
333
369
|
|
|
370
|
+
const payloadString = (
|
|
371
|
+
payload: Record<string, unknown> | undefined,
|
|
372
|
+
key: string,
|
|
373
|
+
) => {
|
|
374
|
+
const value = payload?.[key];
|
|
375
|
+
return typeof value === "string" && value.trim() ? value : undefined;
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
const resolveTooltipColor = (
|
|
379
|
+
entry: TooltipPayloadItem,
|
|
380
|
+
colorKey: string | undefined,
|
|
381
|
+
) =>
|
|
382
|
+
(colorKey ? payloadString(entry.payload, colorKey) : undefined) ??
|
|
383
|
+
payloadString(entry.payload, "__color") ??
|
|
384
|
+
payloadString(entry.payload, "fill") ??
|
|
385
|
+
payloadString(entry.payload, "stroke") ??
|
|
386
|
+
entry.color ??
|
|
387
|
+
"var(--color-muted-foreground)";
|
|
388
|
+
|
|
334
389
|
function ChartTooltip({
|
|
335
390
|
active,
|
|
336
391
|
payload,
|
|
@@ -338,6 +393,7 @@ function ChartTooltip({
|
|
|
338
393
|
valueFormatter,
|
|
339
394
|
labelFormatter,
|
|
340
395
|
nameKey,
|
|
396
|
+
colorKey,
|
|
341
397
|
}: ChartTooltipProps) {
|
|
342
398
|
if (!active || !payload?.length) return null;
|
|
343
399
|
|
|
@@ -354,7 +410,10 @@ function ChartTooltip({
|
|
|
354
410
|
key={`${entry.dataKey ?? entry.name ?? "value"}-${index}`}
|
|
355
411
|
className={s.tooltip_row}
|
|
356
412
|
>
|
|
357
|
-
<span
|
|
413
|
+
<span
|
|
414
|
+
className={s.tooltip_dot}
|
|
415
|
+
style={{ backgroundColor: resolveTooltipColor(entry, colorKey) }}
|
|
416
|
+
/>
|
|
358
417
|
<span>{String(rowName)}</span>
|
|
359
418
|
<span className={s.tooltip_value}>
|
|
360
419
|
{formatTooltipEntryValue(entry.value, valueFormatter, {
|
|
@@ -373,11 +432,11 @@ function ChartTooltip({
|
|
|
373
432
|
|
|
374
433
|
function ChartLegend({ items }: { items: LegendItem[] }) {
|
|
375
434
|
return (
|
|
376
|
-
<div className={s.legend}>
|
|
377
|
-
{items.map((item) => (
|
|
378
|
-
<div key={item.key} className={s.legend_item}>
|
|
435
|
+
<div className={s.legend} aria-label="Chart legend">
|
|
436
|
+
{items.map((item, index) => (
|
|
437
|
+
<div key={`${item.key}-${index}`} className={s.legend_item}>
|
|
379
438
|
<span className={s.legend_dot} style={{ backgroundColor: item.color }} />
|
|
380
|
-
<span>{item.label}</span>
|
|
439
|
+
<span title={item.label}>{item.label}</span>
|
|
381
440
|
</div>
|
|
382
441
|
))}
|
|
383
442
|
</div>
|
|
@@ -394,6 +453,7 @@ const renderCartesianFrame = ({
|
|
|
394
453
|
options,
|
|
395
454
|
axisConfig,
|
|
396
455
|
tooltipCursor,
|
|
456
|
+
tooltipColorKey,
|
|
397
457
|
orientation = "vertical",
|
|
398
458
|
}: {
|
|
399
459
|
categoryKey: string;
|
|
@@ -401,6 +461,7 @@ const renderCartesianFrame = ({
|
|
|
401
461
|
options: ResolvedCartesianOptions;
|
|
402
462
|
axisConfig: AxisRenderConfig;
|
|
403
463
|
tooltipCursor: TooltipCursor;
|
|
464
|
+
tooltipColorKey?: string;
|
|
404
465
|
orientation?: "vertical" | "horizontal";
|
|
405
466
|
}): ReactNode[] => {
|
|
406
467
|
const isHorizontal = orientation === "horizontal";
|
|
@@ -496,6 +557,7 @@ const renderCartesianFrame = ({
|
|
|
496
557
|
<ChartTooltip
|
|
497
558
|
valueFormatter={options.tooltipValueFormatter}
|
|
498
559
|
labelFormatter={options.tooltipLabelFormatter}
|
|
560
|
+
colorKey={tooltipColorKey}
|
|
499
561
|
/>
|
|
500
562
|
}
|
|
501
563
|
cursor={tooltipCursor}
|
|
@@ -579,14 +641,30 @@ function BarChartRenderer({
|
|
|
579
641
|
const topStackSeriesKeys = resolveStackTopSeriesKeys(preparedSeries);
|
|
580
642
|
const usesCategoryColorMap =
|
|
581
643
|
preparedSeries.length === 1 && !series?.length && Boolean(colors);
|
|
582
|
-
const legendItems =
|
|
644
|
+
const legendItems = usesCategoryColorMap
|
|
645
|
+
? toCategoryLegendItems({
|
|
646
|
+
data,
|
|
647
|
+
categoryKey,
|
|
648
|
+
colors,
|
|
649
|
+
fallbackColor: preparedSeries[0]?.color ?? palette.primary,
|
|
650
|
+
})
|
|
651
|
+
: toLegendItems(preparedSeries);
|
|
583
652
|
const isHorizontal = orientation === "horizontal";
|
|
653
|
+
const chartData: Array<ChartDatum & { __color?: string }> = usesCategoryColorMap
|
|
654
|
+
? data.map((entry) => {
|
|
655
|
+
const key = toLookupKey(entry[categoryKey]);
|
|
656
|
+
return {
|
|
657
|
+
...entry,
|
|
658
|
+
__color: resolveColorToken(colors?.[key], preparedSeries[0]?.color ?? palette.primary),
|
|
659
|
+
};
|
|
660
|
+
})
|
|
661
|
+
: data;
|
|
584
662
|
|
|
585
663
|
return (
|
|
586
664
|
<>
|
|
587
665
|
<ResponsiveContainer width="100%" height={height}>
|
|
588
666
|
<RechartsBarChart
|
|
589
|
-
data={
|
|
667
|
+
data={chartData}
|
|
590
668
|
layout={isHorizontal ? "vertical" : "horizontal"}
|
|
591
669
|
margin={CARTESIAN_MARGIN}
|
|
592
670
|
{...(barCategoryGap !== undefined ? { barCategoryGap } : {})}
|
|
@@ -597,6 +675,7 @@ function BarChartRenderer({
|
|
|
597
675
|
options,
|
|
598
676
|
axisConfig,
|
|
599
677
|
tooltipCursor: { fill: palette.cursor },
|
|
678
|
+
tooltipColorKey: usesCategoryColorMap ? "__color" : undefined,
|
|
600
679
|
orientation,
|
|
601
680
|
})}
|
|
602
681
|
|
|
@@ -612,9 +691,10 @@ function BarChartRenderer({
|
|
|
612
691
|
yAxisId={options.showYAxis ? item.yAxisId : undefined}
|
|
613
692
|
>
|
|
614
693
|
{usesCategoryColorMap &&
|
|
615
|
-
|
|
694
|
+
chartData.map((entry, index) => {
|
|
616
695
|
const key = toLookupKey(entry[categoryKey]);
|
|
617
|
-
const fill =
|
|
696
|
+
const fill =
|
|
697
|
+
typeof entry.__color === "string" ? entry.__color : item.color;
|
|
618
698
|
return <Cell key={`${item.key}-${key}-${index}`} fill={fill} />;
|
|
619
699
|
})}
|
|
620
700
|
</Bar>
|
|
@@ -967,29 +1047,28 @@ function DonutChartRenderer({
|
|
|
967
1047
|
}: DonutChartProps & { height: number }) {
|
|
968
1048
|
const palette = createChartPalette();
|
|
969
1049
|
|
|
970
|
-
const chartData = data.map((entry) =>
|
|
971
|
-
...entry,
|
|
972
|
-
__chartValue:
|
|
973
|
-
typeof entry[dataKey] === "number"
|
|
974
|
-
? (entry[dataKey] as number)
|
|
975
|
-
: Number(entry[dataKey] ?? 0) || 0,
|
|
976
|
-
}));
|
|
977
|
-
|
|
978
|
-
const legendItems: LegendItem[] = data.map((entry, index) => {
|
|
1050
|
+
const chartData = data.map((entry, index) => {
|
|
979
1051
|
const rawLabel = entry[nameKey];
|
|
980
1052
|
const label =
|
|
981
1053
|
rawLabel !== undefined && rawLabel !== null && tooltipLabelFormatter
|
|
982
1054
|
? tooltipLabelFormatter(rawLabel as string | number)
|
|
983
1055
|
: String(rawLabel ?? `Item ${index + 1}`);
|
|
984
1056
|
const lookup = toLookupKey(rawLabel);
|
|
1057
|
+
const color = resolveColorToken(colors?.[lookup], pickSeriesColor(palette, index));
|
|
985
1058
|
return {
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
1059
|
+
...entry,
|
|
1060
|
+
__chartValue: Math.max(0, toFiniteNumber(entry[dataKey])),
|
|
1061
|
+
__color: color,
|
|
1062
|
+
__label: label,
|
|
990
1063
|
};
|
|
991
1064
|
});
|
|
992
1065
|
|
|
1066
|
+
const legendItems: LegendItem[] = chartData.map((entry) => ({
|
|
1067
|
+
key: entry.__label,
|
|
1068
|
+
label: entry.__label,
|
|
1069
|
+
color: entry.__color,
|
|
1070
|
+
}));
|
|
1071
|
+
|
|
993
1072
|
const resolvedOuterRadius = Math.max(outerRadius ?? Math.max(height * 0.34, 88), 36);
|
|
994
1073
|
const resolvedInnerRadius = Math.max(
|
|
995
1074
|
16,
|
|
@@ -1024,6 +1103,8 @@ function DonutChartRenderer({
|
|
|
1024
1103
|
<ChartTooltip
|
|
1025
1104
|
valueFormatter={tooltipValueFormatter}
|
|
1026
1105
|
labelFormatter={tooltipLabelFormatter}
|
|
1106
|
+
nameKey="__label"
|
|
1107
|
+
colorKey="__color"
|
|
1027
1108
|
/>
|
|
1028
1109
|
}
|
|
1029
1110
|
/>
|
|
@@ -1065,9 +1146,7 @@ function RadialBarChartRenderer({
|
|
|
1065
1146
|
const lookup = toLookupKey(rawLabel);
|
|
1066
1147
|
const color = resolveColorToken(colors?.[lookup], pickSeriesColor(palette, index));
|
|
1067
1148
|
const value =
|
|
1068
|
-
|
|
1069
|
-
? (entry[dataKey] as number)
|
|
1070
|
-
: Number(entry[dataKey] ?? 0) || 0;
|
|
1149
|
+
Math.max(0, toFiniteNumber(entry[dataKey]));
|
|
1071
1150
|
return {
|
|
1072
1151
|
...entry,
|
|
1073
1152
|
__value: value,
|
|
@@ -1082,7 +1161,10 @@ function RadialBarChartRenderer({
|
|
|
1082
1161
|
color: item.__color,
|
|
1083
1162
|
}));
|
|
1084
1163
|
|
|
1085
|
-
const domainMax =
|
|
1164
|
+
const domainMax =
|
|
1165
|
+
typeof maxValue === "number" && Number.isFinite(maxValue) && maxValue > 0
|
|
1166
|
+
? maxValue
|
|
1167
|
+
: Math.max(...chartData.map((d) => d.__value), 1);
|
|
1086
1168
|
|
|
1087
1169
|
return (
|
|
1088
1170
|
<>
|
|
@@ -1111,7 +1193,8 @@ function RadialBarChartRenderer({
|
|
|
1111
1193
|
<ChartTooltip
|
|
1112
1194
|
valueFormatter={tooltipValueFormatter}
|
|
1113
1195
|
labelFormatter={tooltipLabelFormatter}
|
|
1114
|
-
nameKey=
|
|
1196
|
+
nameKey="__label"
|
|
1197
|
+
colorKey="__color"
|
|
1115
1198
|
/>
|
|
1116
1199
|
}
|
|
1117
1200
|
/>
|
|
@@ -1228,7 +1311,10 @@ const EMPTY_STATE_GRACE_MS = 150;
|
|
|
1228
1311
|
|
|
1229
1312
|
export function Chart(props: ChartProps) {
|
|
1230
1313
|
const { title, description, className, emptyMessage, isLoading = false } = props;
|
|
1231
|
-
const height =
|
|
1314
|
+
const height =
|
|
1315
|
+
typeof props.height === "number" && Number.isFinite(props.height)
|
|
1316
|
+
? Math.max(120, props.height)
|
|
1317
|
+
: DEFAULT_CHART_HEIGHT;
|
|
1232
1318
|
|
|
1233
1319
|
const isEmpty =
|
|
1234
1320
|
props.type === "scatter"
|
|
@@ -92,6 +92,7 @@
|
|
|
92
92
|
|
|
93
93
|
.dropzone_inner {
|
|
94
94
|
display: flex;
|
|
95
|
+
min-width: 0;
|
|
95
96
|
flex-direction: column;
|
|
96
97
|
align-items: center;
|
|
97
98
|
justify-content: center;
|
|
@@ -107,24 +108,30 @@
|
|
|
107
108
|
|
|
108
109
|
.dropzone_copy {
|
|
109
110
|
display: flex;
|
|
111
|
+
max-width: 100%;
|
|
112
|
+
min-width: 0;
|
|
110
113
|
flex-direction: column;
|
|
111
114
|
gap: var(--spacing-2);
|
|
112
115
|
align-items: center;
|
|
113
116
|
}
|
|
114
117
|
|
|
115
118
|
.dropzone_title {
|
|
119
|
+
max-width: 100%;
|
|
116
120
|
font-family: var(--font-family-1);
|
|
117
121
|
font-size: var(--font-size-sm);
|
|
118
122
|
color: var(--field-content-color, var(--color-foreground));
|
|
119
123
|
font-weight: 500;
|
|
120
124
|
line-height: 1.3;
|
|
125
|
+
overflow-wrap: anywhere;
|
|
121
126
|
}
|
|
122
127
|
|
|
123
128
|
.dropzone_hint {
|
|
129
|
+
max-width: 100%;
|
|
124
130
|
font-family: var(--font-family-1);
|
|
125
131
|
font-size: var(--font-size-xs);
|
|
126
132
|
color: var(--field-placeholder-color, var(--color-muted-foreground));
|
|
127
133
|
line-height: 1.4;
|
|
134
|
+
overflow-wrap: anywhere;
|
|
128
135
|
}
|
|
129
136
|
|
|
130
137
|
/* ── Preview list ─────────────────────────────────────────────────────── */
|
|
@@ -272,18 +279,23 @@
|
|
|
272
279
|
|
|
273
280
|
.preview_sub {
|
|
274
281
|
display: inline-flex;
|
|
282
|
+
min-width: 0;
|
|
275
283
|
align-items: center;
|
|
276
284
|
gap: var(--spacing-1);
|
|
277
285
|
font-family: var(--font-family-1);
|
|
278
286
|
font-size: var(--font-size-xs);
|
|
279
287
|
color: var(--color-muted-foreground);
|
|
280
288
|
line-height: 1.3;
|
|
289
|
+
overflow: hidden;
|
|
290
|
+
text-overflow: ellipsis;
|
|
291
|
+
white-space: nowrap;
|
|
281
292
|
}
|
|
282
293
|
|
|
283
294
|
.preview_error {
|
|
284
295
|
font-family: var(--font-family-1);
|
|
285
296
|
font-size: var(--font-size-xs);
|
|
286
297
|
color: var(--color-error-foreground);
|
|
298
|
+
overflow-wrap: anywhere;
|
|
287
299
|
}
|
|
288
300
|
|
|
289
301
|
/* Position only - visuals come from the Button component (variant + size + box layout). */
|
|
@@ -109,15 +109,12 @@ export const MediaInput = (props: MediaInputProps) => {
|
|
|
109
109
|
const resolvedTitle =
|
|
110
110
|
dropzoneTitle ??
|
|
111
111
|
(multiple ? "Drop files here or browse" : "Drop a file here or browse");
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
]
|
|
119
|
-
.filter(Boolean)
|
|
120
|
-
.join(" • ");
|
|
112
|
+
const hintParts = [
|
|
113
|
+
accept ? `Accepts ${accept}` : null,
|
|
114
|
+
maxSize ? `Up to ${formatBytes(maxSize)} per file` : null,
|
|
115
|
+
multiple && maxFiles ? `Max ${maxFiles} files` : null,
|
|
116
|
+
].filter(Boolean);
|
|
117
|
+
const resolvedHint = dropzoneHint ?? hintParts.join(" - ");
|
|
121
118
|
|
|
122
119
|
const inputProps = getInputProps({
|
|
123
120
|
id: inputId,
|
|
@@ -210,6 +207,10 @@ export const MediaInput = (props: MediaInputProps) => {
|
|
|
210
207
|
"aria-labelledby": label ? labelId : undefined,
|
|
211
208
|
"aria-describedby": resolvedHint ? hintId : undefined,
|
|
212
209
|
onClick: dropzoneSelectable ? undefined : (e) => e.preventDefault(),
|
|
210
|
+
onDragEnter: dropzoneSelectable ? undefined : (e) => e.preventDefault(),
|
|
211
|
+
onDragLeave: dropzoneSelectable ? undefined : (e) => e.preventDefault(),
|
|
212
|
+
onDragOver: dropzoneSelectable ? undefined : (e) => e.preventDefault(),
|
|
213
|
+
onDrop: dropzoneSelectable ? undefined : (e) => e.preventDefault(),
|
|
213
214
|
});
|
|
214
215
|
|
|
215
216
|
return (
|
|
@@ -96,6 +96,12 @@ export function useMediaUpload({
|
|
|
96
96
|
filesRef.current = isControlled ? value! : internal;
|
|
97
97
|
}, [isControlled, value, internal]);
|
|
98
98
|
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
if (!disabled && !readOnly) return;
|
|
101
|
+
dragCounter.current = 0;
|
|
102
|
+
setIsDragging(false);
|
|
103
|
+
}, [disabled, readOnly]);
|
|
104
|
+
|
|
99
105
|
// Revoke any preview URLs the hook created on unmount so blob memory is
|
|
100
106
|
// freed even if the consumer forgets to clear state.
|
|
101
107
|
useEffect(
|
|
@@ -130,8 +136,11 @@ export function useMediaUpload({
|
|
|
130
136
|
existing: baseExisting,
|
|
131
137
|
});
|
|
132
138
|
|
|
133
|
-
// Single-mode replacement frees the previous preview blob immediately
|
|
134
|
-
|
|
139
|
+
// Single-mode replacement frees the previous preview blob immediately,
|
|
140
|
+
// but only after a valid replacement exists.
|
|
141
|
+
if (!multiple && accepted.length > 0 && existing.length > 0) {
|
|
142
|
+
existing.forEach(revokePreviewUrl);
|
|
143
|
+
}
|
|
135
144
|
|
|
136
145
|
const next = multiple ? [...existing, ...accepted] : accepted;
|
|
137
146
|
if (accepted.length > 0) {
|
|
@@ -162,6 +171,7 @@ export function useMediaUpload({
|
|
|
162
171
|
revokePreviewUrl(target);
|
|
163
172
|
const next = filesRef.current.filter((f) => f.id !== id);
|
|
164
173
|
commit(next);
|
|
174
|
+
setRejections([]);
|
|
165
175
|
onRemove?.(target);
|
|
166
176
|
},
|
|
167
177
|
[commit, disabled, onRemove, readOnly],
|
|
@@ -190,9 +200,9 @@ export function useMediaUpload({
|
|
|
190
200
|
|
|
191
201
|
const handleDragEnter = useCallback(
|
|
192
202
|
(e: DragEvent) => {
|
|
193
|
-
if (disabled || readOnly) return;
|
|
194
203
|
e.preventDefault();
|
|
195
204
|
e.stopPropagation();
|
|
205
|
+
if (disabled || readOnly) return;
|
|
196
206
|
dragCounter.current += 1;
|
|
197
207
|
if (e.dataTransfer?.types?.includes("Files")) setIsDragging(true);
|
|
198
208
|
},
|
|
@@ -208,9 +218,9 @@ export function useMediaUpload({
|
|
|
208
218
|
|
|
209
219
|
const handleDragOver = useCallback(
|
|
210
220
|
(e: DragEvent) => {
|
|
211
|
-
if (disabled || readOnly) return;
|
|
212
221
|
e.preventDefault();
|
|
213
222
|
e.stopPropagation();
|
|
223
|
+
if (disabled || readOnly) return;
|
|
214
224
|
if (e.dataTransfer) e.dataTransfer.dropEffect = "copy";
|
|
215
225
|
},
|
|
216
226
|
[disabled, readOnly],
|
|
@@ -218,11 +228,11 @@ export function useMediaUpload({
|
|
|
218
228
|
|
|
219
229
|
const handleDrop = useCallback(
|
|
220
230
|
(e: DragEvent) => {
|
|
221
|
-
if (disabled || readOnly) return;
|
|
222
231
|
e.preventDefault();
|
|
223
232
|
e.stopPropagation();
|
|
224
233
|
dragCounter.current = 0;
|
|
225
234
|
setIsDragging(false);
|
|
235
|
+
if (disabled || readOnly) return;
|
|
226
236
|
add(e.dataTransfer?.files);
|
|
227
237
|
},
|
|
228
238
|
[add, disabled, readOnly],
|
|
@@ -247,18 +257,22 @@ export function useMediaUpload({
|
|
|
247
257
|
},
|
|
248
258
|
onDragEnter: (e) => {
|
|
249
259
|
overrides.onDragEnter?.(e);
|
|
260
|
+
if (e.defaultPrevented) return;
|
|
250
261
|
handleDragEnter(e);
|
|
251
262
|
},
|
|
252
263
|
onDragLeave: (e) => {
|
|
253
264
|
overrides.onDragLeave?.(e);
|
|
265
|
+
if (e.defaultPrevented) return;
|
|
254
266
|
handleDragLeave(e);
|
|
255
267
|
},
|
|
256
268
|
onDragOver: (e) => {
|
|
257
269
|
overrides.onDragOver?.(e);
|
|
270
|
+
if (e.defaultPrevented) return;
|
|
258
271
|
handleDragOver(e);
|
|
259
272
|
},
|
|
260
273
|
onDrop: (e) => {
|
|
261
274
|
overrides.onDrop?.(e);
|
|
275
|
+
if (e.defaultPrevented) return;
|
|
262
276
|
handleDrop(e);
|
|
263
277
|
},
|
|
264
278
|
}),
|