@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
@@ -6,64 +6,19 @@ import { ChartFrame } from "./chart-frame.js";
6
6
  * Dense horizontal category ranking for cost-by-model / calls-by-tool
7
7
  * style tables. Bars share one series colour; length encodes magnitude.
8
8
  * Absent secondary labels stay empty rather than inventing a zero.
9
- */ export function CategoryBars({ title, description, data, valueLabel = "Value", format = formatCompact, className }) {
10
- const max = Math.max(0, ...data.map((d)=>d.value));
9
+ */
10
+ export function CategoryBars({ title, description, data, valueLabel = "Value", format = formatCompact, className, }) {
11
+ const max = Math.max(0, ...data.map((d) => d.value));
11
12
  const axisMax = max === 0 ? 1 : max;
12
- return /*#__PURE__*/ _jsx(ChartFrame, {
13
- title: title,
14
- ...description === undefined ? {} : {
15
- description
16
- },
17
- table: {
18
- columns: [
19
- "Category",
20
- valueLabel,
21
- "Detail"
22
- ],
23
- rows: data.map((datum)=>[
24
- datum.label,
25
- format(datum.value),
26
- datum.secondaryLabel ?? "—"
27
- ])
28
- },
29
- className: className,
30
- children: /*#__PURE__*/ _jsx("div", {
31
- className: "flex flex-col gap-2",
32
- role: "presentation",
33
- children: data.map((datum)=>{
34
- const pct = datum.value / axisMax * 100;
35
- return /*#__PURE__*/ _jsxs("div", {
36
- className: "grid grid-cols-[minmax(6rem,10rem)_1fr_auto] items-center gap-2",
37
- children: [
38
- /*#__PURE__*/ _jsx("p", {
39
- className: "truncate text-xs font-medium",
40
- children: datum.label
41
- }),
42
- /*#__PURE__*/ _jsx("div", {
43
- className: "h-3 w-full bg-muted",
44
- children: /*#__PURE__*/ _jsx("div", {
45
- className: cn("h-full bg-primary-emphasis"),
46
- style: {
47
- width: `${pct}%`
48
- }
49
- })
50
- }),
51
- /*#__PURE__*/ _jsxs("div", {
52
- className: "min-w-[3.5rem] text-right font-mono text-[11px] leading-tight",
53
- children: [
54
- /*#__PURE__*/ _jsx("p", {
55
- className: "font-semibold",
56
- children: format(datum.value)
57
- }),
58
- datum.secondaryLabel === undefined ? null : /*#__PURE__*/ _jsx("p", {
59
- className: "text-muted-foreground",
60
- children: datum.secondaryLabel
61
- })
62
- ]
63
- })
64
- ]
65
- }, datum.label);
66
- })
67
- })
68
- });
13
+ return (_jsx(ChartFrame, { title: title, ...(description === undefined ? {} : { description }), table: {
14
+ columns: ["Category", valueLabel, "Detail"],
15
+ rows: data.map((datum) => [
16
+ datum.label,
17
+ format(datum.value),
18
+ datum.secondaryLabel ?? "—",
19
+ ]),
20
+ }, className: className, children: _jsx("div", { className: "flex flex-col gap-2", role: "presentation", children: data.map((datum) => {
21
+ const pct = (datum.value / axisMax) * 100;
22
+ return (_jsxs("div", { className: "grid grid-cols-[minmax(6rem,10rem)_1fr_auto] items-center gap-2", children: [_jsx("p", { className: "truncate text-xs font-medium", children: datum.label }), _jsx("div", { className: "h-3 w-full bg-muted", children: _jsx("div", { className: cn("h-full bg-primary-emphasis"), style: { width: `${pct}%` } }) }), _jsxs("div", { className: "min-w-[3.5rem] text-right font-mono text-[11px] leading-tight", children: [_jsx("p", { className: "font-semibold", children: format(datum.value) }), datum.secondaryLabel === undefined ? null : (_jsx("p", { className: "text-muted-foreground", children: datum.secondaryLabel }))] })] }, datum.label));
23
+ }) }) }));
69
24
  }
@@ -29,7 +29,7 @@ export type ChartFrameProps = {
29
29
  readonly table: ChartTable;
30
30
  /** The plot itself — an `<svg>`. */
31
31
  readonly children: ReactNode;
32
- readonly className?: string;
32
+ readonly className?: string | undefined;
33
33
  };
34
34
  /**
35
35
  * The frame every chart here sits in: a caption, a legend, the plot, and the
@@ -51,4 +51,4 @@ export type ChartFrameProps = {
51
51
  * cross-reference. Text never wears the series colour: a mid-lightness hue that
52
52
  * is correct for a 24px bar is unreadable at 12px.
53
53
  */
54
- export declare function ChartFrame({ title, description, legend, table, children, className }: ChartFrameProps): import("react").JSX.Element;
54
+ export declare function ChartFrame({ title, description, legend, table, children, className, }: ChartFrameProps): import("react").JSX.Element;
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cn } from "../lib/utils.js";
3
- import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "./table.js";
3
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "./table.js";
4
4
  /**
5
5
  * The frame every chart here sits in: a caption, a legend, the plot, and the
6
6
  * same data as a table.
@@ -20,89 +20,10 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from ".
20
20
  * swatch with text — the text is the identity channel, the swatch is the
21
21
  * cross-reference. Text never wears the series colour: a mid-lightness hue that
22
22
  * is correct for a 24px bar is unreadable at 12px.
23
- */ export function ChartFrame({ title, description, legend, table, children, className }) {
23
+ */
24
+ export function ChartFrame({ title, description, legend, table, children, className, }) {
24
25
  const showLegend = legend !== undefined && legend.length > 1;
25
- return /*#__PURE__*/ _jsxs("figure", {
26
- className: cn("flex flex-col gap-3", className),
27
- children: [
28
- /*#__PURE__*/ _jsxs("figcaption", {
29
- className: "flex flex-col gap-0.5",
30
- children: [
31
- /*#__PURE__*/ _jsx("span", {
32
- className: "text-sm font-semibold",
33
- children: title
34
- }),
35
- description === undefined ? null : /*#__PURE__*/ _jsx("span", {
36
- className: "text-xs text-muted-foreground",
37
- children: description
38
- })
39
- ]
40
- }),
41
- showLegend ? /*#__PURE__*/ _jsx("ul", {
42
- className: "flex flex-wrap items-center gap-x-4 gap-y-1",
43
- children: legend.map((entry)=>/*#__PURE__*/ _jsxs("li", {
44
- className: "flex items-center gap-1.5 text-xs text-muted-foreground",
45
- children: [
46
- entry.dash === undefined ? /*#__PURE__*/ _jsx("span", {
47
- "aria-hidden": true,
48
- className: "size-2.5 shrink-0 rounded-xs",
49
- style: {
50
- backgroundColor: entry.color
51
- }
52
- }) : /*#__PURE__*/ _jsx("svg", {
53
- "aria-hidden": true,
54
- viewBox: "0 0 20 4",
55
- className: "h-1 w-5 shrink-0 overflow-visible",
56
- children: /*#__PURE__*/ _jsx("line", {
57
- x1: 0,
58
- y1: 2,
59
- x2: 20,
60
- y2: 2,
61
- stroke: entry.color,
62
- strokeWidth: 2.5,
63
- strokeLinecap: "butt",
64
- strokeDasharray: entry.dash
65
- })
66
- }),
67
- entry.label
68
- ]
69
- }, entry.label))
70
- }) : null,
71
- children,
72
- /*#__PURE__*/ _jsxs("details", {
73
- className: "text-xs",
74
- children: [
75
- /*#__PURE__*/ _jsx("summary", {
76
- className: "cursor-pointer text-muted-foreground hover:text-foreground",
77
- children: "Show the data"
78
- }),
79
- /*#__PURE__*/ _jsx("div", {
80
- className: "mt-2",
81
- children: /*#__PURE__*/ _jsxs(Table, {
82
- "aria-label": `${title} — data`,
83
- children: [
84
- /*#__PURE__*/ _jsx(TableHeader, {
85
- children: /*#__PURE__*/ _jsx(TableRow, {
86
- children: table.columns.map((column, index)=>/*#__PURE__*/ _jsx(TableHead, {
87
- scope: "col",
88
- className: index === 0 ? undefined : "text-right",
89
- children: column
90
- }, column))
91
- })
92
- }),
93
- /*#__PURE__*/ _jsx(TableBody, {
94
- children: table.rows.map((row, rowIndex)=>/*#__PURE__*/ _jsx(TableRow, {
95
- children: row.map((cell, cellIndex)=>/*#__PURE__*/ _jsx(TableCell, {
96
- className: cellIndex === 0 ? undefined : "text-right font-mono tabular-nums",
97
- children: cell
98
- }, cellIndex))
99
- }, rowIndex))
100
- })
101
- ]
102
- })
103
- })
104
- ]
105
- })
106
- ]
107
- });
26
+ return (_jsxs("figure", { className: cn("flex flex-col gap-3", className), children: [_jsxs("figcaption", { className: "flex flex-col gap-0.5", children: [_jsx("span", { className: "text-sm font-semibold", children: title }), description === undefined ? null : (_jsx("span", { className: "text-xs text-muted-foreground", children: description }))] }), showLegend ? (_jsx("ul", { className: "flex flex-wrap items-center gap-x-4 gap-y-1", children: legend.map((entry) => (_jsxs("li", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [entry.dash === undefined ? (_jsx("span", { "aria-hidden": true, className: "size-2.5 shrink-0 rounded-xs", style: { backgroundColor: entry.color } })) : (_jsx("svg", { "aria-hidden": true, viewBox: "0 0 20 4", className: "h-1 w-5 shrink-0 overflow-visible", children: _jsx("line", { x1: 0, y1: 2, x2: 20, y2: 2, stroke: entry.color, strokeWidth: 2.5, strokeLinecap: "butt", strokeDasharray: entry.dash }) })), entry.label] }, entry.label))) })) : null, children, _jsxs("details", { className: "text-xs", children: [_jsx("summary", { className: "cursor-pointer text-muted-foreground hover:text-foreground", children: "Show the data" }), _jsx("div", { className: "mt-2", children: _jsxs(Table, { "aria-label": `${title} — data`, children: [_jsx(TableHeader, { children: _jsx(TableRow, { children: table.columns.map((column, index) => (_jsx(TableHead, { scope: "col", className: index === 0 ? undefined : "text-right", children: column }, column))) }) }), _jsx(TableBody, { children: table.rows.map((row, rowIndex) => (_jsx(TableRow, { children: row.map((cell, cellIndex) => (_jsx(TableCell, { className: cellIndex === 0
27
+ ? undefined
28
+ : "text-right font-mono tabular-nums", children: cell }, cellIndex))) }, rowIndex))) })] }) })] })] }));
108
29
  }
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { useRef } from "react";
3
4
  import { useDelayedAutofocus } from "../hooks/use-delayed-autofocus.js";
