@corbits/react-ui 0.1.0 → 0.2.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 +11 -11
  35. package/dist/test/render-hook.js +11 -12
  36. package/dist/theme.css +51 -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 +1 -1
  45. package/dist/ui/avatar.js +11 -48
  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 +71 -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 +94 -535
package/README.md CHANGED
@@ -1,125 +1,268 @@
1
1
  # @corbits/react-ui
2
2
 
3
- React components for agent and workflow surfaces — chat, runs, artifacts, analytics, collections. Import one module at a time, or from the root barrel.
3
+ React components for agent products: chat transcripts with reasoning and tool calls, approval cards, run status, artifacts and charts, styled by one Tailwind v4 token theme. The UI package of Corbits: it renders data a host web app loads from Interchange, and works in any React 18 or 19 app without Interchange.
4
4
 
5
- ## Runtime support
5
+ ## Why @corbits/react-ui?
6
6
 
7
- Node >= 24 consumes built `dist/`. Peers: `react` and `react-dom` (18 or 19), `lucide-react`, `sonner`, `@radix-ui/react-dialog`, `@radix-ui/react-dropdown-menu`, `@radix-ui/react-slot`, `@radix-ui/react-tooltip`.
7
+ 1. **Props in, markup out.** No component fetches, subscribes or holds a client. Your data layer stays yours, and every component renders the same in stories, tests and production.
8
+ 2. **Agent-shaped building blocks.** `ChatThread` renders a message as ordered parts (text, reasoning, tool calls), and `ApprovalCard`, `GateBlock` and `StepList` cover a run that waits on a person.
9
+ 3. **Pay for what you import.** One root entry, the package is side-effect free apart from its CSS, and heavy peers (Radix dialog, dropdown-menu, tooltip, `sonner`) are optional.
10
+ 4. **Server Components ready.** Stateful modules ship `"use client"`, so a React Server Components app imports them directly. Outside RSC the directive is a no-op.
8
11
 
9
- ## Quickstart
12
+ ## Install
10
13
 
11
14
  ```bash
12
- npm add @corbits/react-ui
13
- pnpm add @corbits/react-ui
14
- yarn add @corbits/react-ui
15
- bun add @corbits/react-ui
15
+ bun add @corbits/react-ui react react-dom lucide-react @radix-ui/react-slot
16
16
  ```
17
17
 
18
- Until a release is cut, consumers pin a commit instead of a version range:
18
+ Requires Node 24 or later. Tested on React 19; the peer range also accepts React 18.2+.
19
19
 
20
- ```json
21
- "@corbits/react-ui": "github:corbitsdev/react-ui#<commit-sha>"
22
- ```
20
+ Optional peers are needed only by the modules that import them. Those modules are not in the root entry, so import them by subpath:
21
+
22
+ | Peer | Subpaths |
23
+ | ------------------------------- | ------------------------------------------------------------- |
24
+ | `@radix-ui/react-dialog` | `ui/dialog`, `ui/command-palette`, `ui/mic-permission-dialog` |
25
+ | `@radix-ui/react-dropdown-menu` | `ui/menu` |
26
+ | `@radix-ui/react-tooltip` | `ui/tooltip` |
27
+ | `sonner` | `ui/toast` |
28
+
29
+ ## Quickstart
23
30
 
24
- Import the stylesheet once at the app root, before your own CSS (no Tailwind build required). Mount `ThemeProvider` near the root so dark mode persists per user, and `Toaster` once alongside it so any component can call `toast()`:
31
+ Import the stylesheet once at the app root, before your own CSS, and mount `ThemeProvider` near the root:
25
32
 
26
33
  ```tsx
27
34
  import "@corbits/react-ui/styles.css";
28
35
 
29
- import { ThemeProvider, Toaster, toast } from "@corbits/react-ui";
30
-
31
- export function Root() {
36
+ import { ChatThread, ThemeProvider, type ChatMessage } from "@corbits/react-ui";
37
+
38
+ const messages: ChatMessage[] = [
39
+ {
40
+ id: "m1",
41
+ role: "user",
42
+ createdAt: "2026-01-01T09:00:00Z",
43
+ parts: [{ type: "text", text: "What changed in the repo today?" }],
44
+ },
45
+ {
46
+ id: "m2",
47
+ role: "agent",
48
+ createdAt: "2026-01-01T09:00:04Z",
49
+ parts: [
50
+ { type: "reasoning", text: "Check the commit log first." },
51
+ {
52
+ type: "tool",
53
+ toolCallId: "t1",
54
+ toolName: "git__log",
55
+ label: "Read today's commits",
56
+ state: "done",
57
+ output: "3 commits",
58
+ },
59
+ { type: "text", text: "Three commits landed today, all in the docs." },
60
+ ],
61
+ },
62
+ ];
63
+
64
+ export function App() {
32
65
  return (
33
- <ThemeProvider storageKey="corbits-theme" defaultMode="light">
34
- <App />
35
- <Toaster position="bottom-right" />
66
+ <ThemeProvider>
67
+ <ChatThread messages={messages} identity={{ name: "Scout" }} />
36
68
  </ThemeProvider>
37
69
  );
38
70
  }
39
-
40
- function App() {
41
- return <button onClick={() => toast("Signed out.")}>Sign out</button>;
42
- }
43
71
  ```
