@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.
Files changed (190) hide show
  1. package/.turbo/turbo-build.log +628 -0
  2. package/.turbo/turbo-typecheck.log +1 -1
  3. package/CHANGELOG.md +38 -0
  4. package/dist/components/context-editor/CapabilitySection.cjs +57 -0
  5. package/dist/components/context-editor/CapabilitySection.d.cts +20 -0
  6. package/dist/components/context-editor/CapabilitySection.d.ts +20 -0
  7. package/dist/components/context-editor/CapabilitySection.js +57 -0
  8. package/dist/components/context-editor/ContextEditor.cjs +108 -0
  9. package/dist/components/context-editor/ContextEditor.d.cts +26 -0
  10. package/dist/components/context-editor/ContextEditor.d.ts +26 -0
  11. package/dist/components/context-editor/ContextEditor.js +108 -0
  12. package/dist/components/context-editor/PresetBar.cjs +71 -0
  13. package/dist/components/context-editor/PresetBar.d.cts +19 -0
  14. package/dist/components/context-editor/PresetBar.d.ts +19 -0
  15. package/dist/components/context-editor/PresetBar.js +71 -0
  16. package/dist/components/context-editor/SkillsSection.cjs +31 -0
  17. package/dist/components/context-editor/SkillsSection.d.cts +14 -0
  18. package/dist/components/context-editor/SkillsSection.d.ts +14 -0
  19. package/dist/components/context-editor/SkillsSection.js +31 -0
  20. package/dist/components/context-editor/SystemPromptSection.cjs +58 -0
  21. package/dist/components/context-editor/SystemPromptSection.d.cts +25 -0
  22. package/dist/components/context-editor/SystemPromptSection.d.ts +25 -0
  23. package/dist/components/context-editor/SystemPromptSection.js +58 -0
  24. package/dist/components/context-editor/ToolsSection.cjs +31 -0
  25. package/dist/components/context-editor/ToolsSection.d.cts +14 -0
  26. package/dist/components/context-editor/ToolsSection.d.ts +14 -0
  27. package/dist/components/context-editor/ToolsSection.js +31 -0
  28. package/dist/components/context-editor/index.cjs +8 -0
  29. package/dist/components/context-editor/index.d.cts +5 -0
  30. package/dist/components/context-editor/index.d.ts +5 -0
  31. package/dist/components/context-editor/index.js +8 -0
  32. package/dist/components/context-editor/parts.cjs +184 -0
  33. package/dist/components/context-editor/parts.d.cts +32 -0
  34. package/dist/components/context-editor/parts.d.ts +32 -0
  35. package/dist/components/context-editor/parts.js +184 -0
  36. package/dist/components/context-editor/use-context-editor-model.cjs +194 -0
  37. package/dist/components/context-editor/use-context-editor-model.d.cts +57 -0
  38. package/dist/components/context-editor/use-context-editor-model.d.ts +57 -0
  39. package/dist/components/context-editor/use-context-editor-model.js +194 -0
  40. package/dist/components/dashboard/activity-list.cjs +59 -0
  41. package/dist/components/dashboard/activity-list.d.cts +27 -0
  42. package/dist/components/dashboard/activity-list.d.ts +27 -0
  43. package/dist/components/dashboard/activity-list.js +59 -0
  44. package/dist/components/dashboard/catalog.cjs +322 -0
  45. package/dist/components/dashboard/catalog.d.cts +14 -0
  46. package/dist/components/dashboard/catalog.d.ts +14 -0
  47. package/dist/components/dashboard/catalog.js +322 -0
  48. package/dist/components/dashboard/data-boundary.cjs +29 -0
  49. package/dist/components/dashboard/data-boundary.d.cts +21 -0
  50. package/dist/components/dashboard/data-boundary.d.ts +21 -0
  51. package/dist/components/dashboard/data-boundary.js +29 -0
  52. package/dist/components/dashboard/empty-state.cjs +49 -0
  53. package/dist/components/dashboard/empty-state.d.cts +20 -0
  54. package/dist/components/dashboard/empty-state.d.ts +20 -0
  55. package/dist/components/dashboard/empty-state.js +49 -0
  56. package/dist/components/dashboard/icon-button.cjs +40 -0
  57. package/dist/components/dashboard/icon-button.d.cts +24 -0
  58. package/dist/components/dashboard/icon-button.d.ts +24 -0
  59. package/dist/components/dashboard/icon-button.js +40 -0
  60. package/dist/components/dashboard/index.cjs +15 -0
  61. package/dist/components/dashboard/index.d.cts +24 -0
  62. package/dist/components/dashboard/index.d.ts +24 -0
  63. package/dist/components/dashboard/index.js +15 -0
  64. package/dist/components/dashboard/item-list.cjs +71 -0
  65. package/dist/components/dashboard/item-list.d.cts +28 -0
  66. package/dist/components/dashboard/item-list.d.ts +28 -0
  67. package/dist/components/dashboard/item-list.js +71 -0
  68. package/dist/components/dashboard/key-value.cjs +39 -0
  69. package/dist/components/dashboard/key-value.d.cts +17 -0
  70. package/dist/components/dashboard/key-value.d.ts +17 -0
  71. package/dist/components/dashboard/key-value.js +39 -0
  72. package/dist/components/dashboard/layout.cjs +193 -0
  73. package/dist/components/dashboard/layout.d.cts +91 -0
  74. package/dist/components/dashboard/layout.d.ts +91 -0
  75. package/dist/components/dashboard/layout.js +193 -0
  76. package/dist/components/dashboard/metric.cjs +98 -0
  77. package/dist/components/dashboard/metric.d.cts +44 -0
  78. package/dist/components/dashboard/metric.d.ts +44 -0
  79. package/dist/components/dashboard/metric.js +98 -0
  80. package/dist/components/dashboard/progress-ring.cjs +77 -0
  81. package/dist/components/dashboard/progress-ring.d.cts +25 -0
  82. package/dist/components/dashboard/progress-ring.d.ts +25 -0
  83. package/dist/components/dashboard/progress-ring.js +77 -0
  84. package/dist/components/dashboard/skeletons.cjs +54 -0
  85. package/dist/components/dashboard/skeletons.d.cts +15 -0
  86. package/dist/components/dashboard/skeletons.d.ts +15 -0
  87. package/dist/components/dashboard/skeletons.js +54 -0
  88. package/dist/components/dashboard/spacing.cjs +10 -0
  89. package/dist/components/dashboard/spacing.d.cts +4 -0
  90. package/dist/components/dashboard/spacing.d.ts +4 -0
  91. package/dist/components/dashboard/spacing.js +10 -0
  92. package/dist/components/dashboard/stale-indicator.cjs +42 -0
  93. package/dist/components/dashboard/stale-indicator.d.cts +15 -0
  94. package/dist/components/dashboard/stale-indicator.d.ts +15 -0
  95. package/dist/components/dashboard/stale-indicator.js +42 -0
  96. package/dist/components/dashboard/status.cjs +62 -0
  97. package/dist/components/dashboard/status.d.cts +15 -0
  98. package/dist/components/dashboard/status.d.ts +15 -0
  99. package/dist/components/dashboard/status.js +62 -0
  100. package/dist/components/dashboard/tone.cjs +26 -0
  101. package/dist/components/dashboard/tone.d.cts +9 -0
  102. package/dist/components/dashboard/tone.d.ts +9 -0
  103. package/dist/components/dashboard/tone.js +26 -0
  104. package/dist/components/dashboard/typography.cjs +99 -0
  105. package/dist/components/dashboard/typography.d.cts +57 -0
  106. package/dist/components/dashboard/typography.d.ts +57 -0
  107. package/dist/components/dashboard/typography.js +99 -0
  108. package/dist/components/reference/ActivityExample.cjs +47 -0
  109. package/dist/components/reference/ActivityExample.d.cts +5 -0
  110. package/dist/components/reference/ActivityExample.d.ts +5 -0
  111. package/dist/components/reference/ActivityExample.js +47 -0
  112. package/dist/components/reference/ResourceExample.cjs +54 -0
  113. package/dist/components/reference/ResourceExample.d.cts +5 -0
  114. package/dist/components/reference/ResourceExample.d.ts +5 -0
  115. package/dist/components/reference/ResourceExample.js +54 -0
  116. package/dist/components/reference/SchedulerExample.cjs +68 -0
  117. package/dist/components/reference/SchedulerExample.d.cts +5 -0
  118. package/dist/components/reference/SchedulerExample.d.ts +5 -0
  119. package/dist/components/reference/SchedulerExample.js +68 -0
  120. package/dist/components/reference/StarterExample.cjs +34 -0
  121. package/dist/components/reference/StarterExample.d.cts +5 -0
  122. package/dist/components/reference/StarterExample.d.ts +5 -0
  123. package/dist/components/reference/StarterExample.js +34 -0
  124. package/dist/components/reference/index.cjs +10 -0
  125. package/dist/components/reference/index.d.cts +5 -0
  126. package/dist/components/reference/index.d.ts +5 -0
  127. package/dist/components/reference/index.js +10 -0
  128. package/dist/components/ui/badge.cjs +5 -1
  129. package/dist/components/ui/badge.d.cts +1 -1
  130. package/dist/components/ui/badge.d.ts +1 -1
  131. package/dist/components/ui/badge.js +5 -1
  132. package/dist/dashboard-catalog.cjs +8 -0
  133. package/dist/dashboard-catalog.d.cts +1 -0
  134. package/dist/dashboard-catalog.d.ts +1 -0
  135. package/dist/dashboard-catalog.js +8 -0
  136. package/dist/index.cjs +1 -0
  137. package/dist/index.d.cts +15 -0
  138. package/dist/index.d.ts +15 -0
  139. package/dist/index.js +1 -0
  140. package/dist/reference.cjs +12 -0
  141. package/dist/reference.d.cts +5 -0
  142. package/dist/reference.d.ts +5 -0
  143. package/dist/reference.js +12 -0
  144. package/dist/styles/glass-board.css +43 -0
  145. package/dist/styles/globals.css +59 -0
  146. package/package.json +70 -41
  147. package/src/components/context-editor/CapabilitySection.tsx +85 -0
  148. package/src/components/context-editor/ContextEditor.tsx +138 -0
  149. package/src/components/context-editor/PresetBar.tsx +94 -0
  150. package/src/components/context-editor/SkillsSection.tsx +37 -0
  151. package/src/components/context-editor/SystemPromptSection.tsx +88 -0
  152. package/src/components/context-editor/ToolsSection.tsx +37 -0
  153. package/src/components/context-editor/index.ts +7 -0
  154. package/src/components/context-editor/parts.tsx +217 -0
  155. package/src/components/context-editor/use-context-editor-model.ts +296 -0
  156. package/src/components/dashboard/__tests__/catalog.test.ts +57 -0
  157. package/src/components/dashboard/__tests__/glass.test.tsx +42 -0
  158. package/src/components/dashboard/__tests__/state.test.tsx +110 -0
  159. package/src/components/dashboard/__tests__/variants.test.tsx +95 -0
  160. package/src/components/dashboard/activity-list.tsx +99 -0
  161. package/src/components/dashboard/catalog.ts +351 -0
  162. package/src/components/dashboard/data-boundary.tsx +54 -0
  163. package/src/components/dashboard/empty-state.tsx +70 -0
  164. package/src/components/dashboard/icon-button.tsx +58 -0
  165. package/src/components/dashboard/index.ts +21 -0
  166. package/src/components/dashboard/item-list.tsx +106 -0
  167. package/src/components/dashboard/key-value.tsx +54 -0
  168. package/src/components/dashboard/layout.tsx +286 -0
  169. package/src/components/dashboard/metric.tsx +139 -0
  170. package/src/components/dashboard/progress-ring.tsx +100 -0
  171. package/src/components/dashboard/skeletons.tsx +73 -0
  172. package/src/components/dashboard/spacing.ts +14 -0
  173. package/src/components/dashboard/stale-indicator.tsx +55 -0
  174. package/src/components/dashboard/status.tsx +75 -0
  175. package/src/components/dashboard/tone.ts +35 -0
  176. package/src/components/dashboard/typography.tsx +151 -0
  177. package/src/components/reference/ActivityExample.tsx +68 -0
  178. package/src/components/reference/ResourceExample.tsx +78 -0
  179. package/src/components/reference/SchedulerExample.tsx +88 -0
  180. package/src/components/reference/StarterExample.tsx +48 -0
  181. package/src/components/reference/index.ts +10 -0
  182. package/src/components/ui/badge.tsx +6 -0
  183. package/src/dashboard-catalog.ts +13 -0
  184. package/src/index.ts +5 -0
  185. package/src/reference.ts +15 -0
  186. package/src/styles/glass-board.css +43 -0
  187. package/src/styles/globals.css +59 -0
  188. package/tsup.config.ts +6 -2
  189. package/vitest.config.ts +8 -0
  190. 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 };