@corbits/react-ui 0.1.0 → 0.3.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 (170) hide show
  1. package/LICENSE +174 -499
  2. package/README.md +228 -85
  3. package/dist/blocks/login/auth-layout.d.ts +1 -1
  4. package/dist/blocks/login/auth-layout.js +3 -33
  5. package/dist/blocks/login/login-form.js +6 -119
  6. package/dist/hooks/use-command-palette-navigation.js +18 -17
  7. package/dist/hooks/use-controllable-state.d.ts +2 -2
  8. package/dist/hooks/use-controllable-state.js +23 -15
  9. package/dist/hooks/use-delayed-autofocus.js +10 -11
  10. package/dist/hooks/use-dismissable-popover.d.ts +2 -2
  11. package/dist/hooks/use-dismissable-popover.js +21 -29
  12. package/dist/hooks/use-list-selection.js +54 -85
  13. package/dist/hooks/use-prefers-reduced-motion.js +8 -4
  14. package/dist/hooks/use-scroll-current-into-view.js +5 -7
  15. package/dist/index.d.ts +0 -11
  16. package/dist/index.js +10 -19
  17. package/dist/lib/artifact.js +16 -35
  18. package/dist/lib/chart-geometry.js +33 -20
  19. package/dist/lib/chart-palette.js +17 -31
  20. package/dist/lib/chat-message.js +32 -13
  21. package/dist/lib/chat-parts.js +2 -1
  22. package/dist/lib/csv.js +42 -26
  23. package/dist/lib/metrics.js +30 -48
  24. package/dist/lib/now-item.js +26 -25
  25. package/dist/lib/relative-time.js +14 -29
  26. package/dist/lib/theme.js +32 -30
  27. package/dist/lib/url.js +9 -8
  28. package/dist/lib/workflow-display-flow.js +37 -33
  29. package/dist/lib/workflow-registry.d.ts +0 -41
  30. package/dist/lib/workflow-registry.js +13 -23
  31. package/dist/lib/workflow-run-error.js +16 -20
  32. package/dist/lib/workflow-run-progress.js +94 -52
  33. package/dist/lib/workflow-run.js +17 -13
  34. package/dist/styles.css +257 -19
  35. package/dist/test/render-hook.js +11 -12
  36. package/dist/theme.css +92 -14
  37. package/dist/ui/agent-turn.d.ts +1 -1
  38. package/dist/ui/agent-turn.js +4 -37
  39. package/dist/ui/animated-number.js +15 -28
  40. package/dist/ui/approval-card.js +5 -90
  41. package/dist/ui/artifact-body.js +47 -125
  42. package/dist/ui/artifact-notice.d.ts +1 -1
  43. package/dist/ui/artifact-notice.js +9 -16
  44. package/dist/ui/avatar.d.ts +21 -4
  45. package/dist/ui/avatar.js +40 -49
  46. package/dist/ui/badge.js +4 -12
  47. package/dist/ui/bar-chart.d.ts +1 -1
  48. package/dist/ui/bar-chart.js +22 -74
  49. package/dist/ui/block-card.js +7 -45
  50. package/dist/ui/boot-screen.d.ts +1 -1
  51. package/dist/ui/boot-screen.js +4 -33
  52. package/dist/ui/bulk-action-bar.d.ts +1 -1
  53. package/dist/ui/bulk-action-bar.js +14 -33
  54. package/dist/ui/button.js +10 -24
  55. package/dist/ui/card.js +5 -25
  56. package/dist/ui/category-bars.js +14 -59
  57. package/dist/ui/chart-frame.d.ts +2 -2
  58. package/dist/ui/chart-frame.js +6 -85
  59. package/dist/ui/chat-composer.js +4 -16
  60. package/dist/ui/chat-dock-timing.js +7 -3
  61. package/dist/ui/chat-input.js +20 -100
  62. package/dist/ui/chat-panel.d.ts +1 -1
  63. package/dist/ui/chat-panel.js +7 -41
  64. package/dist/ui/chat-thread.js +13 -31
  65. package/dist/ui/checkbox.js +10 -52
  66. package/dist/ui/command-palette.js +85 -246
  67. package/dist/ui/compare-body.d.ts +1 -1
  68. package/dist/ui/compare-body.js +28 -98
  69. package/dist/ui/confirm-button.js +8 -15
  70. package/dist/ui/corbits-mark.js +7 -16
  71. package/dist/ui/csv-table.d.ts +1 -1
  72. package/dist/ui/csv-table.js +8 -50
  73. package/dist/ui/dialog.d.ts +1 -1
  74. package/dist/ui/dialog.js +32 -62
  75. package/dist/ui/dictation-status-line.js +7 -25
  76. package/dist/ui/dither-canvas.d.ts +1 -1
  77. package/dist/ui/dither-canvas.js +31 -56
  78. package/dist/ui/embed-body.d.ts +1 -1
  79. package/dist/ui/embed-body.js +7 -56
  80. package/dist/ui/empty-state.d.ts +1 -1
  81. package/dist/ui/empty-state.js +3 -26
  82. package/dist/ui/file-input.js +17 -37
  83. package/dist/ui/filter-bar.d.ts +1 -1
  84. package/dist/ui/filter-bar.js +11 -71
  85. package/dist/ui/filter-chip.d.ts +1 -1
  86. package/dist/ui/filter-chip.js +7 -15
  87. package/dist/ui/gate-block.d.ts +2 -1
  88. package/dist/ui/gate-block.js +4 -29
  89. package/dist/ui/horizontal-stepper.d.ts +1 -1
  90. package/dist/ui/horizontal-stepper.js +18 -45
  91. package/dist/ui/input.js +1 -6
  92. package/dist/ui/inspector-shell.d.ts +5 -5
  93. package/dist/ui/inspector-shell.js +14 -81
  94. package/dist/ui/intake-form.d.ts +1 -1
  95. package/dist/ui/intake-form.js +21 -97
  96. package/dist/ui/kind-card-grid.js +9 -31
  97. package/dist/ui/library-page-header.js +4 -32
  98. package/dist/ui/list-detail.js +5 -35
  99. package/dist/ui/live-run-banner.d.ts +1 -1
  100. package/dist/ui/live-run-banner.js +5 -15
  101. package/dist/ui/live-status-line.js +6 -24
  102. package/dist/ui/menu.js +7 -24
  103. package/dist/ui/message-bubble.d.ts +3 -3
  104. package/dist/ui/message-bubble.js +5 -44
  105. package/dist/ui/mic-button.d.ts +1 -1
  106. package/dist/ui/mic-button.js +15 -21
  107. package/dist/ui/mic-permission-dialog.js +5 -41
  108. package/dist/ui/notifications-bell.js +9 -43
  109. package/dist/ui/now-cards.d.ts +2 -2
  110. package/dist/ui/now-cards.js +16 -94
  111. package/dist/ui/page-panel.js +11 -11
  112. package/dist/ui/page-shell.js +4 -11
  113. package/dist/ui/parts-renderer.js +51 -149
  114. package/dist/ui/profile-card.js +7 -111
  115. package/dist/ui/progress-checklist.js +11 -49
  116. package/dist/ui/provider-mark.d.ts +2 -2
  117. package/dist/ui/provider-mark.js +9 -16
  118. package/dist/ui/quick-reply-chips.js +6 -14
  119. package/dist/ui/quote-card.js +12 -32
  120. package/dist/ui/reasoning-block.d.ts +1 -1
  121. package/dist/ui/reasoning-block.js +14 -33
  122. package/dist/ui/research-body.js +32 -213
  123. package/dist/ui/rich-empty-state.js +6 -48
  124. package/dist/ui/run-now-button.js +14 -15
  125. package/dist/ui/section.d.ts +1 -1
  126. package/dist/ui/section.js +4 -40
  127. package/dist/ui/select.js +1 -15
  128. package/dist/ui/selection-checkbox.d.ts +1 -1
  129. package/dist/ui/selection-checkbox.js +11 -29
  130. package/dist/ui/settings-panel.js +7 -50
  131. package/dist/ui/shimmer-text.js +3 -6
  132. package/dist/ui/sidebar-item-row.d.ts +1 -1
  133. package/dist/ui/sidebar-item-row.js +9 -39
  134. package/dist/ui/sidebar-panel.js +15 -40
  135. package/dist/ui/sidebar.js +15 -76
  136. package/dist/ui/skeleton.js +3 -7
  137. package/dist/ui/sortable-table.js +29 -72
  138. package/dist/ui/sparkline.js +14 -38
  139. package/dist/ui/stat-grid.js +17 -52
  140. package/dist/ui/status-dot.d.ts +1 -1
  141. package/dist/ui/status-dot.js +8 -20
  142. package/dist/ui/step-list.d.ts +1 -0
  143. package/dist/ui/step-list.js +11 -50
  144. package/dist/ui/switch.js +4 -16
  145. package/dist/ui/table.js +7 -32
  146. package/dist/ui/tabs.js +39 -54
  147. package/dist/ui/textarea.js +10 -19
  148. package/dist/ui/theme-provider.js +54 -78
  149. package/dist/ui/theme-toggle.js +13 -16
  150. package/dist/ui/thinking-indicator.js +3 -15
  151. package/dist/ui/thinking-label.js +16 -35
  152. package/dist/ui/thinking-mark.d.ts +1 -1
  153. package/dist/ui/thinking-mark.js +4 -145
  154. package/dist/ui/time-series-chart.js +43 -174
  155. package/dist/ui/toast.js +12 -20
  156. package/dist/ui/token-mosaic.js +5 -43
  157. package/dist/ui/tool-block.js +18 -70
  158. package/dist/ui/tool-narrative.d.ts +1 -1
  159. package/dist/ui/tool-narrative.js +10 -57
  160. package/dist/ui/tool-picker.js +57 -145
  161. package/dist/ui/tooltip.d.ts +1 -1
  162. package/dist/ui/tooltip.js +4 -33
  163. package/dist/ui/top-bar.js +14 -72
  164. package/dist/ui/trace-waterfall.d.ts +1 -1
  165. package/dist/ui/trace-waterfall.js +21 -93
  166. package/dist/ui/typing-indicator.d.ts +1 -1
  167. package/dist/ui/typing-indicator.js +4 -18
  168. package/dist/ui/view-toggle.js +10 -29
  169. package/dist/ui/voice-waveform.js +3 -11
  170. package/package.json +96 -535
