@usetheo/ui 0.17.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 };
@@ -0,0 +1,33 @@
1
+ import type { HTMLAttributes } from "react";
2
+ /**
3
+ * DescriptionList — semantic key/value pairs on native `dl/dt/dd`.
4
+ *
5
+ * Layouts: `vertical` (default — stacked groups) and `horizontal` (two-column
6
+ * grid: terms in a fixed max-content column, details beside them; the Item
7
+ * wrapper uses `display: contents` so dt/dd participate in the root grid).
8
+ * `dense` tightens the gaps for crowded detail panels.
9
+ *
10
+ * <DescriptionList layout="horizontal">
11
+ * <DescriptionList.Item>
12
+ * <DescriptionList.Term>Status</DescriptionList.Term>
13
+ * <DescriptionList.Detail><Badge>live</Badge></DescriptionList.Detail>
14
+ * </DescriptionList.Item>
15
+ * </DescriptionList>
16
+ *
17
+ * One Term per Item; multiple Details per Term are valid HTML and supported.
18
+ * Composing dd without a preceding dt is the consumer's responsibility
19
+ * (invalid HTML — same contract as Breadcrumb.Page placement).
20
+ */
21
+ interface DescriptionListProps extends HTMLAttributes<HTMLDListElement> {
22
+ /** vertical (default): stacked groups. horizontal: term column + detail column. */
23
+ layout?: "vertical" | "horizontal";
24
+ /** Compact spacing for dense detail panels. */
25
+ dense?: boolean;
26
+ }
27
+ declare const DescriptionList: import("react").ForwardRefExoticComponent<DescriptionListProps & import("react").RefAttributes<HTMLDListElement>> & {
28
+ Item: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & import("react").RefAttributes<HTMLDivElement>>;
29
+ Term: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLElement> & import("react").RefAttributes<HTMLElement>>;
30
+ Detail: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLElement> & import("react").RefAttributes<HTMLElement>>;
31
+ };
32
+ export { DescriptionList };
33
+ export type { DescriptionListProps };
@@ -0,0 +1 @@
1
+ export { DescriptionList, type DescriptionListProps } from "./description-list.js";
@@ -0,0 +1 @@
1
+ export { JsonViewer, type JsonViewerProps } from "./json-viewer.js";
@@ -0,0 +1,32 @@
1
+ import type { HTMLAttributes } from "react";
2
+ /**
3
+ * JsonViewer — read-only collapsible JSON tree, dependency-free.
4
+ *
5
+ * Collapse: `collapsed` mirrors the SOTA contract — `true` collapses every
6
+ * node, a number collapses everything at depth >= N (root is depth 0).
7
+ * Collapsed subtrees are NOT rendered (lazy): that is the performance guard —
8
+ * a huge payload fully expanded is the consumer's responsibility (ADR D1);
9
+ * prefer `collapsed={1}` for large payloads.
10
+ *
11
+ * Safety: circular references render a "[Circular]" marker (and serialize as
12
+ * such on copy) instead of overflowing the stack. Copy limitation: the
13
+ * serializer conservatively marks REPEATED (shared, non-circular) references
14
+ * as "[Circular]" too — output is always JSON.parse-able — the studied SOTA reference
15
+ * does not handle this. BigInt renders with the `n` suffix; NaN/Infinity
16
+ * follow JSON semantics on copy (null).
17
+ *
18
+ * <JsonViewer value={eventPayload} collapsed={1} enableCopy />
19
+ */
20
+ interface JsonViewerProps extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
21
+ /** Data to render. Any value — objects, arrays, primitives, BigInt, Date. */
22
+ value: unknown;
23
+ /** true = all collapsed; number = collapse at depth >= N. Default: all expanded. */
24
+ collapsed?: boolean | number;
25
+ /** Truncate strings longer than this (click to reveal). Default 60. */
26
+ shortenTextAfterLength?: number;
27
+ /** Show a copy button on the root and on object/array nodes. */
28
+ enableCopy?: boolean;
29
+ }
30
+ declare const JsonViewer: import("react").ForwardRefExoticComponent<JsonViewerProps & import("react").RefAttributes<HTMLDivElement>>;
31
+ export { JsonViewer };
32
+ export type { JsonViewerProps };
package/dist/index.d.ts CHANGED
@@ -2,6 +2,8 @@ export { cn } from "./lib/cn.js";
2
2
  export { Button, buttonVariants, type ButtonProps } from "./components/primitives/button/index.js";
3
3
  export { Badge, badgeVariants, type BadgeProps } from "./components/primitives/badge/index.js";
4
4
  export { Breadcrumb, type BreadcrumbLinkProps, } from "./components/primitives/breadcrumb/index.js";
5
+ export { DescriptionList, type DescriptionListProps, } from "./components/primitives/description-list/index.js";
6
+ export { JsonViewer, type JsonViewerProps } from "./components/primitives/json-viewer/index.js";
5
7
  export { Combobox, type ComboboxContentProps, type ComboboxInputProps, type ComboboxItemProps, type ComboboxProps, } from "./components/primitives/combobox/index.js";
6
8
  export { Slider, type SliderMark, type SliderProps, } from "./components/primitives/slider/index.js";
