@dfosco/storyboard-react 4.2.0-beta.1 → 4.2.0-beta.17

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 (48) hide show
  1. package/package.json +5 -4
  2. package/src/AuthModal/AuthModal.jsx +6 -2
  3. package/src/BranchBar/BranchBar.jsx +17 -5
  4. package/src/BranchBar/BranchBar.module.css +11 -2
  5. package/src/CommandPalette/CommandPalette.jsx +267 -164
  6. package/src/CommandPalette/command-palette.css +130 -78
  7. package/src/Icon.jsx +112 -48
  8. package/src/Viewfinder.jsx +511 -61
  9. package/src/Viewfinder.module.css +414 -2
  10. package/src/canvas/CanvasPage.bridge.test.jsx +14 -6
  11. package/src/canvas/CanvasPage.dragdrop.test.jsx +10 -6
  12. package/src/canvas/CanvasPage.jsx +157 -174
  13. package/src/canvas/CanvasPage.module.css +0 -15
  14. package/src/canvas/CanvasPage.multiselect.test.jsx +10 -6
  15. package/src/canvas/ConnectorLayer.jsx +5 -5
  16. package/src/canvas/PageSelector.test.jsx +15 -6
  17. package/src/canvas/useCanvas.js +1 -1
  18. package/src/canvas/widgets/ActionWidget.jsx +200 -0
  19. package/src/canvas/widgets/ActionWidget.module.css +122 -0
  20. package/src/canvas/widgets/FigmaEmbed.jsx +97 -29
  21. package/src/canvas/widgets/FigmaEmbed.module.css +61 -0
  22. package/src/canvas/widgets/ImageWidget.jsx +1 -1
  23. package/src/canvas/widgets/LinkPreview.jsx +64 -5
  24. package/src/canvas/widgets/LinkPreview.module.css +127 -0
  25. package/src/canvas/widgets/MarkdownBlock.jsx +39 -17
  26. package/src/canvas/widgets/MarkdownBlock.module.css +123 -0
  27. package/src/canvas/widgets/PrototypeEmbed.jsx +183 -20
  28. package/src/canvas/widgets/PrototypeEmbed.module.css +117 -0
  29. package/src/canvas/widgets/PrototypeEmbed.test.jsx +2 -2
  30. package/src/canvas/widgets/SplitExpandModal.jsx +234 -0
  31. package/src/canvas/widgets/SplitExpandModal.module.css +335 -0
  32. package/src/canvas/widgets/SplitScreenTopBar.jsx +30 -0
  33. package/src/canvas/widgets/SplitScreenTopBar.module.css +58 -0
  34. package/src/canvas/widgets/StoryWidget.jsx +7 -4
  35. package/src/canvas/widgets/TerminalReadWidget.jsx +140 -0
  36. package/src/canvas/widgets/TerminalReadWidget.module.css +92 -0
  37. package/src/canvas/widgets/TerminalWidget.jsx +299 -49
  38. package/src/canvas/widgets/TerminalWidget.module.css +155 -1
  39. package/src/canvas/widgets/WidgetChrome.jsx +19 -14
  40. package/src/canvas/widgets/WidgetChrome.module.css +10 -0
  41. package/src/canvas/widgets/embedInteraction.test.jsx +24 -26
  42. package/src/canvas/widgets/expandUtils.js +188 -0
  43. package/src/canvas/widgets/index.js +5 -0
  44. package/src/canvas/widgets/snapshotDisplay.test.jsx +23 -71
  45. package/src/canvas/widgets/widgetConfig.js +19 -1
  46. package/src/hooks/useConfig.js +14 -0
  47. package/src/index.js +4 -0
  48. package/src/vite/data-plugin.js +264 -14
