@dfosco/storyboard-react 4.2.0-beta.17 → 4.2.0-beta.18
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 +3 -3
- package/src/BranchBar/BranchBar.jsx +3 -1
- package/src/BranchBar/BranchBar.module.css +2 -2
- package/src/BranchBar/useBranches.js +20 -6
- package/src/BranchBar/useBranches.test.js +68 -0
- package/src/CommandPalette/CommandPalette.jsx +250 -61
- package/src/CommandPalette/command-palette.css +12 -0
- package/src/Icon.jsx +46 -11
- package/src/Viewfinder.jsx +53 -133
- package/src/Viewfinder.module.css +20 -91
- package/src/Workspace.jsx +7 -0
- package/src/canvas/CanvasPage.jsx +601 -62
- package/src/canvas/CanvasPage.module.css +15 -2
- package/src/canvas/CanvasPage.multiselect.test.jsx +7 -0
- package/src/canvas/ConnectorLayer.jsx +120 -152
- package/src/canvas/ConnectorLayer.module.css +69 -0
- package/src/canvas/canvasApi.js +68 -2
- package/src/canvas/connectorGeometry.js +132 -0
- package/src/canvas/hotPoolDevLogs.js +25 -0
- package/src/canvas/useMarqueeSelect.js +30 -4
- package/src/canvas/widgets/CodePenEmbed.jsx +1 -0
- package/src/canvas/widgets/ComponentSetWidget.jsx +199 -0
- package/src/canvas/widgets/ComponentSetWidget.module.css +89 -0
- package/src/canvas/widgets/ComponentWidget.jsx +1 -0
- package/src/canvas/widgets/CropOverlay.jsx +219 -0
- package/src/canvas/widgets/CropOverlay.module.css +118 -0
- package/src/canvas/widgets/ExpandedPane.jsx +472 -0
- package/src/canvas/widgets/ExpandedPane.module.css +179 -0
- package/src/canvas/widgets/ExpandedPane.test.jsx +240 -0
- package/src/canvas/widgets/ExpandedPaneTopBar.jsx +111 -0
- package/src/canvas/widgets/ExpandedPaneTopBar.module.css +59 -0
- package/src/canvas/widgets/ExpandedPaneTopBar.test.jsx +45 -0
- package/src/canvas/widgets/FigmaEmbed.jsx +49 -102
- package/src/canvas/widgets/ImageWidget.jsx +129 -8
- package/src/canvas/widgets/ImageWidget.module.css +30 -0
- package/src/canvas/widgets/LinkPreview.jsx +93 -44
- package/src/canvas/widgets/MarkdownBlock.jsx +141 -16
- package/src/canvas/widgets/MarkdownBlock.module.css +25 -0
- package/src/canvas/widgets/PromptWidget.jsx +414 -0
- package/src/canvas/widgets/PromptWidget.module.css +273 -0
- package/src/canvas/widgets/PrototypeEmbed.jsx +46 -170
- package/src/canvas/widgets/ResizeHandle.jsx +17 -6
- package/src/canvas/widgets/StoryWidget.jsx +65 -11
- package/src/canvas/widgets/TerminalReadWidget.jsx +11 -5
- package/src/canvas/widgets/TerminalReadWidget.module.css +3 -1
- package/src/canvas/widgets/TerminalWidget.jsx +301 -124
- package/src/canvas/widgets/TerminalWidget.module.css +121 -12
- package/src/canvas/widgets/TilesWidget.jsx +302 -0
- package/src/canvas/widgets/TilesWidget.module.css +133 -0
- package/src/canvas/widgets/WidgetChrome.jsx +67 -152
- package/src/canvas/widgets/WidgetChrome.module.css +20 -1
- package/src/canvas/widgets/expandUtils.js +385 -16
- package/src/canvas/widgets/expandUtils.test.js +155 -0
- package/src/canvas/widgets/index.js +6 -2
- package/src/canvas/widgets/tilePool.js +23 -0
- package/src/canvas/widgets/tiles/diagonal-bl.png +0 -0
- package/src/canvas/widgets/tiles/diagonal-br.png +0 -0
- package/src/canvas/widgets/tiles/diagonal-tl.png +0 -0
- package/src/canvas/widgets/tiles/leaf.png +0 -0
- package/src/canvas/widgets/tiles/quarter-tl.png +0 -0
- package/src/canvas/widgets/tiles/quarter-tr.png +0 -0
- package/src/canvas/widgets/tiles/solid-a.png +0 -0
- package/src/canvas/widgets/tiles/solid-b.png +0 -0
- package/src/canvas/widgets/widgetConfig.js +37 -4
- package/src/canvas/widgets/widgetIcons.jsx +190 -0
- package/src/canvas/widgets/widgetProps.js +1 -0
- package/src/context.jsx +47 -19
- package/src/hooks/usePrototypeReloadGuard.js +64 -0
- package/src/index.js +4 -2
- package/src/story/ComponentSetPage.jsx +186 -0
- package/src/story/ComponentSetPage.module.css +121 -0
- package/src/story/StoryPage.jsx +32 -2
- package/src/vite/data-plugin.js +79 -35
- package/src/canvas/widgets/ActionWidget.jsx +0 -200
- package/src/canvas/widgets/ActionWidget.module.css +0 -122
- package/src/canvas/widgets/SplitExpandModal.jsx +0 -234
- package/src/canvas/widgets/SplitExpandModal.module.css +0 -335
- package/src/canvas/widgets/SplitScreenTopBar.jsx +0 -30
- 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
|
-
}
|