7
9
  export { UpdateBanner, type UpdateBannerProps, } from "./components/primitives/update-banner/index.js";
@@ -45,6 +47,7 @@ export { ActionBar, type ActionBarProps } from "./components/primitives/action-b
45
47
  export { PinInput, type PinInputProps } from "./components/primitives/pin-input/index.js";
46
48
  export { DataTable, type DataTableColumn, type DataTableProps, type DataTableSort, } from "./components/composites/data-table/index.js";
47
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";
48
51
  export { MetricsPanel, type Metric } from "./components/primitives/metrics-panel/index.js";
49
52
  export { SocialAuthRow, type SocialProvider, } from "./components/primitives/social-auth-row/index.js";
50
53
  export { LoginSplit } from "./components/primitives/login-split/index.js";
package/dist/index.js CHANGED
@@ -2,9 +2,9 @@ import { clsx } from 'clsx';
2
2
  import { extendTailwindMerge } from 'tailwind-merge';
3
3
  import { Slot } from '@radix-ui/react-slot';
4
4
  import { cva } from 'class-variance-authority';
5
- import { forwardRef, createContext, useState, useRef, useId, useCallback, useEffect, useMemo, Children, isValidElement, cloneElement, Fragment as Fragment$1, useContext, createElement } from 'react';
5
+ import { forwardRef, useState, useRef, useEffect, useCallback, createContext, useId, useMemo, Children, isValidElement, cloneElement, Fragment as Fragment$1, useContext, createElement } from 'react';
6
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
- import { Loader2, Check, ArrowUpCircle, X, AlertCircle, ChevronDown, ChevronUp, Minus, Circle, ChevronRight, ChevronsUpDown, Copy, TrendingDown, TrendingUp, ArrowUpRight, TriangleAlert, CheckCircle2, Info, ChevronsLeft, ChevronLeft, ChevronsRight, Search, Filter, GitCommit, GitBranch, Activity, Plus, Lock, EyeOff, Eye, Trash2, GitPullRequest, Server, ExternalLink, Globe, ShieldCheck, ShieldX, Clock, RotateCcw, ArrowDownLeft, MoreHorizontal } from 'lucide-react';
7
+ import { Check, X, Copy, Loader2, ArrowUpCircle, AlertCircle, ChevronDown, ChevronUp, Minus, Circle, ChevronRight, ChevronsUpDown, TrendingDown, TrendingUp, ArrowUpRight, TriangleAlert, CheckCircle2, Info, ChevronsLeft, ChevronLeft, ChevronsRight, Search, Filter, GitCommit, GitBranch, Activity, Plus, Lock, EyeOff, Eye, Trash2, GitPullRequest, Server, ExternalLink, Globe, ShieldCheck, ShieldX, Clock, RotateCcw, ArrowDownLeft, MoreHorizontal } from 'lucide-react';
8
8
  import { Command, useCommandState } from 'cmdk';
9
9
  import * as SliderPrimitive from '@radix-ui/react-slider';
10
10
  import * as DialogPrimitive from '@radix-ui/react-dialog';
@@ -287,6 +287,329 @@ var Breadcrumb = Object.assign(Root, {
287
287
  Separator,
288
288
  Ellipsis
289
289
  });