44
72
 
45
- That sheet includes Tailwind preflight and a base layer — it restyles the page, and neither import is safe to skip: without one of them the components render unstyled markup, not a fallback look. If you already use Tailwind v4, import `@corbits/react-ui/theme.css` instead and let your own build generate utilities; don't import both.
73
+ This renders the user message, a collapsed reasoning block, a finished tool call and the agent's reply.
74
+
75
+ `styles.css` includes Tailwind preflight and a base layer, so it restyles the page. If you already run Tailwind v4, import `@corbits/react-ui/theme.css` instead and let your build generate the utilities. Import one of the two, never both; without either, components render unstyled.
46
76
 
47
- Dark mode is a `dark` class on an ancestor; the stylesheet reads it and does not manage it. With neither `.dark` nor `.light` set, the theme follows the OS — a `@media (prefers-color-scheme: dark)` block applies the dark tokens to `:root` and sets `color-scheme: dark` so native controls follow, so a zero-JS install already renders dark on a dark-OS host. That media-query path is tokens only — `dark:` utilities still need a `.dark` ancestor.
77
+ ## Where it fits
48
78
 
49
- Removing `.dark` is not light: hosts that toggle by adding and removing `.dark` must now add `.light` for an explicit light choice (or mount `ThemeProvider`, which toggles both), or a dark-OS user falls back to OS dark. An explicit-light choice on a dark OS first-paints dark then swaps when JS adds `.light`; to block the flash, apply it before first paint *only when the stored choice is light* — `<script>if (localStorage.getItem("corbits-theme")?.includes('"light"')) document.documentElement.classList.add("light")</script>` — adding it unconditionally pins light for dark-OS users too. `ThemeProvider` still wins once it mounts — mount it only if you want the library to persist the choice and apply named presets.
79
+ - **Interchange** runs AI agents as principals (first-class identities with their own permissions and credentials). The hub is its multi-tenant control plane: tenants, principals, grants (permissions a principal holds) and credentials. The sidecar is the portable agent runtime.
80
+ - **Corbits** is the set of published packages around Interchange that turn an agent into a product. This package is the UI bucket: it runs in the host web app and plugs into the host's data layer, not into the hub or the sidecar.
81
+ - It pairs with the hub modules whose data it renders, such as [`@corbits/artifacts`](https://github.com/corbitsdev/corbits-artifacts), [`@corbits/mailbox`](https://github.com/corbitsdev/corbits-mailbox) and [`@corbits/cron`](https://github.com/corbitsdev/corbits-cron).
50
82
 
51
- A page body typically pairs `PageShell` (margins and scroll ownership) with `EmptyState` (title, description, an optional icon, and an action):
83
+ ## Reference
84
+
85
+ Import from the root; it tree-shakes to what you use. Only the optional-peer modules have their own subpath:
52
86
 
53
87
  ```tsx
54
- import { Button, EmptyState, PageShell } from "@corbits/react-ui";
88
+ import { Button } from "@corbits/react-ui";
89
+ import { Toaster, toast } from "@corbits/react-ui/ui/toast";
55
90
 
56
- export function NotFoundPage() {
91
+ export function SignOut() {
57
92
  return (
58
- <PageShell width="full">
59
- <EmptyState
60
- title="Page not found"
61
- description="This page doesn't exist."
62
- action={
63
- <Button variant="outline" onClick={() => window.history.back()}>
64
- Go back
65
- </Button>
66
- }
67
- />
68
- </PageShell>
93
+ <>
94
+ <Button onClick={() => toast("Signed out.")}>Sign out</Button>
95
+ <Toaster position="bottom-right" />
96
+ </>
69
97
  );
70
98
  }
71
99
  ```
72
100
 
73
- Every component is importable by subpath (`@corbits/react-ui/ui/button`) or from the root (`@corbits/react-ui`).
74
-
75
- The brand faces (Red Hat Display, Space Mono) are named by the theme but not bundled. Load them yourself, or the stack falls through to system fonts; to use a face loaded under a generated name, override `--font-sans` / `--font-mono`.
76
-
77
- This package ships no `"use client"` directives. In a React Server Components app, re-export stateful components from a file you mark yourself, using subpaths rather than the root barrel.
78
-
79
- ## How it works
80
-
81
- Components never fetch — data arrives as props. The root entry is re-exports only and the package is side-effect free aside from the CSS files, so either import style tree-shakes to what you used. Tokens and keyframes live in `theme.css`; `styles.css` is the prebuilt sheet for hosts that do not run Tailwind.
82
-
83
- See [PRODUCT.md](./PRODUCT.md) for why this library exists, [ARCHITECTURE.md](./ARCHITECTURE.md) for the no-fetch rule and the theme layer, and [IMPLEMENTATION.md](./IMPLEMENTATION.md) for toolchain and peers.
84
-
85
- ## Development
86
-
87
- ```sh
88
- git clone https://github.com/corbitsdev/react-ui.git
89
- cd react-ui
90
- bun install
91
- bun run build # generate → SWC → tsc → Tailwind → contrast gate
92
- bun run typecheck
93
- bun run lint
94
- bun run dep-guard
95
- bun run test
96
- bun run stories # Ladle workbench
97
- ```
98
-
99
- A new component is not done until it has at least one story per meaningful state, checked in both themes. See [CONTRIBUTING.md](./CONTRIBUTING.md).
101
+ ### Theming
100
102
 
101
- ## Thinking indicator
103
+ Dark mode is a `.dark` class on an ancestor. With neither `.dark` nor `.light` on the root, the tokens follow `prefers-color-scheme` and set `color-scheme` for native controls; `dark:` utilities still need a `.dark` ancestor.
102
104
 
103
- The Corbits logo has three motion variants: `silk` (default), `strata`, and `echo`.
105
+ `ThemeProvider` toggles both classes and persists the choice under `storageKey`. Hosts that manage the class themselves add `.light` for an explicit light choice, since removing `.dark` falls back to the OS. To avoid a dark first paint for a stored light choice on a dark OS, add `.light` before first paint only when the stored choice is light:
104
106
 
105
- ```tsx
106
- import { ThinkingIndicator, ThinkingMark } from "@corbits/react-ui";
107
-
108
- export function Thinking() {
109
- return (
110
- <>
111
- <ThinkingIndicator variant="silk" />
112
- <ThinkingIndicator variant="strata" label="Reviewing files..." />
113
- <ThinkingMark variant="echo" className="w-8" />
114
- </>
115
- );
116
- }
107
+ ```html
108
+ <script>
109
+ if (localStorage.getItem("corbits-theme")?.includes('"light"'))
110
+ document.documentElement.classList.add("light");
111
+ </script>
117
112
  ```
118
113
 
119
- `ThinkingIndicator` supplies an accessible status label; `ThinkingMark` is decorative.
120
- Both accept `variant`, `paused`, `className`, and `style`. Reduced-motion users see a
121
- static highlight instead.
114
+ The theme names Red Hat Display and Space Mono but does not bundle them. Load them yourself or the stack falls back to system fonts; override `--font-sans` and `--font-mono` for a face loaded under another name.
115
+
116
+ ### Components
117
+
118
+ Everything imports from `@corbits/react-ui`, except rows marked "optional peer", which import from the subpath shown; see [Install](#install).
119
+
120
+ #### Chat
121
+
122
+ | Component | What it is |
123
+ | --------------------- | ------------------------------------------------------------------------ |
124
+ | `ChatThread` | Scrolling transcript of user and agent messages |
125
+ | `AgentTurn` | One agent reply: reasoning, tool calls, answer, in order |
126
+ | `MessageBubble` | One user or system message |
127
+ | `ChatInput` | Auto-growing message input with a send/stop button |
128
+ | `ChatComposer` | `ChatInput` with suggestion chips for an empty conversation |
129
+ | `ChatPanel` | Docked chat frame with header and footer |
130
+ | `PartsRenderer` | Renders a list of message parts |
131
+ | `ReasoningBlock` | Collapsible thinking text |
132
+ | `ToolBlock` | A tool call's lifecycle: running, done, failed |
133
+ | `ToolNarrative` | What the agent did, as one expandable line |
134
+ | `ToolPicker` | Browse, search and select tools |
135
+ | `QuickReplyChips` | Tappable suggested replies |
136
+ | `TypingIndicator` | Agent-is-typing dots |
137
+ | `ThinkingIndicator` | Animated Corbits mark with a status label |
138
+ | `ThinkingMark` | Decorative animated Corbits mark |
139
+ | `ThinkingLabel` | Rotating-verb status label |
140
+ | `ShimmerText` | Shimmer sweep over text |
141
+ | `MicButton` | Permission-aware microphone toggle |
142
+ | `VoiceWaveform` | Live audio level bars |
143
+ | `DictationStatusLine` | Composer dictation status row |
144
+ | `MicPermissionDialog` | Microphone permission prompt (optional peer, `ui/mic-permission-dialog`) |
145
+
146
+ #### Runs
147
+
148
+ | Component | What it is |
149
+ | ------------------- | ------------------------------------------------- |
150
+ | `LiveRunBanner` | Status of a run in progress |
151
+ | `LiveStatusLine` | The single "what's happening now" line |
152
+ | `ApprovalCard` | Approve or reject a pending action |
153
+ | `GateBlock` | Chrome for a run parked on a human decision |
154
+ | `StepList` | Vertical list of run steps and their status |
155
+ | `HorizontalStepper` | Horizontal step progress |
156
+ | `ProgressChecklist` | Steps of a long operation and how far along it is |
157
+ | `TraceWaterfall` | Run-trace timeline, one row per span |
158
+ | `IntakeForm` | Inputs a workflow asks for before it runs |
159
+ | `RunNowButton` | Run something immediately |
160
+ | `ConfirmButton` | Button that asks for a second click |
161
+ | `NowCards` | Attention band of items needing action |
162
+ | `NotificationsBell` | Bell with count and a panel slot |
163
+
164
+ #### Artifacts
165
+
166
+ | Component | What it is |
167
+ | ---------------- | --------------------------------------------------- |
168
+ | `ArtifactBody` | One artifact, drawn the way its kind implies |
169
+ | `ArtifactNotice` | "Nothing to draw here" message for artifact viewers |
170
+ | `BlockCard` | Generative-UI block frame |
171
+ | `ResearchBody` | Research brief and report |
172
+ | `CompareBody` | Side-by-side comparison |
173
+ | `EmbedBody` | Sandboxed third-party embed |
174
+ | `CsvTable` | Capped preview of CSV text |
175
+ | `QuoteCard` | A rotating quote |
176
+ | `ProfileCard` | Identity, status, actions and channels |
177
+
178
+ #### Charts
179
+
180
+ | Component | What it is |
181
+ | ----------------- | --------------------------------------------------- |
182
+ | `ChartFrame` | Caption, legend, plot, and the same data as a table |
183
+ | `TimeSeriesChart` | Values over time |
184
+ | `BarChart` | Vertical bars |
185
+ | `CategoryBars` | Dense horizontal category ranking |
186
+ | `Sparkline` | Inline trend line |
187
+ | `TokenMosaic` | Shares of a whole as one stacked strip |
188
+ | `StatGrid` | Grid of headline numbers |
189
+ | `AnimatedNumber` | Number that counts to its new value |
190
+
191
+ #### Layout
192
+
193
+ | Component | What it is |
194
+ | ------------------- | -------------------------------------------- |
195
+ | `PageShell` | Page margins and scroll ownership |
196
+ | `PagePanel` | Bordered page-content frame |
197
+ | `TopBar` | Title, breadcrumbs and actions row |
198
+ | `Sidebar` | Collapsible navigation sidebar |
199
+ | `SidebarPanel` | Sidebar with pins, body and footer |
200
+ | `SidebarItemRow` | One sidebar row |
201
+ | `ListDetail` | Index on the left, open item on the right |
202
+ | `InspectorShell` | Side inspector rail |
203
+ | `Section` | Titled block of content |
204
+ | `SettingsPanel` | Settings frame with optional save |
205
+ | `LibraryPageHeader` | Title, count and controls for a library page |
206
+ | `Card` | Bordered content card |
207
+ | `EmptyState` | Title, description, icon and action |
208
+ | `RichEmptyState` | Empty state with suggested next-step actions |
209
+ | `BootScreen` | Screen shown before the app loads |
210
+ | `DitherCanvas` | Animated dithered background |
211
+ | `CorbitsMark` | Corbits logo |
212
+ | `AuthLayout` | Sign-in page layout |
213
+ | `LoginForm` | Sign-in and sign-up form, with optional SSO |
214
+
215
+ #### Controls
216
+
217
+ | Component | What it is |
218
+ | ------------------- | --------------------------------------------------------------- |
219
+ | `Button` | Button with variants and sizes |
220
+ | `Input` | Text input |
221
+ | `Textarea` | Multi-line input |
222
+ | `Select` | Native select |
223
+ | `Checkbox` | Checkbox |
224
+ | `SelectionCheckbox` | Row-selection checkbox |
225
+ | `Switch` | On/off toggle |
226
+ | `FileInput` | File picker |
227
+ | `Tabs` | Tab strip |
228
+ | `ViewToggle` | Grid or rows segmented control |
229
+ | `FilterBar` | Row of filters with an active-filter summary |
230
+ | `FilterChip` | One toggleable filter |
231
+ | `KindCardGrid` | Selectable card grid |
232
+ | `BulkActionBar` | Actions for a multi-row selection |
233
+ | `Table` | Table primitives |
234
+ | `SortableTable` | Table with sortable columns |
235
+ | `Badge` | Status label |
236
+ | `StatusDot` | Colored status dot |
237
+ | `Avatar` | Initials or image avatar |
238
+ | `ProviderMark` | Which service an action goes through |
239
+ | `Skeleton` | Loading placeholder |
240
+ | `Dialog` | Modal dialog (optional peer, `ui/dialog`) |
241
+ | `CommandPalette` | Global search overlay (optional peer, `ui/command-palette`) |
242
+ | `Menu` | Dropdown menu (optional peer, `ui/menu`) |
243
+ | `InfoTooltip` | Hover info tooltip (optional peer, `ui/tooltip`) |
244
+ | `Toaster` | Toast host; call `toast()` anywhere (optional peer, `ui/toast`) |
245
+ | `ThemeProvider` | Persists light, dark or system mode and presets |
246
+ | `ThemeToggle` | Light, dark or system switch |
247
+
248
+ ## Using with Interchange
249
+
250
+ The host app owns the connection to the hub; components only take props.
251
+
252
+ 1. Load run state with [`@intx/hub-client`](https://github.com/faremeter/interchange/tree/main/packages/hub-client): `createRunSession` polls a workflow run's event log and exposes a seq-ordered `WorkflowRunEvent` timeline.
253
+ 2. Map that timeline to props: agent output to `ChatMessage` parts for `ChatThread`, step events to `StepList` items, and a run parked on a person to `GateBlock` or `ApprovalCard`.
254
+ 3. Wire callbacks back to the hub. `ApprovalCard`'s `onApprove` and `onReject` call your hub route that resolves the awaited signal; the hub checks the caller's grant, not the component.
255
+
256
+ ## Upgrading from 0.1
257
+
258
+ - Install from npm. Git installs (`github:corbitsdev/react-ui#<sha>`) no longer build `dist/`.
259
+ - Per-module subpaths are gone: import from `@corbits/react-ui` (`import { Button } from "@corbits/react-ui"`).
260
+ - `ui/dialog`, `ui/command-palette`, `ui/mic-permission-dialog`, `ui/menu`, `ui/tooltip` and `ui/toast` are subpath-only, and their peers are optional: `import { Toaster, toast } from "@corbits/react-ui/ui/toast"`. Install the peer for each subpath you keep.
261
+ - `lib/csv`, `lib/url`, `lib/utils`, `lib/workflow-registry` and `ui/chat-dock-timing` are internal. Replace `cn` with `twMerge(clsx(...))`.
262
+ - Stateful components ship `"use client"`. Client wrappers you wrote for them still work, and can go.
263
+ - 84 unused modules are removed, `ui/command` is removed, `ActivityBlock` is renamed `ReasoningBlock`, and `AuthLayout` no longer renders `DitherCanvas` by default.
264
+ - With neither `.dark` nor `.light` on the root, the theme follows the OS. Hosts that toggle `.dark` must add `.light` for an explicit light choice, or mount `ThemeProvider`.
122
265
 
123
266
  ## License
124
267
 
125
- LGPL-2.1-only. See [LICENSE](./LICENSE).
268
+ LGPL-2.1-only. See [LICENSE](https://github.com/corbitsdev/react-ui/blob/main/LICENSE).
@@ -35,4 +35,4 @@ export type AuthLayoutProps = {
35
35
  * *without* the retracting toolbar, so a `100vh` sign-in page is reliably a
36
36
  * little too tall and the button sits just under the fold.
37
37
  */
38
- export declare function AuthLayout({ brand, children, panel, aside, className }: AuthLayoutProps): import("react").JSX.Element;
38
+ export declare function AuthLayout({ brand, children, panel, aside, className, }: AuthLayoutProps): import("react").JSX.Element;
@@ -16,37 +16,7 @@ import { cn } from "../../lib/utils.js";
16
16
  * `min-h-svh`, not `min-h-screen`. On mobile browsers `100vh` is the viewport
17
17
  * *without* the retracting toolbar, so a `100vh` sign-in page is reliably a
18
18
  * little too tall and the button sits just under the fold.
19
- */ export function AuthLayout({ brand, children, panel, aside, className }) {
20
- return /*#__PURE__*/ _jsxs("div", {
21
- className: cn("grid min-h-svh bg-background lg:grid-cols-2", className),
22
- children: [
23
- /*#__PURE__*/ _jsxs("main", {
24
- className: "relative flex flex-col",
25
- children: [
26
- brand === undefined ? null : /*#__PURE__*/ _jsx("div", {
27
- className: "flex items-center gap-2.5 p-6 text-base font-semibold tracking-tight md:p-8",
28
- children: brand
29
- }),
30
- /*#__PURE__*/ _jsx("div", {
31
- className: "flex flex-1 items-center justify-center px-6 pb-16 md:px-10",
32
- children: /*#__PURE__*/ _jsx("div", {
33
- className: "w-full max-w-sm",
34
- children: children
35
- })
36
- })
37
- ]
38
- }),
39
- /*#__PURE__*/ _jsxs("div", {
40
- "aria-hidden": true,
41
- className: "relative hidden overflow-hidden bg-card lg:block",
42
- children: [
43
- panel,
44
- aside === undefined ? null : /*#__PURE__*/ _jsx("div", {
45
- className: "absolute inset-0 flex items-center justify-center p-12",
46
- children: aside
47
- })
48
- ]
49
- })
50
- ]
51
- });
19
+ */
20
+ export function AuthLayout({ brand, children, panel, aside, className, }) {
21
+ return (_jsxs("div", { className: cn("grid min-h-svh bg-background lg:grid-cols-2", className), children: [_jsxs("main", { className: "relative flex flex-col", children: [brand === undefined ? null : (_jsx("div", { className: "flex items-center gap-2.5 p-6 text-base font-semibold tracking-tight md:p-8", children: brand })), _jsx("div", { className: "flex flex-1 items-center justify-center px-6 pb-16 md:px-10", children: _jsx("div", { className: "w-full max-w-sm", children: children }) })] }), _jsxs("div", { "aria-hidden": true, className: "relative hidden overflow-hidden bg-card lg:block", children: [panel, aside === undefined ? null : (_jsx("div", { className: "absolute inset-0 flex items-center justify-center p-12", children: aside }))] })] }));
52
22
  }
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { useState } from "react";
3
4
  import { cn } from "../../lib/utils.js";
@@ -24,128 +25,14 @@ import { Input } from "../../ui/input.js";
24
25
  * The submit button reads "Signing in…" / "Signing up…" while busy rather
25
26
  * than swapping to a spinner. The label is the status, so it is announced by
26
27
  * the same mechanism that announces the button.
27
- */ export function LoginForm({ mode = "sign-in", heading = "Welcome back", providers = [], onProvider, onSubmit, busy = false, error = null, footer, className }) {
28
+ */
29
+ export function LoginForm({ mode = "sign-in", heading = "Welcome back", providers = [], onProvider, onSubmit, busy = false, error = null, footer, className, }) {
28
30
  const [name, setName] = useState("");
29
31
  const [email, setEmail] = useState("");
30
32
  const [password, setPassword] = useState("");
31
33
  const isSignUp = mode === "sign-up";
32
- return /*#__PURE__*/ _jsxs("div", {
33
- className: cn("flex flex-col gap-6", className),
34
- children: [
35
- /*#__PURE__*/ _jsx("h1", {
36
- className: "text-3xl leading-none font-semibold tracking-tight text-balance",
37
- children: heading
38
- }),
39
- error === null ? null : /*#__PURE__*/ _jsx("p", {
40
- role: "alert",
41
- "aria-live": "assertive",
42
- className: "rounded-md border border-destructive px-3 py-2 text-sm text-destructive",
43
- children: error
44
- }),
45
- onSubmit === undefined ? null : /*#__PURE__*/ _jsxs("form", {
46
- className: "flex flex-col gap-4",
47
- onSubmit: (event)=>{
34
+ return (_jsxs("div", { className: cn("flex flex-col gap-6", className), children: [_jsx("h1", { className: "text-3xl leading-none font-semibold tracking-tight text-balance", children: heading }), error === null ? null : (_jsx("p", { role: "alert", "aria-live": "assertive", className: "rounded-md border border-destructive px-3 py-2 text-sm text-destructive", children: error })), onSubmit === undefined ? null : (_jsxs("form", { className: "flex flex-col gap-4", onSubmit: (event) => {
48
35
  event.preventDefault();
49
- onSubmit(isSignUp ? {
50
- name,
51
- email,
52
- password
53
- } : {
54
- email,
55
- password
56
- });
57
- },
58
- children: [
59
- !isSignUp ? null : /*#__PURE__*/ _jsxs("label", {
60
- className: "flex flex-col gap-1.5 text-sm font-medium",
61
- children: [
62
- "Name",
63
- /*#__PURE__*/ _jsx(Input, {
64
- type: "text",
65
- name: "name",
66
- autoComplete: "name",
67
- placeholder: "Ada Lovelace",
68
- value: name,
69
- onChange: (event)=>setName(event.target.value),
70
- disabled: busy,
71
- required: true,
72
- className: "h-11"
73
- })
74
- ]
75
- }),
76
- /*#__PURE__*/ _jsxs("label", {
77
- className: "flex flex-col gap-1.5 text-sm font-medium",
78
- children: [
79
- "Email",
80
- /*#__PURE__*/ _jsx(Input, {
81
- type: "email",
82
- name: "email",
83
- autoComplete: "email",
84
- placeholder: "you@example.com",
85
- value: email,
86
- onChange: (event)=>setEmail(event.target.value),
87
- disabled: busy,
88
- required: true,
89
- className: "h-11"
90
- })
91
- ]
92
- }),
93
- /*#__PURE__*/ _jsxs("label", {
94
- className: "flex flex-col gap-1.5 text-sm font-medium",
95
- children: [
96
- "Password",
97
- /*#__PURE__*/ _jsx(Input, {
98
- type: "password",
99
- name: "password",
100
- autoComplete: isSignUp ? "new-password" : "current-password",
101
- value: password,
102
- onChange: (event)=>setPassword(event.target.value),
103
- disabled: busy,
104
- required: true,
105
- className: "h-11"
106
- })
107
- ]
108
- }),
109
- /*#__PURE__*/ _jsx(Button, {
110
- type: "submit",
111
- size: "lg",
112
- disabled: busy,
113
- children: busy ? isSignUp ? "Signing up…" : "Signing in…" : "Continue"
114
- })
115
- ]
116
- }),
117
- onSubmit === undefined || providers.length === 0 ? null : /*#__PURE__*/ _jsxs("div", {
118
- className: "flex items-center gap-3",
119
- children: [
120
- /*#__PURE__*/ _jsx("span", {
121
- "aria-hidden": true,
122
- className: "h-px flex-1 bg-border"
123
- }),
124
- /*#__PURE__*/ _jsx("span", {
125
- className: "text-xs tracking-wider text-muted-foreground uppercase",
126
- children: "or"
127
- }),
128
- /*#__PURE__*/ _jsx("span", {
129
- "aria-hidden": true,
130
- className: "h-px flex-1 bg-border"
131
- })
132
- ]
133
- }),
134
- providers.map((provider)=>/*#__PURE__*/ _jsxs(Button, {
135
- type: "button",
136
- variant: "outline",
137
- size: "lg",
138
- disabled: busy,
139
- onClick: ()=>onProvider?.(provider.id),
140
- children: [
141
- provider.icon,
142
- provider.label
143
- ]
144
- }, provider.id)),
145
- footer === undefined ? null : /*#__PURE__*/ _jsx("div", {
146
- className: "text-sm text-muted-foreground",
147
- children: footer
148
- })
149
- ]
150
- });
36
+ onSubmit(isSignUp ? { name, email, password } : { email, password });
37
+ }, children: [!isSignUp ? null : (_jsxs("label", { className: "flex flex-col gap-1.5 text-sm font-medium", children: ["Name", _jsx(Input, { type: "text", name: "name", autoComplete: "name", placeholder: "Ada Lovelace", value: name, onChange: (event) => setName(event.target.value), disabled: busy, required: true, className: "h-11" })] })), _jsxs("label", { className: "flex flex-col gap-1.5 text-sm font-medium", children: ["Email", _jsx(Input, { type: "email", name: "email", autoComplete: "email", placeholder: "you@example.com", value: email, onChange: (event) => setEmail(event.target.value), disabled: busy, required: true, className: "h-11" })] }), _jsxs("label", { className: "flex flex-col gap-1.5 text-sm font-medium", children: ["Password", _jsx(Input, { type: "password", name: "password", autoComplete: isSignUp ? "new-password" : "current-password", value: password, onChange: (event) => setPassword(event.target.value), disabled: busy, required: true, className: "h-11" })] }), _jsx(Button, { type: "submit", size: "lg", disabled: busy, children: busy ? (isSignUp ? "Signing up…" : "Signing in…") : "Continue" })] })), onSubmit === undefined || providers.length === 0 ? null : (_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { "aria-hidden": true, className: "h-px flex-1 bg-border" }), _jsx("span", { className: "text-xs tracking-wider text-muted-foreground uppercase", children: "or" }), _jsx("span", { "aria-hidden": true, className: "h-px flex-1 bg-border" })] })), providers.map((provider) => (_jsxs(Button, { type: "button", variant: "outline", size: "lg", disabled: busy, onClick: () => onProvider?.(provider.id), children: [provider.icon, provider.label] }, provider.id))), footer === undefined ? null : (_jsx("div", { className: "text-sm text-muted-foreground", children: footer }))] }));
151
38
  }
@@ -6,24 +6,28 @@ import { useMemo, useState } from "react";
6
6
  * Selection is held as an id rather than an index so it survives the list
7
7
  * reordering that happens every keystroke (fresh search results land at
8
8
  * different positions) — an index would silently point at the wrong row.
9
- */ export function useCommandPaletteNavigation({ items, onSelect, onClose }) {
9
+ */
10
+ export function useCommandPaletteNavigation({ items, onSelect, onClose, }) {
10
11
  const [requestedId, setRequestedId] = useState(undefined);
11
- const activeId = useMemo(()=>{
12
- if (items.length === 0) return undefined;
13
- if (requestedId !== undefined && items.some((item)=>item.id === requestedId)) return requestedId;
12
+ const activeId = useMemo(() => {
13
+ if (items.length === 0)
14
+ return undefined;
15
+ if (requestedId !== undefined &&
16
+ items.some((item) => item.id === requestedId))
17
+ return requestedId;
14
18
  return items[0]?.id;
15
- }, [
16
- items,
17
- requestedId
18
- ]);
19
+ }, [items, requestedId]);
19
20
  function moveBy(delta) {
20
- if (items.length === 0) return;
21
- const currentIndex = activeId === undefined ? -1 : items.findIndex((item)=>item.id === activeId);
21
+ if (items.length === 0)
22
+ return;
23
+ const currentIndex = activeId === undefined
24
+ ? -1
25
+ : items.findIndex((item) => item.id === activeId);
22
26
  const nextIndex = (currentIndex + delta + items.length) % items.length;
23
27
  setRequestedId(items[nextIndex]?.id);
24
28
  }
25
29
  function onKeyDown(event) {
26
- switch(event.key){
30
+ switch (event.key) {
27
31
  case "ArrowDown":
28
32
  event.preventDefault();
29
33
  moveBy(1);
@@ -34,7 +38,8 @@ import { useMemo, useState } from "react";
34
38
  return;
35
39
  case "Enter":
36
40
  event.preventDefault();
37
- if (activeId !== undefined) onSelect(activeId);
41
+ if (activeId !== undefined)
42
+ onSelect(activeId);
38
43
  return;
39
44
  case "Escape":
40
45
  event.preventDefault();
@@ -44,9 +49,5 @@ import { useMemo, useState } from "react";
44
49
  return;
45
50
  }
46
51
  }
47
- return {
48
- activeId,
49
- setActiveId: setRequestedId,
50
- onKeyDown
51
- };
52
+ return { activeId, setActiveId: setRequestedId, onKeyDown };
52
53
  }
@@ -1,10 +1,10 @@
1
1
  export type ControllableStateAction<T> = T | ((previous: T) => T);
2
2
  export type UseControllableStateOptions<T> = {
3
3
  /** Controlled value. Supply this with `onChange` to lift state to a parent. */
4
- readonly value?: T;
4
+ readonly value?: T | undefined;
5
5
  /** Seeds the hook's own state when `value` is not supplied. */
6
6
  readonly defaultValue: T;
7
- readonly onChange?: (value: T) => void;
7
+ readonly onChange?: ((value: T) => void) | undefined;
8
8
  /** Identifies the caller in the dev-mode controlled/uncontrolled warning below. */
9
9
  readonly name: string;
10
10
  };