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

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 (79) hide show
  1. package/package.json +7 -3
  2. package/src/BranchBar/BranchBar.jsx +3 -1
  3. package/src/BranchBar/BranchBar.module.css +2 -2
  4. package/src/BranchBar/useBranches.js +20 -6
  5. package/src/BranchBar/useBranches.test.js +68 -0
  6. package/src/CommandPalette/CommandPalette.jsx +250 -61
  7. package/src/CommandPalette/command-palette.css +12 -0
  8. package/src/Icon.jsx +46 -11
  9. package/src/Viewfinder.jsx +53 -133
  10. package/src/Viewfinder.module.css +20 -91
  11. package/src/Workspace.jsx +7 -0
  12. package/src/canvas/CanvasPage.jsx +601 -62
  13. package/src/canvas/CanvasPage.module.css +15 -2
  14. package/src/canvas/CanvasPage.multiselect.test.jsx +7 -0
  15. package/src/canvas/ConnectorLayer.jsx +120 -152
  16. package/src/canvas/ConnectorLayer.module.css +69 -0
  17. package/src/canvas/canvasApi.js +68 -2
  18. package/src/canvas/connectorGeometry.js +132 -0
  19. package/src/canvas/hotPoolDevLogs.js +25 -0
  20. package/src/canvas/useMarqueeSelect.js +30 -4
  21. package/src/canvas/widgets/CodePenEmbed.jsx +1 -0
  22. package/src/canvas/widgets/ComponentSetWidget.jsx +199 -0
  23. package/src/canvas/widgets/ComponentSetWidget.module.css +89 -0
  24. package/src/canvas/widgets/ComponentWidget.jsx +1 -0
  25. package/src/canvas/widgets/CropOverlay.jsx +219 -0
  26. package/src/canvas/widgets/CropOverlay.module.css +118 -0
  27. package/src/canvas/widgets/ExpandedPane.jsx +472 -0
  28. package/src/canvas/widgets/ExpandedPane.module.css +179 -0
  29. package/src/canvas/widgets/ExpandedPane.test.jsx +240 -0
  30. package/src/canvas/widgets/ExpandedPaneTopBar.jsx +111 -0
  31. package/src/canvas/widgets/ExpandedPaneTopBar.module.css +59 -0
  32. package/src/canvas/widgets/ExpandedPaneTopBar.test.jsx +45 -0
  33. package/src/canvas/widgets/FigmaEmbed.jsx +49 -102
  34. package/src/canvas/widgets/ImageWidget.jsx +129 -8
  35. package/src/canvas/widgets/ImageWidget.module.css +30 -0
  36. package/src/canvas/widgets/LinkPreview.jsx +93 -44
  37. package/src/canvas/widgets/MarkdownBlock.jsx +141 -16
  38. package/src/canvas/widgets/MarkdownBlock.module.css +25 -0
  39. package/src/canvas/widgets/PromptWidget.jsx +414 -0
  40. package/src/canvas/widgets/PromptWidget.module.css +273 -0
  41. package/src/canvas/widgets/PrototypeEmbed.jsx +46 -170
  42. package/src/canvas/widgets/ResizeHandle.jsx +17 -6
  43. package/src/canvas/widgets/StoryWidget.jsx +65 -11
  44. package/src/canvas/widgets/TerminalReadWidget.jsx +11 -5
  45. package/src/canvas/widgets/TerminalReadWidget.module.css +3 -1
  46. package/src/canvas/widgets/TerminalWidget.jsx +301 -124
  47. package/src/canvas/widgets/TerminalWidget.module.css +121 -12
  48. package/src/canvas/widgets/TilesWidget.jsx +302 -0
  49. package/src/canvas/widgets/TilesWidget.module.css +133 -0
  50. package/src/canvas/widgets/WidgetChrome.jsx +67 -152
  51. package/src/canvas/widgets/WidgetChrome.module.css +20 -1
  52. package/src/canvas/widgets/expandUtils.js +385 -16
  53. package/src/canvas/widgets/expandUtils.test.js +155 -0
  54. package/src/canvas/widgets/index.js +6 -2
  55. package/src/canvas/widgets/tilePool.js +23 -0
  56. package/src/canvas/widgets/tiles/diagonal-bl.png +0 -0
  57. package/src/canvas/widgets/tiles/diagonal-br.png +0 -0
  58. package/src/canvas/widgets/tiles/diagonal-tl.png +0 -0
  59. package/src/canvas/widgets/tiles/leaf.png +0 -0
  60. package/src/canvas/widgets/tiles/quarter-tl.png +0 -0
  61. package/src/canvas/widgets/tiles/quarter-tr.png +0 -0
  62. package/src/canvas/widgets/tiles/solid-a.png +0 -0
  63. package/src/canvas/widgets/tiles/solid-b.png +0 -0
  64. package/src/canvas/widgets/widgetConfig.js +37 -4
  65. package/src/canvas/widgets/widgetIcons.jsx +190 -0
  66. package/src/canvas/widgets/widgetProps.js +1 -0
  67. package/src/context.jsx +47 -19
  68. package/src/hooks/usePrototypeReloadGuard.js +64 -0
  69. package/src/index.js +4 -2
  70. package/src/story/ComponentSetPage.jsx +186 -0
  71. package/src/story/ComponentSetPage.module.css +121 -0
  72. package/src/story/StoryPage.jsx +32 -2
  73. package/src/vite/data-plugin.js +79 -35
  74. package/src/canvas/widgets/ActionWidget.jsx +0 -200
  75. package/src/canvas/widgets/ActionWidget.module.css +0 -122
  76. package/src/canvas/widgets/SplitExpandModal.jsx +0 -234
  77. package/src/canvas/widgets/SplitExpandModal.module.css +0 -335
  78. package/src/canvas/widgets/SplitScreenTopBar.jsx +0 -30
  79. package/src/canvas/widgets/SplitScreenTopBar.module.css +0 -58
