@dfosco/storyboard-react 4.0.0-beta.9 → 4.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/package.json +6 -3
- package/src/AuthModal/AuthModal.jsx +134 -0
- package/src/AuthModal/AuthModal.module.css +221 -0
- package/src/BranchBar/BranchBar.jsx +56 -0
- package/src/BranchBar/BranchBar.module.css +230 -0
- package/src/BranchBar/useBranches.js +79 -0
- package/src/CommandPalette/CommandPalette.jsx +936 -0
- package/src/CommandPalette/CreateDialog.jsx +219 -0
- package/src/CommandPalette/command-palette.css +111 -0
- package/src/Icon.jsx +180 -0
- package/src/Viewfinder.jsx +1104 -57
- package/src/Viewfinder.module.css +1107 -149
- package/src/canvas/CanvasControls.jsx +51 -2
- package/src/canvas/CanvasControls.module.css +31 -0
- package/src/canvas/CanvasPage.bridge.test.jsx +142 -19
- package/src/canvas/CanvasPage.dragdrop.test.jsx +346 -0
- package/src/canvas/CanvasPage.jsx +807 -251
- package/src/canvas/CanvasPage.module.css +98 -50
- package/src/canvas/CanvasPage.multiselect.test.jsx +13 -11
- package/src/canvas/CanvasToolbar.jsx +2 -2
- package/src/canvas/MarqueeOverlay.jsx +20 -0
- package/src/canvas/PageSelector.jsx +239 -0
- package/src/canvas/PageSelector.module.css +165 -0
- package/src/canvas/PageSelector.test.jsx +104 -0
- package/src/canvas/canvasApi.js +22 -8
- package/src/canvas/canvasTheme.js +96 -52
- package/src/canvas/componentIsolate.jsx +33 -7
- package/src/canvas/useCanvas.js +9 -8
- package/src/canvas/useCanvas.test.js +4 -4
- package/src/canvas/useMarqueeSelect.js +187 -0
- package/src/canvas/useMarqueeSelect.test.js +78 -0
- package/src/canvas/widgets/CodePenEmbed.jsx +292 -0
- package/src/canvas/widgets/CodePenEmbed.module.css +161 -0
- package/src/canvas/widgets/ComponentWidget.jsx +42 -10
- package/src/canvas/widgets/ComponentWidget.module.css +6 -5
- package/src/canvas/widgets/FigmaEmbed.jsx +110 -24
- package/src/canvas/widgets/FigmaEmbed.module.css +21 -7
- package/src/canvas/widgets/LinkPreview.jsx +297 -11
- package/src/canvas/widgets/LinkPreview.module.css +386 -18
- package/src/canvas/widgets/LinkPreview.test.jsx +193 -0
- package/src/canvas/widgets/MarkdownBlock.jsx +86 -5
- package/src/canvas/widgets/MarkdownBlock.module.css +64 -15
- package/src/canvas/widgets/PrototypeEmbed.jsx +96 -145
- package/src/canvas/widgets/PrototypeEmbed.module.css +74 -4
- package/src/canvas/widgets/StickyNote.module.css +5 -0
- package/src/canvas/widgets/StickyNote.test.jsx +9 -9
- package/src/canvas/widgets/StoryWidget.jsx +277 -0
- package/src/canvas/widgets/StoryWidget.module.css +211 -0
- package/src/canvas/widgets/WidgetChrome.jsx +76 -20
- package/src/canvas/widgets/WidgetChrome.module.css +2 -6
- package/src/canvas/widgets/WidgetWrapper.module.css +2 -0
- package/src/canvas/widgets/codepenUrl.js +75 -0
- package/src/canvas/widgets/codepenUrl.test.js +76 -0
- package/src/canvas/widgets/embedInteraction.test.jsx +235 -0
- package/src/canvas/widgets/embedOverlay.module.css +35 -0
- package/src/canvas/widgets/embedTheme.js +138 -39
- package/src/canvas/widgets/githubUrl.js +82 -0
- package/src/canvas/widgets/githubUrl.test.js +74 -0
- package/src/canvas/widgets/iframeDevLogs.js +49 -0
- package/src/canvas/widgets/iframeDevLogs.test.jsx +81 -0
- package/src/canvas/widgets/index.js +4 -0
- package/src/canvas/widgets/pasteRules.js +295 -0
- package/src/canvas/widgets/pasteRules.test.js +474 -0
- package/src/canvas/widgets/snapshotDisplay.test.jsx +259 -0
- package/src/canvas/widgets/widgetConfig.js +16 -5
- package/src/canvas/widgets/widgetConfig.test.js +34 -12
- package/src/context.jsx +145 -16
- package/src/hooks/useSceneData.js +4 -2
- package/src/hooks/useThemeState.js +61 -0
- package/src/hooks/useThemeState.test.js +66 -0
- package/src/index.js +10 -0
- package/src/story/StoryPage.jsx +117 -0
- package/src/story/StoryPage.module.css +18 -0
- package/src/vite/data-plugin.js +348 -66
- package/src/vite/data-plugin.test.js +405 -5
package/package.json
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dfosco/storyboard-react",
|
|
3
|
-
"version": "4.0.0
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@
|
|
7
|
-
"@dfosco/
|
|
6
|
+
"@base-ui/react": "^1.4.0",
|
|
7
|
+
"@dfosco/storyboard-core": "4.0.0",
|
|
8
|
+
"@dfosco/tiny-canvas": "4.0.0",
|
|
8
9
|
"@neodrag/react": "^2.3.1",
|
|
9
10
|
"glob": "^11.0.0",
|
|
10
11
|
"jsonc-parser": "^3.3.1",
|
|
11
12
|
"remark": "^15.0.1",
|
|
12
13
|
"remark-gfm": "^4.0.1",
|
|
14
|
+
"react-cmdk": "^1.3.9",
|
|
13
15
|
"remark-html": "^16.0.1"
|
|
14
16
|
},
|
|
15
17
|
"license": "MIT",
|
|
@@ -22,6 +24,7 @@
|
|
|
22
24
|
"src"
|
|
23
25
|
],
|
|
24
26
|
"peerDependencies": {
|
|
27
|
+
"@primer/octicons-react": ">=19",
|
|
25
28
|
"react": ">=18",
|
|
26
29
|
"react-router-dom": ">=6",
|
|
27
30
|
"vite": ">=5"
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AuthModal — Global PAT entry dialog for comments authentication.
|
|
3
|
+
* Mounted at app root, triggered by:
|
|
4
|
+
* - Svelte CoreUIBar (comments tool / "C" shortcut) via 'storyboard:open-auth-modal' event
|
|
5
|
+
* - ViewfinderNew sidebar login button via same event
|
|
6
|
+
*/
|
|
7
|
+
import { useState, useEffect, useCallback } from 'react'
|
|
8
|
+
import { Dialog } from '@base-ui/react/dialog'
|
|
9
|
+
import { Button } from '@base-ui/react/button'
|
|
10
|
+
import css from './AuthModal.module.css'
|
|
11
|
+
|
|
12
|
+
const COMMENTS_TOKEN_KEY = 'sb-comments-token'
|
|
13
|
+
|
|
14
|
+
function getRepoInfo() {
|
|
15
|
+
try {
|
|
16
|
+
// eslint-disable-next-line no-undef
|
|
17
|
+
const cfg = typeof __STORYBOARD_CONFIG__ !== 'undefined' ? __STORYBOARD_CONFIG__ : null
|
|
18
|
+
const repo = cfg?.repository
|
|
19
|
+
if (repo?.owner && repo?.name) return repo
|
|
20
|
+
} catch { /* ignore */ }
|
|
21
|
+
return { owner: 'github', name: 'storyboard' }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default function AuthModal() {
|
|
25
|
+
const [open, setOpen] = useState(false)
|
|
26
|
+
const [tokenValue, setTokenValue] = useState('')
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
function handleOpen() { setOpen(true) }
|
|
30
|
+
document.addEventListener('storyboard:open-auth-modal', handleOpen)
|
|
31
|
+
return () => document.removeEventListener('storyboard:open-auth-modal', handleOpen)
|
|
32
|
+
}, [])
|
|
33
|
+
|
|
34
|
+
const handleClose = useCallback(() => {
|
|
35
|
+
setOpen(false)
|
|
36
|
+
setTokenValue('')
|
|
37
|
+
}, [])
|
|
38
|
+
|
|
39
|
+
const handleSignIn = useCallback(() => {
|
|
40
|
+
const trimmed = tokenValue.trim()
|
|
41
|
+
if (!trimmed) return
|
|
42
|
+
|
|
43
|
+
try { localStorage.setItem(COMMENTS_TOKEN_KEY, trimmed) } catch { /* ignore */ }
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
import('@dfosco/storyboard-core/comments').then(({ setToken }) => {
|
|
47
|
+
setToken(trimmed)
|
|
48
|
+
}).catch(() => {})
|
|
49
|
+
} catch { /* comments module may not be initialized */ }
|
|
50
|
+
|
|
51
|
+
setTokenValue('')
|
|
52
|
+
setOpen(false)
|
|
53
|
+
}, [tokenValue])
|
|
54
|
+
|
|
55
|
+
const handleKeyDown = useCallback((e) => {
|
|
56
|
+
if (e.key === 'Enter') handleSignIn()
|
|
57
|
+
}, [handleSignIn])
|
|
58
|
+
|
|
59
|
+
const repo = getRepoInfo()
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<Dialog.Root open={open} onOpenChange={setOpen}>
|
|
63
|
+
<Dialog.Portal>
|
|
64
|
+
<Dialog.Backdrop className={css.backdrop} />
|
|
65
|
+
<div className={css.popupWrap}>
|
|
66
|
+
<Dialog.Popup className={css.popup}>
|
|
67
|
+
<Dialog.Title className={css.title}>Sign in for comments</Dialog.Title>
|
|
68
|
+
<Dialog.Description className={css.desc}>
|
|
69
|
+
Leave comments for other users to see and respond, and react to! Storyboard
|
|
70
|
+
comments use Discussions as a back-end and require a GitHub PAT to be enabled.
|
|
71
|
+
</Dialog.Description>
|
|
72
|
+
<Dialog.Close className={css.closeBtn} aria-label="Close">×</Dialog.Close>
|
|
73
|
+
|
|
74
|
+
<hr className={css.separator} />
|
|
75
|
+
|
|
76
|
+
<div className={css.tokenCard}>
|
|
77
|
+
<p className={css.tokenCardTitle}>Fine-grained Personal Access Token</p>
|
|
78
|
+
<div className={css.tokenCardRow}>
|
|
79
|
+
<span className={css.tokenCardLabel}>Owner:</span>
|
|
80
|
+
<code className={css.tokenCardCode}>{repo.owner}</code>
|
|
81
|
+
</div>
|
|
82
|
+
<div className={css.tokenCardRow}>
|
|
83
|
+
<span className={css.tokenCardLabel}>Expiration:</span>
|
|
84
|
+
<code className={css.tokenCardCode}>366 days</code>
|
|
85
|
+
<span className={css.tokenCardHint}>(recommended)</span>
|
|
86
|
+
</div>
|
|
87
|
+
<div className={css.tokenCardRow}>
|
|
88
|
+
<span className={css.tokenCardLabel}>Repository access:</span>
|
|
89
|
+
<code className={css.tokenCardCode}>Only select repositories > {repo.owner}/{repo.name}</code>
|
|
90
|
+
</div>
|
|
91
|
+
<div className={css.tokenCardRow}>
|
|
92
|
+
<span className={css.tokenCardLabel}>Permissions:</span>
|
|
93
|
+
<code className={css.tokenCardCode}>Repositories > Discussions > Access: Read and Write</code>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<a
|
|
98
|
+
className={css.tokenLink}
|
|
99
|
+
href="https://github.com/settings/personal-access-tokens/new"
|
|
100
|
+
target="_blank"
|
|
101
|
+
rel="noopener noreferrer"
|
|
102
|
+
>
|
|
103
|
+
Create a GitHub Fine-Grained Personal Access Token ↗
|
|
104
|
+
</a>
|
|
105
|
+
|
|
106
|
+
<hr className={css.separator} />
|
|
107
|
+
|
|
108
|
+
<label className={css.label} htmlFor="auth-modal-token">Personal Access Token</label>
|
|
109
|
+
<input
|
|
110
|
+
id="auth-modal-token"
|
|
111
|
+
className={css.input}
|
|
112
|
+
placeholder="github_pat_… or ghp_…"
|
|
113
|
+
type="password"
|
|
114
|
+
autoFocus
|
|
115
|
+
value={tokenValue}
|
|
116
|
+
onChange={e => setTokenValue(e.target.value)}
|
|
117
|
+
onKeyDown={handleKeyDown}
|
|
118
|
+
/>
|
|
119
|
+
|
|
120
|
+
<div className={css.warning}>
|
|
121
|
+
<span className={css.warningIcon}>⚠️</span>
|
|
122
|
+
<span>Comments are an experimental feature and may be unstable.</span>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
<div className={css.actions}>
|
|
126
|
+
<Dialog.Close className={css.btnSecondary}>Cancel</Dialog.Close>
|
|
127
|
+
<button className={css.btnPrimary} onClick={handleSignIn}>Sign in</button>
|
|
128
|
+
</div>
|
|
129
|
+
</Dialog.Popup>
|
|
130
|
+
</div>
|
|
131
|
+
</Dialog.Portal>
|
|
132
|
+
</Dialog.Root>
|
|
133
|
+
)
|
|
134
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/* AuthModal — PAT entry dialog (BaseUI Dialog) */
|
|
2
|
+
|
|
3
|
+
.backdrop {
|
|
4
|
+
position: fixed;
|
|
5
|
+
inset: 0;
|
|
6
|
+
background: var(--overlay-backdrop-bgColor, rgba(0, 0, 0, 0.4));
|
|
7
|
+
z-index: 10000;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.popupWrap {
|
|
11
|
+
position: fixed;
|
|
12
|
+
inset: 0;
|
|
13
|
+
z-index: 10001;
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
justify-content: center;
|
|
17
|
+
pointer-events: none;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.popupWrap > * {
|
|
21
|
+
pointer-events: auto;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.popup {
|
|
25
|
+
background: var(--bgColor-default);
|
|
26
|
+
border-radius: 12px;
|
|
27
|
+
box-shadow: var(--shadow-overlay, 0 16px 48px rgba(0, 0, 0, 0.12));
|
|
28
|
+
padding: 28px;
|
|
29
|
+
max-width: 620px;
|
|
30
|
+
max-height: 90vh;
|
|
31
|
+
overflow-y: auto;
|
|
32
|
+
color: var(--fgColor-default);
|
|
33
|
+
position: relative;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.closeBtn {
|
|
37
|
+
position: absolute;
|
|
38
|
+
top: 16px;
|
|
39
|
+
right: 16px;
|
|
40
|
+
background: none;
|
|
41
|
+
border: none;
|
|
42
|
+
font-size: 24px;
|
|
43
|
+
line-height: 1;
|
|
44
|
+
color: var(--fgColor-muted);
|
|
45
|
+
cursor: pointer;
|
|
46
|
+
padding: 4px 8px;
|
|
47
|
+
border-radius: 4px;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.closeBtn:hover {
|
|
51
|
+
background: var(--bgColor-neutral-muted);
|
|
52
|
+
color: var(--fgColor-default);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.title {
|
|
56
|
+
font-size: 18px;
|
|
57
|
+
font-weight: 600;
|
|
58
|
+
margin-bottom: 8px;
|
|
59
|
+
color: var(--fgColor-default);
|
|
60
|
+
padding-right: 32px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.desc {
|
|
64
|
+
font-size: 15px;
|
|
65
|
+
color: var(--fgColor-muted);
|
|
66
|
+
margin-bottom: 16px;
|
|
67
|
+
line-height: 1.5;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.separator {
|
|
71
|
+
border: none;
|
|
72
|
+
border-top: 1px solid var(--borderColor-default);
|
|
73
|
+
margin: 16px 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* Token config card — matches GitHub PAT settings screenshot */
|
|
77
|
+
|
|
78
|
+
.tokenCard {
|
|
79
|
+
background: var(--bgColor-muted);
|
|
80
|
+
border: 1px solid var(--borderColor-default);
|
|
81
|
+
border-radius: 8px;
|
|
82
|
+
padding: 14px 16px;
|
|
83
|
+
margin-bottom: 12px;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.tokenCardTitle {
|
|
87
|
+
font-size: 15px;
|
|
88
|
+
font-weight: 600;
|
|
89
|
+
color: var(--fgColor-default);
|
|
90
|
+
margin-bottom: 8px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.tokenCardRow {
|
|
94
|
+
display: flex;
|
|
95
|
+
align-items: baseline;
|
|
96
|
+
gap: 6px;
|
|
97
|
+
font-size: 14px;
|
|
98
|
+
line-height: 1.8;
|
|
99
|
+
color: var(--fgColor-muted);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.tokenCardLabel {
|
|
103
|
+
flex-shrink: 0;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.tokenCardCode {
|
|
107
|
+
font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
|
|
108
|
+
font-size: 13px;
|
|
109
|
+
color: var(--fgColor-default);
|
|
110
|
+
background: var(--bgColor-default);
|
|
111
|
+
padding: 1px 6px;
|
|
112
|
+
border-radius: 4px;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.tokenCardHint {
|
|
116
|
+
color: var(--fgColor-muted);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.tokenLink {
|
|
120
|
+
display: inline-block;
|
|
121
|
+
font-size: 15px;
|
|
122
|
+
color: var(--fgColor-accent);
|
|
123
|
+
text-decoration: none;
|
|
124
|
+
margin-bottom: 4px;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.tokenLink:hover {
|
|
128
|
+
text-decoration: underline;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* Input */
|
|
132
|
+
|
|
133
|
+
.label {
|
|
134
|
+
display: block;
|
|
135
|
+
font-size: 15px;
|
|
136
|
+
font-weight: 600;
|
|
137
|
+
color: var(--fgColor-default);
|
|
138
|
+
margin-bottom: 6px;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.input {
|
|
142
|
+
width: 100%;
|
|
143
|
+
padding: 10px 12px;
|
|
144
|
+
border: 1px solid var(--borderColor-default);
|
|
145
|
+
border-radius: 6px;
|
|
146
|
+
font-size: 15px;
|
|
147
|
+
font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
|
|
148
|
+
margin-bottom: 16px;
|
|
149
|
+
box-sizing: border-box;
|
|
150
|
+
background: var(--bgColor-default);
|
|
151
|
+
color: var(--fgColor-default);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.input::placeholder {
|
|
155
|
+
color: var(--fgColor-muted);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.input:focus {
|
|
159
|
+
outline: none;
|
|
160
|
+
border-color: var(--borderColor-accent-emphasis);
|
|
161
|
+
box-shadow: 0 0 0 3px var(--borderColor-accent-muted);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* Warning banner */
|
|
165
|
+
|
|
166
|
+
.warning {
|
|
167
|
+
display: flex;
|
|
168
|
+
align-items: center;
|
|
169
|
+
gap: 8px;
|
|
170
|
+
padding: 10px 12px;
|
|
171
|
+
background: var(--bgColor-attention-muted);
|
|
172
|
+
border: 1px solid var(--borderColor-attention-muted);
|
|
173
|
+
border-radius: 6px;
|
|
174
|
+
font-size: 14px;
|
|
175
|
+
color: var(--fgColor-attention);
|
|
176
|
+
margin-bottom: 8px;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.warningIcon {
|
|
180
|
+
flex-shrink: 0;
|
|
181
|
+
font-size: 16px;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/* Actions */
|
|
185
|
+
|
|
186
|
+
.actions {
|
|
187
|
+
display: flex;
|
|
188
|
+
justify-content: flex-end;
|
|
189
|
+
gap: 8px;
|
|
190
|
+
margin-top: 8px;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.btnSecondary {
|
|
194
|
+
padding: 8px 16px;
|
|
195
|
+
background: transparent;
|
|
196
|
+
border: none;
|
|
197
|
+
border-radius: 6px;
|
|
198
|
+
font-size: 15px;
|
|
199
|
+
color: var(--fgColor-muted);
|
|
200
|
+
cursor: pointer;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.btnSecondary:hover {
|
|
204
|
+
color: var(--fgColor-default);
|
|
205
|
+
background: var(--bgColor-neutral-muted);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.btnPrimary {
|
|
209
|
+
padding: 8px 20px;
|
|
210
|
+
background: var(--bgColor-accent-emphasis);
|
|
211
|
+
border: none;
|
|
212
|
+
border-radius: 6px;
|
|
213
|
+
font-size: 15px;
|
|
214
|
+
font-weight: 600;
|
|
215
|
+
color: var(--fgColor-onEmphasis);
|
|
216
|
+
cursor: pointer;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.btnPrimary:hover {
|
|
220
|
+
background: var(--bgColor-accent-emphasis-hover, var(--bgColor-accent-emphasis));
|
|
221
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BranchBar — dark top bar showing current branch on non-main routes.
|
|
3
|
+
*
|
|
4
|
+
* Dev: shows branch name as a static label (use CLI to switch branches).
|
|
5
|
+
* Prod: same label (dropdown switching deferred to ViewfinderNew).
|
|
6
|
+
*/
|
|
7
|
+
import { useState, useEffect, useMemo } from 'react'
|
|
8
|
+
import { GitBranchIcon } from '@primer/octicons-react'
|
|
9
|
+
import css from './BranchBar.module.css'
|
|
10
|
+
|
|
11
|
+
export default function BranchBar({ basePath }) {
|
|
12
|
+
const [hidden, setHidden] = useState(false)
|
|
13
|
+
|
|
14
|
+
const isHiddenByParam = useMemo(() => {
|
|
15
|
+
if (typeof window === 'undefined') return false
|
|
16
|
+
const params = new URLSearchParams(window.location.search)
|
|
17
|
+
return params.has('_sb_hide_branch_bar') || params.has('_sb_embed')
|
|
18
|
+
}, [])
|
|
19
|
+
|
|
20
|
+
const currentBranch = useMemo(() => {
|
|
21
|
+
const m = (basePath || '').match(/\/branch--([^/]+)\/?$/)
|
|
22
|
+
return m ? m[1] : 'main'
|
|
23
|
+
}, [basePath])
|
|
24
|
+
|
|
25
|
+
const isOnBranch = currentBranch !== 'main'
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
const observer = new MutationObserver(() => {
|
|
29
|
+
setHidden(document.documentElement.classList.contains('storyboard-chrome-hidden'))
|
|
30
|
+
})
|
|
31
|
+
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] })
|
|
32
|
+
return () => observer.disconnect()
|
|
33
|
+
}, [])
|
|
34
|
+
|
|
35
|
+
if (!isOnBranch || hidden || isHiddenByParam) return null
|
|
36
|
+
|
|
37
|
+
function hideChrome() {
|
|
38
|
+
window.dispatchEvent(new KeyboardEvent('keydown', {
|
|
39
|
+
key: '.', metaKey: true, bubbles: true,
|
|
40
|
+
}))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div className={css.bar} data-branch-bar>
|
|
45
|
+
<div className={css.barInner}>
|
|
46
|
+
<span className={css.barLabel}>
|
|
47
|
+
<GitBranchIcon size={12} />
|
|
48
|
+
<span className={css.barBranchName}>{currentBranch}</span>
|
|
49
|
+
</span>
|
|
50
|
+
<div className={css.barActions}>
|
|
51
|
+
<button className={css.barAction} onClick={hideChrome}>Hide</button>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/* ─── BranchBar (fixed top bar) ─── */
|
|
2
|
+
|
|
3
|
+
.bar {
|
|
4
|
+
position: fixed;
|
|
5
|
+
top: 0;
|
|
6
|
+
left: 0;
|
|
7
|
+
right: 0;
|
|
8
|
+
z-index: 10000;
|
|
9
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
10
|
+
font-size: 12px;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.barInner {
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
justify-content: center;
|
|
17
|
+
gap: 8px;
|
|
18
|
+
height: 32px;
|
|
19
|
+
background: var(--bgColor-emphasis, #1a1a1a);
|
|
20
|
+
color: var(--fgColor-onEmphasis, #ccc);
|
|
21
|
+
padding: 4px 12px;
|
|
22
|
+
position: relative;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.barTrigger {
|
|
26
|
+
display: flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
gap: 5px;
|
|
29
|
+
background: none;
|
|
30
|
+
border: none;
|
|
31
|
+
color: var(--fgColor-onEmphasis, #ddd);
|
|
32
|
+
font-size: 11px;
|
|
33
|
+
font-weight: 400;
|
|
34
|
+
font-family: inherit;
|
|
35
|
+
cursor: pointer;
|
|
36
|
+
padding: 2px 8px;
|
|
37
|
+
border-radius: 4px;
|
|
38
|
+
transition: background 0.1s;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.barTrigger:hover {
|
|
42
|
+
background: rgba(255, 255, 255, 0.1);
|
|
43
|
+
color: #fff;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.barActions {
|
|
47
|
+
position: absolute;
|
|
48
|
+
right: 8px;
|
|
49
|
+
display: flex;
|
|
50
|
+
align-items: center;
|
|
51
|
+
gap: 2px;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.barLabel {
|
|
55
|
+
display: flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
gap: 5px;
|
|
58
|
+
color: var(--fgColor-onEmphasis, #ddd);
|
|
59
|
+
font-size: 11px;
|
|
60
|
+
font-weight: 400;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.barBranchName {
|
|
64
|
+
font-weight: 500;
|
|
65
|
+
max-width: 240px;
|
|
66
|
+
overflow: hidden;
|
|
67
|
+
text-overflow: ellipsis;
|
|
68
|
+
white-space: nowrap;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.barAction {
|
|
72
|
+
background: none;
|
|
73
|
+
border: none;
|
|
74
|
+
color: var(--fgColor-onEmphasis, #777);
|
|
75
|
+
font-size: 11px;
|
|
76
|
+
font-weight: 400;
|
|
77
|
+
font-family: inherit;
|
|
78
|
+
cursor: pointer;
|
|
79
|
+
padding: 2px 8px;
|
|
80
|
+
border-radius: 3px;
|
|
81
|
+
transition: all 0.1s;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.barAction:hover {
|
|
85
|
+
color: var(--fgColor-onEmphasis, #fff);
|
|
86
|
+
background: rgba(255, 255, 255, 0.1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* Push page content down */
|
|
90
|
+
:global(html:has([data-branch-bar])) {
|
|
91
|
+
--sb-branch-bar-height: 32px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
:global(html:has([data-branch-bar]) body) {
|
|
95
|
+
padding-top: 32px;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
:global(html.storyboard-chrome-hidden [data-branch-bar]) {
|
|
99
|
+
display: none;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* ─── Shared dropdown (used by both BranchBar and ViewfinderNew) ─── */
|
|
103
|
+
|
|
104
|
+
.branchBtn {
|
|
105
|
+
display: flex;
|
|
106
|
+
align-items: center;
|
|
107
|
+
gap: 6px;
|
|
108
|
+
padding: 7px 14px;
|
|
109
|
+
background: #fff;
|
|
110
|
+
color: #555;
|
|
111
|
+
border: 1px solid #e5e5e5;
|
|
112
|
+
border-radius: 8px;
|
|
113
|
+
font-size: 16px;
|
|
114
|
+
font-weight: 500;
|
|
115
|
+
cursor: pointer;
|
|
116
|
+
transition: all 0.15s;
|
|
117
|
+
font-family: inherit;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.branchBtn:hover {
|
|
121
|
+
background: #f5f5f5;
|
|
122
|
+
border-color: #ccc;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.branchBtnText {
|
|
126
|
+
max-width: 160px;
|
|
127
|
+
overflow: hidden;
|
|
128
|
+
text-overflow: ellipsis;
|
|
129
|
+
white-space: nowrap;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.spinner {
|
|
133
|
+
display: inline-block;
|
|
134
|
+
width: 12px;
|
|
135
|
+
height: 12px;
|
|
136
|
+
border: 1.5px solid rgba(255, 255, 255, 0.15);
|
|
137
|
+
border-top-color: currentColor;
|
|
138
|
+
border-radius: 50%;
|
|
139
|
+
animation: spin 0.6s linear infinite;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
@keyframes spin {
|
|
143
|
+
to { transform: rotate(360deg); }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.branchPositioner {
|
|
147
|
+
z-index: 10001;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.branchPopup {
|
|
151
|
+
background: #fff;
|
|
152
|
+
border: 1px solid #e5e5e5;
|
|
153
|
+
border-radius: 10px;
|
|
154
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
|
155
|
+
min-width: 240px;
|
|
156
|
+
max-width: 320px;
|
|
157
|
+
padding: 6px 0;
|
|
158
|
+
font-family: 'Mona Sans', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.branchSectionLabel {
|
|
162
|
+
padding: 8px 14px 4px;
|
|
163
|
+
font-size: 14px;
|
|
164
|
+
font-weight: 600;
|
|
165
|
+
color: #999;
|
|
166
|
+
text-transform: uppercase;
|
|
167
|
+
letter-spacing: 0.4px;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.branchItem {
|
|
171
|
+
display: flex;
|
|
172
|
+
align-items: center;
|
|
173
|
+
gap: 8px;
|
|
174
|
+
padding: 7px 14px;
|
|
175
|
+
font-size: 16px;
|
|
176
|
+
color: #555;
|
|
177
|
+
cursor: pointer;
|
|
178
|
+
transition: background 0.1s;
|
|
179
|
+
border: none;
|
|
180
|
+
background: none;
|
|
181
|
+
width: 100%;
|
|
182
|
+
text-align: left;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.branchItem:hover,
|
|
186
|
+
.branchItem[data-highlighted] {
|
|
187
|
+
background: #f5f5f5;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.branchItemActive {
|
|
191
|
+
composes: branchItem;
|
|
192
|
+
color: #1a1a1a;
|
|
193
|
+
font-weight: 600;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.branchSeparator {
|
|
197
|
+
height: 1px;
|
|
198
|
+
background: #e5e5e5;
|
|
199
|
+
margin: 4px 10px;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.branchViewport {
|
|
203
|
+
max-height: 280px;
|
|
204
|
+
overflow-y: auto;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.branchShowAll {
|
|
208
|
+
display: block;
|
|
209
|
+
width: 100%;
|
|
210
|
+
padding: 8px 14px;
|
|
211
|
+
font-size: 16px;
|
|
212
|
+
color: #2563eb;
|
|
213
|
+
background: none;
|
|
214
|
+
border: none;
|
|
215
|
+
cursor: pointer;
|
|
216
|
+
text-align: left;
|
|
217
|
+
font-family: inherit;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.branchShowAll:hover {
|
|
221
|
+
background: #f5f5f5;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.branchError {
|
|
225
|
+
padding: 6px 14px;
|
|
226
|
+
font-size: 12px;
|
|
227
|
+
color: #ef4444;
|
|
228
|
+
background: #fef2f2;
|
|
229
|
+
border-bottom: 1px solid #fecaca;
|
|
230
|
+
}
|