@sero-ai/ui 0.3.2 → 0.4.1
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 +615 -0
- package/.turbo/turbo-typecheck.log +1 -1
- package/CHANGELOG.md +58 -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 +1 -0
- package/dist/components/dashboard/catalog.d.cts +13 -0
- package/dist/components/dashboard/catalog.d.ts +13 -0
- package/dist/components/dashboard/catalog.js +0 -0
- package/dist/components/dashboard/catalog.json +312 -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 +66 -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 +66 -0
- package/dist/components/dashboard/tone.cjs +34 -0
- package/dist/components/dashboard/tone.d.cts +15 -0
- package/dist/components/dashboard/tone.d.ts +15 -0
- package/dist/components/dashboard/tone.js +34 -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/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/index.cjs +1 -0
- package/dist/index.d.cts +16 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +1 -0
- package/dist/reference.cjs +1260 -0
- package/dist/reference.d.cts +11 -0
- package/dist/reference.d.ts +11 -0
- package/dist/reference.js +1230 -0
- package/dist/styles/glass-board.css +43 -0
- package/dist/styles/globals.css +59 -0
- package/package.json +16 -4
- package/scripts/ensure-pnpm-publish.mjs +23 -0
- package/scripts/smoke-dist.mjs +68 -0
- 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 +62 -0
- package/src/components/dashboard/__tests__/glass.test.tsx +42 -0
- package/src/components/dashboard/__tests__/state.test.tsx +127 -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.json +312 -0
- package/src/components/dashboard/catalog.ts +29 -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 +88 -0
- package/src/components/dashboard/tone.ts +48 -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/index.ts +7 -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 +18 -4
- package/tsup.reference.config.ts +15 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// KeyValue and KeyValueList — compact label/value metadata.
|
|
2
|
+
//
|
|
3
|
+
// For configuration summaries, totals and metadata where a full metric is too
|
|
4
|
+
// heavy. Uses a semantic description list with predictable alignment and
|
|
5
|
+
// value truncation.
|
|
6
|
+
|
|
7
|
+
import * as React from "react";
|
|
8
|
+
|
|
9
|
+
import { cn } from "../../lib/utils";
|
|
10
|
+
|
|
11
|
+
export interface KeyValueProps extends React.ComponentProps<"div"> {
|
|
12
|
+
label: React.ReactNode;
|
|
13
|
+
value: React.ReactNode;
|
|
14
|
+
/** Use mono + tabular numerals for the value (ids, counts, sizes). */
|
|
15
|
+
mono?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** A single label/value row rendered as a `dt`/`dd` pair. */
|
|
19
|
+
function KeyValue({ className, label, value, mono, ...props }: KeyValueProps) {
|
|
20
|
+
return (
|
|
21
|
+
<div
|
|
22
|
+
data-slot="key-value"
|
|
23
|
+
className={cn("flex items-baseline justify-between gap-3", className)}
|
|
24
|
+
{...props}
|
|
25
|
+
>
|
|
26
|
+
<dt className="shrink-0 truncate text-xs text-[var(--text-muted)]">
|
|
27
|
+
{label}
|
|
28
|
+
</dt>
|
|
29
|
+
<dd
|
|
30
|
+
className={cn(
|
|
31
|
+
"min-w-0 truncate text-right text-xs text-[var(--text-primary)]",
|
|
32
|
+
mono && "font-mono tabular-nums",
|
|
33
|
+
)}
|
|
34
|
+
>
|
|
35
|
+
{value}
|
|
36
|
+
</dd>
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface KeyValueListProps extends React.ComponentProps<"dl"> {}
|
|
42
|
+
|
|
43
|
+
/** A vertical list of KeyValue rows. */
|
|
44
|
+
function KeyValueList({ className, ...props }: KeyValueListProps) {
|
|
45
|
+
return (
|
|
46
|
+
<dl
|
|
47
|
+
data-slot="key-value-list"
|
|
48
|
+
className={cn("flex flex-col gap-1.5", className)}
|
|
49
|
+
{...props}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export { KeyValue, KeyValueList };
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
// Layout and structure primitives for dashboard widgets.
|
|
2
|
+
//
|
|
3
|
+
// WidgetContent, Stack, Inline, Grid, Section and Divider give widgets a
|
|
4
|
+
// consistent frame, spacing and container-query boundary without re-declaring
|
|
5
|
+
// `flex h-full flex-col gap-2 p-3` in every widget. They compose normal React
|
|
6
|
+
// children and accept `className` for escape hatches.
|
|
7
|
+
|
|
8
|
+
import * as React from "react";
|
|
9
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
10
|
+
|
|
11
|
+
import { cn } from "../../lib/utils";
|
|
12
|
+
import { Separator } from "../ui/separator";
|
|
13
|
+
import { gapClass, type Gap } from "./spacing";
|
|
14
|
+
|
|
15
|
+
// ── WidgetContent ────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
const widgetContentVariants = cva(
|
|
18
|
+
// Full-height frame with a container-query boundary so child components can
|
|
19
|
+
// respond to the widget's own size rather than the viewport.
|
|
20
|
+
"@container/widget flex h-full min-h-0 flex-col",
|
|
21
|
+
{
|
|
22
|
+
variants: {
|
|
23
|
+
padding: {
|
|
24
|
+
none: "p-0",
|
|
25
|
+
sm: "p-2",
|
|
26
|
+
md: "p-3",
|
|
27
|
+
lg: "p-4",
|
|
28
|
+
},
|
|
29
|
+
scroll: {
|
|
30
|
+
true: "overflow-auto",
|
|
31
|
+
false: "overflow-hidden",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
defaultVariants: { padding: "md", scroll: false },
|
|
35
|
+
},
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
export interface WidgetContentProps
|
|
39
|
+
extends React.ComponentProps<"div">,
|
|
40
|
+
VariantProps<typeof widgetContentVariants> {
|
|
41
|
+
/**
|
|
42
|
+
* Apply the glass token scope so surfaces read as translucent on the
|
|
43
|
+
* frosted dashboard tile. On by default; set `false` for solid full
|
|
44
|
+
* plugin views (outside a dashboard tile).
|
|
45
|
+
*/
|
|
46
|
+
glass?: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Full-height widget content frame with standard padding + a container query. */
|
|
50
|
+
function WidgetContent({
|
|
51
|
+
className,
|
|
52
|
+
padding,
|
|
53
|
+
scroll,
|
|
54
|
+
glass = true,
|
|
55
|
+
...props
|
|
56
|
+
}: WidgetContentProps) {
|
|
57
|
+
return (
|
|
58
|
+
<div
|
|
59
|
+
data-slot="widget-content"
|
|
60
|
+
className={cn(widgetContentVariants({ padding, scroll }), glass && "glass", className)}
|
|
61
|
+
{...props}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ── Stack ────────────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
const alignClass = {
|
|
69
|
+
start: "items-start",
|
|
70
|
+
center: "items-center",
|
|
71
|
+
end: "items-end",
|
|
72
|
+
stretch: "items-stretch",
|
|
73
|
+
} as const;
|
|
74
|
+
|
|
75
|
+
const justifyClass = {
|
|
76
|
+
start: "justify-start",
|
|
77
|
+
center: "justify-center",
|
|
78
|
+
end: "justify-end",
|
|
79
|
+
between: "justify-between",
|
|
80
|
+
around: "justify-around",
|
|
81
|
+
} as const;
|
|
82
|
+
|
|
83
|
+
export interface StackProps extends React.ComponentProps<"div"> {
|
|
84
|
+
gap?: Gap;
|
|
85
|
+
align?: keyof typeof alignClass;
|
|
86
|
+
justify?: keyof typeof justifyClass;
|
|
87
|
+
/**
|
|
88
|
+
* Grow to fill the remaining height of a flex parent (e.g. the top-level
|
|
89
|
+
* Stack under `WidgetContent`, so a nested `scroll` region can bound).
|
|
90
|
+
*/
|
|
91
|
+
fill?: boolean;
|
|
92
|
+
/** Fill remaining height and scroll overflow. Use for the main content flow. */
|
|
93
|
+
scroll?: boolean;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Vertical layout with semantic spacing, alignment and optional scrolling. */
|
|
97
|
+
function Stack({
|
|
98
|
+
className,
|
|
99
|
+
gap = "sm",
|
|
100
|
+
align,
|
|
101
|
+
justify,
|
|
102
|
+
fill = false,
|
|
103
|
+
scroll = false,
|
|
104
|
+
...props
|
|
105
|
+
}: StackProps) {
|
|
106
|
+
return (
|
|
107
|
+
<div
|
|
108
|
+
data-slot="stack"
|
|
109
|
+
className={cn(
|
|
110
|
+
"flex flex-col",
|
|
111
|
+
gapClass[gap],
|
|
112
|
+
align && alignClass[align],
|
|
113
|
+
justify && justifyClass[justify],
|
|
114
|
+
(fill || scroll) && "min-h-0 flex-1",
|
|
115
|
+
scroll && "overflow-auto",
|
|
116
|
+
className,
|
|
117
|
+
)}
|
|
118
|
+
{...props}
|
|
119
|
+
/>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ── Inline ───────────────────────────────────────────────────────
|
|
124
|
+
|
|
125
|
+
export interface InlineProps extends React.ComponentProps<"div"> {
|
|
126
|
+
gap?: Gap;
|
|
127
|
+
align?: keyof typeof alignClass;
|
|
128
|
+
justify?: keyof typeof justifyClass;
|
|
129
|
+
wrap?: boolean;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Horizontal layout with semantic spacing, alignment, wrap and justify. */
|
|
133
|
+
function Inline({
|
|
134
|
+
className,
|
|
135
|
+
gap = "sm",
|
|
136
|
+
align = "center",
|
|
137
|
+
justify,
|
|
138
|
+
wrap = false,
|
|
139
|
+
...props
|
|
140
|
+
}: InlineProps) {
|
|
141
|
+
return (
|
|
142
|
+
<div
|
|
143
|
+
data-slot="inline"
|
|
144
|
+
className={cn(
|
|
145
|
+
"flex flex-row",
|
|
146
|
+
gapClass[gap],
|
|
147
|
+
alignClass[align],
|
|
148
|
+
justify && justifyClass[justify],
|
|
149
|
+
wrap ? "flex-wrap" : "min-w-0",
|
|
150
|
+
className,
|
|
151
|
+
)}
|
|
152
|
+
{...props}
|
|
153
|
+
/>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ── Grid ─────────────────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
const columnsClass = {
|
|
160
|
+
1: "grid-cols-1",
|
|
161
|
+
2: "grid-cols-2",
|
|
162
|
+
3: "grid-cols-3",
|
|
163
|
+
4: "grid-cols-4",
|
|
164
|
+
} as const;
|
|
165
|
+
|
|
166
|
+
export interface GridProps extends React.ComponentProps<"div"> {
|
|
167
|
+
/** Fixed column count, or `auto` for a responsive min-width fill. */
|
|
168
|
+
columns?: keyof typeof columnsClass | "auto";
|
|
169
|
+
gap?: Gap;
|
|
170
|
+
/** Minimum column width for `columns="auto"`. */
|
|
171
|
+
minColumnWidth?: number;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Responsive grid with bounded columns for metric and summary layouts. */
|
|
175
|
+
function Grid({
|
|
176
|
+
className,
|
|
177
|
+
columns = "auto",
|
|
178
|
+
gap = "sm",
|
|
179
|
+
minColumnWidth = 120,
|
|
180
|
+
style,
|
|
181
|
+
...props
|
|
182
|
+
}: GridProps) {
|
|
183
|
+
const auto = columns === "auto";
|
|
184
|
+
return (
|
|
185
|
+
<div
|
|
186
|
+
data-slot="grid"
|
|
187
|
+
className={cn(
|
|
188
|
+
"grid",
|
|
189
|
+
gapClass[gap],
|
|
190
|
+
!auto && columnsClass[columns],
|
|
191
|
+
className,
|
|
192
|
+
)}
|
|
193
|
+
style={
|
|
194
|
+
auto
|
|
195
|
+
? {
|
|
196
|
+
gridTemplateColumns: `repeat(auto-fill, minmax(${minColumnWidth}px, 1fr))`,
|
|
197
|
+
...style,
|
|
198
|
+
}
|
|
199
|
+
: style
|
|
200
|
+
}
|
|
201
|
+
{...props}
|
|
202
|
+
/>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ── Section ──────────────────────────────────────────────────────
|
|
207
|
+
|
|
208
|
+
export interface SectionProps extends React.ComponentProps<"div"> {
|
|
209
|
+
/** Section heading text or node. Omit for an unlabelled group. */
|
|
210
|
+
heading?: React.ReactNode;
|
|
211
|
+
/** Supporting text shown under the heading. */
|
|
212
|
+
description?: React.ReactNode;
|
|
213
|
+
/** Trailing action aligned to the heading row (e.g. an IconButton). */
|
|
214
|
+
action?: React.ReactNode;
|
|
215
|
+
gap?: Gap;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Compact section with optional heading, description and trailing action. */
|
|
219
|
+
function Section({
|
|
220
|
+
className,
|
|
221
|
+
heading,
|
|
222
|
+
description,
|
|
223
|
+
action,
|
|
224
|
+
gap = "sm",
|
|
225
|
+
children,
|
|
226
|
+
...props
|
|
227
|
+
}: SectionProps) {
|
|
228
|
+
return (
|
|
229
|
+
<section
|
|
230
|
+
data-slot="section"
|
|
231
|
+
className={cn("flex flex-col", gapClass[gap], className)}
|
|
232
|
+
{...props}
|
|
233
|
+
>
|
|
234
|
+
{(heading || action) && (
|
|
235
|
+
<div className="flex items-center justify-between gap-2">
|
|
236
|
+
<div className="flex min-w-0 flex-col gap-0.5">
|
|
237
|
+
{heading && (
|
|
238
|
+
<h3 className="truncate text-[11px] font-semibold uppercase tracking-wider text-[var(--text-muted)]">
|
|
239
|
+
{heading}
|
|
240
|
+
</h3>
|
|
241
|
+
)}
|
|
242
|
+
{description && (
|
|
243
|
+
<p className="truncate text-xs text-[var(--text-muted)]">
|
|
244
|
+
{description}
|
|
245
|
+
</p>
|
|
246
|
+
)}
|
|
247
|
+
</div>
|
|
248
|
+
{action && <div className="shrink-0">{action}</div>}
|
|
249
|
+
</div>
|
|
250
|
+
)}
|
|
251
|
+
{children}
|
|
252
|
+
</section>
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ── Divider ──────────────────────────────────────────────────────
|
|
257
|
+
|
|
258
|
+
export interface DividerProps
|
|
259
|
+
extends React.ComponentProps<typeof Separator> {
|
|
260
|
+
/** Vertical spacing around a horizontal divider. */
|
|
261
|
+
spacing?: "none" | "sm" | "md";
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const dividerSpacing = { none: "my-0", sm: "my-1", md: "my-2" } as const;
|
|
265
|
+
|
|
266
|
+
/** Compact divider built on the Separator primitive. */
|
|
267
|
+
function Divider({
|
|
268
|
+
className,
|
|
269
|
+
orientation = "horizontal",
|
|
270
|
+
spacing = "sm",
|
|
271
|
+
...props
|
|
272
|
+
}: DividerProps) {
|
|
273
|
+
return (
|
|
274
|
+
<Separator
|
|
275
|
+
data-slot="divider"
|
|
276
|
+
orientation={orientation}
|
|
277
|
+
className={cn(
|
|
278
|
+
orientation === "horizontal" ? dividerSpacing[spacing] : "mx-1",
|
|
279
|
+
className,
|
|
280
|
+
)}
|
|
281
|
+
{...props}
|
|
282
|
+
/>
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export { WidgetContent, Stack, Inline, Grid, Section, Divider };
|
|
@@ -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
|
+
};
|