@@ -1,234 +0,0 @@
1
- /**
2
- * SplitExpandModal — reusable full-screen modal for expandable widgets.
3
- *
4
- * When a connected split-screen-capable widget exists, renders a 50/50
5
- * split (ordered by x-coordinate). Otherwise renders single-pane.
6
- */
7
- import { useState, useEffect, useRef, useMemo, useCallback } from 'react'
8
- import { createPortal } from 'react-dom'
9
- import { ScreenNormalIcon, MarkGithubIcon } from '@primer/octicons-react'
10
- import { findConnectedSplitTarget, getPaneOrder, buildSecondaryIframeUrl, reparentTerminalInto, getSplitPaneLabel } from './expandUtils.js'
11
- import SplitScreenTopBar from './SplitScreenTopBar.jsx'
12
- import styles from './SplitExpandModal.module.css'
13
-
14
- /**
15
- * Renders the content for a secondary pane based on widget type.
16
- */
17
- function SecondaryPane({ widget }) {
18
- const iframeUrl = useMemo(() => buildSecondaryIframeUrl(widget), [widget])
19
- const terminalRef = useRef(null)
20
- const cleanupRef = useRef(null)
21
-
22
- // Reparent terminal DOM into the pane
23
- useEffect(() => {
24
- if ((widget.type !== 'terminal' && widget.type !== 'terminal-read' && widget.type !== 'agent') || !terminalRef.current) return
25
- cleanupRef.current = reparentTerminalInto(widget.id, terminalRef.current)
26
- return () => {
27
- cleanupRef.current?.()
28
- cleanupRef.current = null
29
- }
30
- }, [widget.id, widget.type])
31
-
32
- // iframe-embeddable types
33
- if (iframeUrl) {
34
- return (
35
- <div className={styles.secondaryPane}>
36
- <iframe src={iframeUrl} className={styles.secondaryIframe} title="Connected widget" />
37
- </div>
38
- )
39
- }
40
-
41
- // Terminal: reparent its DOM
42
- if (widget.type === 'terminal' || widget.type === 'terminal-read' || widget.type === 'agent') {
43
- return (
44
- <div className={styles.secondaryPane}>
45
- <div ref={terminalRef} className={styles.terminalContainer} />
46
- </div>
47
- )
48
- }
49
-
50
- // Markdown: render content from props
51
- if (widget.type === 'markdown') {
52
- // Import remark inline to avoid making it a hard dep of this module
53
- const content = widget.props?.content || ''
54
- return (
55
- <div className={styles.secondaryPane}>
56
- <MarkdownSecondary content={content} />
57
- </div>
58
- )
59
- }
60
-
61
- // Link-preview (GitHub card or plain)
62
- if (widget.type === 'link-preview') {
63
- return (
64
- <div className={styles.secondaryPane}>
65
- <LinkPreviewSecondary widget={widget} />
66
- </div>
67
- )
68
- }
69
-
70
- return null
71
- }
72
-
73
- /**
74
- * Renders markdown as HTML for the secondary pane.
75
- * Uses dynamic import to avoid bundling remark in this module eagerly.
76
- */
77
- function MarkdownSecondary({ content }) {
78
- const ref = useRef(null)
79
-
80
- useEffect(() => {
81
- if (!ref.current || !content) return
82
- let cancelled = false
83
- ;(async () => {
84
- const { remark } = await import('remark')
85
- const remarkGfm = (await import('remark-gfm')).default
86
- const remarkHtml = (await import('remark-html')).default
87
- if (cancelled) return
88
- const result = remark().use(remarkGfm).use(remarkHtml, { sanitize: false }).processSync(content)
89
- let html = String(result).replace(/<a\s/g, '<a target="_blank" rel="noopener noreferrer" ')
90
- if (ref.current) ref.current.innerHTML = html
91
- })()
92
- return () => { cancelled = true }
93
- }, [content])
94
-
95
- return <div ref={ref} className={styles.markdownContent} />
96
- }
97
-
98
- /**
99
- * Renders a link-preview widget's content for the secondary pane.
100
- */
101
- function LinkPreviewSecondary({ widget }) {
102
- const { url, title, github } = widget.props || {}
103
- const bodyRef = useRef(null)
104
-
105
- useEffect(() => {
106
- if (!bodyRef.current) return
107
- const bodyHtml = github?.bodyHtml || ''
108
- if (bodyHtml) bodyRef.current.innerHTML = bodyHtml
109
- }, [github?.bodyHtml])
110
-
111
- if (github) {
112
- return (
113
- <div className={styles.githubCard}>
114
- <div className={styles.githubHeader}>
115
- <MarkGithubIcon size={16} />
116
- <span className={styles.githubTitle}>{title || url || 'GitHub'}</span>
117
- </div>
118
- {github.bodyHtml && <div ref={bodyRef} className={styles.githubBody} />}
119
- </div>
120
- )
121
- }
122
-
123
- return (
124
- <div className={styles.linkCard}>
125
- <p className={styles.linkTitle}>{title || url || 'Link'}</p>
126
- {url && (
127
- <a href={url} target="_blank" rel="noopener noreferrer" className={styles.linkUrl}>
128
- {url}
129
- </a>
130
- )}
131
- </div>
132
- )
133
- }
134
-
135
- /**
136
- * @param {Object} props
137
- * @param {boolean} props.expanded — whether the modal is visible
138
- * @param {() => void} props.onClose — callback to close the modal
139
- * @param {string} props.widgetId — the primary widget's ID
140
- * @param {string} [props.title] — optional title for the top bar
141
- * @param {React.ReactNode} props.children — primary pane content
142
- */
143
- export default function SplitExpandModal({ expanded, onClose, widgetId, title, children }) {
144
- const connectedWidget = useMemo(
145
- () => (expanded ? findConnectedSplitTarget(widgetId) : null),
146
- [expanded, widgetId],
147
- )
148
- const hasSplit = Boolean(connectedWidget)
149
- const paneOrder = useMemo(
150
- () => (hasSplit ? getPaneOrder(widgetId, connectedWidget) : { primaryIsLeft: true }),
151
- [hasSplit, widgetId, connectedWidget],
152
- )
153
- const [activePane, setActivePane] = useState('left')
154
-
155
- const primaryWidget = useMemo(() => {
156
- const bridge = window.__storyboardCanvasBridgeState
157
- return bridge?.widgets?.find((w) => w.id === widgetId) || null
158
- }, [widgetId, expanded])
159
-
160
- const primaryLabel = useMemo(() => getSplitPaneLabel(primaryWidget) || title || '', [primaryWidget, title])
161
- const secondaryLabel = useMemo(() => getSplitPaneLabel(connectedWidget), [connectedWidget])
162
- const leftLabel = paneOrder.primaryIsLeft ? primaryLabel : secondaryLabel
163
- const rightLabel = paneOrder.primaryIsLeft ? secondaryLabel : primaryLabel
164
-
165
- // Close on Escape
166
- useEffect(() => {
167
- if (!expanded) return
168
- function handleKeyDown(e) {
169
- if (e.key === 'Escape') {
170
- e.stopPropagation()
171
- onClose()
172
- }
173
- }
174
- document.addEventListener('keydown', handleKeyDown, true)
175
- return () => document.removeEventListener('keydown', handleKeyDown, true)
176
- }, [expanded, onClose])
177
-
178
- if (!expanded) return null
179
-
180
- const primarySide = paneOrder.primaryIsLeft ? 'left' : 'right'
181
- const secondarySide = paneOrder.primaryIsLeft ? 'right' : 'left'
182
- const primaryPane = <div className={styles.primaryPane} onPointerDown={() => setActivePane(primarySide)}>{children}</div>
183
- const secondaryPane = hasSplit ? <div onPointerDown={() => setActivePane(secondarySide)}><SecondaryPane widget={connectedWidget} /></div> : null
184
-
185
- const leftPane = paneOrder.primaryIsLeft ? primaryPane : secondaryPane
186
- const rightPane = paneOrder.primaryIsLeft ? secondaryPane : primaryPane
187
-
188
- return createPortal(
189
- <div
190
- className={styles.backdrop}
191
- onClick={onClose}
192
- onPointerDown={(e) => e.stopPropagation()}
193
- onKeyDown={(e) => e.stopPropagation()}
194
- onWheel={(e) => e.stopPropagation()}
195
- >
196
- <div className={hasSplit ? styles.modalFullscreen : styles.modal} onClick={(e) => e.stopPropagation()}>
197
- {hasSplit ? (
198
- <>
199
- <SplitScreenTopBar
200
- leftLabel={leftLabel}
201
- rightLabel={rightLabel}
202
- activePane={activePane}
203
- onClose={onClose}
204
- />
205
- <div className={styles.bodySplit}>
206
- <div className={styles.splitLeft}>{leftPane}</div>
207
- <div className={styles.splitRight}>{rightPane}</div>
208
- </div>
209
- </>
210
- ) : (
211
- <>
212
- {title && (
213
- <div className={styles.topBar}>
214
- <span className={styles.topBarTitle}>{title}</span>
215
- <button className={styles.closeBtn} onClick={onClose} aria-label="Close expanded view" autoFocus>
216
- <ScreenNormalIcon size={16} />
217
- </button>
218
- </div>
219
- )}
220
- <div className={styles.body}>
221
- {primaryPane}
222
- </div>
223
- {!title && (
224
- <button className={styles.closeBtnFloat} onClick={onClose} aria-label="Close expanded view" autoFocus>
225
-
226
- </button>
227
- )}
228
- </>
229
- )}
230
- </div>
231
- </div>,
232
- document.body,
233
- )
234
- }
@@ -1,335 +0,0 @@
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
- }
@@ -1,30 +0,0 @@
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
- }
@@ -1,58 +0,0 @@
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
- }