sid-ui 0.1.3 → 0.2.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.
- package/package.json +1 -1
- package/src/components/BarChart.tsx +3 -3
- package/src/components/CardEffects.tsx +199 -0
- package/src/components/Chip.tsx +250 -0
- package/src/components/CodeInput.tsx +83 -1
- package/src/components/DonutChart.tsx +32 -15
- package/src/components/Glyph.tsx +10 -0
- package/src/components/Notification.tsx +115 -34
- package/src/components/NotificationTrigger.tsx +71 -9
- package/src/components/RadarChart.tsx +220 -0
- package/src/components/RadialBarChart.tsx +147 -0
- package/src/components/Rating.tsx +108 -19
- package/src/components/ScatterChart.tsx +267 -0
- package/src/components/StatusIndicator.tsx +52 -0
- package/src/components/Treemap.tsx +167 -0
- package/src/components/TrendChart.tsx +55 -15
- package/src/components/chartMath.ts +151 -0
- package/src/components/index.ts +41 -3
- package/src/styles/components/card-effects.css +304 -0
- package/src/styles/components/charts.css +407 -7
- package/src/styles/components/chip.css +343 -0
- package/src/styles/components/code-input.css +137 -84
- package/src/styles/components/notification.css +236 -41
- package/src/styles/components/status.css +117 -0
- package/src/styles/components.css +99 -14
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useRef, useState, type CSSProperties, type KeyboardEvent } from "react";
|
|
4
|
+
|
|
5
|
+
import { segmentShares, squarify } from "./chartMath";
|
|
6
|
+
import {
|
|
7
|
+
ChartLegend,
|
|
8
|
+
ChartTable,
|
|
9
|
+
ChartTip,
|
|
10
|
+
defaultFormat,
|
|
11
|
+
toneAt,
|
|
12
|
+
type ChartTone,
|
|
13
|
+
} from "./chartParts";
|
|
14
|
+
import { useElementWidth } from "./Responsive";
|
|
15
|
+
|
|
16
|
+
export type TreemapDatum = {
|
|
17
|
+
label: string;
|
|
18
|
+
value: number;
|
|
19
|
+
/** Tiles in the same group share a tone and a legend entry. */
|
|
20
|
+
group?: string;
|
|
21
|
+
tone?: ChartTone;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const DEFAULT_HEIGHT = 320;
|
|
25
|
+
/** The 2px surface gap between tiles. */
|
|
26
|
+
const GAP = 2;
|
|
27
|
+
/** A tile needs this much room before it prints its name. */
|
|
28
|
+
const LABEL_MIN = { width: 64, height: 40 };
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Part of a whole for many items at once — budget lines, storage by folder. Each tile's area is
|
|
32
|
+
* its share, laid out squarified so tiles stay readable. Group tiles to color them by category.
|
|
33
|
+
*/
|
|
34
|
+
export function Treemap({
|
|
35
|
+
data,
|
|
36
|
+
label,
|
|
37
|
+
height = DEFAULT_HEIGHT,
|
|
38
|
+
formatValue = defaultFormat,
|
|
39
|
+
}: {
|
|
40
|
+
data: TreemapDatum[];
|
|
41
|
+
/** Accessible name for the chart. */
|
|
42
|
+
label: string;
|
|
43
|
+
height?: number;
|
|
44
|
+
formatValue?: (value: number) => string;
|
|
45
|
+
}) {
|
|
46
|
+
const frame = useRef<HTMLDivElement>(null);
|
|
47
|
+
const width = useElementWidth(frame);
|
|
48
|
+
const [active, setActive] = useState<number | null>(null);
|
|
49
|
+
|
|
50
|
+
const sorted = [...data].sort((a, b) => b.value - a.value);
|
|
51
|
+
const groups = [...new Set(sorted.map((d) => d.group ?? d.label))];
|
|
52
|
+
const grouped = sorted.some((d) => d.group);
|
|
53
|
+
const toneOf = (d: TreemapDatum) =>
|
|
54
|
+
d.tone ?? toneAt(groups.indexOf(d.group ?? d.label) % 4, undefined);
|
|
55
|
+
const tiles = squarify(
|
|
56
|
+
sorted.map((d) => d.value),
|
|
57
|
+
{ x: 0, y: 0, width, height },
|
|
58
|
+
);
|
|
59
|
+
const shares = segmentShares(sorted.map((d) => d.value));
|
|
60
|
+
const largest = Math.max(1, ...sorted.map((d) => d.value));
|
|
61
|
+
|
|
62
|
+
const onKey = (event: KeyboardEvent<HTMLDivElement>) => {
|
|
63
|
+
if (sorted.length === 0) return;
|
|
64
|
+
if (event.key === "ArrowRight" || event.key === "ArrowLeft") {
|
|
65
|
+
event.preventDefault();
|
|
66
|
+
const step = event.key === "ArrowRight" ? 1 : -1;
|
|
67
|
+
setActive((current) => ((current ?? -step) + step + sorted.length) % sorted.length);
|
|
68
|
+
} else if (event.key === "Escape") {
|
|
69
|
+
setActive(null);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const focusTile = active === null ? null : tiles[active];
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<div className="os-chart">
|
|
77
|
+
{grouped ? (
|
|
78
|
+
<ChartLegend
|
|
79
|
+
items={groups.map((g) => ({
|
|
80
|
+
label: g,
|
|
81
|
+
tone: toneOf(sorted.find((d) => (d.group ?? d.label) === g) ?? sorted[0]),
|
|
82
|
+
}))}
|
|
83
|
+
/>
|
|
84
|
+
) : null}
|
|
85
|
+
<div
|
|
86
|
+
ref={frame}
|
|
87
|
+
className="os-chart-frame os-treemap"
|
|
88
|
+
style={{ height }}
|
|
89
|
+
tabIndex={0}
|
|
90
|
+
role="group"
|
|
91
|
+
aria-label={`${label}. Use the arrow keys to read each tile.`}
|
|
92
|
+
onKeyDown={onKey}
|
|
93
|
+
onBlur={() => setActive(null)}
|
|
94
|
+
onPointerLeave={() => setActive(null)}
|
|
95
|
+
>
|
|
96
|
+
{width > 0
|
|
97
|
+
? sorted.map((d, index) => {
|
|
98
|
+
const tile = tiles[index];
|
|
99
|
+
if (tile.width <= 0 || tile.height <= 0) return null;
|
|
100
|
+
const roomy = tile.width >= LABEL_MIN.width && tile.height >= LABEL_MIN.height;
|
|
101
|
+
// Larger tiles in a group read stronger.
|
|
102
|
+
const strength = 62 + Math.round((d.value / largest) * 38);
|
|
103
|
+
return (
|
|
104
|
+
<div
|
|
105
|
+
key={`${d.label}-${index}`}
|
|
106
|
+
className="os-treemap-tile"
|
|
107
|
+
data-tone={toneOf(d)}
|
|
108
|
+
data-active={active === index || undefined}
|
|
109
|
+
data-dim={active !== null && active !== index ? true : undefined}
|
|
110
|
+
style={
|
|
111
|
+
{
|
|
112
|
+
left: tile.x + GAP / 2,
|
|
113
|
+
top: tile.y + GAP / 2,
|
|
114
|
+
width: Math.max(0, tile.width - GAP),
|
|
115
|
+
height: Math.max(0, tile.height - GAP),
|
|
116
|
+
"--os-treemap-strength": `${strength}%`,
|
|
117
|
+
animationDelay: `${Math.min(500, index * 35)}ms`,
|
|
118
|
+
} as CSSProperties
|
|
119
|
+
}
|
|
120
|
+
onPointerEnter={() => setActive(index)}
|
|
121
|
+
aria-hidden
|
|
122
|
+
>
|
|
123
|
+
{roomy ? (
|
|
124
|
+
<>
|
|
125
|
+
<span className="os-treemap-name">{d.label}</span>
|
|
126
|
+
<span className="os-treemap-value">{formatValue(d.value)}</span>
|
|
127
|
+
</>
|
|
128
|
+
) : null}
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
})
|
|
132
|
+
: null}
|
|
133
|
+
{active !== null && focusTile ? (
|
|
134
|
+
<ChartTip
|
|
135
|
+
title={sorted[active].group ?? undefined}
|
|
136
|
+
left={focusTile.x + focusTile.width / 2}
|
|
137
|
+
top={Math.max(0, focusTile.y + 8)}
|
|
138
|
+
align={
|
|
139
|
+
focusTile.x + focusTile.width / 2 < width / 4
|
|
140
|
+
? "start"
|
|
141
|
+
: focusTile.x + focusTile.width / 2 > (width * 3) / 4
|
|
142
|
+
? "end"
|
|
143
|
+
: "center"
|
|
144
|
+
}
|
|
145
|
+
rows={[
|
|
146
|
+
{
|
|
147
|
+
label: `${sorted[active].label} · ${Math.round(shares[active])}%`,
|
|
148
|
+
value: formatValue(sorted[active].value),
|
|
149
|
+
tone: toneOf(sorted[active]),
|
|
150
|
+
},
|
|
151
|
+
]}
|
|
152
|
+
/>
|
|
153
|
+
) : null}
|
|
154
|
+
</div>
|
|
155
|
+
<ChartTable
|
|
156
|
+
caption={label}
|
|
157
|
+
headers={["", ...(grouped ? ["Group"] : []), "Value", "Share"]}
|
|
158
|
+
rows={sorted.map((d, index) => [
|
|
159
|
+
d.label,
|
|
160
|
+
...(grouped ? [d.group ?? ""] : []),
|
|
161
|
+
formatValue(d.value),
|
|
162
|
+
`${Math.round(shares[index])}%`,
|
|
163
|
+
])}
|
|
164
|
+
/>
|
|
165
|
+
</div>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useRef, useState, type KeyboardEvent, type PointerEvent } from "react";
|
|
3
|
+
import { useId, useRef, useState, type KeyboardEvent, type PointerEvent } from "react";
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
areaPath,
|
|
7
|
+
linePath,
|
|
8
|
+
linearScale,
|
|
9
|
+
nearestIndex,
|
|
10
|
+
niceTicks,
|
|
11
|
+
smoothAreaPath,
|
|
12
|
+
smoothPath,
|
|
13
|
+
} from "./chartMath";
|
|
6
14
|
import {
|
|
7
15
|
ChartLegend,
|
|
8
16
|
ChartTable,
|
|
@@ -27,8 +35,9 @@ const X_LABEL_ROOM = 64;
|
|
|
27
35
|
const TIP_GAP = 12;
|
|
28
36
|
|
|
29
37
|
/**
|
|
30
|
-
* Change over time for up to four series on one axis. Lines are 2px
|
|
31
|
-
*
|
|
38
|
+
* Change over time for up to four series on one axis. Lines are 2px — smooth by default —
|
|
39
|
+
* with a gradient wash in `area`, and draw themselves in on first paint. A crosshair snaps to
|
|
40
|
+
* the nearest point and reads out every series at once.
|
|
32
41
|
* Arrow keys move the readout for keyboard users; a hidden table carries every value.
|
|
33
42
|
*/
|
|
34
43
|
export function TrendChart({
|
|
@@ -36,6 +45,7 @@ export function TrendChart({
|
|
|
36
45
|
series,
|
|
37
46
|
label,
|
|
38
47
|
variant = "area",
|
|
48
|
+
curve = "smooth",
|
|
39
49
|
height = DEFAULT_HEIGHT,
|
|
40
50
|
formatValue = defaultFormat,
|
|
41
51
|
hasLegend,
|
|
@@ -46,6 +56,8 @@ export function TrendChart({
|
|
|
46
56
|
/** Accessible name for the chart. */
|
|
47
57
|
label: string;
|
|
48
58
|
variant?: "area" | "line";
|
|
59
|
+
/** smooth bends through each point without overshooting it; linear joins them straight. */
|
|
60
|
+
curve?: "smooth" | "linear";
|
|
49
61
|
height?: number;
|
|
50
62
|
formatValue?: (value: number) => string;
|
|
51
63
|
/** Defaults to on for two or more series; one series is named by its card title. */
|
|
@@ -53,6 +65,10 @@ export function TrendChart({
|
|
|
53
65
|
}) {
|
|
54
66
|
const frame = useRef<HTMLDivElement>(null);
|
|
55
67
|
const width = useElementWidth(frame);
|
|
68
|
+
// useId can contain colons, which url(#…) references do not tolerate.
|
|
69
|
+
const gradientId = `os-trend-${useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
|
|
70
|
+
const line = curve === "smooth" ? smoothPath : linePath;
|
|
71
|
+
const area = curve === "smooth" ? smoothAreaPath : areaPath;
|
|
56
72
|
const [active, setActive] = useState<number | null>(null);
|
|
57
73
|
|
|
58
74
|
const plotW = Math.max(0, width - MARGIN.left - MARGIN.right);
|
|
@@ -141,14 +157,32 @@ export function TrendChart({
|
|
|
141
157
|
</text>
|
|
142
158
|
) : null,
|
|
143
159
|
)}
|
|
144
|
-
{toned.map((item) => {
|
|
160
|
+
{toned.map((item, seriesIndex) => {
|
|
145
161
|
const points = item.values.map((value, index) => ({ x: xs[index], y: y(value) }));
|
|
162
|
+
const fill = `${gradientId}-${seriesIndex}`;
|
|
146
163
|
return (
|
|
147
164
|
<g key={item.label} data-tone={item.tone}>
|
|
148
165
|
{variant === "area" ? (
|
|
149
|
-
|
|
166
|
+
<>
|
|
167
|
+
<defs>
|
|
168
|
+
<linearGradient id={fill} x1="0" x2="0" y1="0" y2="1">
|
|
169
|
+
<stop className="os-chart-stop-top" offset="0%" />
|
|
170
|
+
<stop className="os-chart-stop-bottom" offset="100%" />
|
|
171
|
+
</linearGradient>
|
|
172
|
+
</defs>
|
|
173
|
+
<path
|
|
174
|
+
className="os-chart-area"
|
|
175
|
+
d={area(points, y(0))}
|
|
176
|
+
fill={`url(#${fill})`}
|
|
177
|
+
/>
|
|
178
|
+
</>
|
|
150
179
|
) : null}
|
|
151
|
-
<path
|
|
180
|
+
<path
|
|
181
|
+
className="os-chart-line"
|
|
182
|
+
d={line(points)}
|
|
183
|
+
pathLength={1}
|
|
184
|
+
style={{ animationDelay: `${seriesIndex * 120}ms` }}
|
|
185
|
+
/>
|
|
152
186
|
</g>
|
|
153
187
|
);
|
|
154
188
|
})}
|
|
@@ -162,14 +196,20 @@ export function TrendChart({
|
|
|
162
196
|
y2={MARGIN.top + plotH}
|
|
163
197
|
/>
|
|
164
198
|
{toned.map((item) => (
|
|
165
|
-
<
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
199
|
+
<g key={item.label} data-tone={item.tone}>
|
|
200
|
+
<circle
|
|
201
|
+
className="os-chart-halo"
|
|
202
|
+
cx={activeX}
|
|
203
|
+
cy={y(item.values[active] ?? 0)}
|
|
204
|
+
r={9}
|
|
205
|
+
/>
|
|
206
|
+
<circle
|
|
207
|
+
className="os-chart-dot"
|
|
208
|
+
cx={activeX}
|
|
209
|
+
cy={y(item.values[active] ?? 0)}
|
|
210
|
+
r={4.5}
|
|
211
|
+
/>
|
|
212
|
+
</g>
|
|
173
213
|
))}
|
|
174
214
|
</g>
|
|
175
215
|
) : null}
|
|
@@ -123,3 +123,154 @@ export function compactNumber(value: number): string {
|
|
|
123
123
|
value,
|
|
124
124
|
);
|
|
125
125
|
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* A smooth path through the points that never overshoots them — monotone cubic
|
|
129
|
+
* interpolation (Fritsch–Carlson), the same curve as D3's curveMonotoneX. Points must be
|
|
130
|
+
* sorted by x.
|
|
131
|
+
*/
|
|
132
|
+
export function smoothPath(points: Point[]): string {
|
|
133
|
+
const n = points.length;
|
|
134
|
+
if (n < 3) return linePath(points);
|
|
135
|
+
const dx: number[] = [];
|
|
136
|
+
const slope: number[] = [];
|
|
137
|
+
for (let i = 0; i < n - 1; i++) {
|
|
138
|
+
dx.push(points[i + 1].x - points[i].x);
|
|
139
|
+
slope.push(dx[i] === 0 ? 0 : (points[i + 1].y - points[i].y) / dx[i]);
|
|
140
|
+
}
|
|
141
|
+
const tangent: number[] = [slope[0]];
|
|
142
|
+
for (let i = 1; i < n - 1; i++) {
|
|
143
|
+
tangent.push(slope[i - 1] * slope[i] <= 0 ? 0 : (slope[i - 1] + slope[i]) / 2);
|
|
144
|
+
}
|
|
145
|
+
tangent.push(slope[n - 2]);
|
|
146
|
+
// Fritsch–Carlson: cap the tangents so no segment bulges past its endpoints.
|
|
147
|
+
for (let i = 0; i < n - 1; i++) {
|
|
148
|
+
if (slope[i] === 0) {
|
|
149
|
+
tangent[i] = 0;
|
|
150
|
+
tangent[i + 1] = 0;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const a = tangent[i] / slope[i];
|
|
154
|
+
const b = tangent[i + 1] / slope[i];
|
|
155
|
+
const h = a * a + b * b;
|
|
156
|
+
if (h > 9) {
|
|
157
|
+
const t = 3 / Math.sqrt(h);
|
|
158
|
+
tangent[i] = t * a * slope[i];
|
|
159
|
+
tangent[i + 1] = t * b * slope[i];
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
let d = `M${fmt(points[0].x)},${fmt(points[0].y)}`;
|
|
163
|
+
for (let i = 0; i < n - 1; i++) {
|
|
164
|
+
const p0 = points[i];
|
|
165
|
+
const p1 = points[i + 1];
|
|
166
|
+
const third = dx[i] / 3;
|
|
167
|
+
d +=
|
|
168
|
+
`C${fmt(p0.x + third)},${fmt(p0.y + tangent[i] * third)} ` +
|
|
169
|
+
`${fmt(p1.x - third)},${fmt(p1.y - tangent[i + 1] * third)} ` +
|
|
170
|
+
`${fmt(p1.x)},${fmt(p1.y)}`;
|
|
171
|
+
}
|
|
172
|
+
return d;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** The smooth line closed down to a baseline. */
|
|
176
|
+
export function smoothAreaPath(points: Point[], baseline: number): string {
|
|
177
|
+
if (points.length === 0) return "";
|
|
178
|
+
const first = points[0];
|
|
179
|
+
const last = points[points.length - 1];
|
|
180
|
+
return `${smoothPath(points)}L${fmt(last.x)},${fmt(baseline)}L${fmt(first.x)},${fmt(baseline)}Z`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Clean ticks spanning [min, max] for axes that need not start at zero, such as a scatter
|
|
185
|
+
* plot's: 20, 40, 60, 80 rather than 17.3 … 83.9.
|
|
186
|
+
*/
|
|
187
|
+
export function niceDomain(min: number, max: number, count = 5): number[] {
|
|
188
|
+
if (!Number.isFinite(min) || !Number.isFinite(max)) return [0, 1];
|
|
189
|
+
if (min === max) {
|
|
190
|
+
const pad = min === 0 ? 1 : Math.abs(min) * 0.1;
|
|
191
|
+
return niceDomain(min - pad, max + pad, count);
|
|
192
|
+
}
|
|
193
|
+
const raw = (max - min) / Math.max(1, count);
|
|
194
|
+
const magnitude = 10 ** Math.floor(Math.log10(raw));
|
|
195
|
+
const step = (NICE_STEPS.find((nice) => nice * magnitude >= raw) ?? 10) * magnitude;
|
|
196
|
+
const start = Math.floor(min / step) * step;
|
|
197
|
+
const end = Math.ceil(max / step) * step;
|
|
198
|
+
const ticks: number[] = [];
|
|
199
|
+
for (let tick = start; tick <= end + step / 2; tick += step) ticks.push(round(tick));
|
|
200
|
+
return ticks;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** The point at `fraction` of a turn (0 at twelve o'clock, clockwise) on a circle. */
|
|
204
|
+
export function polarPoint(cx: number, cy: number, radius: number, fraction: number): Point {
|
|
205
|
+
const angle = fraction * 2 * Math.PI - Math.PI / 2;
|
|
206
|
+
return { x: cx + radius * Math.cos(angle), y: cy + radius * Math.sin(angle) };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** A closed polygon through the points, for radar shapes and grids. */
|
|
210
|
+
export function polygonPath(points: Point[]): string {
|
|
211
|
+
return points.length ? `${linePath(points)}Z` : "";
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export type Rect = { x: number; y: number; width: number; height: number };
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Squarified treemap layout (Bruls, Huizing, van Wijk): tiles each value into `rect` with
|
|
218
|
+
* area proportional to it, keeping tiles as close to square as it can. Returns one rect per
|
|
219
|
+
* value, in input order; values should be sorted largest first for the best shapes.
|
|
220
|
+
*/
|
|
221
|
+
export function squarify(values: number[], rect: Rect): Rect[] {
|
|
222
|
+
const out: Rect[] = values.map(() => ({ x: rect.x, y: rect.y, width: 0, height: 0 }));
|
|
223
|
+
const total = values.reduce((sum, v) => sum + Math.max(0, v), 0);
|
|
224
|
+
if (total <= 0 || rect.width <= 0 || rect.height <= 0) return out;
|
|
225
|
+
const scale = (rect.width * rect.height) / total;
|
|
226
|
+
const items = values
|
|
227
|
+
.map((value, index) => ({ index, area: Math.max(0, value) * scale }))
|
|
228
|
+
.filter((item) => item.area > 0);
|
|
229
|
+
|
|
230
|
+
let free = { ...rect };
|
|
231
|
+
let row: typeof items = [];
|
|
232
|
+
|
|
233
|
+
const worst = (areas: number[], side: number) => {
|
|
234
|
+
const sum = areas.reduce((a, b) => a + b, 0);
|
|
235
|
+
const max = Math.max(...areas);
|
|
236
|
+
const min = Math.min(...areas);
|
|
237
|
+
return Math.max((side * side * max) / (sum * sum), (sum * sum) / (side * side * min));
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const layRow = (cells: typeof items) => {
|
|
241
|
+
const sum = cells.reduce((a, c) => a + c.area, 0);
|
|
242
|
+
const horizontal = free.width >= free.height;
|
|
243
|
+
if (horizontal) {
|
|
244
|
+
const width = sum / free.height;
|
|
245
|
+
let y = free.y;
|
|
246
|
+
for (const cell of cells) {
|
|
247
|
+
const height = cell.area / width;
|
|
248
|
+
out[cell.index] = { x: free.x, y, width, height };
|
|
249
|
+
y += height;
|
|
250
|
+
}
|
|
251
|
+
free = { x: free.x + width, y: free.y, width: free.width - width, height: free.height };
|
|
252
|
+
} else {
|
|
253
|
+
const height = sum / free.width;
|
|
254
|
+
let x = free.x;
|
|
255
|
+
for (const cell of cells) {
|
|
256
|
+
const width = cell.area / height;
|
|
257
|
+
out[cell.index] = { x, y: free.y, width, height };
|
|
258
|
+
x += width;
|
|
259
|
+
}
|
|
260
|
+
free = { x: free.x, y: free.y + height, width: free.width, height: free.height - height };
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
for (const item of items) {
|
|
265
|
+
const side = Math.min(free.width, free.height);
|
|
266
|
+
const current = row.map((c) => c.area);
|
|
267
|
+
if (row.length === 0 || worst([...current, item.area], side) <= worst(current, side)) {
|
|
268
|
+
row.push(item);
|
|
269
|
+
} else {
|
|
270
|
+
layRow(row);
|
|
271
|
+
row = [item];
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (row.length) layRow(row);
|
|
275
|
+
return out;
|
|
276
|
+
}
|
package/src/components/index.ts
CHANGED
|
@@ -122,12 +122,42 @@ export { useFileDrop, formatBytes, matchesAccept } from "./useFileDrop";
|
|
|
122
122
|
export type { UseFileDropOptions, FileDropProps } from "./useFileDrop";
|
|
123
123
|
|
|
124
124
|
export { Rating, RATING_MAX } from "./Rating";
|
|
125
|
-
export type { RatingProps } from "./Rating";
|
|
125
|
+
export type { RatingProps, RatingIcon } from "./Rating";
|
|
126
126
|
|
|
127
|
-
export {
|
|
128
|
-
export type {
|
|
127
|
+
export { MotionCard, FlipCard, ExpandableCard } from "./CardEffects";
|
|
128
|
+
export type {
|
|
129
|
+
MotionCardProps,
|
|
130
|
+
MotionCardEffect,
|
|
131
|
+
FlipCardProps,
|
|
132
|
+
ExpandableCardProps,
|
|
133
|
+
} from "./CardEffects";
|
|
134
|
+
|
|
135
|
+
export { Chip, ChipGroup } from "./Chip";
|
|
136
|
+
export type { ChipProps, ChipGroupProps, ChipColor, ChipVariant, ChipSize } from "./Chip";
|
|
137
|
+
|
|
138
|
+
export { StatusIndicator } from "./StatusIndicator";
|
|
139
|
+
export type {
|
|
140
|
+
StatusIndicatorProps,
|
|
141
|
+
StatusIndicatorSize,
|
|
142
|
+
StatusIndicatorAppearance,
|
|
143
|
+
} from "./StatusIndicator";
|
|
144
|
+
|
|
145
|
+
export {
|
|
146
|
+
Notification,
|
|
147
|
+
NotificationGroup,
|
|
148
|
+
NotificationIcon,
|
|
149
|
+
NotificationList,
|
|
150
|
+
} from "./Notification";
|
|
151
|
+
export type {
|
|
152
|
+
NotificationProps,
|
|
153
|
+
NotificationGroupProps,
|
|
154
|
+
NotificationIconProps,
|
|
155
|
+
NotificationListProps,
|
|
156
|
+
NotificationTone,
|
|
157
|
+
} from "./Notification";
|
|
129
158
|
export { NotificationViewport, useNotification } from "./NotificationTrigger";
|
|
130
159
|
export type {
|
|
160
|
+
NotificationAction,
|
|
131
161
|
NotificationPosition,
|
|
132
162
|
ShowNotificationOptions,
|
|
133
163
|
NotificationDismiss,
|
|
@@ -179,6 +209,14 @@ export { BarChart } from "./BarChart";
|
|
|
179
209
|
export type { BarDatum } from "./BarChart";
|
|
180
210
|
export { DonutChart } from "./DonutChart";
|
|
181
211
|
export type { DonutSlice } from "./DonutChart";
|
|
212
|
+
export { RadarChart } from "./RadarChart";
|
|
213
|
+
export type { RadarSeries } from "./RadarChart";
|
|
214
|
+
export { ScatterChart } from "./ScatterChart";
|
|
215
|
+
export type { ScatterPoint, ScatterSeries } from "./ScatterChart";
|
|
216
|
+
export { RadialBarChart } from "./RadialBarChart";
|
|
217
|
+
export type { RadialBarDatum } from "./RadialBarChart";
|
|
218
|
+
export { Treemap } from "./Treemap";
|
|
219
|
+
export type { TreemapDatum } from "./Treemap";
|
|
182
220
|
export { Sparkline } from "./Sparkline";
|
|
183
221
|
export { FunnelChart } from "./FunnelChart";
|
|
184
222
|
export type { FunnelStage } from "./FunnelChart";
|