@@ -1,12 +1,16 @@
1
+ "use client";
1
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
3
  import { useId } from "react";
3
4
  import { cn } from "../lib/utils.js";
4
5
  import { Input } from "./input.js";
5
- /** True when every required field has a value — for enabling a submit control. */ export function intakeFieldsComplete(fields, values) {
6
- return fields.every((field)=>{
7
- if (field.required !== true) return true;
6
+ /** True when every required field has a value — for enabling a submit control. */
7
+ export function intakeFieldsComplete(fields, values) {
8
+ return fields.every((field) => {
9
+ if (field.required !== true)
10
+ return true;
8
11
  const value = values[field.name];
9
- if (typeof value === "string") return value.trim().length > 0;
12
+ if (typeof value === "string")
13
+ return value.trim().length > 0;
10
14
  return value !== undefined && value !== null;
11
15
  });
12
16
  }
@@ -14,106 +18,26 @@ import { Input } from "./input.js";
14
18
  * The inputs a run needs, rendered from a field list. Fully controlled: the
15
19
  * host owns the values, so the same form backs a one-off run and a saved
16
20
  * schedule without this file knowing which.
17
- */ export function IntakeForm({ fields, values, onChange, idPrefix, disabled = false, className }) {
21
+ */
22
+ export function IntakeForm({ fields, values, onChange, idPrefix, disabled = false, className, }) {
18
23
  const generatedPrefix = useId();
19
24
  const prefix = idPrefix ?? generatedPrefix;
20
- if (fields.length === 0) return null;
21
- const set = (name, value)=>onChange({
22
- ...values,
23
- [name]: value
24
- });
25
- return /*#__PURE__*/ _jsx("div", {
26
- className: cn("flex flex-col gap-4", className),
27
- children: fields.map((field)=>{
25
+ if (fields.length === 0)
26
+ return null;
27
+ const set = (name, value) => onChange({ ...values, [name]: value });
28
+ return (_jsx("div", { className: cn("flex flex-col gap-4", className), children: fields.map((field) => {
28
29
  const id = `${prefix}-${field.name}`;
29
30
  const helpId = field.help === undefined ? undefined : `${id}-help`;
30
31
  const value = values[field.name];
31
- return /*#__PURE__*/ _jsxs("div", {
32
- className: "flex flex-col gap-1.5",
33
- children: [
34
- field.type === "boolean" ? /*#__PURE__*/ _jsxs("label", {
35
- htmlFor: id,
36
- className: "flex items-center gap-2 text-sm",
37
- children: [
38
- /*#__PURE__*/ _jsx("input", {
39
- id: id,
40
- type: "checkbox",
41
- checked: value === true,
42
- required: field.required,
43
- disabled: disabled,
44
- "aria-describedby": helpId,
45
- onChange: (event)=>set(field.name, event.target.checked),
46
- className: "size-4 rounded-sm border border-input accent-[var(--primary)]"
47
- }),
48
- field.label,
49
- field.required === true ? /*#__PURE__*/ _jsx(RequiredMark, {}) : null
50
- ]
51
- }) : /*#__PURE__*/ _jsxs(_Fragment, {
52
- children: [
53
- /*#__PURE__*/ _jsxs("label", {
54
- htmlFor: id,
55
- className: "text-sm font-medium",
56
- children: [
57
- field.label,
58
- field.required === true ? /*#__PURE__*/ _jsx(RequiredMark, {}) : null
59
- ]
60
- }),
61
- field.type === "textarea" ? /*#__PURE__*/ _jsx("textarea", {
62
- id: id,
63
- value: typeof value === "string" ? value : "",
64
- placeholder: field.placeholder,
65
- required: field.required,
66
- disabled: disabled,
67
- "aria-describedby": helpId,
68
- rows: 4,
69
- onChange: (event)=>set(field.name, event.target.value),
70
- className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm disabled:opacity-50"
71
- }) : field.type === "select" ? /*#__PURE__*/ _jsxs("select", {
72
- id: id,
73
- value: typeof value === "string" ? value : "",
74
- required: field.required,
75
- disabled: disabled,
76
- "aria-describedby": helpId,
77
- onChange: (event)=>set(field.name, event.target.value),
78
- className: "h-9 w-full rounded-md border border-input bg-background px-3 text-sm disabled:opacity-50",
79
- children: [
80
- /*#__PURE__*/ _jsx("option", {
81
- value: "",
82
- children: "Choose…"
83
- }),
84
- (field.options ?? []).map((option)=>/*#__PURE__*/ _jsx("option", {
85
- value: option.value,
86
- children: option.label
87
- }, option.value))
88
- ]
89
- }) : /*#__PURE__*/ _jsx(Input, {
90
- id: id,
91
- type: field.type === "number" ? "number" : "text",
92
- value: typeof value === "string" || typeof value === "number" ? String(value) : "",
93
- placeholder: field.placeholder,
94
- required: field.required,
95
- disabled: disabled,
96
- "aria-describedby": helpId,
97
- onChange: (event)=>set(field.name, field.type === "number" ? event.target.valueAsNumber : event.target.value)
98
- })
99
- ]
100
- }),
101
- field.help === undefined ? null : /*#__PURE__*/ _jsx("p", {
102
- id: helpId,
103
- className: "text-xs text-muted-foreground",
104
- children: field.help
105
- })
106
- ]
107
- }, field.name);
108
- })
109
- });
32
+ return (_jsxs("div", { className: "flex flex-col gap-1.5", children: [field.type === "boolean" ? (_jsxs("label", { htmlFor: id, className: "flex items-center gap-2 text-sm", children: [_jsx("input", { id: id, type: "checkbox", checked: value === true, required: field.required, disabled: disabled, "aria-describedby": helpId, onChange: (event) => set(field.name, event.target.checked), className: "size-4 rounded-sm border border-input accent-[var(--primary)]" }), field.label, field.required === true ? _jsx(RequiredMark, {}) : null] })) : (_jsxs(_Fragment, { children: [_jsxs("label", { htmlFor: id, className: "text-sm font-medium", children: [field.label, field.required === true ? _jsx(RequiredMark, {}) : null] }), field.type === "textarea" ? (_jsx("textarea", { id: id, value: typeof value === "string" ? value : "", placeholder: field.placeholder, required: field.required, disabled: disabled, "aria-describedby": helpId, rows: 4, onChange: (event) => set(field.name, event.target.value), className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm disabled:opacity-50" })) : field.type === "select" ? (_jsxs("select", { id: id, value: typeof value === "string" ? value : "", required: field.required, disabled: disabled, "aria-describedby": helpId, onChange: (event) => set(field.name, event.target.value), className: "h-9 w-full rounded-md border border-input bg-background px-3 text-sm disabled:opacity-50", children: [_jsx("option", { value: "", children: "Choose\u2026" }), (field.options ?? []).map((option) => (_jsx("option", { value: option.value, children: option.label }, option.value)))] })) : (_jsx(Input, { id: id, type: field.type === "number" ? "number" : "text", value: typeof value === "string" || typeof value === "number"
33
+ ? String(value)
34
+ : "", placeholder: field.placeholder, required: field.required, disabled: disabled, "aria-describedby": helpId, onChange: (event) => set(field.name, field.type === "number"
35
+ ? event.target.valueAsNumber
36
+ : event.target.value) }))] })), field.help === undefined ? null : (_jsx("p", { id: helpId, className: "text-xs text-muted-foreground", children: field.help }))] }, field.name));
37
+ }) }));
110
38
  }
111
39
  // The asterisk is decorative: `required` on the control is what actually tells
112
40
  // assistive tech, and a screen reader announcing "asterisk" helps nobody.
113
41
  function RequiredMark() {
114
- return /*#__PURE__*/ _jsx("span", {
115
- "aria-hidden": true,
116
- className: "ml-0.5 text-primary-emphasis",
117
- children: "*"
118
- });
42
+ return (_jsx("span", { "aria-hidden": true, className: "ml-0.5 text-primary-emphasis", children: "*" }));
119
43
  }
@@ -1,43 +1,21 @@
1
+ "use client";
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { cn } from "../lib/utils.js";
3
4
  const COLUMNS = {
4
5
  2: "grid-cols-2",
5
6
  3: "grid-cols-3",
6
- 4: "grid-cols-4"
7
+ 4: "grid-cols-4",
7
8
  };
8
9
  /**
9
10
  * Selectable card grid for channel-kind, grant-effect, provider, and
10
11
  * workbench-type pickers. Each card is a real button with `aria-pressed`;
11
12
  * the group is labelled for assistive tech.
12
- */ export function KindCardGrid({ options, value, onChange, label, columns = 2, className }) {
13
- return /*#__PURE__*/ _jsx("div", {
14
- role: "group",
15
- "aria-label": label,
16
- className: cn("grid gap-2", COLUMNS[columns], className),
17
- children: options.map((option)=>{
13
+ */
14
+ export function KindCardGrid({ options, value, onChange, label, columns = 2, className, }) {
15
+ return (_jsx("div", { role: "group", "aria-label": label, className: cn("grid gap-2", COLUMNS[columns], className), children: options.map((option) => {
18
16
  const selected = value === option.id;
19
- return /*#__PURE__*/ _jsxs("button", {
20
- type: "button",
21
- "aria-pressed": selected,
22
- disabled: option.disabled === true,
23
- onClick: ()=>onChange?.(option.id),
24
- className: cn("flex flex-col gap-1 border px-3.5 py-3 text-left transition-colors ease-out", "hover:bg-muted active:brightness-95 disabled:pointer-events-none disabled:opacity-50", selected ? "border-primary-emphasis bg-primary/10" : "border-border bg-card"),
25
- children: [
26
- option.icon === undefined ? null : /*#__PURE__*/ _jsx("span", {
27
- className: "mb-0.5 text-muted-foreground",
28
- "aria-hidden": true,
29
- children: option.icon
30
- }),
31
- /*#__PURE__*/ _jsx("span", {
32
- className: "text-sm font-semibold",
33
- children: option.title
34
- }),
35
- option.description === undefined ? null : /*#__PURE__*/ _jsx("span", {
36
- className: "text-xs leading-snug text-muted-foreground",
37
- children: option.description
38
- })
39
- ]
40
- }, option.id);
41
- })
42
- });
17
+ return (_jsxs("button", { type: "button", "aria-pressed": selected, disabled: option.disabled === true, onClick: () => onChange?.(option.id), className: cn("flex flex-col gap-1 border px-3.5 py-3 text-left transition-colors ease-out", "hover:bg-muted active:brightness-95 disabled:pointer-events-none disabled:opacity-50", selected
18
+ ? "border-primary-emphasis bg-primary/10"
19
+ : "border-border bg-card"), children: [option.icon === undefined ? null : (_jsx("span", { className: "mb-0.5 text-muted-foreground", "aria-hidden": true, children: option.icon })), _jsx("span", { className: "text-sm font-semibold", children: option.title }), option.description === undefined ? null : (_jsx("span", { className: "text-xs leading-snug text-muted-foreground", children: option.description }))] }, option.id));
20
+ }) }));
43
21
  }
@@ -2,42 +2,14 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cn } from "../lib/utils.js";
3
3
  const TITLE_CLASS = {
4
4
  lg: "text-[21px] font-semibold tracking-[-0.02em]",
5
- sm: "text-[17px] font-semibold tracking-[-0.01em]"
5
+ sm: "text-[17px] font-semibold tracking-[-0.01em]",
6
6
  };
7
7
  /**
8
8
  * The header row every library page shares: title, item count, then trailing
9
9
  * controls after a flexible spacer. The spacer is a real element rather than
10
10
  * `justify-between` so a `LibrarySearchInput` can expand leftward over it
11
11
  * without shifting its neighbours.
12
- */ export function LibraryPageHeader({ title, titleSize = "lg", count, subtitle, children, className }) {
13
- return /*#__PURE__*/ _jsxs("div", {
14
- "data-slot": "library-page-header",
15
- className: cn("flex min-w-0 flex-wrap items-center gap-[14px] px-4 pt-5 pb-[14px] sm:px-7", className),
16
- children: [
17
- /*#__PURE__*/ _jsxs("div", {
18
- className: "flex min-w-0 shrink-0 flex-col gap-0.5",
19
- children: [
20
- /*#__PURE__*/ _jsx("h1", {
21
- className: TITLE_CLASS[titleSize],
22
- children: title
23
- }),
24
- subtitle === undefined || subtitle === "" ? null : /*#__PURE__*/ _jsx("p", {
25
- className: "truncate text-[12px] text-muted-foreground",
26
- children: subtitle
27
- })
28
- ]
29
- }),
30
- count === undefined ? null : /*#__PURE__*/ _jsxs("span", {
31
- className: "shrink-0 rounded-md bg-muted px-[9px] py-[3px] font-mono text-[12px] text-muted-foreground",
32
- children: [
33
- count,
34
- " items"
35
- ]
36
- }),
37
- /*#__PURE__*/ _jsx("div", {
38
- className: "min-w-2 flex-1"
39
- }),
40
- children
41
- ]
42
- });
12
+ */
13
+ export function LibraryPageHeader({ title, titleSize = "lg", count, subtitle, children, className, }) {
14
+ return (_jsxs("div", { "data-slot": "library-page-header", className: cn("flex min-w-0 flex-wrap items-center gap-[14px] px-4 pt-5 pb-[14px] sm:px-7", className), children: [_jsxs("div", { className: "flex min-w-0 shrink-0 flex-col gap-0.5", children: [_jsx("h1", { className: TITLE_CLASS[titleSize], children: title }), subtitle === undefined || subtitle === "" ? null : (_jsx("p", { className: "truncate text-[12px] text-muted-foreground", children: subtitle }))] }), count === undefined ? null : (_jsxs("span", { className: "shrink-0 rounded-md bg-muted px-[9px] py-[3px] font-mono text-[12px] text-muted-foreground", children: [count, " items"] })), _jsx("div", { className: "min-w-2 flex-1" }), children] }));
43
15
  }
@@ -15,41 +15,11 @@ import { EmptyState } from "./empty-state.js";
15
15
  *
16
16
  * Both panes scroll independently, so reading a long detail does not scroll the
17
17
  * list out from under the selection.
18
- */ export function ListDetail({ list, detail, onCloseDetail, placeholder, detailLabel = "Details", className }) {
18
+ */
19
+ export function ListDetail({ list, detail, onCloseDetail, placeholder, detailLabel = "Details", className, }) {
19
20
  const open = detail !== null;
20
- return /*#__PURE__*/ _jsxs("div", {
21
- className: cn("flex min-h-0 flex-1 gap-6", className),
22
- children: [
23
- /*#__PURE__*/ _jsx("div", {
24
- className: cn("min-h-0 flex-1 overflow-y-auto lg:max-w-md lg:shrink-0 lg:border-r lg:border-border lg:pr-6", // Hidden, not merely visually stacked: leaving it in the tree on
21
+ return (_jsxs("div", { className: cn("flex min-h-0 flex-1 gap-6", className), children: [_jsx("div", { className: cn("min-h-0 flex-1 overflow-y-auto lg:max-w-md lg:shrink-0 lg:border-r lg:border-border lg:pr-6",
22
+ // Hidden, not merely visually stacked: leaving it in the tree on
25
23
  // mobile would keep every row in the tab order behind the detail.
26
- open && "hidden lg:block"),
27
- children: list
28
- }),
29
- /*#__PURE__*/ _jsx("section", {
30
- "aria-label": detailLabel,
31
- className: cn("min-h-0 min-w-0 flex-1 overflow-y-auto", !open && "hidden lg:block"),
32
- children: open ? /*#__PURE__*/ _jsxs(_Fragment, {
33
- children: [
34
- /*#__PURE__*/ _jsxs("button", {
35
- type: "button",
36
- onClick: onCloseDetail,
37
- className: "mb-3 inline-flex items-center gap-1.5 rounded-md text-sm text-muted-foreground transition-colors hover:text-foreground lg:hidden",
38
- children: [
39
- /*#__PURE__*/ _jsx(ArrowLeft, {
40
- className: "size-4",
41
- "aria-hidden": true
42
- }),
43
- "Back to list"
44
- ]
45
- }),
46
- detail
47
- ]
48
- }) : placeholder ?? /*#__PURE__*/ _jsx(EmptyState, {
49
- title: "Nothing selected",
50
- description: "Pick something from the list."
51
- })
52
- })
53
- ]
54
- });
24
+ open && "hidden lg:block"), children: list }), _jsx("section", { "aria-label": detailLabel, className: cn("min-h-0 min-w-0 flex-1 overflow-y-auto", !open && "hidden lg:block"), children: open ? (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", onClick: onCloseDetail, className: "mb-3 inline-flex items-center gap-1.5 rounded-md text-sm text-muted-foreground transition-colors hover:text-foreground lg:hidden", children: [_jsx(ArrowLeft, { className: "size-4", "aria-hidden": true }), "Back to list"] }), detail] })) : ((placeholder ?? (_jsx(EmptyState, { title: "Nothing selected", description: "Pick something from the list." })))) })] }));
55
25
  }
@@ -10,4 +10,4 @@ export type LiveRunBannerProps = {
10
10
  * running gets the info tone. Standalone so a consumer building its own run
11
11
  * surface can reuse just this strip.
12
12
  */
13
- export declare function LiveRunBanner({ awaiting, message, elapsed, className }: LiveRunBannerProps): import("react").JSX.Element;
13
+ export declare function LiveRunBanner({ awaiting, message, elapsed, className, }: LiveRunBannerProps): import("react").JSX.Element;
@@ -5,19 +5,9 @@ import { cn } from "../lib/utils.js";
5
5
  * accent tone that marks "this needs you" everywhere else in the registry,
6
6
  * running gets the info tone. Standalone so a consumer building its own run
7
7
  * surface can reuse just this strip.
8
- */ export function LiveRunBanner({ awaiting = false, message, elapsed, className }) {
9
- return /*#__PURE__*/ _jsxs("div", {
10
- role: "status",
11
- className: cn("mb-3 flex flex-wrap items-center justify-between gap-2 rounded-lg border px-3 py-2 text-xs", awaiting ? "border-primary-emphasis/35 bg-primary/10 text-primary-emphasis" : "border-accent bg-accent text-accent-foreground", className),
12
- children: [
13
- /*#__PURE__*/ _jsx("span", {
14
- className: "font-semibold",
15
- children: message ?? (awaiting ? "Paused for your input" : "Running")
16
- }),
17
- elapsed === undefined ? null : /*#__PURE__*/ _jsx("span", {
18
- className: "font-mono text-[11.5px] tabular-nums opacity-90",
19
- children: elapsed
20
- })
21
- ]
22
- });
8
+ */
9
+ export function LiveRunBanner({ awaiting = false, message, elapsed, className, }) {
10
+ return (_jsxs("div", { role: "status", className: cn("mb-3 flex flex-wrap items-center justify-between gap-2 rounded-lg border px-3 py-2 text-xs", awaiting
11
+ ? "border-primary-emphasis/35 bg-primary/10 text-primary-emphasis"
12
+ : "border-accent bg-accent text-accent-foreground", className), children: [_jsx("span", { className: "font-semibold", children: message ?? (awaiting ? "Paused for your input" : "Running") }), elapsed === undefined ? null : (_jsx("span", { className: "font-mono text-[11.5px] tabular-nums opacity-90", children: elapsed }))] }));
23
13
  }
@@ -3,20 +3,9 @@ import { cn } from "../lib/utils.js";
3
3
  /** The single "what's happening now" line under a stepper. A spinning ring
4
4
  * plus text, not colour alone — `motion-reduce:animate-none` stills the
5
5
  * spinner without hiding the line, since the words already carry the
6
- * meaning. */ export function LiveStatusLine({ label, className }) {
7
- return /*#__PURE__*/ _jsxs("div", {
8
- role: "status",
9
- "aria-live": "polite",
10
- className: cn("flex items-center gap-2 border-b border-border bg-card px-6 py-2.5 text-sm text-muted-foreground", className),
11
- children: [
12
- /*#__PURE__*/ _jsx("span", {
13
- "aria-hidden": true,
14
- className: "size-3 animate-spin rounded-full border-2 border-border border-t-primary-emphasis motion-reduce:animate-none"
15
- }),
16
- label,
17
- "…"
18
- ]
19
- });
6
+ * meaning. */
7
+ export function LiveStatusLine({ label, className }) {
8
+ return (_jsxs("div", { role: "status", "aria-live": "polite", className: cn("flex items-center gap-2 border-b border-border bg-card px-6 py-2.5 text-sm text-muted-foreground", className), children: [_jsx("span", { "aria-hidden": true, className: "size-3 animate-spin rounded-full border-2 border-border border-t-primary-emphasis motion-reduce:animate-none" }), label, "\u2026"] }));
20
9
  }
21
10
  /**
22
11
  * Layout-stable wrapper for `LiveStatusLine`: a `null` label collapses the
@@ -24,14 +13,7 @@ import { cn } from "../lib/utils.js";
24
13
  * below it doesn't jump at every gate-to-processing boundary. The transition
25
14
  * is a plain CSS grid-rows trick — no animation library — and
26
15
  * `prefers-reduced-motion` disables it globally via the theme's base layer.
27
- */ export function LiveStatusSlot({ label, className }) {
28
- return /*#__PURE__*/ _jsx("div", {
29
- className: cn("grid transition-[grid-template-rows] duration-200 ease-out", label === null ? "grid-rows-[0fr]" : "grid-rows-[1fr]", className),
30
- children: /*#__PURE__*/ _jsx("div", {
31
- className: "overflow-hidden",
32
- children: label === null ? null : /*#__PURE__*/ _jsx(LiveStatusLine, {
33
- label: label
34
- })
35
- })
36
- });
16
+ */
17
+ export function LiveStatusSlot({ label, className }) {
18
+ return (_jsx("div", { className: cn("grid transition-[grid-template-rows] duration-200 ease-out", label === null ? "grid-rows-[0fr]" : "grid-rows-[1fr]", className), children: _jsx("div", { className: "overflow-hidden", children: label === null ? null : _jsx(LiveStatusLine, { label: label }) }) }));
37
19
  }
package/dist/ui/menu.js CHANGED
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { jsx as _jsx } from "react/jsx-runtime";
2
3
  import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
3
4
  import { cn } from "../lib/utils.js";
@@ -9,37 +10,19 @@ import { cn } from "../lib/utils.js";
9
10
  * popover surface — a see-through menu over a busy page is the bug this
10
11
  * primitive exists to prevent — and the same item hover, so a menu opened from
11
12
  * a toolbar and one opened from a table row read as the same control.
12
- */ export const Menu = DropdownMenuPrimitive.Root;
13
+ */
14
+ export const Menu = DropdownMenuPrimitive.Root;
13
15
  export const MenuTrigger = DropdownMenuPrimitive.Trigger;
14
16
  export const MenuGroup = DropdownMenuPrimitive.Group;
15
17
  export function MenuContent({ className, sideOffset = 4, ...props }) {
16
- return /*#__PURE__*/ _jsx(DropdownMenuPrimitive.Portal, {
17
- children: /*#__PURE__*/ _jsx(DropdownMenuPrimitive.Content, {
18
- "data-slot": "menu-content",
19
- sideOffset: sideOffset,
20
- className: cn("z-50 min-w-32 overflow-hidden rounded-lg border border-border bg-popover p-1 text-popover-foreground shadow-lg", className),
21
- ...props
22
- })
23
- });
18
+ return (_jsx(DropdownMenuPrimitive.Portal, { children: _jsx(DropdownMenuPrimitive.Content, { "data-slot": "menu-content", sideOffset: sideOffset, className: cn("z-50 min-w-32 overflow-hidden rounded-lg border border-border bg-popover p-1 text-popover-foreground shadow-lg", className), ...props }) }));
24
19
  }
25
20
  export function MenuItem({ className, ...props }) {
26
- return /*#__PURE__*/ _jsx(DropdownMenuPrimitive.Item, {
27
- "data-slot": "menu-item",
28
- className: cn("flex w-full cursor-pointer items-center gap-2 rounded-md px-2.5 py-2 text-left text-sm outline-none select-none transition-colors data-[highlighted]:bg-muted data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className),
29
- ...props
30
- });
21
+ return (_jsx(DropdownMenuPrimitive.Item, { "data-slot": "menu-item", className: cn("flex w-full cursor-pointer items-center gap-2 rounded-md px-2.5 py-2 text-left text-sm outline-none select-none transition-colors data-[highlighted]:bg-muted data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className), ...props }));
31
22
  }
32
23
  export function MenuLabel({ className, ...props }) {
33
- return /*#__PURE__*/ _jsx(DropdownMenuPrimitive.Label, {
34
- "data-slot": "menu-label",
35
- className: cn("px-2.5 py-1.5 text-xs font-semibold text-muted-foreground", className),
36
- ...props
37
- });
24
+ return (_jsx(DropdownMenuPrimitive.Label, { "data-slot": "menu-label", className: cn("px-2.5 py-1.5 text-xs font-semibold text-muted-foreground", className), ...props }));
38
25
  }
39
26
  export function MenuSeparator({ className, ...props }) {
40
- return /*#__PURE__*/ _jsx(DropdownMenuPrimitive.Separator, {
41
- "data-slot": "menu-separator",
42
- className: cn("my-1 h-px bg-border", className),
43
- ...props
44
- });
27
+ return (_jsx(DropdownMenuPrimitive.Separator, { "data-slot": "menu-separator", className: cn("my-1 h-px bg-border", className), ...props }));
45
28
  }
@@ -3,8 +3,8 @@ import type { ChatMessage } from "../lib/chat-message.js";
3
3
  export type MessageBubbleProps = {
4
4
  readonly message: ChatMessage;
5
5
  /** Retry affordance for a failed send. Omit and a failure is only reported. */
6
- readonly onRetry?: () => void;
7
- readonly now?: number;
6
+ readonly onRetry?: (() => void) | undefined;
7
+ readonly now?: number | undefined;
8
8
  /** Rich body — a markdown renderer. Defaults to the message's plain text. */
9
9
  readonly children?: ReactNode;
10
10
  readonly className?: string;
@@ -21,4 +21,4 @@ export type MessageBubbleProps = {
21
21
  * renderer is the host's decision to make, with the host's sanitiser — pass one
22
22
  * as `children`.
23
23
  */
24
- export declare function MessageBubble({ message, onRetry, now, children, className }: MessageBubbleProps): import("react").JSX.Element;
24
+ export declare function MessageBubble({ message, onRetry, now, children, className, }: MessageBubbleProps): import("react").JSX.Element;
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { messageText } from "../lib/chat-message.js";
3
3
  import { formatRelativeTime } from "../lib/relative-time.js";
4
4
  import { cn } from "../lib/utils.js";
@@ -13,52 +13,13 @@ import { cn } from "../lib/utils.js";
13
13
  * Text is rendered as text. Passing agent output through a markdown or HTML
14
14
  * renderer is the host's decision to make, with the host's sanitiser — pass one
15
15
  * as `children`.
16
- */ export function MessageBubble({ message, onRetry, now, children, className }) {
16
+ */
17
+ export function MessageBubble({ message, onRetry, now, children, className, }) {
17
18
  const isUser = message.role === "user";
18
19
  const isSystem = message.role === "system";
19
20
  const failed = message.status === "failed";
20
21
  if (isSystem) {
21
- return /*#__PURE__*/ _jsx("p", {
22
- "data-slot": "message-bubble",
23
- className: cn("px-2 py-1 text-center text-xs text-muted-foreground", className),
24
- children: children ?? messageText(message)
25
- });
22
+ return (_jsx("p", { "data-slot": "message-bubble", className: cn("px-2 py-1 text-center text-xs text-muted-foreground", className), children: children ?? messageText(message) }));
26
23
  }
27
- return /*#__PURE__*/ _jsxs("div", {
28
- "data-slot": "message-bubble",
29
- className: cn("flex flex-col gap-1", isUser ? "items-end" : "items-start", className),
30
- children: [
31
- /*#__PURE__*/ _jsx("div", {
32
- className: cn("max-w-[46rem] text-sm leading-relaxed break-words whitespace-pre-wrap", isUser && "rounded-lg bg-muted px-3 py-2", failed && "opacity-60"),
33
- children: children ?? messageText(message)
34
- }),
35
- /*#__PURE__*/ _jsxs("div", {
36
- className: "flex items-center gap-2 text-[11px] text-muted-foreground",
37
- children: [
38
- /*#__PURE__*/ _jsx("time", {
39
- dateTime: message.createdAt,
40
- children: formatRelativeTime(message.createdAt, now)
41
- }),
42
- message.status === "sending" ? /*#__PURE__*/ _jsx("span", {
43
- children: "Sending…"
44
- }) : null,
45
- failed ? /*#__PURE__*/ _jsxs(_Fragment, {
46
- children: [
47
- /*#__PURE__*/ _jsx("span", {
48
- role: "alert",
49
- className: "text-destructive",
50
- children: "Not sent"
51
- }),
52
- onRetry === undefined ? null : /*#__PURE__*/ _jsx("button", {
53
- type: "button",
54
- onClick: onRetry,
55
- className: "text-primary-emphasis hover:underline",
56
- children: "Retry"
57
- })
58
- ]
59
- }) : null
60
- ]
61
- })
62
- ]
63
- });
24
+ return (_jsxs("div", { "data-slot": "message-bubble", className: cn("flex flex-col gap-1", isUser ? "items-end" : "items-start", className), children: [_jsx("div", { className: cn("max-w-[46rem] text-sm leading-relaxed break-words whitespace-pre-wrap", isUser && "rounded-lg bg-muted px-3 py-2", failed && "opacity-60"), children: children ?? messageText(message) }), _jsxs("div", { className: "flex items-center gap-2 text-[11px] text-muted-foreground", children: [_jsx("time", { dateTime: message.createdAt, children: formatRelativeTime(message.createdAt, now) }), message.status === "sending" ? _jsx("span", { children: "Sending\u2026" }) : null, failed ? (_jsxs(_Fragment, { children: [_jsx("span", { role: "alert", className: "text-destructive", children: "Not sent" }), onRetry === undefined ? null : (_jsx("button", { type: "button", onClick: onRetry, className: "text-primary-emphasis hover:underline", children: "Retry" }))] })) : null] })] }));
64
25
  }
@@ -32,4 +32,4 @@ export type MicButtonProps = {
32
32
  * `unsupported` is disabled. `denied` stays clickable so the host can open
33
33
  * `MicPermissionDialog` from `onToggle`.
34
34
  */
35
- export declare function MicButton({ state, onToggle, disabled, className }: MicButtonProps): import("react").JSX.Element;
35
+ export declare function MicButton({ state, onToggle, disabled, className, }: MicButtonProps): import("react").JSX.Element;
@@ -1,11 +1,16 @@
1
+ "use client";
1
2
  import { jsx as _jsx } from "react/jsx-runtime";
2
3
  import { Mic, MicOff, Square } from "lucide-react";
3
4
  import { cn } from "../lib/utils.js";
4
5
  function labelFor(state) {
5
- if (state === "denied") return "Microphone blocked";
6
- if (state === "unsupported") return "Dictation unavailable";
7
- if (state === "listening") return "Stop dictating";
8
- if (state === "starting") return "Starting microphone";
6
+ if (state === "denied")
7
+ return "Microphone blocked";
8
+ if (state === "unsupported")
9
+ return "Dictation unavailable";
10
+ if (state === "listening")
11
+ return "Stop dictating";
12
+ if (state === "starting")
13
+ return "Starting microphone";
9
14
  return "Dictate instead of typing";
10
15
  }
11
16
  /**
@@ -34,28 +39,17 @@ function labelFor(state) {
34
39
  * stays the idle mic so it cannot be mistaken for a live capture.
35
40
  * `unsupported` is disabled. `denied` stays clickable so the host can open
36
41
  * `MicPermissionDialog` from `onToggle`.
37
- */ export function MicButton({ state, onToggle, disabled = false, className }) {
42
+ */
43
+ export function MicButton({ state, onToggle, disabled = false, className, }) {
38
44
  const listening = state === "listening";
39
45
  const denied = state === "denied";
40
46
  const unusable = disabled || state === "unsupported";
41
47
  const label = labelFor(state);
42
48
  const Glyph = listening ? Square : denied ? MicOff : Mic;
43
- return /*#__PURE__*/ _jsx("button", {
44
- type: "button",
45
- // Denied opens the permission dialog rather than toggling — declare the
46
- // popup and drop the toggle semantic.
47
- "aria-pressed": denied ? undefined : listening,
48
- "aria-haspopup": denied ? "dialog" : undefined,
49
- "aria-label": label,
50
- disabled: unusable,
49
+ return (_jsx("button", { type: "button", "aria-pressed": denied ? undefined : listening, "aria-haspopup": denied ? "dialog" : undefined, "aria-label": label, disabled: unusable,
51
50
  // Tapping the mic must not blur the field being dictated into — a field
52
51
  // that commits on blur would lose the edit to its own microphone tap.
53
- onMouseDown: (event)=>event.preventDefault(),
54
- onClick: onToggle,
55
- className: cn("grid size-9 shrink-0 place-items-center rounded-md transition-colors disabled:opacity-50", !unusable && "active:brightness-95 motion-safe:active:scale-[0.97]", listening ? "bg-success text-success-foreground hover:bg-success/90" : "text-muted-foreground hover:bg-muted hover:text-foreground", className),
56
- children: /*#__PURE__*/ _jsx(Glyph, {
57
- className: listening ? "size-3.5 fill-current" : "size-4",
58
- "aria-hidden": true
59
- })
60
- });
52
+ onMouseDown: (event) => event.preventDefault(), onClick: onToggle, className: cn("grid size-9 shrink-0 place-items-center rounded-md transition-colors disabled:opacity-50", !unusable && "active:brightness-95 motion-safe:active:scale-[0.97]", listening
53
+ ? "bg-success text-success-foreground hover:bg-success/90"
54
+ : "text-muted-foreground hover:bg-muted hover:text-foreground", className), children: _jsx(Glyph, { className: listening ? "size-3.5 fill-current" : "size-4", "aria-hidden": true }) }));
61
55
  }
@@ -1,50 +1,14 @@
1
+ "use client";
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { Button } from "./button.js";
3
- import { Dialog, DialogBody, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "./dialog.js";
4
+ import { Dialog, DialogBody, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "./dialog.js";
4
5
  /**
5
6
  * A refusal that is a setting, put in front of the person with the button
6
7
  * that opens the pane. Presentational: it does not know what "open settings"
7
8
  * means for a given host (a native shell invoking an OS pane, a browser
8
9
  * opening a help link) — the consumer's dictation hook supplies `onAction`
9
10
  * and, if that action can itself fail, `failure`.
10
- */ export function MicPermissionDialog({ open, onOpenChange, title, detail, actionLabel, onAction, failure = null, dismissLabel = "Not now" }) {
11
- return /*#__PURE__*/ _jsx(Dialog, {
12
- open: open,
13
- onOpenChange: onOpenChange,
14
- children: /*#__PURE__*/ _jsxs(DialogContent, {
15
- children: [
16
- /*#__PURE__*/ _jsxs(DialogHeader, {
17
- children: [
18
- /*#__PURE__*/ _jsx(DialogTitle, {
19
- children: title
20
- }),
21
- /*#__PURE__*/ _jsx(DialogDescription, {
22
- children: detail
23
- })
24
- ]
25
- }),
26
- failure ? /*#__PURE__*/ _jsx(DialogBody, {
27
- children: /*#__PURE__*/ _jsx("p", {
28
- role: "alert",
29
- className: "text-sm text-destructive",
30
- children: failure
31
- })
32
- }) : null,
33
- /*#__PURE__*/ _jsxs(DialogFooter, {
34
- children: [
35
- /*#__PURE__*/ _jsx(Button, {
36
- variant: "ghost",
37
- onClick: ()=>onOpenChange(false),
38
- children: dismissLabel
39
- }),
40
- /*#__PURE__*/ _jsx(Button, {
41
- variant: "primary",
42
- onClick: onAction,
43
- children: actionLabel
44
- })
45
- ]
46
- })
47
- ]
48
- })
49
- });
11
+ */
12
+ export function MicPermissionDialog({ open, onOpenChange, title, detail, actionLabel, onAction, failure = null, dismissLabel = "Not now", }) {
13
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: title }), _jsx(DialogDescription, { children: detail })] }), failure ? (_jsx(DialogBody, { children: _jsx("p", { role: "alert", className: "text-sm text-destructive", children: failure }) })) : null, _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "ghost", onClick: () => onOpenChange(false), children: dismissLabel }), _jsx(Button, { variant: "primary", onClick: onAction, children: actionLabel })] })] }) }));
50
14
  }