@@ -10,23 +11,10 @@ import { QuickReplyChips } from "./quick-reply-chips.js";
10
11
  * always-on, because a suggestion list next to ten prior turns is not a
11
12
  * suggestion anymore — it is clutter competing with what the user already
12
13
  * said.
13
- */ export function ChatComposer({ suggestions = [], onSuggestionSelect, busy = false, autoFocusOn = false, className, ...inputProps }) {
14
+ */
15
+ export function ChatComposer({ suggestions = [], onSuggestionSelect, busy = false, autoFocusOn = false, className, ...inputProps }) {
14
16
  const textareaRef = useRef(null);
15
17
  useDelayedAutofocus(textareaRef, autoFocusOn);
16
18
  const showSuggestions = suggestions.length > 0 && onSuggestionSelect !== undefined;
17
- return /*#__PURE__*/ _jsxs("div", {
18
- className: cn("flex flex-col gap-2", className),
19
- children: [
20
- showSuggestions ? /*#__PURE__*/ _jsx(QuickReplyChips, {
21
- replies: suggestions,
22
- onSelect: onSuggestionSelect
23
- }) : null,
24
- /*#__PURE__*/ _jsx(ChatInput, {
25
- ...inputProps,
26
- working: busy,
27
- disabled: busy,
28
- textareaRef: textareaRef
29
- })
30
- ]
31
- });
19
+ return (_jsxs("div", { className: cn("flex flex-col gap-2", className), children: [showSuggestions ? (_jsx(QuickReplyChips, { replies: suggestions, onSelect: onSuggestionSelect })) : null, _jsx(ChatInput, { ...inputProps, working: busy, disabled: busy, textareaRef: textareaRef })] }));
32
20
  }
@@ -2,10 +2,14 @@
2
2
  * The dock's one source of truth for motion timing. Every duration that has
3
3
  * to line up with another — the entrance pop, the scrim fade, the autofocus
4
4
  * delay — lives here so fixing one never means hunting the others down.
5
- */ /** How long the closed→docked entrance pop takes, in milliseconds. */ export const CHAT_DOCK_ENTRANCE_MS = 280;
6
- /** The scrim's own fade, coordinated to finish alongside the entrance. */ export const CHAT_DOCK_SCRIM_MS = CHAT_DOCK_ENTRANCE_MS;
5
+ */
6
+ /** How long the closed→docked entrance pop takes, in milliseconds. */
7
+ export const CHAT_DOCK_ENTRANCE_MS = 280;
8
+ /** The scrim's own fade, coordinated to finish alongside the entrance. */
9
+ export const CHAT_DOCK_SCRIM_MS = CHAT_DOCK_ENTRANCE_MS;
7
10
  /**
8
11
  * The composer's autofocus delay. Must be `>=` the entrance duration — focus
9
12
  * landing before the panel has finished animating steals the caret out from
10
13
  * under a still-moving element.
11
- */ export const CHAT_DOCK_AUTOFOCUS_DELAY_MS = CHAT_DOCK_ENTRANCE_MS;
14
+ */
15
+ export const CHAT_DOCK_AUTOFOCUS_DELAY_MS = CHAT_DOCK_ENTRANCE_MS;
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
3
  import { ArrowUp, Paperclip, Square, X } from "lucide-react";
3
4
  import { useEffect, useRef } from "react";
@@ -18,121 +19,40 @@ const MAX_ROWS_PX = 200;
18
19
  * The textarea grows with its content up to a cap, then scrolls. It is measured
19
20
  * rather than counted in rows, because a wrapped long line takes more height
20
21
  * than its newline count implies.
21
- */ export function ChatInput({ value, onValueChange, onSend, onStop, working = false, placeholder = "Send a message…", disabled = false, attachments = [], onAttach, onRemoveAttachment, textareaRef: externalTextareaRef, className }) {
22
+ */
23
+ export function ChatInput({ value, onValueChange, onSend, onStop, working = false, placeholder = "Send a message…", disabled = false, attachments = [], onAttach, onRemoveAttachment, textareaRef: externalTextareaRef, className, }) {
22
24
  const internalTextareaRef = useRef(null);
23
25
  const textareaRef = externalTextareaRef ?? internalTextareaRef;
24
26
  const fileRef = useRef(null);
25
- useEffect(()=>{
27
+ useEffect(() => {
26
28
  const node = textareaRef.current;
27
- if (node === null) return;
29
+ if (node === null)
30
+ return;
28
31
  node.style.height = "auto";
29
32
  node.style.height = `${Math.min(node.scrollHeight, MAX_ROWS_PX)}px`;
30
- }, [
31
- value
32
- ]);
33
+ }, [value]);
33
34
  const canSend = value.trim().length > 0 && !disabled;
34
35
  const showStop = working && onStop !== undefined;
35
- const submit = ()=>{
36
- if (!canSend) return;
36
+ const submit = () => {
37
+ if (!canSend)
38
+ return;
37
39
  onSend();
38
40
  };
39
- return /*#__PURE__*/ _jsxs("form", {
40
- className: cn("flex flex-col gap-2 rounded-lg border border-input bg-background p-2", className),
41
- onSubmit: (event)=>{
41
+ return (_jsxs("form", { className: cn("flex flex-col gap-2 rounded-lg border border-input bg-background p-2", className), onSubmit: (event) => {
42
42
  event.preventDefault();
43
43
  submit();
44
- },
45
- children: [
46
- attachments.length === 0 ? null : /*#__PURE__*/ _jsx("ul", {
47
- "aria-label": "Attachments",
48
- className: "flex flex-wrap gap-1.5",
49
- children: attachments.map((attachment)=>/*#__PURE__*/ _jsxs("li", {
50
- className: "flex items-center gap-1.5 rounded-md bg-muted py-1 pr-1 pl-2 text-xs",
51
- children: [
52
- /*#__PURE__*/ _jsx("span", {
53
- className: "max-w-40 truncate",
54
- children: attachment.name
55
- }),
56
- onRemoveAttachment === undefined ? null : /*#__PURE__*/ _jsx("button", {
57
- type: "button",
58
- onClick: ()=>onRemoveAttachment(attachment),
59
- "aria-label": `Remove ${attachment.name}`,
60
- className: "grid size-5 place-items-center rounded-sm text-muted-foreground hover:text-foreground",
61
- children: /*#__PURE__*/ _jsx(X, {
62
- className: "size-3",
63
- "aria-hidden": true
64
- })
65
- })
66
- ]
67
- }, attachment.id))
68
- }),
69
- /*#__PURE__*/ _jsxs("div", {
70
- className: "flex items-end gap-2",
71
- children: [
72
- onAttach === undefined ? null : /*#__PURE__*/ _jsxs(_Fragment, {
73
- children: [
74
- /*#__PURE__*/ _jsx("input", {
75
- ref: fileRef,
76
- type: "file",
77
- multiple: true,
78
- className: "sr-only",
79
- onChange: (event)=>{
80
- if (event.target.files !== null) onAttach(event.target.files);
44
+ }, children: [attachments.length === 0 ? null : (_jsx("ul", { "aria-label": "Attachments", className: "flex flex-wrap gap-1.5", children: attachments.map((attachment) => (_jsxs("li", { className: "flex items-center gap-1.5 rounded-md bg-muted py-1 pr-1 pl-2 text-xs", children: [_jsx("span", { className: "max-w-40 truncate", children: attachment.name }), onRemoveAttachment === undefined ? null : (_jsx("button", { type: "button", onClick: () => onRemoveAttachment(attachment), "aria-label": `Remove ${attachment.name}`, className: "grid size-5 place-items-center rounded-sm text-muted-foreground hover:text-foreground", children: _jsx(X, { className: "size-3", "aria-hidden": true }) }))] }, attachment.id))) })), _jsxs("div", { className: "flex items-end gap-2", children: [onAttach === undefined ? null : (_jsxs(_Fragment, { children: [_jsx("input", { ref: fileRef, type: "file", multiple: true, className: "sr-only", onChange: (event) => {
45
+ if (event.target.files !== null)
46
+ onAttach(event.target.files);
81
47
  event.target.value = "";
82
- }
83
- }),
84
- /*#__PURE__*/ _jsx("button", {
85
- type: "button",
86
- onClick: ()=>fileRef.current?.click(),
87
- disabled: disabled,
88
- "aria-label": "Attach files",
89
- className: "grid size-9 shrink-0 place-items-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-50",
90
- children: /*#__PURE__*/ _jsx(Paperclip, {
91
- className: "size-4",
92
- "aria-hidden": true
93
- })
94
- })
95
- ]
96
- }),
97
- /*#__PURE__*/ _jsx("textarea", {
98
- ref: textareaRef,
99
- value: value,
100
- rows: 1,
101
- disabled: disabled,
102
- placeholder: placeholder,
103
- "aria-label": "Message",
104
- onChange: (event)=>onValueChange(event.target.value),
105
- onKeyDown: (event)=>{
106
- if (event.key !== "Enter" || event.shiftKey) return;
48
+ } }), _jsx("button", { type: "button", onClick: () => fileRef.current?.click(), disabled: disabled, "aria-label": "Attach files", className: "grid size-9 shrink-0 place-items-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-50", children: _jsx(Paperclip, { className: "size-4", "aria-hidden": true }) })] })), _jsx("textarea", { ref: textareaRef, value: value, rows: 1, disabled: disabled, placeholder: placeholder, "aria-label": "Message", onChange: (event) => onValueChange(event.target.value), onKeyDown: (event) => {
49
+ if (event.key !== "Enter" || event.shiftKey)
50
+ return;
107
51
  // `isComposing` is the IME guard — without it, committing a
108
52
  // Japanese or Chinese candidate sends the message instead.
109
- if (event.nativeEvent.isComposing) return;
53
+ if (event.nativeEvent.isComposing)
54
+ return;
110
55
  event.preventDefault();
111
56
  submit();
112
- },
113
- className: "max-h-50 min-h-9 flex-1 resize-none bg-transparent py-2 text-sm outline-none placeholder:text-muted-foreground disabled:opacity-50"
114
- }),
115
- showStop ? /*#__PURE__*/ _jsx("button", {
116
- type: "button",
117
- onClick: onStop,
118
- "aria-label": "Stop generating",
119
- className: "grid size-9 shrink-0 place-items-center rounded-md bg-muted text-foreground transition-colors hover:bg-secondary",
120
- children: /*#__PURE__*/ _jsx(Square, {
121
- className: "size-3.5 fill-current",
122
- "aria-hidden": true
123
- })
124
- }) : /*#__PURE__*/ _jsx("button", {
125
- type: "submit",
126
- disabled: !canSend,
127
- "aria-label": "Send message",
128
- className: "grid size-9 shrink-0 place-items-center rounded-md bg-primary text-primary-foreground transition-colors hover:bg-primary-active disabled:opacity-40",
129
- children: /*#__PURE__*/ _jsx(ArrowUp, {
130
- className: "size-4",
131
- "aria-hidden": true
132
- })
133
- })
134
- ]
135
- })
136
- ]
137
- });
57
+ }, className: "max-h-50 min-h-9 flex-1 resize-none bg-transparent py-2 text-sm outline-none placeholder:text-muted-foreground disabled:opacity-50" }), showStop ? (_jsx("button", { type: "button", onClick: onStop, "aria-label": "Stop generating", className: "grid size-9 shrink-0 place-items-center rounded-md bg-muted text-foreground transition-colors hover:bg-secondary", children: _jsx(Square, { className: "size-3.5 fill-current", "aria-hidden": true }) })) : (_jsx("button", { type: "submit", disabled: !canSend, "aria-label": "Send message", className: "grid size-9 shrink-0 place-items-center rounded-md bg-primary text-primary-foreground transition-colors hover:bg-primary-active disabled:opacity-40", children: _jsx(ArrowUp, { className: "size-4", "aria-hidden": true }) }))] })] }));
138
58
  }
@@ -14,7 +14,7 @@ export declare function ChatPanelHeader({ identity, children, className, ...prop
14
14
  identity: AgentIdentity;
15
15
  }): import("react").JSX.Element;
16
16
  /** The composer area: quick replies, the input, anything else pinned to the bottom. */
17
- export declare function ChatPanelFooter({ className, children }: {
17
+ export declare function ChatPanelFooter({ className, children, }: {
18
18
  className?: string;
19
19
  children: ReactNode;
20
20
  }): import("react").JSX.Element;
@@ -9,48 +9,14 @@ import { cn } from "../lib/utils.js";
9
9
  * frame carries a full-page conversation, a docked bar and a floating panel,
10
10
  * and each wants something different in the header — a thread switcher here, a
11
11
  * close button there. Composition is what makes one frame serve three shells.
12
- */ export function ChatPanel({ className, ...props }) {
13
- return /*#__PURE__*/ _jsx("div", {
14
- "data-slot": "chat-panel",
15
- className: cn("flex min-h-0 flex-1 flex-col bg-background text-foreground", className),
16
- ...props
17
- });
12
+ */
13
+ export function ChatPanel({ className, ...props }) {
14
+ return (_jsx("div", { "data-slot": "chat-panel", className: cn("flex min-h-0 flex-1 flex-col bg-background text-foreground", className), ...props }));
18
15
  }
19
16
  export function ChatPanelHeader({ identity, children, className, ...props }) {
20
- return /*#__PURE__*/ _jsxs("header", {
21
- "data-slot": "chat-panel-header",
22
- className: cn("flex shrink-0 items-center gap-2.5 border-b border-border px-4 py-3", className),
23
- ...props,
24
- children: [
25
- /*#__PURE__*/ _jsx("span", {
26
- "aria-hidden": true,
27
- className: "grid size-8 shrink-0 place-items-center rounded-full bg-secondary text-[11px] font-bold text-secondary-foreground",
28
- children: agentInitials(identity)
29
- }),
30
- /*#__PURE__*/ _jsxs("div", {
31
- className: "flex min-w-0 flex-col",
32
- children: [
33
- /*#__PURE__*/ _jsx("p", {
34
- className: "truncate text-sm font-semibold",
35
- children: identity.name
36
- }),
37
- identity.tagline === undefined ? null : /*#__PURE__*/ _jsx("p", {
38
- className: "truncate text-xs text-muted-foreground",
39
- children: identity.tagline
40
- })
41
- ]
42
- }),
43
- /*#__PURE__*/ _jsx("div", {
44
- className: "ml-auto flex shrink-0 items-center gap-1",
45
- children: children
46
- })
47
- ]
48
- });
17
+ return (_jsxs("header", { "data-slot": "chat-panel-header", className: cn("flex shrink-0 items-center gap-2.5 border-b border-border px-4 py-3", className), ...props, children: [_jsx("span", { "aria-hidden": true, className: "grid size-8 shrink-0 place-items-center rounded-full bg-secondary text-[11px] font-bold text-secondary-foreground", children: agentInitials(identity) }), _jsxs("div", { className: "flex min-w-0 flex-col", children: [_jsx("p", { className: "truncate text-sm font-semibold", children: identity.name }), identity.tagline === undefined ? null : (_jsx("p", { className: "truncate text-xs text-muted-foreground", children: identity.tagline }))] }), _jsx("div", { className: "ml-auto flex shrink-0 items-center gap-1", children: children })] }));
49
18
  }
50
- /** The composer area: quick replies, the input, anything else pinned to the bottom. */ export function ChatPanelFooter({ className, children }) {
51
- return /*#__PURE__*/ _jsx("div", {
52
- "data-slot": "chat-panel-footer",
53
- className: cn("flex shrink-0 flex-col gap-2 border-t border-border p-3", className),
54
- children: children
55
- });
19
+ /** The composer area: quick replies, the input, anything else pinned to the bottom. */
20
+ export function ChatPanelFooter({ className, children, }) {
21
+ return (_jsx("div", { "data-slot": "chat-panel-footer", className: cn("flex shrink-0 flex-col gap-2 border-t border-border p-3", className), children: children }));
56
22
  }
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { jsx as _jsx } from "react/jsx-runtime";
2
3
  import { useEffect, useRef } from "react";
3
4
  import { cn } from "../lib/utils.js";
@@ -16,46 +17,27 @@ import { MessageBubble } from "./message-bubble.js";
16
17
  * It pins to the bottom only when the user is already there. Yanking someone
17
18
  * back down while they are reading earlier context is the single most common
18
19
  * way chat scrolling goes wrong.
19
- */ export function ChatThread({ messages, identity, renderBody, onRetry, empty, now, className }) {
20
+ */
21
+ export function ChatThread({ messages, identity, renderBody, onRetry, empty, now, className, }) {
20
22
  const scrollRef = useRef(null);
21
23
  const pinnedRef = useRef(true);
22
- useEffect(()=>{
24
+ useEffect(() => {
23
25
  const node = scrollRef.current;
24
- if (node === null || !pinnedRef.current) return;
26
+ if (node === null || !pinnedRef.current)
27
+ return;
25
28
  node.scrollTop = node.scrollHeight;
26
- }, [
27
- messages
28
- ]);
29
+ }, [messages]);
29
30
  if (messages.length === 0 && empty !== undefined) {
30
- return /*#__PURE__*/ _jsx("div", {
31
- className: cn("flex min-h-0 flex-1 items-center justify-center p-6", className),
32
- children: empty
33
- });
31
+ return (_jsx("div", { className: cn("flex min-h-0 flex-1 items-center justify-center p-6", className), children: empty }));
34
32
  }
35
- return /*#__PURE__*/ _jsx("div", {
36
- ref: scrollRef,
37
- onScroll: (event)=>{
33
+ return (_jsx("div", { ref: scrollRef, onScroll: (event) => {
38
34
  const node = event.currentTarget;
39
35
  // A few pixels of slack: sub-pixel layout means an exact comparison is
40
36
  // false on plenty of screens that are, visually, at the bottom.
41
- pinnedRef.current = node.scrollHeight - node.scrollTop - node.clientHeight < 32;
42
- },
37
+ pinnedRef.current =
38
+ node.scrollHeight - node.scrollTop - node.clientHeight < 32;
39
+ },
43
40
  // `log` + polite: new turns are announced without interrupting, which is
44
41
  // what an arriving reply is.
45
- role: "log",
46
- "aria-live": "polite",
47
- "aria-label": "Conversation",
48
- className: cn("flex min-h-0 flex-1 flex-col gap-5 overflow-y-auto p-4", className),
49
- children: messages.map((message)=>message.role === "agent" ? /*#__PURE__*/ _jsx(AgentTurn, {
50
- message: message,
51
- identity: identity,
52
- now: now,
53
- children: renderBody?.(message)
54
- }, message.id) : /*#__PURE__*/ _jsx(MessageBubble, {
55
- message: message,
56
- now: now,
57
- onRetry: onRetry === undefined ? undefined : ()=>onRetry(message),
58
- children: renderBody?.(message)
59
- }, message.id))
60
- });
42
+ role: "log", "aria-live": "polite", "aria-label": "Conversation", className: cn("flex min-h-0 flex-1 flex-col gap-5 overflow-y-auto p-4", className), children: messages.map((message) => message.role === "agent" ? (_jsx(AgentTurn, { message: message, identity: identity, now: now, children: renderBody?.(message) }, message.id)) : (_jsx(MessageBubble, { message: message, now: now, onRetry: onRetry === undefined ? undefined : () => onRetry(message), children: renderBody?.(message) }, message.id))) }));
61
43
  }
@@ -1,63 +1,21 @@
1
+ "use client";
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { Check, Minus } from "lucide-react";
3
4
  import { useEffect, useId, useRef } from "react";
4
5
  import { cn } from "../lib/utils.js";
5
6
  const CONTROL_CLASS = "relative mt-0.5 flex size-4 shrink-0 items-center justify-center";
6
- export function Checkbox({ checked, onCheckedChange, label, description, indeterminate = false, id, disabled = false, invalid = false, describedBy, className }) {
7
+ export function Checkbox({ checked, onCheckedChange, label, description, indeterminate = false, id, disabled = false, invalid = false, describedBy, className, }) {
7
8
  const generatedId = useId();
8
9
  const checkboxId = id ?? generatedId;
9
10
  const descriptionId = description === undefined ? undefined : `${checkboxId}-description`;
10
11
  const ref = useRef(null);
11
12
  const isBare = label === undefined;
12
- useEffect(()=>{
13
- if (ref.current) ref.current.indeterminate = indeterminate;
14
- }, [
15
- indeterminate
16
- ]);
17
- const control = /*#__PURE__*/ _jsxs("div", {
18
- className: cn(CONTROL_CLASS, isBare && className),
19
- children: [
20
- /*#__PURE__*/ _jsx("input", {
21
- ref: ref,
22
- type: "checkbox",
23
- id: checkboxId,
24
- checked: checked,
25
- disabled: disabled,
26
- "aria-describedby": isBare ? describedBy : descriptionId,
27
- "aria-invalid": invalid,
28
- onChange: (event)=>onCheckedChange(event.target.checked),
29
- className: cn("peer size-4 shrink-0 appearance-none rounded-sm border border-input bg-card shadow-xs transition-colors", "checked:border-primary checked:bg-primary indeterminate:border-primary indeterminate:bg-primary", "aria-invalid:border-destructive disabled:cursor-not-allowed disabled:opacity-50")
30
- }),
31
- /*#__PURE__*/ _jsx(Check, {
32
- "aria-hidden": true,
33
- className: "pointer-events-none absolute size-3 scale-0 text-primary-foreground transition-transform peer-checked:scale-100"
34
- }),
35
- /*#__PURE__*/ _jsx(Minus, {
36
- "aria-hidden": true,
37
- className: "pointer-events-none absolute size-3 scale-0 text-primary-foreground transition-transform peer-indeterminate:scale-100 peer-checked:scale-0"
38
- })
39
- ]
40
- });
41
- if (isBare) return control;
42
- return /*#__PURE__*/ _jsxs("label", {
43
- htmlFor: checkboxId,
44
- className: cn("flex items-start gap-2", disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer", className),
45
- children: [
46
- control,
47
- /*#__PURE__*/ _jsxs("span", {
48
- className: "flex flex-col gap-0.5",
49
- children: [
50
- /*#__PURE__*/ _jsx("span", {
51
- className: "text-sm font-medium",
52
- children: label
53
- }),
54
- description === undefined ? null : /*#__PURE__*/ _jsx("span", {
55
- id: descriptionId,
56
- className: "text-xs leading-relaxed text-muted-foreground",
57
- children: description
58
- })
59
- ]
60
- })
61
- ]
62
- });
13
+ useEffect(() => {
14
+ if (ref.current)
15
+ ref.current.indeterminate = indeterminate;
16
+ }, [indeterminate]);
17
+ const control = (_jsxs("div", { className: cn(CONTROL_CLASS, isBare && className), children: [_jsx("input", { ref: ref, type: "checkbox", id: checkboxId, checked: checked, disabled: disabled, "aria-describedby": isBare ? describedBy : descriptionId, "aria-invalid": invalid, onChange: (event) => onCheckedChange(event.target.checked), className: cn("peer size-4 shrink-0 appearance-none rounded-sm border border-input bg-card shadow-xs transition-colors", "checked:border-primary checked:bg-primary indeterminate:border-primary indeterminate:bg-primary", "aria-invalid:border-destructive disabled:cursor-not-allowed disabled:opacity-50") }), _jsx(Check, { "aria-hidden": true, className: "pointer-events-none absolute size-3 scale-0 text-primary-foreground transition-transform peer-checked:scale-100" }), _jsx(Minus, { "aria-hidden": true, className: "pointer-events-none absolute size-3 scale-0 text-primary-foreground transition-transform peer-indeterminate:scale-100 peer-checked:scale-0" })] }));
18
+ if (isBare)
19
+ return control;
20
+ return (_jsxs("label", { htmlFor: checkboxId, className: cn("flex items-start gap-2", disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer", className), children: [control, _jsxs("span", { className: "flex flex-col gap-0.5", children: [_jsx("span", { className: "text-sm font-medium", children: label }), description === undefined ? null : (_jsx("span", { id: descriptionId, className: "text-xs leading-relaxed text-muted-foreground", children: description }))] })] }));
63
21
  }