@mdocui/react 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,265 @@
1
+ # @mdocui/react
2
+
3
+ React adapter for [mdocui](https://github.com/mdocui/mdocui) -- provides a streaming `Renderer` component, the `useRenderer` hook, and 22 default UI components for LLM generative UI powered by Markdoc `{% %}` tag syntax.
4
+
5
+ Part of the [mdocui](https://github.com/mdocui/mdocui) monorepo.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @mdocui/react @mdocui/core
11
+ ```
12
+
13
+ `@mdocui/core` is a peer dependency.
14
+
15
+ ## Quick Start
16
+
17
+ ```tsx
18
+ import { Renderer, useRenderer, defaultComponents, createDefaultRegistry } from '@mdocui/react'
19
+
20
+ const registry = createDefaultRegistry()
21
+
22
+ function Chat() {
23
+ const { nodes, meta, isStreaming, push, done, reset } = useRenderer({ registry })
24
+
25
+ // Feed chunks from your LLM stream
26
+ async function onStream(reader: ReadableStreamDefaultReader<string>) {
27
+ reset()
28
+ while (true) {
29
+ const { value, done: streamDone } = await reader.read()
30
+ if (streamDone) break
31
+ push(value)
32
+ }
33
+ done()
34
+ }
35
+
36
+ return (
37
+ <Renderer
38
+ nodes={nodes}
39
+ components={defaultComponents}
40
+ isStreaming={isStreaming}
41
+ onAction={(event) => console.log('action:', event)}
42
+ />
43
+ )
44
+ }
45
+ ```
46
+
47
+ ---
48
+
49
+ ## `useRenderer`
50
+
51
+ Hook that wraps `@mdocui/core`'s `StreamingParser`. Manages parse state in React and returns reactive AST nodes.
52
+
53
+ ```ts
54
+ const { nodes, meta, isStreaming, push, done, reset } = useRenderer({ registry })
55
+ ```
56
+
57
+ ### Options
58
+
59
+ | Option | Type | Description |
60
+ |--------|------|-------------|
61
+ | `registry` | `ComponentRegistry` | Component registry from `@mdocui/core` |
62
+
63
+ ### Return value (`UseRendererReturn`)
64
+
65
+ | Field | Type | Description |
66
+ |-------|------|-------------|
67
+ | `nodes` | `ASTNode[]` | Current parsed AST |
68
+ | `meta` | `ParseMeta` | Parse errors, node count, completion status |
69
+ | `isStreaming` | `boolean` | `true` between the first `push` and `done` |
70
+ | `push(chunk)` | `(chunk: string) => void` | Feed a text chunk from the LLM stream |
71
+ | `done()` | `() => void` | Signal the stream has ended; flushes the parser |
72
+ | `reset()` | `() => void` | Clear all state for a new conversation turn |
73
+
74
+ ---
75
+
76
+ ## `Renderer`
77
+
78
+ Renders an `ASTNode[]` tree into React elements using a component map.
79
+
80
+ ```tsx
81
+ <Renderer
82
+ nodes={nodes}
83
+ components={defaultComponents}
84
+ onAction={handleAction}
85
+ isStreaming={isStreaming}
86
+ renderProse={(content, key) => <Markdown key={key}>{content}</Markdown>}
87
+ />
88
+ ```
89
+
90
+ ### Props (`RendererProps`)
91
+
92
+ | Prop | Type | Default | Description |
93
+ |------|------|---------|-------------|
94
+ | `nodes` | `ASTNode[]` | required | AST from `useRenderer` or `StreamingParser` |
95
+ | `components` | `ComponentMap` | required | Map of tag name to React component |
96
+ | `onAction` | `ActionHandler` | no-op | Callback for interactive events |
97
+ | `isStreaming` | `boolean` | `false` | Whether the LLM is still streaming |
98
+ | `renderProse` | `(content: string, key: string) => ReactNode` | plain `<span>` | Custom renderer for prose nodes (see below) |
99
+
100
+ ---
101
+
102
+ ## `renderProse` -- Markdown Rendering
103
+
104
+ By default, prose nodes render as plain `<span>` elements. To render markdown, pass a `renderProse` function:
105
+
106
+ ```tsx
107
+ import ReactMarkdown from 'react-markdown'
108
+
109
+ <Renderer
110
+ nodes={nodes}
111
+ components={defaultComponents}
112
+ renderProse={(content, key) => <ReactMarkdown key={key}>{content}</ReactMarkdown>}
113
+ />
114
+ ```
115
+
116
+ Any markdown library works -- `react-markdown`, `marked`, `markdown-it`, etc.
117
+
118
+ ---
119
+
120
+ ## Components (22)
121
+
122
+ All default components are available via `defaultComponents` and registered in `createDefaultRegistry()`.
123
+
124
+ ### Layout (7)
125
+
126
+ | Component | Tag | Description |
127
+ |-----------|-----|-------------|
128
+ | Stack | `stack` | Vertical or horizontal flex container. Props: `direction?`, `gap?`, `align?` |
129
+ | Grid | `grid` | CSS grid layout. Props: `cols?`, `gap?` |
130
+ | Card | `card` | Bordered container. Props: `title?`, `variant?` |
131
+ | Divider | `divider` | Horizontal separator line. Self-closing. |
132
+ | Accordion | `accordion` | Collapsible section. Props: `title`, `open?` |
133
+ | Tabs | `tabs` | Tabbed container. Props: `labels`, `active?` |
134
+ | Tab | `tab` | Single tab panel inside `tabs`. Props: `label` |
135
+
136
+ ### Interactive (6)
137
+
138
+ | Component | Tag | Description |
139
+ |-----------|-----|-------------|
140
+ | Button | `button` | Action button. Props: `action`, `label`, `variant?`, `disabled?` |
141
+ | ButtonGroup | `button-group` | Row of buttons. Props: `direction?` |
142
+ | Input | `input` | Text input field. Props: `name`, `label?`, `placeholder?`, `type?`, `required?` |
143
+ | Select | `select` | Dropdown select. Props: `name`, `label?`, `options`, `placeholder?`, `required?` |
144
+ | Checkbox | `checkbox` | Toggle checkbox. Props: `name`, `label`, `checked?` |
145
+ | Form | `form` | Groups inputs; submits collected state. Props: `name`, `action?` |
146
+
147
+ ### Data (4)
148
+
149
+ | Component | Tag | Description |
150
+ |-----------|-----|-------------|
151
+ | Chart | `chart` | Bar, line, pie, or donut chart. Props: `type`, `labels`, `values`, `title?` |
152
+ | Table | `table` | Data table. Props: `headers`, `rows`, `caption?` |
153
+ | Stat | `stat` | Key metric display. Props: `label`, `value`, `change?`, `trend?` |
154
+ | Progress | `progress` | Progress bar. Props: `value`, `label?`, `max?` |
155
+
156
+ ### Content (5)
157
+
158
+ | Component | Tag | Description |
159
+ |-----------|-----|-------------|
160
+ | Callout | `callout` | Alert / notice block. Props: `type`, `title?`. Accepts body content. |
161
+ | Badge | `badge` | Inline label. Props: `label`, `variant?` |
162
+ | Image | `image` | Inline image. Props: `src`, `alt`, `width?`, `height?` |
163
+ | CodeBlock | `code-block` | Syntax-highlighted code. Props: `language?`, `title?`, `code` |
164
+ | Link | `link` | Action link. Props: `action`, `label`, `url?` |
165
+
166
+ ---
167
+
168
+ ## `ComponentProps`
169
+
170
+ Every component in the `ComponentMap` receives the same props interface:
171
+
172
+ ```ts
173
+ interface ComponentProps {
174
+ name: string // tag name, e.g. "button"
175
+ props: Record<string, unknown> // parsed attributes from the tag
176
+ children?: React.ReactNode // rendered child nodes (for body tags)
177
+ onAction: ActionHandler // fire an ActionEvent
178
+ isStreaming: boolean // whether the LLM is still streaming
179
+ }
180
+ ```
181
+
182
+ ---
183
+
184
+ ## `onAction` Event Handling
185
+
186
+ Interactive components fire `ActionEvent` objects through the `onAction` callback:
187
+
188
+ ```ts
189
+ interface ActionEvent {
190
+ type: 'button_click' | 'form_submit' | 'select_change' | 'link_click'
191
+ action: string
192
+ label?: string
193
+ formName?: string
194
+ formState?: Record<string, unknown>
195
+ tagName: string
196
+ params?: Record<string, unknown>
197
+ }
198
+ ```
199
+
200
+ ```tsx
201
+ function handleAction(event: ActionEvent) {
202
+ switch (event.type) {
203
+ case 'button_click':
204
+ if (event.action === 'continue') {
205
+ // Send event.label as a new user message
206
+ }
207
+ break
208
+ case 'form_submit':
209
+ console.log(event.formName, event.formState)
210
+ break
211
+ }
212
+ }
213
+
214
+ <Renderer nodes={nodes} components={defaultComponents} onAction={handleAction} />
215
+ ```
216
+
217
+ ---
218
+
219
+ ## Custom Component Map
220
+
221
+ Override or extend the defaults by merging your own components:
222
+
223
+ ```tsx
224
+ import { defaultComponents } from '@mdocui/react'
225
+ import type { ComponentProps } from '@mdocui/react'
226
+
227
+ function MyCard({ props, children }: ComponentProps) {
228
+ return (
229
+ <div className="my-card">
230
+ {props.title && <h3>{props.title as string}</h3>}
231
+ {children}
232
+ </div>
233
+ )
234
+ }
235
+
236
+ const components = {
237
+ ...defaultComponents,
238
+ card: MyCard,
239
+ }
240
+
241
+ <Renderer nodes={nodes} components={components} />
242
+ ```
243
+
244
+ ---
245
+
246
+ ## Context
247
+
248
+ `useMdocUI()` provides the renderer context from inside any component in the tree:
249
+
250
+ ```ts
251
+ import { useMdocUI } from '@mdocui/react'
252
+
253
+ function MyComponent() {
254
+ const { onAction, isStreaming, formState, setFormField } = useMdocUI()
255
+ // ...
256
+ }
257
+ ```
258
+
259
+ Must be used inside a `<Renderer />`.
260
+
261
+ ---
262
+
263
+ ## License
264
+
265
+ See the root [mdocui](https://github.com/mdocui/mdocui) repository for license details.