290
+ function sub2(tag, slot, displayName, baseClass) {
291
+ const Component = forwardRef(
292
+ ({ className, children, ...props }, ref) => createElement(
293
+ tag,
294
+ { "data-slot": slot, ref, className: cn(baseClass, className), ...props },
295
+ children
296
+ )
297
+ );
298
+ Component.displayName = displayName;
299
+ return Component;
300
+ }
301
+ var Root2 = forwardRef(
302
+ ({ className, layout = "vertical", dense, ...props }, ref) => /* @__PURE__ */ jsx(
303
+ "dl",
304
+ {
305
+ "data-slot": "description-list",
306
+ "data-layout": layout,
307
+ "data-dense": dense ? "true" : void 0,
308
+ ref,
309
+ className: cn(
310
+ "text-body-sm",
311
+ layout === "vertical" && (dense ? "grid gap-2" : "grid gap-4"),
312
+ layout === "horizontal" && "grid grid-cols-[max-content_1fr] items-baseline [&>div]:contents [&_dd]:col-start-2",
313
+ layout === "horizontal" && (dense ? "gap-x-4 gap-y-1.5" : "gap-x-6 gap-y-3"),
314
+ className
315
+ ),
316
+ ...props
317
+ }
318
+ )
319
+ );
320
+ Root2.displayName = "DescriptionList";
321
+ var Item2 = sub2(
322
+ "div",
323
+ "description-list-item",
324
+ "DescriptionList.Item",
325
+ "grid gap-1"
326
+ );
327
+ var Term = sub2(
328
+ "dt",
329
+ "description-list-term",
330
+ "DescriptionList.Term",
331
+ "font-medium text-muted-foreground"
332
+ );
333
+ var Detail = sub2(
334
+ "dd",
335
+ "description-list-detail",
336
+ "DescriptionList.Detail",
337
+ "m-0 text-foreground"
338
+ );
339
+ var DescriptionList = Object.assign(Root2, { Item: Item2, Term, Detail });
340
+ var VARIANT = {
341
+ ghost: "hover:bg-muted",
342
+ outline: "border border-border/60 rounded-md"
343
+ };
344
+ var SIZE = {
345
+ sm: "px-2 py-1 text-label",
346
+ md: "px-2.5 py-1.5 text-body-sm"
347
+ };
348
+ var CopyButton = forwardRef(
349
+ ({
350
+ className,
351
+ value,
352
+ label,
353
+ variant = "ghost",
354
+ size = "sm",
355
+ onCopied,
356
+ feedbackDuration = 1500,
357
+ ...props
358
+ }, ref) => {
359
+ const [state, setState] = useState("idle");
360
+ const timerRef = useRef(null);
361
+ useEffect(() => {
362
+ return () => {
363
+ if (timerRef.current !== null) {
364
+ clearTimeout(timerRef.current);
365
+ }
366
+ };
367
+ }, []);
368
+ const scheduleRevert = useCallback(() => {
369
+ if (timerRef.current !== null) {
370
+ clearTimeout(timerRef.current);
371
+ }
372
+ timerRef.current = setTimeout(() => {
373
+ setState("idle");
374
+ timerRef.current = null;
375
+ }, feedbackDuration);
376
+ }, [feedbackDuration]);
377
+ const handleClick = useCallback(() => {
378
+ if (state !== "idle") return;
379
+ if (typeof navigator === "undefined" || !navigator.clipboard?.writeText) {
380
+ setState("failed");
381
+ scheduleRevert();
382
+ return;
383
+ }
384
+ navigator.clipboard.writeText(value).then(
385
+ () => {
386
+ setState("copied");
387
+ onCopied?.(value);
388
+ scheduleRevert();
389
+ },
390
+ () => {
391
+ setState("failed");
392
+ scheduleRevert();
393
+ }
394
+ );
395
+ }, [state, value, onCopied, scheduleRevert]);
396
+ const Icon2 = state === "copied" ? Check : state === "failed" ? X : Copy;
397
+ const liveMessage = state === "copied" ? "Copied to clipboard" : state === "failed" ? "Copy failed" : "";
398
+ const labelText = label !== void 0 ? state === "copied" ? "Copied!" : state === "failed" ? "Failed" : label : null;
399
+ return /* @__PURE__ */ jsxs(
400
+ "button",
401
+ {
402
+ "data-slot": "copy-button",
403
+ ref,
404
+ type: "button",
405
+ onClick: handleClick,
406
+ "data-state": state,
407
+ className: cn(
408
+ "inline-flex items-center gap-1.5",
409
+ "font-sans transition-colors",
410
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-card",
411
+ VARIANT[variant],
412
+ SIZE[size],
413
+ className
414
+ ),
415
+ ...props,
416
+ children: [
417
+ /* @__PURE__ */ jsx(
418
+ Icon2,
419
+ {
420
+ "aria-hidden": "true",
421
+ className: cn(
422
+ "size-3.5 shrink-0 transition-opacity duration-200",
423
+ state === "copied" && "text-success",
424
+ state === "failed" && "text-destructive"
425
+ )
426
+ }
427
+ ),
428
+ labelText !== null ? /* @__PURE__ */ jsx("span", { children: labelText }) : null,
429
+ /* @__PURE__ */ jsx("span", { className: "sr-only", "aria-live": "polite", children: liveMessage })
430
+ ]
431
+ }
432
+ );
433
+ }
434
+ );
435
+ CopyButton.displayName = "CopyButton";
436
+ var PATH_SEP = "";
437
+ var EMPTY_CHAIN = { has: () => false };
438
+ function isPlainContainer(value) {
439
+ return typeof value === "object" && value !== null && !(value instanceof Date);
440
+ }
441
+ function safeStringify(value) {
442
+ const seen = /* @__PURE__ */ new WeakSet();
443
+ return JSON.stringify(
444
+ value,
445
+ (_key, entry) => {
446
+ if (typeof entry === "bigint") return `${entry}n`;
447
+ if (typeof entry === "object" && entry !== null) {
448
+ if (seen.has(entry)) return "[Circular]";
449
+ seen.add(entry);
450
+ }
451
+ return entry;
452
+ },
453
+ 2
454
+ );
455
+ }
456
+ function defaultExpanded(depth, collapsed) {
457
+ if (collapsed === true) return false;
458
+ if (typeof collapsed === "number") return depth < collapsed;
459
+ return true;
460
+ }
461
+ function PrimitiveValue({
462
+ value,
463
+ shortenAfter
464
+ }) {
465
+ const [revealed, setRevealed] = useState(false);
466
+ if (typeof value === "string") {
467
+ const shorten = !revealed && value.length > shortenAfter;
468
+ const text2 = shorten ? `${value.slice(0, shortenAfter)}\u2026` : `"${value}"`;
469
+ return /* @__PURE__ */ jsx(
470
+ "span",
471
+ {
472
+ "data-slot": "json-viewer-value",
473
+ "data-type": "string",
474
+ className: cn("text-success", shorten && "cursor-pointer underline decoration-dotted"),
475
+ onClick: shorten ? () => setRevealed(true) : void 0,
476
+ onKeyDown: shorten ? (e) => {
477
+ if (e.key === "Enter" || e.key === " ") {
478
+ e.preventDefault();
479
+ setRevealed(true);
480
+ }
481
+ } : void 0,
482
+ role: shorten ? "button" : void 0,
483
+ tabIndex: shorten ? 0 : void 0,
484
+ children: text2
485
+ }
486
+ );
487
+ }
488
+ let text;
489
+ let type = typeof value;
490
+ if (typeof value === "bigint") text = `${value}n`;
491
+ else if (value === null) {
492
+ text = "null";
493
+ type = "null";
494
+ } else if (value === void 0) text = "undefined";
495
+ else if (typeof value === "function") text = "\u0192";
496
+ else if (value instanceof Date) {
497
+ text = value.toISOString();
498
+ type = "date";
499
+ } else text = String(value);
500
+ return /* @__PURE__ */ jsx("span", { "data-slot": "json-viewer-value", "data-type": type, className: "text-info", children: text });
501
+ }
502
+ function JsonNode(props) {
503
+ const { nodeKey, value, path, depth, ancestors, viewer, expandedOverrides, onToggle } = props;
504
+ const copyValue = useMemo(
505
+ () => viewer.enableCopy && isPlainContainer(value) ? safeStringify(value) : "",
506
+ [value, viewer.enableCopy]
507
+ );
508
+ const keyLabel = nodeKey !== void 0 ? /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
509
+ /* @__PURE__ */ jsx("span", { "data-slot": "json-viewer-key", children: nodeKey === "" ? '""' : nodeKey }),
510
+ ":"
511
+ ] }) : null;
512
+ if (isPlainContainer(value) && ancestors.has(value)) {
513
+ return /* @__PURE__ */ jsxs("li", { "data-slot": "json-viewer-node", className: "flex items-baseline gap-1.5", children: [
514
+ keyLabel,
515
+ /* @__PURE__ */ jsx("span", { "data-slot": "json-viewer-value", "data-type": "circular", className: "text-warning", children: "[Circular]" })
516
+ ] });
517
+ }
518
+ if (!isPlainContainer(value)) {
519
+ return /* @__PURE__ */ jsxs("li", { "data-slot": "json-viewer-node", className: "flex items-baseline gap-1.5", children: [
520
+ keyLabel,
521
+ /* @__PURE__ */ jsx(PrimitiveValue, { value, shortenAfter: viewer.shortenTextAfterLength })
522
+ ] });
523
+ }
524
+ const isArray = Array.isArray(value);
525
+ const entries = isArray ? value.map((entry, index) => [String(index), entry]) : Object.entries(value);
526
+ if (entries.length === 0) {
527
+ return /* @__PURE__ */ jsxs("li", { "data-slot": "json-viewer-node", className: "flex items-baseline gap-1.5", children: [
528
+ keyLabel,
529
+ /* @__PURE__ */ jsx("span", { "data-slot": "json-viewer-value", "data-type": isArray ? "array" : "object", children: isArray ? "[]" : "{}" })
530
+ ] });
531
+ }
532
+ const expanded = expandedOverrides.get(path) ?? defaultExpanded(depth, viewer.collapsed);
533
+ const badge = isArray ? `[${entries.length}]` : `{${entries.length}}`;
534
+ return /* @__PURE__ */ jsxs("li", { "data-slot": "json-viewer-node", children: [
535
+ /* @__PURE__ */ jsxs("span", { className: "group/node flex items-baseline gap-1.5", children: [
536
+ /* @__PURE__ */ jsx(
537
+ "button",
538
+ {
539
+ type: "button",
540
+ "data-slot": "json-viewer-toggle",
541
+ "aria-expanded": expanded,
542
+ "aria-label": `Toggle ${nodeKey ?? "root"}`,
543
+ onClick: () => onToggle(path, !expanded),
544
+ className: "inline-flex size-4 shrink-0 translate-y-0.5 items-center justify-center rounded text-muted-foreground hover:text-foreground",
545
+ children: /* @__PURE__ */ jsx(
546
+ ChevronRight,
547
+ {
548
+ "aria-hidden": true,
549
+ className: cn("size-3.5 transition-transform", expanded && "rotate-90")
550
+ }
551
+ )
552
+ }
553
+ ),
554
+ keyLabel,
555
+ !expanded && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: badge }),
556
+ viewer.enableCopy && /* @__PURE__ */ jsx(
557
+ "span",
558
+ {
559
+ "data-slot": "json-viewer-copy",
560
+ className: "opacity-0 transition-opacity group-focus-within/node:opacity-100 group-hover/node:opacity-100",
561
+ children: /* @__PURE__ */ jsx(CopyButton, { value: copyValue, "aria-label": `Copy ${nodeKey ?? "JSON"}` })
562
+ }
563
+ )
564
+ ] }),
565
+ expanded && /* @__PURE__ */ jsx("ul", { className: "m-0 grid list-none gap-0.5 border-border/40 border-l pl-4", children: entries.map(([entryKey, entryValue]) => /* @__PURE__ */ jsx(
566
+ JsonNode,
567
+ {
568
+ nodeKey: entryKey,
569
+ value: entryValue,
570
+ path: path + PATH_SEP + entryKey,
571
+ depth: depth + 1,
572
+ ancestors: {
573
+ has: (candidate) => candidate === value || ancestors.has(candidate)
574
+ },
575
+ viewer,
576
+ expandedOverrides,
577
+ onToggle
578
+ },
579
+ entryKey
580
+ )) })
581
+ ] });
582
+ }
583
+ var JsonViewer = forwardRef(
584
+ ({ value, collapsed, shortenTextAfterLength = 60, enableCopy = false, className, ...props }, ref) => {
585
+ const [expandedOverrides, setExpandedOverrides] = useState(/* @__PURE__ */ new Map());
586
+ const onToggle = (path, next) => {
587
+ setExpandedOverrides((prev) => new Map(prev).set(path, next));
588
+ };
589
+ return /* @__PURE__ */ jsx(
590
+ "div",
591
+ {
592
+ "data-slot": "json-viewer",
593
+ ref,
594
+ className: cn("font-mono text-code-sm", className),
595
+ ...props,
596
+ children: /* @__PURE__ */ jsx("ul", { className: "m-0 grid list-none gap-0.5 p-0", children: /* @__PURE__ */ jsx(
597
+ JsonNode,
598
+ {
599
+ value,
600
+ path: "$",
601
+ depth: 0,
602
+ ancestors: EMPTY_CHAIN,
603
+ viewer: { collapsed, shortenTextAfterLength, enableCopy },
604
+ expandedOverrides,
605
+ onToggle
606
+ }
607
+ ) })
608
+ }
609
+ );
610
+ }
611
+ );
612
+ JsonViewer.displayName = "JsonViewer";
290
613
  var inputVariants = cva(
291
614
  [
292
615
  "flex w-full rounded-md border border-input bg-card",
@@ -331,12 +654,12 @@ function composeRefs(local, forwarded) {
331
654
  else if (forwarded) forwarded.current = node;
332
655
  };
333
656
  }
334
- function useComboboxContext(sub2) {
657
+ function useComboboxContext(sub3) {
335
658
  const ctx = useContext(ComboboxContext);
336
- if (!ctx) throw new Error(`<Combobox.${sub2}> must be used inside <Combobox>`);
659
+ if (!ctx) throw new Error(`<Combobox.${sub3}> must be used inside <Combobox>`);
337
660
  return ctx;
338
661
  }
339
- var Root2 = forwardRef(
662
+ var Root3 = forwardRef(
340
663
  ({
341
664
  className,
342
665
  children,
@@ -389,7 +712,7 @@ var Root2 = forwardRef(
389
712
  ) });
390
713
  }
391
714
  );
392
- Root2.displayName = "Combobox";
715
+ Root3.displayName = "Combobox";
393
716
  var Input2 = forwardRef(
394
717
  ({ className, onFocus, onKeyDown, onValueChange, ...props }, ref) => {
395
718
  const { open, setOpen, listId } = useComboboxContext("Input");
@@ -471,7 +794,7 @@ var Content = forwardRef(
471
794
  }
472
795
  );
473
796
  Content.displayName = "Combobox.Content";
474
- var Item2 = forwardRef(
797
+ var Item3 = forwardRef(
475
798
  ({ className, children, value, onSelect, ...props }, ref) => {
476
799
  const ctx = useComboboxContext("Item");
477
800
  const selected = value !== void 0 && ctx.value === value;
@@ -506,7 +829,7 @@ var Item2 = forwardRef(
506
829
  );
507
830
  }
508
831
  );
509
- Item2.displayName = "Combobox.Item";
832
+ Item3.displayName = "Combobox.Item";
510
833
  var Empty = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
511
834
  Command.Empty,
512
835
  {
@@ -517,7 +840,7 @@ var Empty = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
517
840
  }
518
841
  ));
519
842
  Empty.displayName = "Combobox.Empty";
520
- var Combobox = Object.assign(Root2, { Input: Input2, Content, Item: Item2, Empty });
843
+ var Combobox = Object.assign(Root3, { Input: Input2, Content, Item: Item3, Empty });
521
844
  var Slider = forwardRef(
522
845
  ({
523
846
  className,
@@ -690,7 +1013,7 @@ var UpdateBanner = forwardRef(
690
1013
  UpdateBanner.displayName = "UpdateBanner";
691
1014
  var CardContext = createContext({ size: "md" });
692
1015
  var useCardSize = () => useContext(CardContext).size;
693
- var Root4 = forwardRef(
1016
+ var Root5 = forwardRef(
694
1017
  ({ className, size = "md", ...props }, ref) => /* @__PURE__ */ jsx(CardContext.Provider, { value: { size }, children: /* @__PURE__ */ jsx(
695
1018
  "div",
696
1019
  {
@@ -705,7 +1028,7 @@ var Root4 = forwardRef(
705
1028
  }
706
1029
  ) })
707
1030
  );
708
- Root4.displayName = "Card";
1031
+ Root5.displayName = "Card";
709
1032
  var headerPadBySize = {
710
1033
  sm: "gap-1 p-3 pb-1.5",
711
1034
  md: "gap-1.5 p-5 pb-2.5",
@@ -807,7 +1130,7 @@ var Footer = forwardRef(
807
1130
  }
808
1131
  );
809
1132
  Footer.displayName = "Card.Footer";
810
- var Card = /* @__PURE__ */ Object.assign(Root4, {
1133
+ var Card = /* @__PURE__ */ Object.assign(Root5, {
811
1134
  Header,
812
1135
  Title,
813
1136
  Description,
@@ -967,7 +1290,7 @@ var Tabs = /* @__PURE__ */ Object.assign(TabsPrimitive.Root, {
967
1290
  Content: Content5
968
1291
  });
969
1292
  var Provider2 = TooltipPrimitive.Provider;
970
- var Root8 = TooltipPrimitive.Root;
1293
+ var Root9 = TooltipPrimitive.Root;
971
1294
  var Trigger5 = TooltipPrimitive.Trigger;
972
1295
  var Content7 = forwardRef(({ className, sideOffset = 6, ...props }, ref) => /* @__PURE__ */ jsx(TooltipPrimitive.Portal, { children: /* @__PURE__ */ jsx(
973
1296
  TooltipPrimitive.Content,
@@ -995,13 +1318,13 @@ var Tooltip = ({
995
1318
  delayDuration = 200,
996
1319
  children,
997
1320
  ...rootProps
998
- }) => /* @__PURE__ */ jsx(Provider2, { delayDuration, children: /* @__PURE__ */ jsxs(Root8, { ...rootProps, children: [
1321
+ }) => /* @__PURE__ */ jsx(Provider2, { delayDuration, children: /* @__PURE__ */ jsxs(Root9, { ...rootProps, children: [
999
1322
  /* @__PURE__ */ jsx(Trigger5, { asChild: true, children }),
1000
1323
  /* @__PURE__ */ jsx(Content7, { side, align, children: label })
1001
1324
  ] }) });
1002
1325
  var TooltipWithStatics = Tooltip;
1003
1326
  TooltipWithStatics.Provider = Provider2;
1004
- TooltipWithStatics.Root = Root8;
1327
+ TooltipWithStatics.Root = Root9;
1005
1328
  TooltipWithStatics.Trigger = Trigger5;
1006
1329
  TooltipWithStatics.Content = Content7;
1007
1330
  var ScrollAreaRoot = forwardRef(
@@ -1853,7 +2176,7 @@ var Textarea = forwardRef(
1853
2176
  )
1854
2177
  );
1855
2178
  Textarea.displayName = "Textarea";
1856
- var Root18 = forwardRef(
2179
+ var Root19 = forwardRef(
1857
2180
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx(
1858
2181
  "aside",
1859
2182
  {
@@ -1867,7 +2190,7 @@ var Root18 = forwardRef(
1867
2190
  }
1868
2191
  )
1869
2192
  );
1870
- Root18.displayName = "Sidebar";
2193
+ Root19.displayName = "Sidebar";
1871
2194
  var Header3 = forwardRef(
1872
2195
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx(
1873
2196
  "div",
@@ -1896,7 +2219,7 @@ var Section = forwardRef(
1896
2219
  )
1897
2220
  );
1898
2221
  Section.displayName = "Sidebar.Section";
1899
- var Item5 = forwardRef(
2222
+ var Item6 = forwardRef(
1900
2223
  ({ className, icon: Icon2, active, count, as = "button", href, children, ...props }, ref) => {
1901
2224
  const classes = cn(
1902
2225
  "group flex w-full items-center gap-3 rounded-lg px-2 py-2",
@@ -1956,7 +2279,7 @@ var Item5 = forwardRef(
1956
2279
  );
1957
2280
  }
1958
2281
  );
1959
- Item5.displayName = "Sidebar.Item";
2282
+ Item6.displayName = "Sidebar.Item";
1960
2283
  var Footer3 = forwardRef(
1961
2284
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx(
1962
2285
  "div",
@@ -1969,13 +2292,13 @@ var Footer3 = forwardRef(
1969
2292
  )
1970
2293
  );
1971
2294
  Footer3.displayName = "Sidebar.Footer";
1972
- var Sidebar = /* @__PURE__ */ Object.assign(Root18, {
2295
+ var Sidebar = /* @__PURE__ */ Object.assign(Root19, {
1973
2296
  Header: Header3,
1974
2297
  Section,
1975
- Item: Item5,
2298
+ Item: Item6,
1976
2299
  Footer: Footer3
1977
2300
  });
1978
- var Root19 = forwardRef(
2301
+ var Root20 = forwardRef(
1979
2302
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx(
1980
2303
  "header",
1981
2304
  {
@@ -1989,7 +2312,7 @@ var Root19 = forwardRef(
1989
2312
  }
1990
2313
  )
1991
2314
  );
1992
- Root19.displayName = "TopNav";
2315
+ Root20.displayName = "TopNav";
1993
2316
  var Left = forwardRef(
1994
2317
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx(
1995
2318
  "div",
@@ -2124,7 +2447,7 @@ var ModeSwitcher = forwardRef(
2124
2447
  }
2125
2448
  );
2126
2449
  ModeSwitcher.displayName = "TopNav.ModeSwitcher";
2127
- var TopNav = /* @__PURE__ */ Object.assign(Root19, {
2450
+ var TopNav = /* @__PURE__ */ Object.assign(Root20, {
2128
2451
  Left,
2129
2452
  Center,
2130
2453
  Right,
@@ -2420,7 +2743,7 @@ var alignClass = {
2420
2743
  center: "text-center",
2421
2744
  right: "text-right"
2422
2745
  };
2423
- var Root21 = forwardRef(
2746
+ var Root22 = forwardRef(
2424
2747
  ({ className, density = "default", children, ...props }, ref) => /* @__PURE__ */ jsx(TableDensityContext.Provider, { value: density, children: /* @__PURE__ */ jsx(
2425
2748
  "table",
2426
2749
  {
@@ -2432,7 +2755,7 @@ var Root21 = forwardRef(
2432
2755
  }
2433
2756
  ) })
2434
2757
  );
2435
- Root21.displayName = "Table";
2758
+ Root22.displayName = "Table";
2436
2759
  var Header5 = forwardRef(
2437
2760
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx(
2438
2761
  "thead",
@@ -2552,7 +2875,7 @@ var HeaderCell = forwardRef(
2552
2875
  }
2553
2876
  );
2554
2877
  HeaderCell.displayName = "Table.HeaderCell";
2555
- var Table = Object.assign(Root21, { Header: Header5, Body: Body4, Row, Cell, HeaderCell });
2878
+ var Table = Object.assign(Root22, { Header: Header5, Body: Body4, Row, Cell, HeaderCell });
2556
2879
 
2557
2880
  // src/lib/env.ts
2558
2881
  function readNodeEnv() {
@@ -2575,7 +2898,7 @@ var LABEL_COLOR = {
2575
2898
  idle: "text-muted-foreground",
2576
2899
  warning: "text-warning"
2577
2900
  };
2578
- var SIZE = {
2901
+ var SIZE2 = {
2579
2902
  xs: "size-1.5",
2580
2903
  sm: "size-2",
2581
2904
  md: "size-2.5"
@@ -2598,7 +2921,7 @@ var StatusDot = forwardRef(
2598
2921
  "aria-hidden": hasVisibleLabel ? "true" : void 0,
2599
2922
  className: cn(
2600
2923
  "inline-block shrink-0 rounded-full",
2601
- SIZE[size],
2924
+ SIZE2[size],
2602
2925
  DOT_COLOR[status],
2603
2926
  shouldPulse && "animate-pulse"
2604
2927
  )
@@ -2639,102 +2962,6 @@ var StatusDot = forwardRef(
2639
2962
  }
2640
2963
  );
2641
2964
  StatusDot.displayName = "StatusDot";
2642
- var VARIANT = {
2643
- ghost: "hover:bg-muted",
2644
- outline: "border border-border/60 rounded-md"
2645
- };
2646
- var SIZE2 = {
2647
- sm: "px-2 py-1 text-label",
2648
- md: "px-2.5 py-1.5 text-body-sm"
2649
- };
2650
- var CopyButton = forwardRef(
2651
- ({
2652
- className,
2653
- value,
2654
- label,
2655
- variant = "ghost",
2656
- size = "sm",
2657
- onCopied,
2658
- feedbackDuration = 1500,
2659
- ...props
2660
- }, ref) => {
2661
- const [state, setState] = useState("idle");
2662
- const timerRef = useRef(null);
2663
- useEffect(() => {
2664
- return () => {
2665
- if (timerRef.current !== null) {
2666
- clearTimeout(timerRef.current);
2667
- }
2668
- };
2669
- }, []);
2670
- const scheduleRevert = useCallback(() => {
2671
- if (timerRef.current !== null) {
2672
- clearTimeout(timerRef.current);
2673
- }
2674
- timerRef.current = setTimeout(() => {
2675
- setState("idle");
2676
- timerRef.current = null;
2677
- }, feedbackDuration);
2678
- }, [feedbackDuration]);
2679
- const handleClick = useCallback(() => {
2680
- if (state !== "idle") return;
2681
- if (typeof navigator === "undefined" || !navigator.clipboard?.writeText) {
2682
- setState("failed");
2683
- scheduleRevert();
2684
- return;
2685
- }
2686
- navigator.clipboard.writeText(value).then(
2687
- () => {
2688
- setState("copied");
2689
- onCopied?.(value);
2690
- scheduleRevert();
2691
- },
2692
- () => {
2693
- setState("failed");
2694
- scheduleRevert();
2695
- }
2696
- );
2697
- }, [state, value, onCopied, scheduleRevert]);
2698
- const Icon2 = state === "copied" ? Check : state === "failed" ? X : Copy;
2699
- const liveMessage = state === "copied" ? "Copied to clipboard" : state === "failed" ? "Copy failed" : "";
2700
- const labelText = label !== void 0 ? state === "copied" ? "Copied!" : state === "failed" ? "Failed" : label : null;
2701
- return /* @__PURE__ */ jsxs(
2702
- "button",
2703
- {
2704
- "data-slot": "copy-button",
2705
- ref,
2706
- type: "button",
2707
- onClick: handleClick,
2708
- "data-state": state,
2709
- className: cn(
2710
- "inline-flex items-center gap-1.5",
2711
- "font-sans transition-colors",
2712
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-card",
2713
- VARIANT[variant],
2714
- SIZE2[size],
2715
- className
2716
- ),
2717
- ...props,
2718
- children: [
2719
- /* @__PURE__ */ jsx(
2720
- Icon2,
2721
- {
2722
- "aria-hidden": "true",
2723
- className: cn(
2724
- "size-3.5 shrink-0 transition-opacity duration-200",
2725
- state === "copied" && "text-success",
2726
- state === "failed" && "text-destructive"
2727
- )
2728
- }
2729
- ),
2730
- labelText !== null ? /* @__PURE__ */ jsx("span", { children: labelText }) : null,
2731
- /* @__PURE__ */ jsx("span", { className: "sr-only", "aria-live": "polite", children: liveMessage })
2732
- ]
2733
- }
2734
- );
2735
- }
2736
- );
2737
- CopyButton.displayName = "CopyButton";
2738
2965
  function toDate(value) {
2739
2966
  const d = value instanceof Date ? value : new Date(value);
2740
2967
  return Number.isNaN(d.getTime()) ? null : d;
@@ -2929,7 +3156,7 @@ var StatTile = forwardRef(
2929
3156
  }
2930
3157
  );
2931
3158
  StatTile.displayName = "StatTile";
2932
- var Root22 = forwardRef(
3159
+ var Root23 = forwardRef(
2933
3160
  ({ className, title = "Danger Zone", children, ...props }, ref) => /* @__PURE__ */ jsxs(
2934
3161
  "section",
2935
3162
  {
@@ -2945,7 +3172,7 @@ var Root22 = forwardRef(
2945
3172
  }
2946
3173
  )
2947
3174
  );
2948
- Root22.displayName = "DangerZone";
3175
+ Root23.displayName = "DangerZone";
2949
3176
  var Action2 = forwardRef(
2950
3177
  ({ className, title, description, action, ...props }, ref) => /* @__PURE__ */ jsxs(
2951
3178
  "div",
@@ -2968,7 +3195,7 @@ var Action2 = forwardRef(
2968
3195
  )
2969
3196
  );
2970
3197
  Action2.displayName = "DangerZone.Action";
2971
- var DangerZone = Object.assign(Root22, { Action: Action2 });
3198
+ var DangerZone = Object.assign(Root23, { Action: Action2 });
2972
3199
  var ConfirmDialog = forwardRef(
2973
3200
  ({
2974
3201
  open,
@@ -3497,7 +3724,7 @@ var Content12 = forwardRef(({ className, sideOffset = 4, ...props }, ref) => /*
3497
3724
  }
3498
3725
  ) }));
3499
3726
  Content12.displayName = "DropdownMenu.Content";
3500
- var Item7 = forwardRef(
3727
+ var Item8 = forwardRef(
3501
3728
  ({ className, inset, ...props }, ref) => /* @__PURE__ */ jsx(
3502
3729
  DropdownMenuPrimitive.Item,
3503
3730
  {
@@ -3516,7 +3743,7 @@ var Item7 = forwardRef(
3516
3743
  }
3517
3744
  )
3518
3745
  );
3519
- Item7.displayName = "DropdownMenu.Item";
3746
+ Item8.displayName = "DropdownMenu.Item";
3520
3747
  var CheckboxItem2 = forwardRef(({ className, children, checked, ...props }, ref) => /* @__PURE__ */ jsxs(
3521
3748
  DropdownMenuPrimitive.CheckboxItem,
3522
3749
  {
@@ -3632,7 +3859,7 @@ var DropdownMenu = Object.assign(DropdownMenuPrimitive.Root, {
3632
3859
  Trigger: Trigger9,
3633
3860
  Portal: Portal6,
3634
3861
  Content: Content12,
3635
- Item: Item7,
3862
+ Item: Item8,
3636
3863
  CheckboxItem: CheckboxItem2,
3637
3864
  RadioItem: RadioItem2,
3638
3865
  Label: Label4,
@@ -4151,6 +4378,167 @@ var PageShell = forwardRef(
4151
4378
  }
4152
4379
  );
4153
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";
4154
4542
  var MetricsPanel = forwardRef(
4155
4543
  ({ className, title, description, metrics, columns, ...props }, ref) => /* @__PURE__ */ jsxs(
4156
4544
  "div",
@@ -5081,4 +5469,4 @@ function CommandPalette({
5081
5469
  ] }) });
5082
5470
  }
5083
5471
 
5084
- export { AccountMenu, ActionBar, Alert, Avatar, BadgeWithDot as Badge, Breadcrumb, Button, Card, Checkbox, CodeBlock, Combobox, CommandPalette, ConfirmDialog, CopyButton, DangerZone, DataTable, DeploymentRow, Dialog, DomainConfig, DropdownMenu, EmptyState, EnvVarEditor, FormField, Input, 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usetheo/ui",
3
- "version": "0.17.0",
3
+ "version": "0.19.0",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "description": "Violet Forge design system — generic React components + primitives (the non-AI foundation of @theokit/ui).",