@brett_lamy/docstream 0.3.7 → 0.4.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/package.json +1 -1
- package/src/playground/ReactCodePreview.tsx +69 -1
- package/src/styles.css +104 -15
package/package.json
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { useEffect, useState, type CSSProperties } from "react"
|
|
1
|
+
import { useEffect, useId, useMemo, useState, type CSSProperties } from "react"
|
|
2
|
+
import { HighlightedCode } from "../docs/HighlightedCode"
|
|
2
3
|
import {
|
|
3
4
|
createAlmostNodeWorkspace,
|
|
4
5
|
createReactDemoFiles,
|
|
6
|
+
normalizeAlmostNodeFiles,
|
|
7
|
+
normalizeAlmostNodePath,
|
|
5
8
|
type AlmostNodeFiles,
|
|
6
9
|
type AlmostNodeWorkspace,
|
|
7
10
|
type AlmostNodeWorkspaceOptions,
|
|
@@ -16,6 +19,12 @@ export interface ReactDemoProps {
|
|
|
16
19
|
port?: number
|
|
17
20
|
/** Height of the embedded preview. */
|
|
18
21
|
height?: number | string
|
|
22
|
+
/** Source file displayed below the preview. Defaults to the entry file. */
|
|
23
|
+
codeFile?: string
|
|
24
|
+
/** Number of source lines visible while the code preview is collapsed. Defaults to 3. */
|
|
25
|
+
collapsedCodeLines?: number
|
|
26
|
+
/** Maximum visible source lines before expanded code scrolls. Defaults to 30. */
|
|
27
|
+
expandedCodeLines?: number
|
|
19
28
|
/** Label shown above the preview. */
|
|
20
29
|
title?: string
|
|
21
30
|
/** Start the runtime on mount (default true). */
|
|
@@ -43,12 +52,33 @@ function frameHeight(height: number | string): CSSProperties {
|
|
|
43
52
|
return { height: typeof height === "number" ? `${height}px` : height }
|
|
44
53
|
}
|
|
45
54
|
|
|
55
|
+
function positiveLineCount(value: number, fallback: number): number {
|
|
56
|
+
return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function sourceLanguage(path: string): string | null {
|
|
60
|
+
const extension = path.split(".").pop()?.toLowerCase()
|
|
61
|
+
return extension && extension !== path ? extension : null
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function sourceText(files: AlmostNodeFiles, requestedPath: string): string | null {
|
|
65
|
+
const normalizedPath = normalizeAlmostNodePath(requestedPath)
|
|
66
|
+
const file = normalizeAlmostNodeFiles(files).find(
|
|
67
|
+
(candidate) => normalizeAlmostNodePath(candidate.path) === normalizedPath,
|
|
68
|
+
)
|
|
69
|
+
if (!file) return null
|
|
70
|
+
return typeof file.content === "string" ? file.content : new TextDecoder().decode(file.content)
|
|
71
|
+
}
|
|
72
|
+
|
|
46
73
|
/** Render a multi-file React/JSX/TSX project through almost-node's Vite server. */
|
|
47
74
|
export function ReactDemo({
|
|
48
75
|
files,
|
|
49
76
|
entry = "/src/main.jsx",
|
|
50
77
|
port = 4173,
|
|
51
78
|
height = 360,
|
|
79
|
+
codeFile,
|
|
80
|
+
collapsedCodeLines = 3,
|
|
81
|
+
expandedCodeLines = 30,
|
|
52
82
|
title = "Live React preview",
|
|
53
83
|
autoStart = true,
|
|
54
84
|
sandbox = "allow-scripts allow-same-origin allow-forms allow-modals",
|
|
@@ -61,6 +91,18 @@ export function ReactDemo({
|
|
|
61
91
|
const [state, setState] = useState<PreviewState>(autoStart ? "starting" : "idle")
|
|
62
92
|
const [url, setUrl] = useState<string | null>(null)
|
|
63
93
|
const [error, setError] = useState<string | null>(null)
|
|
94
|
+
const [codeExpanded, setCodeExpanded] = useState(false)
|
|
95
|
+
const codePanelId = useId()
|
|
96
|
+
const displayedCodeFile = codeFile ?? entry
|
|
97
|
+
const code = useMemo(
|
|
98
|
+
() => sourceText(files, displayedCodeFile),
|
|
99
|
+
[displayedCodeFile, files],
|
|
100
|
+
)
|
|
101
|
+
const collapsedLines = positiveLineCount(collapsedCodeLines, 3)
|
|
102
|
+
const expandedLines = Math.max(
|
|
103
|
+
collapsedLines,
|
|
104
|
+
positiveLineCount(expandedCodeLines, 30),
|
|
105
|
+
)
|
|
64
106
|
|
|
65
107
|
useEffect(() => {
|
|
66
108
|
if (!run) {
|
|
@@ -158,6 +200,32 @@ export function ReactDemo({
|
|
|
158
200
|
{running ? "Starting almost-node…" : "Run the demo to open its virtual filesystem."}
|
|
159
201
|
</div>
|
|
160
202
|
)}
|
|
203
|
+
{code !== null ? (
|
|
204
|
+
<div className="docs-react-demo-code">
|
|
205
|
+
<pre
|
|
206
|
+
id={codePanelId}
|
|
207
|
+
className={codeExpanded ? "docs-react-demo-code-body docs-react-demo-code-body-expanded" : "docs-react-demo-code-body"}
|
|
208
|
+
style={{
|
|
209
|
+
"--docs-react-demo-code-lines": codeExpanded ? expandedLines : collapsedLines,
|
|
210
|
+
} as CSSProperties}
|
|
211
|
+
>
|
|
212
|
+
<HighlightedCode
|
|
213
|
+
code={code}
|
|
214
|
+
language={sourceLanguage(displayedCodeFile)}
|
|
215
|
+
lineNumbers
|
|
216
|
+
/>
|
|
217
|
+
</pre>
|
|
218
|
+
<button
|
|
219
|
+
type="button"
|
|
220
|
+
className="docs-react-demo-code-toggle"
|
|
221
|
+
aria-controls={codePanelId}
|
|
222
|
+
aria-expanded={codeExpanded}
|
|
223
|
+
onClick={() => setCodeExpanded((value) => !value)}
|
|
224
|
+
>
|
|
225
|
+
{codeExpanded ? "Hide Code" : "View Code"}
|
|
226
|
+
</button>
|
|
227
|
+
</div>
|
|
228
|
+
) : null}
|
|
161
229
|
</section>
|
|
162
230
|
)
|
|
163
231
|
}
|
package/src/styles.css
CHANGED
|
@@ -775,27 +775,36 @@
|
|
|
775
775
|
.hljs-comment,
|
|
776
776
|
.hljs-quote,
|
|
777
777
|
.hljs-meta {
|
|
778
|
-
color: var(--gb-muted-foreground);
|
|
778
|
+
color: var(--gb-muted-foreground, #71717a);
|
|
779
779
|
}
|
|
780
780
|
|
|
781
781
|
.hljs-operator,
|
|
782
782
|
.hljs-punctuation {
|
|
783
|
-
color: var(--gb-code-fg);
|
|
783
|
+
color: var(--gb-code-fg, #18181b);
|
|
784
784
|
}
|
|
785
785
|
|
|
786
786
|
.hljs-deletion {
|
|
787
|
-
color: var(--gb-danger);
|
|
787
|
+
color: var(--gb-danger, #ef4444);
|
|
788
788
|
}
|
|
789
789
|
|
|
790
790
|
.hljs-addition {
|
|
791
|
-
color: var(--gb-success);
|
|
791
|
+
color: var(--gb-success, #22c55e);
|
|
792
792
|
}
|
|
793
793
|
|
|
794
794
|
.docs-react-demo {
|
|
795
|
+
--docs-react-demo-panel: var(--gb-panel, var(--card, #ffffff));
|
|
796
|
+
--docs-react-demo-foreground: var(--gb-panel-foreground, var(--card-foreground, #18181b));
|
|
797
|
+
--docs-react-demo-muted-foreground: var(--gb-muted-foreground, var(--muted-foreground, #71717a));
|
|
798
|
+
--docs-react-demo-border: var(--gb-border, var(--border, #e4e4e7));
|
|
799
|
+
--docs-react-demo-accent: var(--gb-accent, var(--accent, #f4f4f5));
|
|
800
|
+
--docs-react-demo-primary: var(--gb-primary, var(--primary, #4f46e5));
|
|
801
|
+
--docs-react-demo-code-bg: var(--gb-code-bg, rgba(128, 128, 128, 0.13));
|
|
802
|
+
--docs-react-demo-success: var(--gb-success, #22c55e);
|
|
803
|
+
--docs-react-demo-danger: var(--gb-danger, #ef4444);
|
|
795
804
|
overflow: hidden;
|
|
796
|
-
border: 1px solid var(--
|
|
805
|
+
border: 1px solid var(--docs-react-demo-border);
|
|
797
806
|
border-radius: 12px;
|
|
798
|
-
background: var(--
|
|
807
|
+
background: var(--docs-react-demo-panel);
|
|
799
808
|
}
|
|
800
809
|
|
|
801
810
|
.docs-react-demo-header {
|
|
@@ -804,33 +813,33 @@
|
|
|
804
813
|
gap: 10px;
|
|
805
814
|
min-height: 42px;
|
|
806
815
|
padding: 8px 12px;
|
|
807
|
-
border-bottom: 1px solid var(--
|
|
808
|
-
color: var(--
|
|
816
|
+
border-bottom: 1px solid var(--docs-react-demo-border);
|
|
817
|
+
color: var(--docs-react-demo-foreground);
|
|
809
818
|
font-size: 13px;
|
|
810
819
|
font-weight: 550;
|
|
811
820
|
}
|
|
812
821
|
|
|
813
822
|
.docs-react-demo-status {
|
|
814
823
|
margin-left: auto;
|
|
815
|
-
color: var(--
|
|
824
|
+
color: var(--docs-react-demo-muted-foreground);
|
|
816
825
|
font-size: 11px;
|
|
817
826
|
text-transform: uppercase;
|
|
818
827
|
letter-spacing: 0.08em;
|
|
819
828
|
}
|
|
820
829
|
|
|
821
830
|
.docs-react-demo-status-ready {
|
|
822
|
-
color: var(--
|
|
831
|
+
color: var(--docs-react-demo-success);
|
|
823
832
|
}
|
|
824
833
|
|
|
825
834
|
.docs-react-demo-status-error {
|
|
826
|
-
color: var(--
|
|
835
|
+
color: var(--docs-react-demo-danger);
|
|
827
836
|
}
|
|
828
837
|
|
|
829
838
|
.docs-react-demo-run {
|
|
830
|
-
border: 1px solid var(--
|
|
839
|
+
border: 1px solid var(--docs-react-demo-border);
|
|
831
840
|
border-radius: 6px;
|
|
832
841
|
padding: 4px 9px;
|
|
833
|
-
color: var(--
|
|
842
|
+
color: var(--docs-react-demo-foreground);
|
|
834
843
|
font-size: 12px;
|
|
835
844
|
cursor: pointer;
|
|
836
845
|
}
|
|
@@ -860,7 +869,87 @@
|
|
|
860
869
|
min-height: 100px;
|
|
861
870
|
overflow: auto;
|
|
862
871
|
padding: 14px;
|
|
863
|
-
color: var(--
|
|
864
|
-
background: var(--
|
|
872
|
+
color: var(--docs-react-demo-danger);
|
|
873
|
+
background: var(--docs-react-demo-code-bg);
|
|
865
874
|
white-space: pre-wrap;
|
|
866
875
|
}
|
|
876
|
+
|
|
877
|
+
.docs-react-demo-code {
|
|
878
|
+
position: relative;
|
|
879
|
+
border-top: 1px solid var(--docs-react-demo-border);
|
|
880
|
+
background: var(--docs-react-demo-code-bg);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
.docs-react-demo-code-body {
|
|
884
|
+
--docs-react-demo-code-line-height: 1.6rem;
|
|
885
|
+
max-height: calc(var(--docs-react-demo-code-lines) * var(--docs-react-demo-code-line-height));
|
|
886
|
+
margin: 0;
|
|
887
|
+
overflow: hidden;
|
|
888
|
+
padding: 1.15rem 1rem 2.4rem;
|
|
889
|
+
font-size: 0.875rem;
|
|
890
|
+
line-height: var(--docs-react-demo-code-line-height);
|
|
891
|
+
tab-size: 2;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
.docs-react-demo-code-body::after {
|
|
895
|
+
position: absolute;
|
|
896
|
+
right: 0;
|
|
897
|
+
bottom: 0;
|
|
898
|
+
left: 0;
|
|
899
|
+
height: 4.5rem;
|
|
900
|
+
background: linear-gradient(to bottom, transparent, var(--docs-react-demo-panel));
|
|
901
|
+
content: "";
|
|
902
|
+
pointer-events: none;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
.docs-react-demo-code-body-expanded {
|
|
906
|
+
overflow: auto;
|
|
907
|
+
padding-bottom: 3.25rem;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
.docs-react-demo-code-body-expanded::after {
|
|
911
|
+
display: none;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
.docs-react-demo-code-body > code {
|
|
915
|
+
display: block;
|
|
916
|
+
min-width: max-content;
|
|
917
|
+
padding: 0;
|
|
918
|
+
background: transparent;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
.docs-react-demo-code-body:not(.docs-react-demo-code-body-expanded) > code {
|
|
922
|
+
max-height: calc(var(--docs-react-demo-code-lines) * var(--docs-react-demo-code-line-height));
|
|
923
|
+
overflow: hidden;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
.docs-react-demo-code-body .docs-code-line {
|
|
927
|
+
min-height: var(--docs-react-demo-code-line-height);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
.docs-react-demo-code-toggle {
|
|
931
|
+
position: absolute;
|
|
932
|
+
bottom: 0.85rem;
|
|
933
|
+
left: 50%;
|
|
934
|
+
z-index: 1;
|
|
935
|
+
transform: translateX(-50%);
|
|
936
|
+
border: 1px solid var(--docs-react-demo-border);
|
|
937
|
+
border-radius: 0.65rem;
|
|
938
|
+
padding: 0.45rem 0.85rem;
|
|
939
|
+
color: var(--docs-react-demo-foreground);
|
|
940
|
+
background: var(--docs-react-demo-panel);
|
|
941
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
|
|
942
|
+
font: inherit;
|
|
943
|
+
font-size: 0.8125rem;
|
|
944
|
+
font-weight: 600;
|
|
945
|
+
cursor: pointer;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
.docs-react-demo-code-toggle:hover {
|
|
949
|
+
background: var(--docs-react-demo-accent);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
.docs-react-demo-code-toggle:focus-visible {
|
|
953
|
+
outline: 2px solid var(--docs-react-demo-primary);
|
|
954
|
+
outline-offset: 2px;
|
|
955
|
+
}
|