@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,54 @@
1
+ // DataBoundary — a presentation boundary for runtime data states.
2
+ //
3
+ // It selects which content to show for loading / ready / empty / stale / error.
4
+ // It is presentation only: it does not fetch data, subscribe to sources or own
5
+ // retry behaviour. The plugin passes the current state and the fallback nodes.
6
+
7
+ import * as React from "react";
8
+
9
+ export type DataState = "loading" | "ready" | "empty" | "stale" | "error";
10
+
11
+ export interface DataBoundaryProps {
12
+ state: DataState;
13
+ /** Shown while `state === "loading"`. */
14
+ loading?: React.ReactNode;
15
+ /** Shown while `state === "empty"`. */
16
+ empty?: React.ReactNode;
17
+ /** Shown while `state === "error"`. */
18
+ error?: React.ReactNode;
19
+ /** Banner rendered above children while `state === "stale"`. */
20
+ stale?: React.ReactNode;
21
+ /** Ready content, also shown (below `stale`) while `state === "stale"`. */
22
+ children?: React.ReactNode;
23
+ }
24
+
25
+ /** Selects presentation for the current data state. */
26
+ function DataBoundary({
27
+ state,
28
+ loading,
29
+ empty,
30
+ error,
31
+ stale,
32
+ children,
33
+ }: DataBoundaryProps) {
34
+ switch (state) {
35
+ case "loading":
36
+ return <>{loading ?? null}</>;
37
+ case "empty":
38
+ return <>{empty ?? null}</>;
39
+ case "error":
40
+ return <>{error ?? null}</>;
41
+ case "stale":
42
+ return (
43
+ <>
44
+ {stale}
45
+ {children}
46
+ </>
47
+ );
48
+ case "ready":
49
+ default:
50
+ return <>{children}</>;
51
+ }
52
+ }
53
+
54
+ export { DataBoundary };
@@ -0,0 +1,70 @@
1
+ // EmptyState — a compact empty state built on the Empty primitives.
2
+ //
3
+ // Tightened for widget use (the base Empty primitive pads for full pages).
4
+ // Optional icon, title, message and action.
5
+
6
+ import * as React from "react";
7
+
8
+ import { cn } from "../../lib/utils";
9
+ import {
10
+ Empty,
11
+ EmptyContent,
12
+ EmptyDescription,
13
+ EmptyHeader,
14
+ EmptyMedia,
15
+ EmptyTitle,
16
+ } from "../ui/empty";
17
+ import { Icon } from "./typography";
18
+
19
+ export interface EmptyStateProps
20
+ extends Omit<React.ComponentProps<typeof Empty>, "title"> {
21
+ /** Leading icon component (e.g. a lucide-react icon). */
22
+ icon?: React.ComponentType<{ className?: string }>;
23
+ title: React.ReactNode;
24
+ message?: React.ReactNode;
25
+ /** Optional action (e.g. a Button). */
26
+ action?: React.ReactNode;
27
+ }
28
+
29
+ /** Compact empty state for widgets and plugin views. */
30
+ function EmptyState({
31
+ className,
32
+ icon,
33
+ title,
34
+ message,
35
+ action,
36
+ ...props
37
+ }: EmptyStateProps) {
38
+ return (
39
+ <Empty
40
+ data-slot="empty-state"
41
+ className={cn(
42
+ "flex-1 gap-3 border-0 p-4 md:p-4",
43
+ className,
44
+ )}
45
+ {...props}
46
+ >
47
+ <EmptyHeader className="gap-1.5">
48
+ {icon && (
49
+ <EmptyMedia
50
+ variant="icon"
51
+ className="size-9 border-[var(--surface-line)] bg-[var(--surface-flat)] text-[var(--text-muted)]"
52
+ >
53
+ <Icon icon={icon} size="md" className="text-current" />
54
+ </EmptyMedia>
55
+ )}
56
+ <EmptyTitle className="text-sm font-medium text-[var(--text-primary)]">
57
+ {title}
58
+ </EmptyTitle>
59
+ {message && (
60
+ <EmptyDescription className="text-xs text-[var(--text-muted)]">
61
+ {message}
62
+ </EmptyDescription>
63
+ )}
64
+ </EmptyHeader>
65
+ {action && <EmptyContent className="gap-2">{action}</EmptyContent>}
66
+ </Empty>
67
+ );
68
+ }
69
+
70
+ export { EmptyState };
@@ -0,0 +1,58 @@
1
+ // IconButton — a small icon-only button built on the Button primitive.
2
+ //
3
+ // Widget-appropriate sizing with a required accessible label, since an
4
+ // icon-only control carries no text for assistive tech.
5
+
6
+ import * as React from "react";
7
+
8
+ import { cn } from "../../lib/utils";
9
+ import { Button } from "../ui/button";
10
+ import { Icon } from "./typography";
11
+
12
+ const sizeMap = {
13
+ xs: "icon-xs",
14
+ sm: "icon-sm",
15
+ md: "icon",
16
+ } as const;
17
+
18
+ const iconSizeMap = {
19
+ xs: "xs",
20
+ sm: "sm",
21
+ md: "md",
22
+ } as const;
23
+
24
+ export interface IconButtonProps
25
+ extends Omit<React.ComponentProps<typeof Button>, "size" | "children"> {
26
+ /** The icon component to render (e.g. a lucide-react icon). */
27
+ icon: React.ComponentType<{ className?: string }>;
28
+ /** Required accessible label — the control has no visible text. */
29
+ label: string;
30
+ size?: keyof typeof sizeMap;
31
+ }
32
+
33
+ /** A compact icon-only button with a required accessible label. */
34
+ function IconButton({
35
+ className,
36
+ icon,
37
+ label,
38
+ size = "sm",
39
+ variant = "ghost",
40
+ ...props
41
+ }: IconButtonProps) {
42
+ return (
43
+ <Button
44
+ data-slot="icon-button"
45
+ type="button"
46
+ variant={variant}
47
+ size={sizeMap[size]}
48
+ aria-label={label}
49
+ title={label}
50
+ className={cn("text-[var(--text-muted)]", className)}
51
+ {...props}
52
+ >
53
+ <Icon icon={icon} size={iconSizeMap[size]} className="text-current" />
54
+ </Button>
55
+ );
56
+ }
57
+
58
+ export { IconButton };
@@ -0,0 +1,21 @@
1
+ // @sero-ai/ui dashboard components — shared compact presentation for widgets
2
+ // and full plugin views. Import from the package root: `@sero-ai/ui`.
3
+ //
4
+ // The component catalogue (metadata for discovery tooling) is exported
5
+ // separately from `@sero-ai/ui/dashboard-catalog`, not from here.
6
+
7
+ export * from "./tone";
8
+ export * from "./spacing";
9
+ export * from "./layout";
10
+ export * from "./typography";
11
+ export * from "./metric";
12
+ export * from "./status";
13
+ export * from "./key-value";
14
+ export * from "./item-list";
15
+ export * from "./activity-list";
16
+ export * from "./progress-ring";
17
+ export * from "./stale-indicator";
18
+ export * from "./data-boundary";
19
+ export * from "./empty-state";
20
+ export * from "./skeletons";
21
+ export * from "./icon-button";
@@ -0,0 +1,106 @@
1
+ // ItemList and ItemListItem — compact rows for dashboard lists.
2
+ //
3
+ // Built on the existing Item primitive, tightened for dense widget use. Each
4
+ // row takes optional leading media, primary/secondary text, trailing metadata
5
+ // and actions. ItemList adds a shared overflow ("+N more") treatment so widgets
6
+ // stop hand-rolling it.
7
+
8
+ import * as React from "react";
9
+
10
+ import { cn } from "../../lib/utils";
11
+ import {
12
+ Item,
13
+ ItemActions,
14
+ ItemContent,
15
+ ItemDescription,
16
+ ItemMedia,
17
+ ItemTitle,
18
+ } from "../ui/item";
19
+
20
+ export interface ItemListItemProps
21
+ extends Omit<React.ComponentProps<typeof Item>, "children"> {
22
+ /** Leading media — an icon, avatar or status marker. */
23
+ leading?: React.ReactNode;
24
+ primary: React.ReactNode;
25
+ secondary?: React.ReactNode;
26
+ /** Trailing metadata (e.g. a timestamp or count). */
27
+ trailing?: React.ReactNode;
28
+ /** Trailing interactive actions (e.g. an IconButton). */
29
+ actions?: React.ReactNode;
30
+ }
31
+
32
+ /** A single compact list row with optional media, text, metadata and actions. */
33
+ function ItemListItem({
34
+ className,
35
+ leading,
36
+ primary,
37
+ secondary,
38
+ trailing,
39
+ actions,
40
+ variant = "default",
41
+ ...props
42
+ }: ItemListItemProps) {
43
+ return (
44
+ <Item
45
+ data-slot="item-list-item"
46
+ variant={variant}
47
+ size="sm"
48
+ className={cn(
49
+ "gap-2 rounded-md bg-[var(--surface-flat)] p-2 transition-colors hover:bg-[var(--glass-hover)]",
50
+ className,
51
+ )}
52
+ {...props}
53
+ >
54
+ {leading && (
55
+ <ItemMedia className="self-center">{leading}</ItemMedia>
56
+ )}
57
+ <ItemContent className="gap-0.5">
58
+ <ItemTitle className="truncate text-xs font-medium text-[var(--text-primary)]">
59
+ {primary}
60
+ </ItemTitle>
61
+ {secondary && (
62
+ <ItemDescription className="line-clamp-1 text-xs text-[var(--text-muted)]">
63
+ {secondary}
64
+ </ItemDescription>
65
+ )}
66
+ </ItemContent>
67
+ {trailing && (
68
+ <span className="shrink-0 text-[11px] tabular-nums text-[var(--text-muted)]">
69
+ {trailing}
70
+ </span>
71
+ )}
72
+ {actions && <ItemActions>{actions}</ItemActions>}
73
+ </Item>
74
+ );
75
+ }
76
+
77
+ export interface ItemListProps extends React.ComponentProps<"div"> {
78
+ /** Count of items not shown; renders a "+N more" footer when > 0. */
79
+ overflowCount?: number;
80
+ }
81
+
82
+ /** A vertical stack of ItemListItem rows with a shared overflow footer. */
83
+ function ItemList({
84
+ className,
85
+ overflowCount = 0,
86
+ children,
87
+ ...props
88
+ }: ItemListProps) {
89
+ return (
90
+ <div
91
+ data-slot="item-list"
92
+ role="list"
93
+ className={cn("flex min-w-0 flex-col gap-1", className)}
94
+ {...props}
95
+ >
96
+ {children}
97
+ {overflowCount > 0 && (
98
+ <span className="px-2 text-[11px] text-[var(--text-muted)]">
99
+ +{overflowCount} more
100
+ </span>
101
+ )}
102
+ </div>
103
+ );
104
+ }
105
+
106
+ export { ItemList, ItemListItem };
@@ -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 };