@sero-ai/ui 0.3.2 → 0.4.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/.turbo/turbo-build.log +628 -0
- package/.turbo/turbo-typecheck.log +1 -1
- package/CHANGELOG.md +38 -0
- package/dist/components/context-editor/CapabilitySection.cjs +57 -0
- package/dist/components/context-editor/CapabilitySection.d.cts +20 -0
- package/dist/components/context-editor/CapabilitySection.d.ts +20 -0
- package/dist/components/context-editor/CapabilitySection.js +57 -0
- package/dist/components/context-editor/ContextEditor.cjs +108 -0
- package/dist/components/context-editor/ContextEditor.d.cts +26 -0
- package/dist/components/context-editor/ContextEditor.d.ts +26 -0
- package/dist/components/context-editor/ContextEditor.js +108 -0
- package/dist/components/context-editor/PresetBar.cjs +71 -0
- package/dist/components/context-editor/PresetBar.d.cts +19 -0
- package/dist/components/context-editor/PresetBar.d.ts +19 -0
- package/dist/components/context-editor/PresetBar.js +71 -0
- package/dist/components/context-editor/SkillsSection.cjs +31 -0
- package/dist/components/context-editor/SkillsSection.d.cts +14 -0
- package/dist/components/context-editor/SkillsSection.d.ts +14 -0
- package/dist/components/context-editor/SkillsSection.js +31 -0
- package/dist/components/context-editor/SystemPromptSection.cjs +58 -0
- package/dist/components/context-editor/SystemPromptSection.d.cts +25 -0
- package/dist/components/context-editor/SystemPromptSection.d.ts +25 -0
- package/dist/components/context-editor/SystemPromptSection.js +58 -0
- package/dist/components/context-editor/ToolsSection.cjs +31 -0
- package/dist/components/context-editor/ToolsSection.d.cts +14 -0
- package/dist/components/context-editor/ToolsSection.d.ts +14 -0
- package/dist/components/context-editor/ToolsSection.js +31 -0
- package/dist/components/context-editor/index.cjs +8 -0
- package/dist/components/context-editor/index.d.cts +5 -0
- package/dist/components/context-editor/index.d.ts +5 -0
- package/dist/components/context-editor/index.js +8 -0
- package/dist/components/context-editor/parts.cjs +184 -0
- package/dist/components/context-editor/parts.d.cts +32 -0
- package/dist/components/context-editor/parts.d.ts +32 -0
- package/dist/components/context-editor/parts.js +184 -0
- package/dist/components/context-editor/use-context-editor-model.cjs +194 -0
- package/dist/components/context-editor/use-context-editor-model.d.cts +57 -0
- package/dist/components/context-editor/use-context-editor-model.d.ts +57 -0
- package/dist/components/context-editor/use-context-editor-model.js +194 -0
- package/dist/components/dashboard/activity-list.cjs +59 -0
- package/dist/components/dashboard/activity-list.d.cts +27 -0
- package/dist/components/dashboard/activity-list.d.ts +27 -0
- package/dist/components/dashboard/activity-list.js +59 -0
- package/dist/components/dashboard/catalog.cjs +322 -0
- package/dist/components/dashboard/catalog.d.cts +14 -0
- package/dist/components/dashboard/catalog.d.ts +14 -0
- package/dist/components/dashboard/catalog.js +322 -0
- package/dist/components/dashboard/data-boundary.cjs +29 -0
- package/dist/components/dashboard/data-boundary.d.cts +21 -0
- package/dist/components/dashboard/data-boundary.d.ts +21 -0
- package/dist/components/dashboard/data-boundary.js +29 -0
- package/dist/components/dashboard/empty-state.cjs +49 -0
- package/dist/components/dashboard/empty-state.d.cts +20 -0
- package/dist/components/dashboard/empty-state.d.ts +20 -0
- package/dist/components/dashboard/empty-state.js +49 -0
- package/dist/components/dashboard/icon-button.cjs +40 -0
- package/dist/components/dashboard/icon-button.d.cts +24 -0
- package/dist/components/dashboard/icon-button.d.ts +24 -0
- package/dist/components/dashboard/icon-button.js +40 -0
- package/dist/components/dashboard/index.cjs +15 -0
- package/dist/components/dashboard/index.d.cts +24 -0
- package/dist/components/dashboard/index.d.ts +24 -0
- package/dist/components/dashboard/index.js +15 -0
- package/dist/components/dashboard/item-list.cjs +71 -0
- package/dist/components/dashboard/item-list.d.cts +28 -0
- package/dist/components/dashboard/item-list.d.ts +28 -0
- package/dist/components/dashboard/item-list.js +71 -0
- package/dist/components/dashboard/key-value.cjs +39 -0
- package/dist/components/dashboard/key-value.d.cts +17 -0
- package/dist/components/dashboard/key-value.d.ts +17 -0
- package/dist/components/dashboard/key-value.js +39 -0
- package/dist/components/dashboard/layout.cjs +193 -0
- package/dist/components/dashboard/layout.d.cts +91 -0
- package/dist/components/dashboard/layout.d.ts +91 -0
- package/dist/components/dashboard/layout.js +193 -0
- package/dist/components/dashboard/metric.cjs +98 -0
- package/dist/components/dashboard/metric.d.cts +44 -0
- package/dist/components/dashboard/metric.d.ts +44 -0
- package/dist/components/dashboard/metric.js +98 -0
- package/dist/components/dashboard/progress-ring.cjs +77 -0
- package/dist/components/dashboard/progress-ring.d.cts +25 -0
- package/dist/components/dashboard/progress-ring.d.ts +25 -0
- package/dist/components/dashboard/progress-ring.js +77 -0
- package/dist/components/dashboard/skeletons.cjs +54 -0
- package/dist/components/dashboard/skeletons.d.cts +15 -0
- package/dist/components/dashboard/skeletons.d.ts +15 -0
- package/dist/components/dashboard/skeletons.js +54 -0
- package/dist/components/dashboard/spacing.cjs +10 -0
- package/dist/components/dashboard/spacing.d.cts +4 -0
- package/dist/components/dashboard/spacing.d.ts +4 -0
- package/dist/components/dashboard/spacing.js +10 -0
- package/dist/components/dashboard/stale-indicator.cjs +42 -0
- package/dist/components/dashboard/stale-indicator.d.cts +15 -0
- package/dist/components/dashboard/stale-indicator.d.ts +15 -0
- package/dist/components/dashboard/stale-indicator.js +42 -0
- package/dist/components/dashboard/status.cjs +62 -0
- package/dist/components/dashboard/status.d.cts +15 -0
- package/dist/components/dashboard/status.d.ts +15 -0
- package/dist/components/dashboard/status.js +62 -0
- package/dist/components/dashboard/tone.cjs +26 -0
- package/dist/components/dashboard/tone.d.cts +9 -0
- package/dist/components/dashboard/tone.d.ts +9 -0
- package/dist/components/dashboard/tone.js +26 -0
- package/dist/components/dashboard/typography.cjs +99 -0
- package/dist/components/dashboard/typography.d.cts +57 -0
- package/dist/components/dashboard/typography.d.ts +57 -0
- package/dist/components/dashboard/typography.js +99 -0
- package/dist/components/reference/ActivityExample.cjs +47 -0
- package/dist/components/reference/ActivityExample.d.cts +5 -0
- package/dist/components/reference/ActivityExample.d.ts +5 -0
- package/dist/components/reference/ActivityExample.js +47 -0
- package/dist/components/reference/ResourceExample.cjs +54 -0
- package/dist/components/reference/ResourceExample.d.cts +5 -0
- package/dist/components/reference/ResourceExample.d.ts +5 -0
- package/dist/components/reference/ResourceExample.js +54 -0
- package/dist/components/reference/SchedulerExample.cjs +68 -0
- package/dist/components/reference/SchedulerExample.d.cts +5 -0
- package/dist/components/reference/SchedulerExample.d.ts +5 -0
- package/dist/components/reference/SchedulerExample.js +68 -0
- package/dist/components/reference/StarterExample.cjs +34 -0
- package/dist/components/reference/StarterExample.d.cts +5 -0
- package/dist/components/reference/StarterExample.d.ts +5 -0
- package/dist/components/reference/StarterExample.js +34 -0
- package/dist/components/reference/index.cjs +10 -0
- package/dist/components/reference/index.d.cts +5 -0
- package/dist/components/reference/index.d.ts +5 -0
- package/dist/components/reference/index.js +10 -0
- package/dist/components/ui/badge.cjs +5 -1
- package/dist/components/ui/badge.d.cts +1 -1
- package/dist/components/ui/badge.d.ts +1 -1
- package/dist/components/ui/badge.js +5 -1
- package/dist/dashboard-catalog.cjs +8 -0
- package/dist/dashboard-catalog.d.cts +1 -0
- package/dist/dashboard-catalog.d.ts +1 -0
- package/dist/dashboard-catalog.js +8 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +15 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +1 -0
- package/dist/reference.cjs +12 -0
- package/dist/reference.d.cts +5 -0
- package/dist/reference.d.ts +5 -0
- package/dist/reference.js +12 -0
- package/dist/styles/glass-board.css +43 -0
- package/dist/styles/globals.css +59 -0
- package/package.json +70 -41
- package/src/components/context-editor/CapabilitySection.tsx +85 -0
- package/src/components/context-editor/ContextEditor.tsx +138 -0
- package/src/components/context-editor/PresetBar.tsx +94 -0
- package/src/components/context-editor/SkillsSection.tsx +37 -0
- package/src/components/context-editor/SystemPromptSection.tsx +88 -0
- package/src/components/context-editor/ToolsSection.tsx +37 -0
- package/src/components/context-editor/index.ts +7 -0
- package/src/components/context-editor/parts.tsx +217 -0
- package/src/components/context-editor/use-context-editor-model.ts +296 -0
- package/src/components/dashboard/__tests__/catalog.test.ts +57 -0
- package/src/components/dashboard/__tests__/glass.test.tsx +42 -0
- package/src/components/dashboard/__tests__/state.test.tsx +110 -0
- package/src/components/dashboard/__tests__/variants.test.tsx +95 -0
- package/src/components/dashboard/activity-list.tsx +99 -0
- package/src/components/dashboard/catalog.ts +351 -0
- package/src/components/dashboard/data-boundary.tsx +54 -0
- package/src/components/dashboard/empty-state.tsx +70 -0
- package/src/components/dashboard/icon-button.tsx +58 -0
- package/src/components/dashboard/index.ts +21 -0
- package/src/components/dashboard/item-list.tsx +106 -0
- package/src/components/dashboard/key-value.tsx +54 -0
- package/src/components/dashboard/layout.tsx +286 -0
- package/src/components/dashboard/metric.tsx +139 -0
- package/src/components/dashboard/progress-ring.tsx +100 -0
- package/src/components/dashboard/skeletons.tsx +73 -0
- package/src/components/dashboard/spacing.ts +14 -0
- package/src/components/dashboard/stale-indicator.tsx +55 -0
- package/src/components/dashboard/status.tsx +75 -0
- package/src/components/dashboard/tone.ts +35 -0
- package/src/components/dashboard/typography.tsx +151 -0
- package/src/components/reference/ActivityExample.tsx +68 -0
- package/src/components/reference/ResourceExample.tsx +78 -0
- package/src/components/reference/SchedulerExample.tsx +88 -0
- package/src/components/reference/StarterExample.tsx +48 -0
- package/src/components/reference/index.ts +10 -0
- package/src/components/ui/badge.tsx +6 -0
- package/src/dashboard-catalog.ts +13 -0
- package/src/index.ts +5 -0
- package/src/reference.ts +15 -0
- package/src/styles/glass-board.css +43 -0
- package/src/styles/globals.css +59 -0
- package/tsup.config.ts +6 -2
- package/vitest.config.ts +8 -0
- package/LICENSE +0 -201
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// Metric, MetricGroup and MetricCard — consistent numeric summaries.
|
|
2
|
+
//
|
|
3
|
+
// A metric is a label + value with optional icon, supporting text and a trend.
|
|
4
|
+
// Metric works inline; MetricCard wraps it in a contained surface; MetricGroup
|
|
5
|
+
// arranges several responsively. Values use tabular numerals so columns align.
|
|
6
|
+
|
|
7
|
+
import * as React from "react";
|
|
8
|
+
import { ArrowDown, ArrowRight, ArrowUp } from "lucide-react";
|
|
9
|
+
|
|
10
|
+
import { cn } from "../../lib/utils";
|
|
11
|
+
import { Icon } from "./typography";
|
|
12
|
+
import { toneColor, type Tone } from "./tone";
|
|
13
|
+
import { Grid, type GridProps } from "./layout";
|
|
14
|
+
|
|
15
|
+
export interface MetricTrend {
|
|
16
|
+
direction: "up" | "down" | "flat";
|
|
17
|
+
/** Change label, e.g. "+12%". */
|
|
18
|
+
value?: React.ReactNode;
|
|
19
|
+
/** Colour of the trend. Defaults to neutral — "up" is not always good. */
|
|
20
|
+
tone?: Tone;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const trendIcon = {
|
|
24
|
+
up: ArrowUp,
|
|
25
|
+
down: ArrowDown,
|
|
26
|
+
flat: ArrowRight,
|
|
27
|
+
} as const;
|
|
28
|
+
|
|
29
|
+
export interface MetricProps extends Omit<React.ComponentProps<"div">, "title"> {
|
|
30
|
+
label: React.ReactNode;
|
|
31
|
+
value: React.ReactNode;
|
|
32
|
+
/** Leading icon component (e.g. a lucide-react icon). */
|
|
33
|
+
icon?: React.ComponentType<{ className?: string }>;
|
|
34
|
+
/** Supporting text under the value. */
|
|
35
|
+
supporting?: React.ReactNode;
|
|
36
|
+
trend?: MetricTrend;
|
|
37
|
+
/** Semantic colour for the value. */
|
|
38
|
+
tone?: Tone;
|
|
39
|
+
/** Vertical (default) or horizontal label/value arrangement. */
|
|
40
|
+
orientation?: "vertical" | "horizontal";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** A single label + value metric, usable inline or inside a MetricCard. */
|
|
44
|
+
function Metric({
|
|
45
|
+
className,
|
|
46
|
+
label,
|
|
47
|
+
value,
|
|
48
|
+
icon,
|
|
49
|
+
supporting,
|
|
50
|
+
trend,
|
|
51
|
+
tone,
|
|
52
|
+
orientation = "vertical",
|
|
53
|
+
...props
|
|
54
|
+
}: MetricProps) {
|
|
55
|
+
const TrendIcon = trend ? trendIcon[trend.direction] : null;
|
|
56
|
+
return (
|
|
57
|
+
<div
|
|
58
|
+
data-slot="metric"
|
|
59
|
+
className={cn(
|
|
60
|
+
"flex min-w-0",
|
|
61
|
+
orientation === "vertical"
|
|
62
|
+
? "flex-col gap-0.5"
|
|
63
|
+
: "items-center justify-between gap-2",
|
|
64
|
+
className,
|
|
65
|
+
)}
|
|
66
|
+
{...props}
|
|
67
|
+
>
|
|
68
|
+
<div className="flex items-center gap-1.5">
|
|
69
|
+
{icon && <Icon icon={icon} size="sm" />}
|
|
70
|
+
<span className="truncate text-[11px] font-medium uppercase tracking-wide text-[var(--text-muted)]">
|
|
71
|
+
{label}
|
|
72
|
+
</span>
|
|
73
|
+
</div>
|
|
74
|
+
<div className="flex items-baseline gap-1.5">
|
|
75
|
+
<span
|
|
76
|
+
className={cn(
|
|
77
|
+
"text-lg font-semibold leading-none tabular-nums",
|
|
78
|
+
tone ? toneColor[tone] : "text-[var(--text-primary)]",
|
|
79
|
+
)}
|
|
80
|
+
>
|
|
81
|
+
{value}
|
|
82
|
+
</span>
|
|
83
|
+
{trend && TrendIcon && (
|
|
84
|
+
<span
|
|
85
|
+
className={cn(
|
|
86
|
+
"inline-flex items-center gap-0.5 text-xs font-medium tabular-nums",
|
|
87
|
+
trend.tone ? toneColor[trend.tone] : "text-[var(--text-muted)]",
|
|
88
|
+
)}
|
|
89
|
+
>
|
|
90
|
+
<TrendIcon className="size-3" aria-hidden />
|
|
91
|
+
{trend.value}
|
|
92
|
+
</span>
|
|
93
|
+
)}
|
|
94
|
+
</div>
|
|
95
|
+
{supporting && (
|
|
96
|
+
<span className="truncate text-xs text-[var(--text-muted)]">
|
|
97
|
+
{supporting}
|
|
98
|
+
</span>
|
|
99
|
+
)}
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ── MetricCard ───────────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
export interface MetricCardProps extends React.ComponentProps<"div"> {}
|
|
107
|
+
|
|
108
|
+
/** A contained surface for a Metric (or any compact summary content). */
|
|
109
|
+
function MetricCard({ className, ...props }: MetricCardProps) {
|
|
110
|
+
return (
|
|
111
|
+
<div
|
|
112
|
+
data-slot="metric-card"
|
|
113
|
+
className={cn(
|
|
114
|
+
"flex min-w-0 flex-col rounded-lg border border-[var(--surface-line)] bg-[var(--surface-raised)] p-3 shadow-[inset_0_1px_0_var(--surface-rim)]",
|
|
115
|
+
className,
|
|
116
|
+
)}
|
|
117
|
+
{...props}
|
|
118
|
+
/>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── MetricGroup ──────────────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
export interface MetricGroupProps extends GridProps {}
|
|
125
|
+
|
|
126
|
+
/** Arranges multiple metrics responsively in a bounded grid. */
|
|
127
|
+
function MetricGroup({ columns = "auto", gap = "sm", minColumnWidth = 96, ...props }: MetricGroupProps) {
|
|
128
|
+
return (
|
|
129
|
+
<Grid
|
|
130
|
+
data-slot="metric-group"
|
|
131
|
+
columns={columns}
|
|
132
|
+
gap={gap}
|
|
133
|
+
minColumnWidth={minColumnWidth}
|
|
134
|
+
{...props}
|
|
135
|
+
/>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export { Metric, MetricCard, MetricGroup };
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// ProgressRing — an accessible circular progress / donut gauge.
|
|
2
|
+
//
|
|
3
|
+
// Pure SVG so consuming plugins need no charting dependency. Exposes an
|
|
4
|
+
// accessible progressbar with value semantics and supports centre content.
|
|
5
|
+
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
|
|
8
|
+
import { cn } from "../../lib/utils";
|
|
9
|
+
import { toneColor, type Tone } from "./tone";
|
|
10
|
+
|
|
11
|
+
export interface ProgressRingProps
|
|
12
|
+
extends Omit<React.ComponentProps<"div">, "children"> {
|
|
13
|
+
/** Current value. */
|
|
14
|
+
value: number;
|
|
15
|
+
/** Maximum value the ring represents. */
|
|
16
|
+
max?: number;
|
|
17
|
+
/** Outer diameter in pixels. */
|
|
18
|
+
size?: number;
|
|
19
|
+
/** Stroke width in pixels. */
|
|
20
|
+
thickness?: number;
|
|
21
|
+
tone?: Tone;
|
|
22
|
+
/** Accessible name for the gauge. */
|
|
23
|
+
label?: string;
|
|
24
|
+
/** Show the percentage in the centre when no children are provided. */
|
|
25
|
+
showValue?: boolean;
|
|
26
|
+
/** Custom centre content (overrides `showValue`). */
|
|
27
|
+
children?: React.ReactNode;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Circular progress / donut gauge for a bounded value. */
|
|
31
|
+
function ProgressRing({
|
|
32
|
+
className,
|
|
33
|
+
value,
|
|
34
|
+
max = 100,
|
|
35
|
+
size = 44,
|
|
36
|
+
thickness = 4,
|
|
37
|
+
tone = "info",
|
|
38
|
+
label,
|
|
39
|
+
showValue = true,
|
|
40
|
+
children,
|
|
41
|
+
...props
|
|
42
|
+
}: ProgressRingProps) {
|
|
43
|
+
const clamped = Math.min(Math.max(value, 0), max);
|
|
44
|
+
const pct = max > 0 ? clamped / max : 0;
|
|
45
|
+
const radius = (size - thickness) / 2;
|
|
46
|
+
const circumference = 2 * Math.PI * radius;
|
|
47
|
+
const dashOffset = circumference * (1 - pct);
|
|
48
|
+
const center = size / 2;
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div
|
|
52
|
+
data-slot="progress-ring"
|
|
53
|
+
role="progressbar"
|
|
54
|
+
aria-label={label}
|
|
55
|
+
aria-valuenow={Math.round(clamped)}
|
|
56
|
+
aria-valuemin={0}
|
|
57
|
+
aria-valuemax={max}
|
|
58
|
+
className={cn(
|
|
59
|
+
"relative inline-flex shrink-0 items-center justify-center",
|
|
60
|
+
toneColor[tone],
|
|
61
|
+
className,
|
|
62
|
+
)}
|
|
63
|
+
style={{ width: size, height: size }}
|
|
64
|
+
{...props}
|
|
65
|
+
>
|
|
66
|
+
<svg width={size} height={size} className="-rotate-90">
|
|
67
|
+
<circle
|
|
68
|
+
cx={center}
|
|
69
|
+
cy={center}
|
|
70
|
+
r={radius}
|
|
71
|
+
fill="none"
|
|
72
|
+
stroke="var(--surface-line)"
|
|
73
|
+
strokeWidth={thickness}
|
|
74
|
+
/>
|
|
75
|
+
<circle
|
|
76
|
+
cx={center}
|
|
77
|
+
cy={center}
|
|
78
|
+
r={radius}
|
|
79
|
+
fill="none"
|
|
80
|
+
stroke="currentColor"
|
|
81
|
+
strokeWidth={thickness}
|
|
82
|
+
strokeLinecap="round"
|
|
83
|
+
strokeDasharray={circumference}
|
|
84
|
+
strokeDashoffset={dashOffset}
|
|
85
|
+
className="transition-[stroke-dashoffset] duration-500"
|
|
86
|
+
/>
|
|
87
|
+
</svg>
|
|
88
|
+
<div className="absolute inset-0 flex items-center justify-center text-[var(--text-primary)]">
|
|
89
|
+
{children ??
|
|
90
|
+
(showValue && (
|
|
91
|
+
<span className="text-xs font-semibold tabular-nums">
|
|
92
|
+
{Math.round(pct * 100)}%
|
|
93
|
+
</span>
|
|
94
|
+
))}
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export { ProgressRing };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Widget skeleton patterns — small compositions of the Skeleton primitive for
|
|
2
|
+
// the common metric, list and activity loading layouts, so every widget shows a
|
|
3
|
+
// consistent loading shape instead of hand-rolling one.
|
|
4
|
+
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
|
|
7
|
+
import { cn } from "../../lib/utils";
|
|
8
|
+
import { Skeleton } from "../ui/skeleton";
|
|
9
|
+
|
|
10
|
+
export interface SkeletonPatternProps extends React.ComponentProps<"div"> {
|
|
11
|
+
/** Number of rows / cells to render. */
|
|
12
|
+
count?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** A row of metric placeholders (label + value stacks). */
|
|
16
|
+
function MetricSkeleton({ className, count = 3, ...props }: SkeletonPatternProps) {
|
|
17
|
+
return (
|
|
18
|
+
<div
|
|
19
|
+
data-slot="metric-skeleton"
|
|
20
|
+
className={cn("flex gap-4", className)}
|
|
21
|
+
{...props}
|
|
22
|
+
>
|
|
23
|
+
{Array.from({ length: count }).map((_, i) => (
|
|
24
|
+
<div key={i} className="flex flex-1 flex-col gap-1.5">
|
|
25
|
+
<Skeleton className="h-2.5 w-12" />
|
|
26
|
+
<Skeleton className="h-5 w-8" />
|
|
27
|
+
</div>
|
|
28
|
+
))}
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** A stack of list-row placeholders. */
|
|
34
|
+
function ListSkeleton({ className, count = 4, ...props }: SkeletonPatternProps) {
|
|
35
|
+
return (
|
|
36
|
+
<div
|
|
37
|
+
data-slot="list-skeleton"
|
|
38
|
+
className={cn("flex flex-col gap-1", className)}
|
|
39
|
+
{...props}
|
|
40
|
+
>
|
|
41
|
+
{Array.from({ length: count }).map((_, i) => (
|
|
42
|
+
<div key={i} className="flex items-center gap-2 rounded-md p-2">
|
|
43
|
+
<Skeleton className="size-2 rounded-full" />
|
|
44
|
+
<Skeleton className="h-3 flex-1" />
|
|
45
|
+
<Skeleton className="h-3 w-8" />
|
|
46
|
+
</div>
|
|
47
|
+
))}
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** A stack of activity-row placeholders (marker + label + timestamp). */
|
|
53
|
+
function ActivitySkeleton({ className, count = 4, ...props }: SkeletonPatternProps) {
|
|
54
|
+
return (
|
|
55
|
+
<div
|
|
56
|
+
data-slot="activity-skeleton"
|
|
57
|
+
className={cn("flex flex-col gap-2", className)}
|
|
58
|
+
{...props}
|
|
59
|
+
>
|
|
60
|
+
{Array.from({ length: count }).map((_, i) => (
|
|
61
|
+
<div key={i} className="flex items-start gap-2">
|
|
62
|
+
<Skeleton className="mt-0.5 size-2 rounded-full" />
|
|
63
|
+
<div className="flex flex-1 flex-col gap-1">
|
|
64
|
+
<Skeleton className="h-3 w-3/4" />
|
|
65
|
+
<Skeleton className="h-2.5 w-1/2" />
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
))}
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export { MetricSkeleton, ListSkeleton, ActivitySkeleton };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Shared semantic spacing scale for dashboard layout components.
|
|
2
|
+
//
|
|
3
|
+
// One scale keeps gaps consistent across Stack, Inline, Grid and Section so
|
|
4
|
+
// widgets do not drift into ad-hoc `gap-[7px]` values.
|
|
5
|
+
|
|
6
|
+
export type Gap = "none" | "xs" | "sm" | "md" | "lg";
|
|
7
|
+
|
|
8
|
+
export const gapClass: Record<Gap, string> = {
|
|
9
|
+
none: "gap-0",
|
|
10
|
+
xs: "gap-1",
|
|
11
|
+
sm: "gap-2",
|
|
12
|
+
md: "gap-3",
|
|
13
|
+
lg: "gap-4",
|
|
14
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// StaleIndicator — a compact hint that displayed data may be out of date.
|
|
2
|
+
//
|
|
3
|
+
// Shows an optional last-updated value and an optional refresh action. The
|
|
4
|
+
// component owns no fetching; the plugin passes the label and handles refresh.
|
|
5
|
+
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import { Clock, RotateCw } from "lucide-react";
|
|
8
|
+
|
|
9
|
+
import { cn } from "../../lib/utils";
|
|
10
|
+
import { Icon } from "./typography";
|
|
11
|
+
import { IconButton } from "./icon-button";
|
|
12
|
+
|
|
13
|
+
export interface StaleIndicatorProps extends React.ComponentProps<"div"> {
|
|
14
|
+
/** Pre-formatted last-updated value, e.g. "Updated 5m ago". */
|
|
15
|
+
lastUpdated?: React.ReactNode;
|
|
16
|
+
/** Refresh handler. When provided, a refresh button is shown. */
|
|
17
|
+
onRefresh?: () => void;
|
|
18
|
+
/** Spin the refresh icon while a refresh is in flight. */
|
|
19
|
+
refreshing?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Compact "data may be stale" indicator with optional refresh. */
|
|
23
|
+
function StaleIndicator({
|
|
24
|
+
className,
|
|
25
|
+
lastUpdated,
|
|
26
|
+
onRefresh,
|
|
27
|
+
refreshing = false,
|
|
28
|
+
...props
|
|
29
|
+
}: StaleIndicatorProps) {
|
|
30
|
+
return (
|
|
31
|
+
<div
|
|
32
|
+
data-slot="stale-indicator"
|
|
33
|
+
className={cn(
|
|
34
|
+
"flex items-center gap-1.5 text-[11px] text-[var(--text-muted)]",
|
|
35
|
+
className,
|
|
36
|
+
)}
|
|
37
|
+
{...props}
|
|
38
|
+
>
|
|
39
|
+
<Icon icon={Clock} size="xs" tone="warning" />
|
|
40
|
+
<span className="truncate">{lastUpdated ?? "Data may be out of date"}</span>
|
|
41
|
+
{onRefresh && (
|
|
42
|
+
<IconButton
|
|
43
|
+
icon={RotateCw}
|
|
44
|
+
label="Refresh"
|
|
45
|
+
size="xs"
|
|
46
|
+
onClick={onRefresh}
|
|
47
|
+
className={cn("ml-auto", refreshing && "animate-spin")}
|
|
48
|
+
disabled={refreshing}
|
|
49
|
+
/>
|
|
50
|
+
)}
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export { StaleIndicator };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Status — a semantic status dot, label or pill.
|
|
2
|
+
//
|
|
3
|
+
// Replaces the hand-rolled `size-2 rounded-full` dots with inline hex colours
|
|
4
|
+
// that appear across the existing widgets. Encodes only neutral semantic tones,
|
|
5
|
+
// never plugin-specific status names — callers map their domain status onto a
|
|
6
|
+
// tone.
|
|
7
|
+
|
|
8
|
+
import * as React from "react";
|
|
9
|
+
|
|
10
|
+
import { cn } from "../../lib/utils";
|
|
11
|
+
import { toneColor, toneDot, tonePill, type Tone } from "./tone";
|
|
12
|
+
|
|
13
|
+
export interface StatusProps extends React.ComponentProps<"span"> {
|
|
14
|
+
tone?: Tone;
|
|
15
|
+
/** Presentation: coloured dot + label, a tinted pill, or a bare dot. */
|
|
16
|
+
variant?: "dot" | "pill";
|
|
17
|
+
/** Add a soft glow to the dot (e.g. a live/active indicator). */
|
|
18
|
+
pulse?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Semantic status dot, label or pill with neutral/success/warning/error/info tones. */
|
|
22
|
+
function Status({
|
|
23
|
+
className,
|
|
24
|
+
tone = "neutral",
|
|
25
|
+
variant = "dot",
|
|
26
|
+
pulse = false,
|
|
27
|
+
children,
|
|
28
|
+
...props
|
|
29
|
+
}: StatusProps) {
|
|
30
|
+
const hasLabel = children != null && children !== "";
|
|
31
|
+
|
|
32
|
+
if (variant === "pill") {
|
|
33
|
+
return (
|
|
34
|
+
<span
|
|
35
|
+
data-slot="status"
|
|
36
|
+
data-tone={tone}
|
|
37
|
+
className={cn(
|
|
38
|
+
"inline-flex w-fit items-center gap-1.5 rounded-full px-2 py-0.5 text-xs font-medium",
|
|
39
|
+
tonePill[tone],
|
|
40
|
+
className,
|
|
41
|
+
)}
|
|
42
|
+
{...props}
|
|
43
|
+
>
|
|
44
|
+
<span className={cn("size-1.5 shrink-0 rounded-full", toneDot[tone])} />
|
|
45
|
+
{children}
|
|
46
|
+
</span>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<span
|
|
52
|
+
data-slot="status"
|
|
53
|
+
data-tone={tone}
|
|
54
|
+
// A bare dot carries no text, so surface the tone to assistive tech.
|
|
55
|
+
role={hasLabel ? undefined : "status"}
|
|
56
|
+
className={cn(
|
|
57
|
+
"inline-flex items-center gap-1.5 text-xs font-medium text-[var(--text-secondary)]",
|
|
58
|
+
className,
|
|
59
|
+
)}
|
|
60
|
+
{...props}
|
|
61
|
+
>
|
|
62
|
+
<span
|
|
63
|
+
className={cn(
|
|
64
|
+
"size-2 shrink-0 rounded-full",
|
|
65
|
+
toneDot[tone],
|
|
66
|
+
// ring-current picks up the tone's text colour for a matching glow.
|
|
67
|
+
pulse && cn(toneColor[tone], "ring-2 ring-current/25"),
|
|
68
|
+
)}
|
|
69
|
+
/>
|
|
70
|
+
{children}
|
|
71
|
+
</span>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { Status };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Shared semantic tone vocabulary for dashboard components.
|
|
2
|
+
//
|
|
3
|
+
// Tones map to the `--status-*` design tokens in styles/globals.css so every
|
|
4
|
+
// dashboard component speaks one status language. Components must never encode
|
|
5
|
+
// plugin-specific status names (e.g. "running", "queued") — they map their
|
|
6
|
+
// domain status onto one of these neutral tones.
|
|
7
|
+
|
|
8
|
+
export type Tone = "neutral" | "success" | "warning" | "error" | "info";
|
|
9
|
+
|
|
10
|
+
/** Solid foreground colour for a tone (dots, icons, emphasised text). */
|
|
11
|
+
export const toneColor: Record<Tone, string> = {
|
|
12
|
+
neutral: "text-[var(--text-muted)]",
|
|
13
|
+
success: "text-[var(--status-success)]",
|
|
14
|
+
warning: "text-[var(--status-warning)]",
|
|
15
|
+
error: "text-[var(--status-error)]",
|
|
16
|
+
info: "text-[var(--status-info)]",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/** Solid background colour for a tone (status dots). */
|
|
20
|
+
export const toneDot: Record<Tone, string> = {
|
|
21
|
+
neutral: "bg-[var(--text-muted)]",
|
|
22
|
+
success: "bg-[var(--status-success)]",
|
|
23
|
+
warning: "bg-[var(--status-warning)]",
|
|
24
|
+
error: "bg-[var(--status-error)]",
|
|
25
|
+
info: "bg-[var(--status-info)]",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** Muted tinted background + matching text for a tone (pills, chips). */
|
|
29
|
+
export const tonePill: Record<Tone, string> = {
|
|
30
|
+
neutral: "bg-[var(--surface-flat)] text-[var(--text-secondary)]",
|
|
31
|
+
success: "bg-[var(--status-success-muted)] text-[var(--status-success)]",
|
|
32
|
+
warning: "bg-[var(--status-warning-muted)] text-[var(--status-warning)]",
|
|
33
|
+
error: "bg-[var(--status-error-muted)] text-[var(--status-error)]",
|
|
34
|
+
info: "bg-[var(--status-info-muted)] text-[var(--status-info)]",
|
|
35
|
+
};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// Typography and media primitives for dashboard widgets.
|
|
2
|
+
//
|
|
3
|
+
// Text, Heading and Icon give one type scale and one icon treatment so
|
|
4
|
+
// equivalent text and icons render identically across widgets, replacing the
|
|
5
|
+
// scattered `text-[9px]`/`text-[10px]` and per-widget muted colours.
|
|
6
|
+
|
|
7
|
+
import * as React from "react";
|
|
8
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
9
|
+
|
|
10
|
+
import { cn } from "../../lib/utils";
|
|
11
|
+
import { toneColor, type Tone } from "./tone";
|
|
12
|
+
|
|
13
|
+
// ── Text ─────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
const textVariants = cva("min-w-0", {
|
|
16
|
+
variants: {
|
|
17
|
+
variant: {
|
|
18
|
+
body: "text-sm text-[var(--text-primary)]",
|
|
19
|
+
label:
|
|
20
|
+
"text-[11px] font-semibold uppercase tracking-wider text-[var(--text-muted)]",
|
|
21
|
+
supporting: "text-xs text-[var(--text-secondary)]",
|
|
22
|
+
muted: "text-xs text-[var(--text-muted)]",
|
|
23
|
+
numeric:
|
|
24
|
+
"text-sm font-semibold tabular-nums text-[var(--text-primary)]",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
defaultVariants: { variant: "body" },
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export interface TextProps
|
|
31
|
+
extends Omit<React.ComponentProps<"span">, "color">,
|
|
32
|
+
VariantProps<typeof textVariants> {
|
|
33
|
+
/** Truncate to a single line with an ellipsis. */
|
|
34
|
+
truncate?: boolean;
|
|
35
|
+
/** Clamp to N lines. Overrides `truncate`. */
|
|
36
|
+
clamp?: 1 | 2 | 3;
|
|
37
|
+
/** Semantic colour override (e.g. a numeric value that turns red on error). */
|
|
38
|
+
tone?: Tone;
|
|
39
|
+
asChild?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const clampClass = {
|
|
43
|
+
1: "line-clamp-1",
|
|
44
|
+
2: "line-clamp-2",
|
|
45
|
+
3: "line-clamp-3",
|
|
46
|
+
} as const;
|
|
47
|
+
|
|
48
|
+
/** Semantic text with truncation, line-clamp and optional tone. */
|
|
49
|
+
function Text({
|
|
50
|
+
className,
|
|
51
|
+
variant,
|
|
52
|
+
truncate,
|
|
53
|
+
clamp,
|
|
54
|
+
tone,
|
|
55
|
+
...props
|
|
56
|
+
}: TextProps) {
|
|
57
|
+
return (
|
|
58
|
+
<span
|
|
59
|
+
data-slot="text"
|
|
60
|
+
className={cn(
|
|
61
|
+
textVariants({ variant }),
|
|
62
|
+
clamp ? clampClass[clamp] : truncate && "truncate",
|
|
63
|
+
tone && toneColor[tone],
|
|
64
|
+
className,
|
|
65
|
+
)}
|
|
66
|
+
{...props}
|
|
67
|
+
/>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── Heading ──────────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
const headingSize = {
|
|
74
|
+
sm: "text-xs font-semibold",
|
|
75
|
+
md: "text-sm font-semibold",
|
|
76
|
+
lg: "text-base font-semibold",
|
|
77
|
+
} as const;
|
|
78
|
+
|
|
79
|
+
export interface HeadingProps extends React.ComponentProps<"h2"> {
|
|
80
|
+
/** HTML heading level for correct document semantics (1–4). */
|
|
81
|
+
level?: 1 | 2 | 3 | 4;
|
|
82
|
+
/** Visual size, decoupled from the semantic level. */
|
|
83
|
+
size?: keyof typeof headingSize;
|
|
84
|
+
truncate?: boolean;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Compact heading with correct HTML semantics decoupled from visual size. */
|
|
88
|
+
function Heading({
|
|
89
|
+
className,
|
|
90
|
+
level = 2,
|
|
91
|
+
size = "md",
|
|
92
|
+
truncate,
|
|
93
|
+
...props
|
|
94
|
+
}: HeadingProps) {
|
|
95
|
+
const Tag = `h${level}` as "h1" | "h2" | "h3" | "h4";
|
|
96
|
+
return (
|
|
97
|
+
<Tag
|
|
98
|
+
data-slot="heading"
|
|
99
|
+
className={cn(
|
|
100
|
+
"leading-snug text-[var(--text-primary)]",
|
|
101
|
+
headingSize[size],
|
|
102
|
+
truncate && "truncate",
|
|
103
|
+
className,
|
|
104
|
+
)}
|
|
105
|
+
{...props}
|
|
106
|
+
/>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ── Icon ─────────────────────────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
const iconSize = {
|
|
113
|
+
xs: "size-3",
|
|
114
|
+
sm: "size-3.5",
|
|
115
|
+
md: "size-4",
|
|
116
|
+
lg: "size-5",
|
|
117
|
+
} as const;
|
|
118
|
+
|
|
119
|
+
export interface IconProps extends React.ComponentProps<"span"> {
|
|
120
|
+
/** The icon component to render (e.g. a lucide-react icon). */
|
|
121
|
+
icon: React.ComponentType<{ className?: string }>;
|
|
122
|
+
size?: keyof typeof iconSize;
|
|
123
|
+
tone?: Tone;
|
|
124
|
+
/**
|
|
125
|
+
* Accessible label. When omitted the icon is decorative and hidden from
|
|
126
|
+
* assistive tech; when provided it is exposed as an image with this name.
|
|
127
|
+
*/
|
|
128
|
+
label?: string;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Consistent icon wrapper for size, tone and accessibility. */
|
|
132
|
+
function Icon({ className, icon: IconCmp, size = "md", tone, label, ...props }: IconProps) {
|
|
133
|
+
return (
|
|
134
|
+
<span
|
|
135
|
+
data-slot="icon"
|
|
136
|
+
role={label ? "img" : undefined}
|
|
137
|
+
aria-label={label}
|
|
138
|
+
aria-hidden={label ? undefined : true}
|
|
139
|
+
className={cn(
|
|
140
|
+
"inline-flex shrink-0 items-center justify-center",
|
|
141
|
+
tone ? toneColor[tone] : "text-[var(--text-muted)]",
|
|
142
|
+
className,
|
|
143
|
+
)}
|
|
144
|
+
{...props}
|
|
145
|
+
>
|
|
146
|
+
<IconCmp className={iconSize[size]} />
|
|
147
|
+
</span>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export { Text, Heading, Icon };
|