@@ -0,0 +1,335 @@
1
+ /* ── Backdrop ──────────────────────────────────────────────────────── */
2
+
3
+ .backdrop {
4
+ position: fixed;
5
+ inset: 0;
6
+ z-index: 100000;
7
+ background: rgba(0, 0, 0, 0.8);
8
+ display: flex;
9
+ align-items: center;
10
+ justify-content: center;
11
+ animation: splitExpandFadeIn 0.15s ease;
12
+ }
13
+
14
+ @keyframes splitExpandFadeIn {
15
+ from { opacity: 0; }
16
+ to { opacity: 1; }
17
+ }
18
+
19
+ /* ── Modal container ──────────────────────────────────────────────── */
20
+
21
+ .modal {
22
+ width: 90vw;
23
+ height: 90vh;
24
+ position: relative;
25
+ border-radius: 12px;
26
+ overflow: hidden;
27
+ background: var(--bgColor-default, #ffffff);
28
+ box-shadow: 0 24px 64px rgba(0, 0, 0, 0.4);
29
+ display: flex;
30
+ flex-direction: column;
31
+ animation: splitExpandScaleIn 0.2s ease;
32
+ }
33
+
34
+ .modalFullscreen {
35
+ position: fixed;
36
+ inset: 0;
37
+ overflow: hidden;
38
+ background: var(--bgColor-default, #ffffff);
39
+ display: flex;
40
+ flex-direction: column;
41
+ animation: splitExpandScaleIn 0.2s ease;
42
+ }
43
+
44
+ @keyframes splitExpandScaleIn {
45
+ from { transform: scale(0.95); opacity: 0; }
46
+ to { transform: scale(1); opacity: 1; }
47
+ }
48
+
49
+ /* ── Top bar ──────────────────────────────────────────────────────── */
50
+
51
+ .topBar {
52
+ display: flex;
53
+ align-items: center;
54
+ height: 40px;
55
+ padding: 0 12px;
56
+ background: var(--bgColor-muted, #f6f8fa);
57
+ border-bottom: 1px solid var(--borderColor-muted, #d8dee4);
58
+ flex-shrink: 0;
59
+ }
60
+
61
+ .topBarTitle {
62
+ font-size: 12px;
63
+ font-weight: 500;
64
+ color: var(--fgColor-muted, #656d76);
65
+ overflow: hidden;
66
+ text-overflow: ellipsis;
67
+ white-space: nowrap;
68
+ }
69
+
70
+ .closeBtn {
71
+ all: unset;
72
+ cursor: pointer;
73
+ margin-left: auto;
74
+ width: 28px;
75
+ height: 28px;
76
+ display: flex;
77
+ align-items: center;
78
+ justify-content: center;
79
+ border-radius: 6px;
80
+ color: var(--fgColor-muted, #656d76);
81
+ font-size: 14px;
82
+ transition: background 100ms, color 100ms;
83
+ }
84
+
85
+ .closeBtn:hover {
86
+ background: var(--bgColor-neutral-muted, #eaeef2);
87
+ color: var(--fgColor-default, #1f2328);
88
+ }
89
+
90
+ .closeBtnFloat {
91
+ all: unset;
92
+ cursor: pointer;
93
+ position: absolute;
94
+ top: 12px;
95
+ right: 12px;
96
+ width: 32px;
97
+ height: 32px;
98
+ display: flex;
99
+ align-items: center;
100
+ justify-content: center;
101
+ border-radius: 8px;
102
+ background: rgba(0, 0, 0, 0.5);
103
+ color: #ffffff;
104
+ font-size: 16px;
105
+ z-index: 1;
106
+ transition: background 100ms;
107
+ backdrop-filter: blur(4px);
108
+ }
109
+
110
+ .closeBtnFloat:hover {
111
+ background: rgba(0, 0, 0, 0.7);
112
+ }
113
+
114
+ /* ── Body ─────────────────────────────────────────────────────────── */
115
+
116
+ .body {
117
+ flex: 1;
118
+ min-height: 0;
119
+ display: flex;
120
+ }
121
+
122
+ .bodySplit {
123
+ flex: 1;
124
+ min-height: 0;
125
+ display: flex;
126
+ }
127
+
128
+ .bodySplit .splitLeft,
129
+ .bodySplit .splitRight {
130
+ flex: 1;
131
+ min-width: 0;
132
+ height: 100%;
133
+ overflow: hidden;
134
+ }
135
+
136
+ .bodySplit .splitLeft {
137
+ border-right: 1px solid var(--borderColor-muted, #d8dee4);
138
+ }
139
+
140
+ /* ── Panes ────────────────────────────────────────────────────────── */
141
+
142
+ .primaryPane {
143
+ width: 100%;
144
+ height: 100%;
145
+ overflow: auto;
146
+ }
147
+
148
+ .secondaryPane {
149
+ width: 100%;
150
+ height: 100%;
151
+ overflow: auto;
152
+ }
153
+
154
+ .secondaryIframe {
155
+ border: none;
156
+ width: 100%;
157
+ height: 100%;
158
+ display: block;
159
+ }
160
+
161
+ .terminalContainer {
162
+ width: 100%;
163
+ height: 100%;
164
+ background: #0d1117;
165
+ }
166
+
167
+ /* ── Markdown content in secondary pane ───────────────────────────── */
168
+
169
+ .markdownContent {
170
+ padding: 32px 40px;
171
+ font-size: 15px;
172
+ line-height: 1.7;
173
+ color: var(--fgColor-default, #1f2328);
174
+ font-family: var(--tc-font-stack, system-ui, -apple-system, sans-serif);
175
+ max-width: 800px;
176
+ margin: 0 auto;
177
+ }
178
+
179
+ .markdownContent * {
180
+ pointer-events: auto;
181
+ }
182
+
183
+ .markdownContent a {
184
+ color: var(--fgColor-accent, #0969da);
185
+ text-decoration: none;
186
+ }
187
+
188
+ .markdownContent a:hover {
189
+ text-decoration: underline;
190
+ }
191
+
192
+ .markdownContent img {
193
+ max-width: 100%;
194
+ height: auto;
195
+ border-radius: 6px;
196
+ border: 1px solid var(--borderColor-default, #d0d7de);
197
+ margin: 8px 0;
198
+ display: block;
199
+ }
200
+
201
+ .markdownContent video {
202
+ max-width: 100%;
203
+ height: auto;
204
+ border-radius: 6px;
205
+ margin: 8px 0;
206
+ display: block;
207
+ }
208
+
209
+ .markdownContent pre {
210
+ padding: 12px 16px;
211
+ border-radius: 6px;
212
+ border: 1px solid var(--borderColor-muted, #d8dee4);
213
+ overflow-x: auto;
214
+ margin: 8px 0;
215
+ background: var(--bgColor-neutral-muted, #afb8c133);
216
+ line-height: 1.4;
217
+ }
218
+
219
+ .markdownContent code {
220
+ background: var(--bgColor-neutral-muted, #afb8c133);
221
+ padding: 2px 5px;
222
+ border-radius: 4px;
223
+ font-size: 13px;
224
+ font-family: ui-monospace, SFMono-Regular, monospace;
225
+ }
226
+
227
+ .markdownContent pre code {
228
+ background: none;
229
+ padding: 0;
230
+ }
231
+
232
+ .markdownContent h1 { font-size: 22px; font-weight: 700; margin: 0 0 12px; }
233
+ .markdownContent h2 { font-size: 18px; font-weight: 600; margin: 0 0 8px; }
234
+ .markdownContent h3 { font-size: 16px; font-weight: 600; margin: 0 0 6px; }
235
+ .markdownContent p { margin: 0 0 12px; }
236
+ .markdownContent ul { margin: 0 0 12px; padding-left: 24px; list-style: disc; }
237
+ .markdownContent ol { margin: 0 0 12px; padding-left: 24px; list-style: decimal; }
238
+ .markdownContent li { margin: 0 0 4px; display: list-item; }
239
+
240
+ .markdownContent blockquote {
241
+ border-left: 4px solid var(--borderColor-default, #d0d7de);
242
+ margin: 12px 0;
243
+ padding: 4px 16px;
244
+ color: var(--fgColor-muted, #656d76);
245
+ }
246
+
247
+ .markdownContent table {
248
+ border-collapse: collapse;
249
+ margin: 12px 0;
250
+ width: 100%;
251
+ font-size: 14px;
252
+ }
253
+
254
+ .markdownContent th,
255
+ .markdownContent td {
256
+ border: 1px solid var(--borderColor-default, #d0d7de);
257
+ padding: 6px 12px;
258
+ text-align: left;
259
+ }
260
+
261
+ .markdownContent th {
262
+ background: var(--bgColor-muted, #f6f8fa);
263
+ font-weight: 600;
264
+ }
265
+
266
+ /* ── GitHub card in secondary pane ────────────────────────────────── */
267
+
268
+ .githubCard {
269
+ height: 100%;
270
+ display: flex;
271
+ flex-direction: column;
272
+ background: var(--bgColor-default, #ffffff);
273
+ }
274
+
275
+ .githubHeader {
276
+ display: flex;
277
+ align-items: center;
278
+ gap: 8px;
279
+ padding: 16px 24px;
280
+ font-size: 18px;
281
+ font-weight: 600;
282
+ color: var(--fgColor-default, #1f2328);
283
+ border-bottom: 1px solid var(--borderColor-muted, #d8dee4);
284
+ }
285
+
286
+ .githubTitle {
287
+ overflow: hidden;
288
+ text-overflow: ellipsis;
289
+ white-space: nowrap;
290
+ }
291
+
292
+ .githubBody {
293
+ flex: 1;
294
+ overflow: auto;
295
+ padding: 24px;
296
+ font-size: 15px;
297
+ line-height: 1.7;
298
+ color: var(--fgColor-default, #1f2328);
299
+ }
300
+
301
+ .githubBody * { pointer-events: auto; }
302
+ .githubBody a { color: var(--fgColor-accent, #0969da); text-decoration: none; }
303
+ .githubBody a:hover { text-decoration: underline; }
304
+ .githubBody img { max-width: 100%; height: auto; border-radius: 6px; margin: 8px 0; display: block; }
305
+ .githubBody video { max-width: 100%; height: auto; border-radius: 6px; margin: 8px 0; display: block; }
306
+ .githubBody pre { padding: 12px 16px; border-radius: 6px; border: 1px solid var(--borderColor-muted, #d8dee4); overflow-x: auto; margin: 12px 0; background: var(--bgColor-neutral-muted, #afb8c133); }
307
+ .githubBody code { background: var(--bgColor-neutral-muted, #afb8c133); padding: 2px 5px; border-radius: 4px; font-size: 13px; font-family: ui-monospace, SFMono-Regular, monospace; }
308
+ .githubBody pre code { background: none; padding: 0; }
309
+ .githubBody h1 { font-size: 20px; font-weight: 700; margin: 16px 0 8px; }
310
+ .githubBody h2 { font-size: 17px; font-weight: 600; margin: 14px 0 6px; }
311
+ .githubBody p { margin: 0 0 12px; }
312
+
313
+ /* ── Plain link card in secondary pane ────────────────────────────── */
314
+
315
+ .linkCard {
316
+ padding: 32px 40px;
317
+ font-family: var(--tc-font-stack, system-ui, -apple-system, sans-serif);
318
+ }
319
+
320
+ .linkTitle {
321
+ font-size: 18px;
322
+ font-weight: 600;
323
+ color: var(--fgColor-default, #1f2328);
324
+ margin: 0 0 8px;
325
+ }
326
+
327
+ .linkUrl {
328
+ font-size: 14px;
329
+ color: var(--fgColor-accent, #0969da);
330
+ text-decoration: none;
331
+ }
332
+
333
+ .linkUrl:hover {
334
+ text-decoration: underline;
335
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * SplitScreenTopBar — terminal-style slim top bar for split-screen modals.
3
+ * Shows "Type · Metadata" for each pane, with active pane in default color
4
+ * and inactive pane in muted color. Theme-responsive.
5
+ */
6
+ import { ScreenNormalIcon } from '@primer/octicons-react'
7
+ import styles from './SplitScreenTopBar.module.css'
8
+
9
+ /**
10
+ * @param {Object} props
11
+ * @param {string} props.leftLabel — "Type · Metadata" for the left pane
12
+ * @param {string} props.rightLabel — "Type · Metadata" for the right pane
13
+ * @param {'left' | 'right'} props.activePane — which pane has focus
14
+ * @param {() => void} props.onClose — close handler
15
+ */
16
+ export default function SplitScreenTopBar({ leftLabel, rightLabel, activePane, onClose }) {
17
+ return (
18
+ <div className={styles.bar}>
19
+ <span className={`${styles.leftLabel} ${activePane === 'left' ? styles.active : styles.muted}`}>
20
+ {leftLabel}
21
+ </span>
22
+ <span className={`${styles.rightLabel} ${activePane === 'right' ? styles.active : styles.muted}`}>
23
+ {rightLabel}
24
+ </span>
25
+ <button className={styles.closeBtn} onClick={onClose} aria-label="Close expanded view" autoFocus>
26
+ <ScreenNormalIcon size={16} />
27
+ </button>
28
+ </div>
29
+ )
30
+ }
@@ -0,0 +1,58 @@
1
+ /* Theme-responsive split-screen top bar (terminal-style slim bar) */
2
+
3
+ .bar {
4
+ display: flex;
5
+ align-items: center;
6
+ height: 40px;
7
+ padding: 0 12px;
8
+ gap: 12px;
9
+ background: var(--bgColor-muted, #f6f8fa);
10
+ border-bottom: 1px solid var(--borderColor-muted, #d8dee4);
11
+ flex-shrink: 0;
12
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
13
+ font-size: 12px;
14
+ font-weight: 500;
15
+ user-select: none;
16
+ }
17
+
18
+ .leftLabel {
19
+ white-space: nowrap;
20
+ overflow: hidden;
21
+ text-overflow: ellipsis;
22
+ min-width: 0;
23
+ }
24
+
25
+ .rightLabel {
26
+ white-space: nowrap;
27
+ overflow: hidden;
28
+ text-overflow: ellipsis;
29
+ min-width: 0;
30
+ margin-left: auto;
31
+ }
32
+
33
+ .active {
34
+ color: var(--fgColor-default, #1f2328);
35
+ }
36
+
37
+ .muted {
38
+ color: var(--fgColor-muted, #656d76);
39
+ }
40
+
41
+ .closeBtn {
42
+ all: unset;
43
+ cursor: pointer;
44
+ flex-shrink: 0;
45
+ width: 28px;
46
+ height: 28px;
47
+ display: flex;
48
+ align-items: center;
49
+ justify-content: center;
50
+ border-radius: 6px;
51
+ color: var(--fgColor-muted, #656d76);
52
+ transition: background 100ms, color 100ms;
53
+ }
54
+
55
+ .closeBtn:hover {
56
+ background: var(--bgColor-neutral-muted, #eaeef2);
57
+ color: var(--fgColor-default, #1f2328);
58
+ }
@@ -171,10 +171,13 @@ export default forwardRef(function StoryWidget({ id: widgetId, props, onUpdate,
171
171
  [storyId, exportName, storyIndexKey],
172
172
  )
173
173
 
174
+ // When paused and not interactive, freeze the iframe src to prevent reloads
175
+ const effectiveSrc = iframeSrc
176
+
174
177
  useIframeDevLogs({
175
178
  widget: 'StoryWidget',
176
- loaded: interactive && !showCode && Boolean(iframeSrc),
177
- src: iframeSrc,
179
+ loaded: interactive && !showCode && Boolean(effectiveSrc),
180
+ src: effectiveSrc,
178
181
  })
179
182
 
180
183
  const displayName = exportName ? `${storyId} / ${exportName}` : storyId
@@ -192,7 +195,7 @@ export default forwardRef(function StoryWidget({ id: widgetId, props, onUpdate,
192
195
  )
193
196
  }
194
197
 
195
- if (!iframeSrc) {
198
+ if (!effectiveSrc) {
196
199
  return (
197
200
  <WidgetWrapper>
198
201
  <div className={styles.container} ref={containerRef}>
@@ -241,7 +244,7 @@ export default forwardRef(function StoryWidget({ id: widgetId, props, onUpdate,
241
244
  <div className={styles.content}>
242
245
  <iframe
243
246
  ref={iframeRef}
244
- src={iframeSrc}
247
+ src={effectiveSrc}
245
248
  className={styles.iframe}
246
249
  title={displayName}
247
250
  />
@@ -0,0 +1,140 @@
1
+ import { useRef, useEffect, useState } from 'react'
2
+ import { readProp, schemas } from './widgetProps.js'
3
+ import styles from './TerminalReadWidget.module.css'
4
+
5
+ const terminalSchema = schemas['terminal']
6
+
7
+ let Convert = null
8
+ let ansiLoadAttempted = false
9
+
10
+ async function getConverter() {
11
+ if (Convert) return new Convert({ fg: '#e6edf3', bg: '#0d1117', newline: true })
12
+ if (ansiLoadAttempted) return null
13
+ ansiLoadAttempted = true
14
+ try {
15
+ const mod = await import(/* @vite-ignore */ 'ansi-to-html')
16
+ Convert = mod.default || mod
17
+ return new Convert({ fg: '#e6edf3', bg: '#0d1117', newline: true })
18
+ } catch {
19
+ return null
20
+ }
21
+ }
22
+
23
+ function stripAnsi(text) {
24
+ // eslint-disable-next-line no-control-regex
25
+ return text.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '')
26
+ }
27
+
28
+ function getBaseUrl() {
29
+ const base = (typeof import.meta !== 'undefined' && import.meta.env?.BASE_URL) || '/'
30
+ return base.endsWith('/') ? base : base + '/'
31
+ }
32
+
33
+ function getCanvasId() {
34
+ return window.__storyboardCanvasBridgeState?.canvasId || null
35
+ }
36
+
37
+ function isProduction() {
38
+ return typeof import.meta !== 'undefined' && import.meta.env?.PROD
39
+ }
40
+
41
+ export default function TerminalReadWidget({ id, props }) {
42
+ const width = readProp(props, 'width', terminalSchema)
43
+ const height = readProp(props, 'height', terminalSchema)
44
+ const prettyName = props?.prettyName || '...'
45
+
46
+ const [content, setContent] = useState(null)
47
+ const [html, setHtml] = useState(null)
48
+ const [failed, setFailed] = useState(false)
49
+ const contentRef = useRef(null)
50
+
51
+ useEffect(() => {
52
+ let cancelled = false
53
+ async function fetchSnapshot() {
54
+ const baseUrl = getBaseUrl()
55
+ const canvasId = getCanvasId()
56
+ if (!canvasId) { setFailed(true); return }
57
+
58
+ const urls = isProduction()
59
+ ? [`${baseUrl}_storyboard/terminal-snapshots/${canvasId}/${id}.json`]
60
+ : [
61
+ `${baseUrl}_storyboard/canvas/${canvasId}/terminal-snapshot/${id}`,
62
+ `${baseUrl}_storyboard/terminal-snapshots/${canvasId}/${id}.json`,
63
+ ]
64
+
65
+ for (const url of urls) {
66
+ try {
67
+ const res = await fetch(url)
68
+ if (!res.ok) continue
69
+ const data = await res.json()
70
+ if (cancelled) return
71
+ const text = data.content || data.output || ''
72
+ setContent(text)
73
+
74
+ const converter = await getConverter()
75
+ if (cancelled) return
76
+ if (converter) {
77
+ setHtml(converter.toHtml(text))
78
+ } else {
79
+ setContent(stripAnsi(text))
80
+ }
81
+ return
82
+ } catch {
83
+ continue
84
+ }
85
+ }
86
+ if (!cancelled) setFailed(true)
87
+ }
88
+ fetchSnapshot()
89
+ return () => { cancelled = true }
90
+ }, [id])
91
+
92
+ // Auto-scroll to bottom
93
+ useEffect(() => {
94
+ if (contentRef.current) {
95
+ contentRef.current.scrollTop = contentRef.current.scrollHeight
96
+ }
97
+ }, [html, content])
98
+
99
+ const titleLabel = `terminal · ${prettyName}`
100
+
101
+ return (
102
+ <div className={styles.container}>
103
+ <div className={styles.titleBar}>
104
+ <span>{titleLabel}</span>
105
+ <span className={styles.readOnlyBadge}>read only</span>
106
+ </div>
107
+ <div
108
+ ref={contentRef}
109
+ className={styles.content}
110
+ style={{
111
+ ...(typeof width === 'number' ? { width: `${width}px` } : undefined),
112
+ ...(typeof height === 'number' ? { height: `${height}px` } : undefined),
113
+ }}
114
+ >
115
+ {failed && (
116
+ <div className={styles.placeholder}>
117
+ <span className={styles.placeholderTitle}>Terminal session · {prettyName}</span>
118
+ <span className={styles.placeholderSub}>No captured output available</span>
119
+ </div>
120
+ )}
121
+ {!failed && content === null && (
122
+ <div className={styles.placeholder}>
123
+ <span className={styles.placeholderSub}>Loading…</span>
124
+ </div>
125
+ )}
126
+ {!failed && html && (
127
+ <pre
128
+ style={{ margin: 0, whiteSpace: 'pre', fontFamily: 'inherit', fontSize: 'inherit', lineHeight: 'inherit' }}
129
+ dangerouslySetInnerHTML={{ __html: html }}
130
+ />
131
+ )}
132
+ {!failed && content !== null && !html && (
133
+ <pre style={{ margin: 0, whiteSpace: 'pre', fontFamily: 'inherit', fontSize: 'inherit', lineHeight: 'inherit' }}>
134
+ {content}
135
+ </pre>
136
+ )}
137
+ </div>
138
+ </div>
139
+ )
140
+ }
@@ -0,0 +1,92 @@
1
+ .container {
2
+ position: relative;
3
+ display: flex;
4
+ flex-direction: column;
5
+ border-radius: var(--base-size-16, 16px);
6
+ }
7
+
8
+ :global(.tc-drag-surface):has(.container) {
9
+ border-radius: 16px;
10
+ }
11
+
12
+ .titleBar {
13
+ position: absolute;
14
+ top: -28px;
15
+ left: 4px;
16
+ display: flex;
17
+ align-items: center;
18
+ gap: 8px;
19
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
20
+ font-size: 11px;
21
+ color: #8b949e;
22
+ pointer-events: none;
23
+ user-select: none;
24
+ white-space: nowrap;
25
+ z-index: 2;
26
+ }
27
+
28
+ [data-widget-selected] .titleBar {
29
+ color: var(--borderColor-accent-emphasis, #0969da);
30
+ }
31
+
32
+ .readOnlyBadge {
33
+ font-size: 10px;
34
+ color: #6e7681;
35
+ background: rgba(110, 118, 129, 0.15);
36
+ padding: 1px 6px;
37
+ border-radius: 4px;
38
+ font-weight: 500;
39
+ }
40
+
41
+ .content {
42
+ background: #0d1117;
43
+ color: #e6edf3;
44
+ font-family: 'SF Mono', 'Menlo', 'Monaco', 'Courier New', monospace;
45
+ font-size: 13px;
46
+ line-height: 17px;
47
+ padding: 12px;
48
+ overflow-y: auto;
49
+ white-space: pre;
50
+ flex: 1;
51
+ border: 1px solid var(--borderColor-default, #30363d);
52
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.24);
53
+ border-radius: var(--base-size-16, 16px);
54
+ box-sizing: border-box;
55
+ }
56
+
57
+ .content::-webkit-scrollbar {
58
+ width: 6px;
59
+ }
60
+
61
+ .content::-webkit-scrollbar-thumb {
62
+ background: rgba(255, 255, 255, 0.15);
63
+ border-radius: 3px;
64
+ }
65
+
66
+ .content::-webkit-scrollbar-track {
67
+ background: transparent;
68
+ }
69
+
70
+ .placeholder {
71
+ display: flex;
72
+ flex-direction: column;
73
+ align-items: center;
74
+ justify-content: center;
75
+ gap: 8px;
76
+ height: 100%;
77
+ color: #8b949e;
78
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
79
+ font-size: 13px;
80
+ text-align: center;
81
+ user-select: none;
82
+ }
83
+
84
+ .placeholderTitle {
85
+ color: #e6edf3;
86
+ font-weight: 500;
87
+ }
88
+
89
+ .placeholderSub {
90
+ color: #6e7681;
91
+ font-size: 12px;
92
+ }