@usetheo/ui 0.18.0 → 0.19.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.
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { linScale, niceMax, seriesPath, TrendChart, type TrendChartProps, type TrendPoint, type TrendSeries, } from "./trend-chart.js";
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { HTMLAttributes } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* TrendChart — multi-series time-trend chart in pure SVG (no chart lib —
|
|
4
|
+
* keeps the registry copy-pasteable). Promoted from the theo-cloud dashboard
|
|
5
|
+
* (production since M51, with the M76 sparse-marker and M90 ragged-series
|
|
6
|
+
* lessons pinned by tests).
|
|
7
|
+
*
|
|
8
|
+
* Input contract: `y` values are expected finite and >= 0 (latency, counts);
|
|
9
|
+
* non-finite points are skipped in the line and read as "—" in the table;
|
|
10
|
+
* gaps are modeled by OMITTING the point (the a11y table reads each value
|
|
11
|
+
* under its own period — never positionally). Points are connected in the
|
|
12
|
+
* order given; sort by `x` upstream. Series `color` is any CSS color — prefer
|
|
13
|
+
* theme tokens (e.g. `var(--primary)`).
|
|
14
|
+
*
|
|
15
|
+
* <TrendChart title="Latency" valueFormatter={(v) => `${v}ms`}
|
|
16
|
+
* series={[{ name: "p50", color: "var(--primary)", points }]} />
|
|
17
|
+
*/
|
|
18
|
+
export interface TrendPoint {
|
|
19
|
+
/** X in the series' own units (bucket index or epoch-ms); scaled linearly. */
|
|
20
|
+
x: number;
|
|
21
|
+
/** Y value for this point. */
|
|
22
|
+
y: number;
|
|
23
|
+
}
|
|
24
|
+
export interface TrendSeries {
|
|
25
|
+
name: string;
|
|
26
|
+
/** CSS color for the line + legend swatch (prefer theme tokens). */
|
|
27
|
+
color: string;
|
|
28
|
+
points: TrendPoint[];
|
|
29
|
+
}
|
|
30
|
+
/** Linear scale mapping a value in `domain` to `range`. A zero-width domain maps everything to r0. */
|
|
31
|
+
export declare function linScale(domain: [number, number], range: [number, number]): (v: number) => number;
|
|
32
|
+
/** A clean upper Y bound ≥ max(values). Returns 0 when there is no positive value. */
|
|
33
|
+
export declare function niceMax(values: number[]): number;
|
|
34
|
+
/** SVG path `d` for a polyline over the given scales. Non-finite points are skipped. */
|
|
35
|
+
export declare function seriesPath(points: TrendPoint[], xScale: (x: number) => number, yScale: (y: number) => number): string;
|
|
36
|
+
export interface TrendChartProps extends HTMLAttributes<HTMLElement> {
|
|
37
|
+
title: string;
|
|
38
|
+
series: TrendSeries[];
|
|
39
|
+
height?: number;
|
|
40
|
+
/** Formats y-axis labels and table cells (tremor vocabulary). */
|
|
41
|
+
valueFormatter?: (v: number) => string;
|
|
42
|
+
}
|
|
43
|
+
declare const TrendChart: import("react").ForwardRefExoticComponent<TrendChartProps & import("react").RefAttributes<HTMLElement>>;
|
|
44
|
+
export { TrendChart };
|
package/dist/index.d.ts
CHANGED
|
@@ -47,6 +47,7 @@ export { ActionBar, type ActionBarProps } from "./components/primitives/action-b
|
|
|
47
47
|
export { PinInput, type PinInputProps } from "./components/primitives/pin-input/index.js";
|
|
48
48
|
export { DataTable, type DataTableColumn, type DataTableProps, type DataTableSort, } from "./components/composites/data-table/index.js";
|
|
49
49
|
export { PageShell, type PageShellProps } from "./components/composites/page-shell/index.js";
|
|
50
|
+
export { linScale, niceMax, seriesPath, TrendChart, type TrendChartProps, type TrendPoint, type TrendSeries, } from "./components/composites/trend-chart/index.js";
|
|
50
51
|
export { MetricsPanel, type Metric } from "./components/primitives/metrics-panel/index.js";
|
|
51
52
|
export { SocialAuthRow, type SocialProvider, } from "./components/primitives/social-auth-row/index.js";
|
|
52
53
|
export { LoginSplit } from "./components/primitives/login-split/index.js";
|
package/dist/index.js
CHANGED
|
@@ -4378,6 +4378,167 @@ var PageShell = forwardRef(
|
|
|
4378
4378
|
}
|
|
4379
4379
|
);
|
|
4380
4380
|
PageShell.displayName = "PageShell";
|
|
4381
|
+
function linScale(domain, range) {
|
|
4382
|
+
const [d0, d1] = domain;
|
|
4383
|
+
const [r0, r1] = range;
|
|
4384
|
+
const span = d1 - d0;
|
|
4385
|
+
return (v) => span === 0 ? r0 : r0 + (v - d0) / span * (r1 - r0);
|
|
4386
|
+
}
|
|
4387
|
+
function niceMax(values) {
|
|
4388
|
+
const m = Math.max(0, ...values.filter((v) => Number.isFinite(v)));
|
|
4389
|
+
if (m <= 0) return 0;
|
|
4390
|
+
const pow = 10 ** Math.floor(Math.log10(m));
|
|
4391
|
+
return Math.ceil(m / pow) * pow;
|
|
4392
|
+
}
|
|
4393
|
+
function seriesPath(points, xScale, yScale) {
|
|
4394
|
+
const finite = points.filter((p) => Number.isFinite(p.y));
|
|
4395
|
+
if (finite.length === 0) return "";
|
|
4396
|
+
return finite.map((p, i) => `${i === 0 ? "M" : "L"}${xScale(p.x).toFixed(2)},${yScale(p.y).toFixed(2)}`).join(" ");
|
|
4397
|
+
}
|
|
4398
|
+
var VIEW_W = 600;
|
|
4399
|
+
var PAD = { top: 8, right: 8, bottom: 4, left: 40 };
|
|
4400
|
+
var SPARSE_MARKER_MAX = 5;
|
|
4401
|
+
var TrendChart = forwardRef(
|
|
4402
|
+
({ title, series, height = 180, valueFormatter = (v) => String(v), className, ...props }, ref) => {
|
|
4403
|
+
const allPoints = series.flatMap((s) => s.points);
|
|
4404
|
+
const hasData = allPoints.some((p) => Number.isFinite(p.y));
|
|
4405
|
+
if (!hasData) {
|
|
4406
|
+
return /* @__PURE__ */ jsxs(
|
|
4407
|
+
"figure",
|
|
4408
|
+
{
|
|
4409
|
+
"data-slot": "trend-chart",
|
|
4410
|
+
ref,
|
|
4411
|
+
className: cn("space-y-1", className),
|
|
4412
|
+
"aria-label": `${title} \u2014 no data`,
|
|
4413
|
+
...props,
|
|
4414
|
+
children: [
|
|
4415
|
+
/* @__PURE__ */ jsx("figcaption", { className: "font-semibold text-label-caps text-muted-foreground uppercase", children: title }),
|
|
4416
|
+
/* @__PURE__ */ jsx(
|
|
4417
|
+
"div",
|
|
4418
|
+
{
|
|
4419
|
+
"data-slot": "trend-chart-empty",
|
|
4420
|
+
className: "flex items-center justify-center rounded-md border border-dashed text-body-sm text-muted-foreground",
|
|
4421
|
+
style: { height },
|
|
4422
|
+
children: "No data in this time range."
|
|
4423
|
+
}
|
|
4424
|
+
)
|
|
4425
|
+
]
|
|
4426
|
+
}
|
|
4427
|
+
);
|
|
4428
|
+
}
|
|
4429
|
+
const xs = allPoints.map((p) => p.x);
|
|
4430
|
+
const xMin = Math.min(...xs);
|
|
4431
|
+
const xMax = Math.max(...xs);
|
|
4432
|
+
const yMax = niceMax(allPoints.map((p) => p.y)) || 1;
|
|
4433
|
+
const axisXs = Array.from(new Set(allPoints.map((p) => p.x))).sort((a, b) => a - b);
|
|
4434
|
+
const yByX = series.map((s) => new Map(s.points.map((p) => [p.x, p.y])));
|
|
4435
|
+
const xScale = linScale([xMin, xMax], [PAD.left, VIEW_W - PAD.right]);
|
|
4436
|
+
const yScale = linScale([0, yMax], [height - PAD.bottom, PAD.top]);
|
|
4437
|
+
return /* @__PURE__ */ jsxs("figure", { "data-slot": "trend-chart", ref, className: cn("space-y-1", className), ...props, children: [
|
|
4438
|
+
/* @__PURE__ */ jsx("figcaption", { className: "font-semibold text-label-caps text-muted-foreground uppercase", children: title }),
|
|
4439
|
+
/* @__PURE__ */ jsxs(
|
|
4440
|
+
"svg",
|
|
4441
|
+
{
|
|
4442
|
+
viewBox: `0 0 ${VIEW_W} ${height}`,
|
|
4443
|
+
preserveAspectRatio: "none",
|
|
4444
|
+
role: "img",
|
|
4445
|
+
"aria-label": `${title} trend`,
|
|
4446
|
+
className: "w-full",
|
|
4447
|
+
style: { height },
|
|
4448
|
+
children: [
|
|
4449
|
+
/* @__PURE__ */ jsx(
|
|
4450
|
+
"line",
|
|
4451
|
+
{
|
|
4452
|
+
x1: PAD.left,
|
|
4453
|
+
y1: height - PAD.bottom,
|
|
4454
|
+
x2: VIEW_W - PAD.right,
|
|
4455
|
+
y2: height - PAD.bottom,
|
|
4456
|
+
className: "stroke-border",
|
|
4457
|
+
strokeWidth: 1
|
|
4458
|
+
}
|
|
4459
|
+
),
|
|
4460
|
+
/* @__PURE__ */ jsx(
|
|
4461
|
+
"line",
|
|
4462
|
+
{
|
|
4463
|
+
x1: PAD.left,
|
|
4464
|
+
y1: PAD.top,
|
|
4465
|
+
x2: VIEW_W - PAD.right,
|
|
4466
|
+
y2: PAD.top,
|
|
4467
|
+
className: "stroke-border/50",
|
|
4468
|
+
strokeWidth: 1,
|
|
4469
|
+
strokeDasharray: "2 3"
|
|
4470
|
+
}
|
|
4471
|
+
),
|
|
4472
|
+
/* @__PURE__ */ jsx("text", { x: 4, y: PAD.top + 8, className: "fill-muted-foreground text-[10px]", children: valueFormatter(yMax) }),
|
|
4473
|
+
/* @__PURE__ */ jsx("text", { x: 4, y: height - PAD.bottom, className: "fill-muted-foreground text-[10px]", children: "0" }),
|
|
4474
|
+
series.map((s) => /* @__PURE__ */ jsxs("g", { children: [
|
|
4475
|
+
/* @__PURE__ */ jsx(
|
|
4476
|
+
"path",
|
|
4477
|
+
{
|
|
4478
|
+
"data-slot": "trend-chart-line",
|
|
4479
|
+
"data-series": s.name,
|
|
4480
|
+
d: seriesPath(s.points, xScale, yScale),
|
|
4481
|
+
fill: "none",
|
|
4482
|
+
stroke: s.color,
|
|
4483
|
+
strokeWidth: 1.5,
|
|
4484
|
+
children: /* @__PURE__ */ jsx("title", { children: s.name })
|
|
4485
|
+
}
|
|
4486
|
+
),
|
|
4487
|
+
s.points.length < SPARSE_MARKER_MAX ? s.points.filter((p) => Number.isFinite(p.y)).map((p, i) => /* @__PURE__ */ jsx(
|
|
4488
|
+
"circle",
|
|
4489
|
+
{
|
|
4490
|
+
"data-slot": "trend-chart-dot",
|
|
4491
|
+
cx: xScale(p.x),
|
|
4492
|
+
cy: yScale(p.y),
|
|
4493
|
+
r: 2.5,
|
|
4494
|
+
fill: s.color,
|
|
4495
|
+
children: /* @__PURE__ */ jsx("title", { children: s.name })
|
|
4496
|
+
},
|
|
4497
|
+
`${s.name}-${p.x}-${i}`
|
|
4498
|
+
)) : null
|
|
4499
|
+
] }, s.name))
|
|
4500
|
+
]
|
|
4501
|
+
}
|
|
4502
|
+
),
|
|
4503
|
+
/* @__PURE__ */ jsx(
|
|
4504
|
+
"ul",
|
|
4505
|
+
{
|
|
4506
|
+
"data-slot": "trend-chart-legend",
|
|
4507
|
+
className: "flex flex-wrap gap-3 text-body-sm text-muted-foreground",
|
|
4508
|
+
children: series.map((s) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-1", children: [
|
|
4509
|
+
/* @__PURE__ */ jsx(
|
|
4510
|
+
"span",
|
|
4511
|
+
{
|
|
4512
|
+
"aria-hidden": "true",
|
|
4513
|
+
className: "inline-block h-2 w-2 rounded-sm",
|
|
4514
|
+
style: { backgroundColor: s.color }
|
|
4515
|
+
}
|
|
4516
|
+
),
|
|
4517
|
+
s.name
|
|
4518
|
+
] }, s.name))
|
|
4519
|
+
}
|
|
4520
|
+
),
|
|
4521
|
+
/* @__PURE__ */ jsxs("table", { "data-slot": "trend-chart-table", className: "sr-only", children: [
|
|
4522
|
+
/* @__PURE__ */ jsxs("caption", { children: [
|
|
4523
|
+
title,
|
|
4524
|
+
" \u2014 data table"
|
|
4525
|
+
] }),
|
|
4526
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
4527
|
+
/* @__PURE__ */ jsx("th", { children: "Point" }),
|
|
4528
|
+
series.map((s) => /* @__PURE__ */ jsx("th", { children: s.name }, s.name))
|
|
4529
|
+
] }) }),
|
|
4530
|
+
/* @__PURE__ */ jsx("tbody", { children: axisXs.map((x, i) => /* @__PURE__ */ jsxs("tr", { children: [
|
|
4531
|
+
/* @__PURE__ */ jsx("td", { children: i + 1 }),
|
|
4532
|
+
series.map((s, si) => {
|
|
4533
|
+
const y = yByX[si]?.get(x);
|
|
4534
|
+
return /* @__PURE__ */ jsx("td", { children: y !== void 0 && Number.isFinite(y) ? valueFormatter(y) : "\u2014" }, s.name);
|
|
4535
|
+
})
|
|
4536
|
+
] }, x)) })
|
|
4537
|
+
] })
|
|
4538
|
+
] });
|
|
4539
|
+
}
|
|
4540
|
+
);
|
|
4541
|
+
TrendChart.displayName = "TrendChart";
|
|
4381
4542
|
var MetricsPanel = forwardRef(
|
|
4382
4543
|
({ className, title, description, metrics, columns, ...props }, ref) => /* @__PURE__ */ jsxs(
|
|
4383
4544
|
"div",
|
|
@@ -5308,4 +5469,4 @@ function CommandPalette({
|
|
|
5308
5469
|
] }) });
|
|
5309
5470
|
}
|
|
5310
5471
|
|
|
5311
|
-
export { AccountMenu, ActionBar, Alert, Avatar, BadgeWithDot as Badge, Breadcrumb, Button, Card, Checkbox, CodeBlock, Combobox, CommandPalette, ConfirmDialog, CopyButton, DangerZone, DataTable, DeploymentRow, DescriptionList, Dialog, DomainConfig, DropdownMenu, EmptyState, EnvVarEditor, FormField, Input, JsonViewer, Label, LoginSplit, MetricCard, MetricsPanel, PageShell, Pagination, PinInput, PlanBadge, PreviewEnvCard, Progress, ProjectCard, RadioGroup, RollbackUI, ScrollArea, Select, Sheet, Sidebar, Skeleton, Slider, SocialAuthRow, StatTile, StatusDot, StatusIndicator, Switch, Table, Tabs, TaskHeader, Textarea, Timestamp, Toast, Toaster, TooltipWithStatics as Tooltip, TopNav, UpdateBanner, avatarVariants, badgeVariants, buttonVariants, cn, computePageRange, isNearBottom, sheetVariants, useStickToBottom, useToast };
|
|
5472
|
+
export { AccountMenu, ActionBar, Alert, Avatar, BadgeWithDot as Badge, Breadcrumb, Button, Card, Checkbox, CodeBlock, Combobox, CommandPalette, ConfirmDialog, CopyButton, DangerZone, DataTable, DeploymentRow, DescriptionList, Dialog, DomainConfig, DropdownMenu, EmptyState, EnvVarEditor, FormField, Input, JsonViewer, Label, LoginSplit, MetricCard, MetricsPanel, PageShell, Pagination, PinInput, PlanBadge, PreviewEnvCard, Progress, ProjectCard, RadioGroup, RollbackUI, ScrollArea, Select, Sheet, Sidebar, Skeleton, Slider, SocialAuthRow, StatTile, StatusDot, StatusIndicator, Switch, Table, Tabs, TaskHeader, Textarea, Timestamp, Toast, Toaster, TooltipWithStatics as Tooltip, TopNav, TrendChart, UpdateBanner, avatarVariants, badgeVariants, buttonVariants, cn, computePageRange, isNearBottom, linScale, niceMax, seriesPath, sheetVariants, useStickToBottom, useToast };
|
package/package.json
CHANGED