@brett_lamy/docstream 0.6.4 → 1.0.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.
- package/README.md +19 -0
- package/package.json +2 -2
- package/src/docs/HighlightedCode.tsx +3 -6
- package/src/playground/ReactCodePreview.tsx +42 -15
- package/src/styles.css +17 -0
package/README.md
CHANGED
|
@@ -311,6 +311,25 @@ The package CSS is intentionally token-driven. It uses normal CSS variables and
|
|
|
311
311
|
|
|
312
312
|
Import the CSS once, then set tokens globally in your app. Components also expose stable classes such as `docs-code`, `docs-tabs`, `docs-hint`, `docs-table`, and `docs-openapi`.
|
|
313
313
|
|
|
314
|
+
Code highlighting follows the surrounding `color-scheme`: set `color-scheme: dark` on a dark page (or any dark container) and code switches to the dark palette. Each token color is a custom property (`--docs-tok-keyword`, `--docs-tok-string`, `--docs-tok-type`, `--docs-tok-function`, `--docs-tok-number`, `--docs-tok-constant`, `--docs-tok-operator`, `--docs-tok-comment`) if you want your own palette.
|
|
315
|
+
|
|
316
|
+
### Live demos with in-page components
|
|
317
|
+
|
|
318
|
+
`ReactDemo` from `@brett_lamy/docstream/playground` boots an almost-node Vite server for self-contained projects. When the components already run on your page, pass `preview` instead: you keep the same frame, header, and collapsible code panel without a runtime.
|
|
319
|
+
|
|
320
|
+
```tsx
|
|
321
|
+
import { ReactDemo } from "@brett_lamy/docstream/playground"
|
|
322
|
+
|
|
323
|
+
<ReactDemo
|
|
324
|
+
title="Counter"
|
|
325
|
+
status={false}
|
|
326
|
+
preview={<Counter />}
|
|
327
|
+
height="auto"
|
|
328
|
+
code={`import { Counter } from "my-ui"\n\nexport default () => <Counter />`}
|
|
329
|
+
language="tsx"
|
|
330
|
+
/>
|
|
331
|
+
```
|
|
332
|
+
|
|
314
333
|
## Bundler Notes
|
|
315
334
|
|
|
316
335
|
This release ships TypeScript and TSX source through ESM exports:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brett_lamy/docstream",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "GitBook-aware readonly markdown and AI stream renderer.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -74,8 +74,8 @@
|
|
|
74
74
|
"dependencies": {
|
|
75
75
|
"@brett_lamy/viz-engine": ">=0.2.0",
|
|
76
76
|
"framer-motion": "^13.1.1",
|
|
77
|
-
"katex": "^0.18.5",
|
|
78
77
|
"gpu-lexer": "^0.0.2",
|
|
78
|
+
"katex": "^0.18.5",
|
|
79
79
|
"lucide-react": "^1.17.0",
|
|
80
80
|
"mermaid": "^11.15.0",
|
|
81
81
|
"rrweb": "2.0.0-alpha.20",
|
|
@@ -7,10 +7,8 @@ type Span = {
|
|
|
7
7
|
end: number
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
keyword: "#C792EA", type: "#FFCB6B", function: "#82AAFF", constant: "#F07178", operator: "#89DDFF",
|
|
13
|
-
}
|
|
10
|
+
/* Token colors live in styles.css (`.docs-tok-*`) and follow the host's color-scheme, so code reads on
|
|
11
|
+
light and dark backgrounds alike. Hosts can retheme via the --docs-tok-* custom properties. */
|
|
14
12
|
|
|
15
13
|
function escapeHtml(value: string) {
|
|
16
14
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\"/g, """)
|
|
@@ -22,8 +20,7 @@ function spansToHtml(code: string, spans: Span[]) {
|
|
|
22
20
|
const prefix = escapeHtml(code.slice(cursor, span.start))
|
|
23
21
|
const text = escapeHtml(code.slice(span.start, span.end))
|
|
24
22
|
cursor = span.end
|
|
25
|
-
|
|
26
|
-
return `${prefix}${color ? `<span style="color:${color}">${text}</span>` : text}`
|
|
23
|
+
return `${prefix}${span.type === "plain" ? text : `<span class="docs-tok docs-tok-${span.type}">${text}</span>`}`
|
|
27
24
|
}).join("") + escapeHtml(code.slice(cursor))
|
|
28
25
|
}
|
|
29
26
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useId, useMemo, useState, type CSSProperties } from "react"
|
|
1
|
+
import { useEffect, useId, useMemo, useState, type CSSProperties, type ReactNode } from "react"
|
|
2
2
|
import { HighlightedCode } from "../docs/HighlightedCode"
|
|
3
3
|
import {
|
|
4
4
|
createAlmostNodeWorkspace,
|
|
@@ -11,8 +11,21 @@ import {
|
|
|
11
11
|
} from "./filesystem"
|
|
12
12
|
|
|
13
13
|
export interface ReactDemoProps {
|
|
14
|
-
/** Complete project file map. Relative paths are rooted at `/`. */
|
|
15
|
-
files
|
|
14
|
+
/** Complete project file map. Relative paths are rooted at `/`. Not needed when `preview` is given. */
|
|
15
|
+
files?: AlmostNodeFiles
|
|
16
|
+
/**
|
|
17
|
+
* Render this in place of the almost-node iframe — for hosts that can mount the demo directly
|
|
18
|
+
* (components already on the page). No runtime starts; the frame, header, and code panel stay the same.
|
|
19
|
+
*/
|
|
20
|
+
preview?: ReactNode
|
|
21
|
+
/** Source text for the code panel. Overrides reading `codeFile` from `files`. */
|
|
22
|
+
code?: string
|
|
23
|
+
/** Highlighting language for `code` (e.g. "tsx"). Defaults to the extension of `codeFile`. */
|
|
24
|
+
language?: string
|
|
25
|
+
/** Header status. Defaults to the runtime state ("ready", "starting"…); pass `false` to hide it. */
|
|
26
|
+
status?: ReactNode | false
|
|
27
|
+
/** Extra header content after the status, e.g. controls that switch between demo variants. */
|
|
28
|
+
actions?: ReactNode
|
|
16
29
|
/** Vite entry file. Defaults to `/src/main.jsx`. */
|
|
17
30
|
entry?: string
|
|
18
31
|
/** Preferred virtual port. almost-node picks the next free port if needed. */
|
|
@@ -71,8 +84,15 @@ function sourceText(files: AlmostNodeFiles, requestedPath: string): string | nul
|
|
|
71
84
|
}
|
|
72
85
|
|
|
73
86
|
/** Render a multi-file React/JSX/TSX project through almost-node's Vite server. */
|
|
87
|
+
const NO_FILES: AlmostNodeFiles = {}
|
|
88
|
+
|
|
74
89
|
export function ReactDemo({
|
|
75
|
-
files,
|
|
90
|
+
files = NO_FILES,
|
|
91
|
+
preview,
|
|
92
|
+
code: codeProp,
|
|
93
|
+
language,
|
|
94
|
+
status,
|
|
95
|
+
actions,
|
|
76
96
|
entry = "/src/main.jsx",
|
|
77
97
|
port = 4173,
|
|
78
98
|
height = 360,
|
|
@@ -87,16 +107,17 @@ export function ReactDemo({
|
|
|
87
107
|
onReady,
|
|
88
108
|
onError,
|
|
89
109
|
}: ReactDemoProps) {
|
|
90
|
-
const
|
|
91
|
-
const [
|
|
110
|
+
const inline = preview !== undefined
|
|
111
|
+
const [run, setRun] = useState(autoStart && !inline ? 1 : 0)
|
|
112
|
+
const [state, setState] = useState<PreviewState>(inline ? "ready" : autoStart ? "starting" : "idle")
|
|
92
113
|
const [url, setUrl] = useState<string | null>(null)
|
|
93
114
|
const [error, setError] = useState<string | null>(null)
|
|
94
115
|
const [codeExpanded, setCodeExpanded] = useState(false)
|
|
95
116
|
const codePanelId = useId()
|
|
96
117
|
const displayedCodeFile = codeFile ?? entry
|
|
97
118
|
const code = useMemo(
|
|
98
|
-
() => sourceText(files, displayedCodeFile),
|
|
99
|
-
[displayedCodeFile, files],
|
|
119
|
+
() => codeProp ?? sourceText(files, displayedCodeFile),
|
|
120
|
+
[codeProp, displayedCodeFile, files],
|
|
100
121
|
)
|
|
101
122
|
const collapsedLines = positiveLineCount(collapsedCodeLines, 3)
|
|
102
123
|
const expandedLines = Math.max(
|
|
@@ -105,6 +126,7 @@ export function ReactDemo({
|
|
|
105
126
|
)
|
|
106
127
|
|
|
107
128
|
useEffect(() => {
|
|
129
|
+
if (inline) return
|
|
108
130
|
if (!run) {
|
|
109
131
|
setState("idle")
|
|
110
132
|
setUrl(null)
|
|
@@ -162,7 +184,7 @@ export function ReactDemo({
|
|
|
162
184
|
cancelled = true
|
|
163
185
|
workspace?.dispose()
|
|
164
186
|
}
|
|
165
|
-
}, [entry, files, onError, onReady, port, run, workspaceOptions])
|
|
187
|
+
}, [entry, files, inline, onError, onReady, port, run, workspaceOptions])
|
|
166
188
|
|
|
167
189
|
const wrapperClass = className ? `docs-react-demo ${className}` : "docs-react-demo"
|
|
168
190
|
const running = state === "starting"
|
|
@@ -171,10 +193,13 @@ export function ReactDemo({
|
|
|
171
193
|
<section className={wrapperClass} data-docstream-react-demo="">
|
|
172
194
|
<header className="docs-react-demo-header">
|
|
173
195
|
<span>{title}</span>
|
|
174
|
-
|
|
175
|
-
{
|
|
176
|
-
|
|
177
|
-
|
|
196
|
+
{status === false ? null : (
|
|
197
|
+
<span className={`docs-react-demo-status docs-react-demo-status-${state}`}>
|
|
198
|
+
{status ?? (state === "ready" ? "ready" : state === "starting" ? "starting" : state)}
|
|
199
|
+
</span>
|
|
200
|
+
)}
|
|
201
|
+
{actions}
|
|
202
|
+
{!inline && (!autoStart || state === "error") ? (
|
|
178
203
|
<button
|
|
179
204
|
type="button"
|
|
180
205
|
className="docs-react-demo-run"
|
|
@@ -185,7 +210,9 @@ export function ReactDemo({
|
|
|
185
210
|
</button>
|
|
186
211
|
) : null}
|
|
187
212
|
</header>
|
|
188
|
-
{
|
|
213
|
+
{inline ? (
|
|
214
|
+
<div className="docs-react-demo-preview" style={frameHeight(height)}>{preview}</div>
|
|
215
|
+
) : error ? (
|
|
189
216
|
<pre className="docs-react-demo-error">{error}</pre>
|
|
190
217
|
) : url ? (
|
|
191
218
|
<iframe
|
|
@@ -211,7 +238,7 @@ export function ReactDemo({
|
|
|
211
238
|
>
|
|
212
239
|
<HighlightedCode
|
|
213
240
|
code={code}
|
|
214
|
-
language={sourceLanguage(displayedCodeFile)}
|
|
241
|
+
language={language ?? sourceLanguage(displayedCodeFile)}
|
|
215
242
|
lineNumbers
|
|
216
243
|
/>
|
|
217
244
|
</pre>
|
package/src/styles.css
CHANGED
|
@@ -507,6 +507,17 @@
|
|
|
507
507
|
display: block;
|
|
508
508
|
}
|
|
509
509
|
|
|
510
|
+
/* Syntax tokens (HighlightedCode). Palenight on dark color-schemes, same-hue AA colors on light ones.
|
|
511
|
+
Override any --docs-tok-* property to retheme. */
|
|
512
|
+
.docs-tok-keyword { color: var(--docs-tok-keyword, light-dark(#8839C4, #C792EA)); }
|
|
513
|
+
.docs-tok-type { color: var(--docs-tok-type, light-dark(#A15C00, #FFCB6B)); }
|
|
514
|
+
.docs-tok-function { color: var(--docs-tok-function, light-dark(#2F5BD3, #82AAFF)); }
|
|
515
|
+
.docs-tok-string { color: var(--docs-tok-string, light-dark(#23803A, #A5D6A7)); }
|
|
516
|
+
.docs-tok-number { color: var(--docs-tok-number, light-dark(#C2410C, #F78C6C)); }
|
|
517
|
+
.docs-tok-constant { color: var(--docs-tok-constant, light-dark(#C8283F, #F07178)); }
|
|
518
|
+
.docs-tok-operator { color: var(--docs-tok-operator, light-dark(#0B7285, #89DDFF)); }
|
|
519
|
+
.docs-tok-comment { color: var(--docs-tok-comment, light-dark(#6E6E7A, #8A8A98)); font-style: italic; }
|
|
520
|
+
|
|
510
521
|
.docs-code-line {
|
|
511
522
|
display: flex;
|
|
512
523
|
gap: 14px;
|
|
@@ -973,6 +984,12 @@
|
|
|
973
984
|
background: #fff;
|
|
974
985
|
}
|
|
975
986
|
|
|
987
|
+
/* Inline preview (`preview` prop): the host's own content, so no forced background. `height: auto` grows to fit. */
|
|
988
|
+
.docs-react-demo-preview {
|
|
989
|
+
position: relative;
|
|
990
|
+
overflow: hidden;
|
|
991
|
+
}
|
|
992
|
+
|
|
976
993
|
.docs-react-demo-placeholder {
|
|
977
994
|
display: grid;
|
|
978
995
|
place-items: center;
|