@dfosco/storyboard-react 4.2.0-beta.4 → 4.2.1
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 +10 -11
- package/src/AuthModal/AuthModal.jsx +6 -8
- package/src/BranchBar/BranchBar.jsx +20 -6
- package/src/BranchBar/BranchBar.module.css +13 -4
- package/src/BranchBar/useBranches.js +20 -6
- package/src/BranchBar/useBranches.test.js +68 -0
- package/src/CommandPalette/CommandPalette.jsx +480 -187
- package/src/CommandPalette/command-palette.css +142 -78
- package/src/Icon.jsx +157 -58
- package/src/Viewfinder.jsx +562 -207
- package/src/Viewfinder.module.css +434 -93
- package/src/Workspace.jsx +7 -0
- package/src/canvas/CanvasPage.bridge.test.jsx +14 -6
- package/src/canvas/CanvasPage.dragdrop.test.jsx +11 -7
- package/src/canvas/CanvasPage.jsx +739 -219
- package/src/canvas/CanvasPage.module.css +13 -15
- package/src/canvas/CanvasPage.multiselect.test.jsx +17 -6
- package/src/canvas/ConnectorLayer.jsx +121 -165
- package/src/canvas/ConnectorLayer.module.css +69 -0
- package/src/canvas/PageSelector.test.jsx +15 -6
- package/src/canvas/canvasApi.js +68 -2
- package/src/canvas/canvasReloadGuard.test.js +1 -1
- package/src/canvas/connectorGeometry.js +132 -0
- package/src/canvas/hotPoolDevLogs.js +25 -0
- package/src/canvas/useCanvas.js +1 -1
- 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 +474 -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 +62 -47
- package/src/canvas/widgets/FigmaEmbed.module.css +61 -0
- package/src/canvas/widgets/ImageWidget.jsx +130 -9
- package/src/canvas/widgets/ImageWidget.module.css +30 -0
- package/src/canvas/widgets/LinkPreview.jsx +113 -5
- package/src/canvas/widgets/LinkPreview.module.css +127 -0
- package/src/canvas/widgets/MarkdownBlock.jsx +167 -17
- package/src/canvas/widgets/MarkdownBlock.module.css +148 -0
- package/src/canvas/widgets/PromptWidget.jsx +414 -0
- package/src/canvas/widgets/PromptWidget.module.css +273 -0
- package/src/canvas/widgets/PrototypeEmbed.jsx +77 -39
- package/src/canvas/widgets/PrototypeEmbed.module.css +117 -0
- package/src/canvas/widgets/PrototypeEmbed.test.jsx +2 -2
- package/src/canvas/widgets/ResizeHandle.jsx +17 -6
- package/src/canvas/widgets/StoryWidget.jsx +73 -15
- package/src/canvas/widgets/TerminalReadWidget.jsx +146 -0
- package/src/canvas/widgets/TerminalReadWidget.module.css +94 -0
- package/src/canvas/widgets/TerminalWidget.jsx +445 -67
- package/src/canvas/widgets/TerminalWidget.module.css +271 -8
- package/src/canvas/widgets/TilesWidget.jsx +300 -0
- package/src/canvas/widgets/TilesWidget.module.css +133 -0
- package/src/canvas/widgets/WidgetChrome.jsx +74 -153
- package/src/canvas/widgets/WidgetChrome.module.css +30 -1
- package/src/canvas/widgets/embedInteraction.test.jsx +24 -26
- package/src/canvas/widgets/expandUtils.js +560 -0
- package/src/canvas/widgets/expandUtils.test.js +155 -0
- package/src/canvas/widgets/index.js +9 -0
- package/src/canvas/widgets/snapshotDisplay.test.jsx +23 -71
- 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 +55 -4
- package/src/canvas/widgets/widgetIcons.jsx +190 -0
- package/src/canvas/widgets/widgetProps.js +1 -0
- package/src/context.jsx +48 -20
- package/src/hooks/useConfig.js +14 -0
- package/src/hooks/usePrototypeReloadGuard.js +64 -0
- package/src/hooks/useSceneData.js +1 -0
- package/src/hooks/useThemeState.test.js +1 -1
- package/src/index.js +8 -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 +407 -67
- package/src/vite/data-plugin.test.js +1 -1
package/package.json
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dfosco/storyboard-react",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@base-ui/react": "^1.4.0",
|
|
7
|
-
"@dfosco/storyboard-core": "4.2.
|
|
8
|
-
"@dfosco/tiny-canvas": "4.2.
|
|
7
|
+
"@dfosco/storyboard-core": "4.2.1",
|
|
8
|
+
"@dfosco/tiny-canvas": "4.2.1",
|
|
9
9
|
"@neodrag/react": "^2.3.1",
|
|
10
|
+
"@radix-ui/react-dialog": "^1.1.15",
|
|
11
|
+
"@radix-ui/react-visually-hidden": "^1.2.4",
|
|
12
|
+
"ansi-to-html": "^0.7.2",
|
|
13
|
+
"cmdk": "^1.1.1",
|
|
14
|
+
"feather-icons": "^4.29.2",
|
|
15
|
+
"ghostty-web": "^0.4.0",
|
|
10
16
|
"glob": "^11.0.0",
|
|
11
17
|
"jsonc-parser": "^3.3.1",
|
|
12
18
|
"remark": "^15.0.1",
|
|
13
19
|
"remark-gfm": "^4.0.1",
|
|
14
|
-
"react-cmdk": "^1.3.9",
|
|
15
20
|
"remark-html": "^16.0.1"
|
|
16
21
|
},
|
|
17
22
|
"license": "MIT",
|
|
@@ -27,13 +32,7 @@
|
|
|
27
32
|
"@primer/octicons-react": ">=19",
|
|
28
33
|
"react": ">=18",
|
|
29
34
|
"react-router-dom": ">=6",
|
|
30
|
-
"vite": ">=5"
|
|
31
|
-
"ghostty-web": ">=0.4.0"
|
|
32
|
-
},
|
|
33
|
-
"peerDependenciesMeta": {
|
|
34
|
-
"ghostty-web": {
|
|
35
|
-
"optional": true
|
|
36
|
-
}
|
|
35
|
+
"vite": ">=5"
|
|
37
36
|
},
|
|
38
37
|
"exports": {
|
|
39
38
|
".": "./src/index.js",
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { useState, useEffect, useCallback } from 'react'
|
|
8
8
|
import { Dialog } from '@base-ui/react/dialog'
|
|
9
|
-
import { Button } from '@base-ui/react/button'
|
|
10
9
|
import css from './AuthModal.module.css'
|
|
11
10
|
|
|
12
11
|
const COMMENTS_TOKEN_KEY = 'sb-comments-token'
|
|
@@ -31,11 +30,6 @@ export default function AuthModal() {
|
|
|
31
30
|
return () => document.removeEventListener('storyboard:open-auth-modal', handleOpen)
|
|
32
31
|
}, [])
|
|
33
32
|
|
|
34
|
-
const handleClose = useCallback(() => {
|
|
35
|
-
setOpen(false)
|
|
36
|
-
setTokenValue('')
|
|
37
|
-
}, [])
|
|
38
|
-
|
|
39
33
|
const handleSignIn = useCallback(() => {
|
|
40
34
|
const trimmed = tokenValue.trim()
|
|
41
35
|
if (!trimmed) return
|
|
@@ -43,8 +37,12 @@ export default function AuthModal() {
|
|
|
43
37
|
try { localStorage.setItem(COMMENTS_TOKEN_KEY, trimmed) } catch { /* ignore */ }
|
|
44
38
|
|
|
45
39
|
try {
|
|
46
|
-
import('@dfosco/storyboard-core/comments').then(({ setToken }) => {
|
|
40
|
+
import('@dfosco/storyboard-core/comments').then(({ setToken, validateToken }) => {
|
|
47
41
|
setToken(trimmed)
|
|
42
|
+
// Validate to cache user info (login + avatar), then notify Viewfinder
|
|
43
|
+
validateToken(trimmed)
|
|
44
|
+
.then(() => document.dispatchEvent(new CustomEvent('storyboard:auth-changed')))
|
|
45
|
+
.catch(() => document.dispatchEvent(new CustomEvent('storyboard:auth-changed')))
|
|
48
46
|
}).catch(() => {})
|
|
49
47
|
} catch { /* comments module may not be initialized */ }
|
|
50
48
|
|
|
@@ -64,7 +62,7 @@ export default function AuthModal() {
|
|
|
64
62
|
<Dialog.Backdrop className={css.backdrop} />
|
|
65
63
|
<div className={css.popupWrap}>
|
|
66
64
|
<Dialog.Popup className={css.popup}>
|
|
67
|
-
<Dialog.Title className={css.title}>Sign in
|
|
65
|
+
<Dialog.Title className={css.title}>Sign in with GitHub</Dialog.Title>
|
|
68
66
|
<Dialog.Description className={css.desc}>
|
|
69
67
|
Leave comments for other users to see and respond, and react to! Storyboard
|
|
70
68
|
comments use Discussions as a back-end and require a GitHub PAT to be enabled.
|
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* BranchBar —
|
|
2
|
+
* BranchBar — blue accent bar showing current branch and local dev status.
|
|
3
3
|
*
|
|
4
|
-
* Dev:
|
|
5
|
-
* Prod:
|
|
4
|
+
* Dev: always visible (main or branch). Shows "Local development" label.
|
|
5
|
+
* Prod: shows on non-main branches only.
|
|
6
6
|
*/
|
|
7
7
|
import { useState, useEffect, useMemo } from 'react'
|
|
8
8
|
import { GitBranchIcon } from '@primer/octicons-react'
|
|
9
9
|
import css from './BranchBar.module.css'
|
|
10
10
|
|
|
11
|
+
/** Check if we're in local dev mode (respects ?prodMode simulation). */
|
|
12
|
+
function checkLocalDev() {
|
|
13
|
+
if (typeof window === 'undefined') return false
|
|
14
|
+
if (new URLSearchParams(window.location.search).has('prodMode')) return false
|
|
15
|
+
return window.__SB_LOCAL_DEV__ === true
|
|
16
|
+
}
|
|
17
|
+
|
|
11
18
|
export default function BranchBar({ basePath }) {
|
|
12
|
-
const [hidden, setHidden] = useState(
|
|
19
|
+
const [hidden, setHidden] = useState(
|
|
20
|
+
() => typeof document !== 'undefined' && document.documentElement.classList.contains('storyboard-chrome-hidden')
|
|
21
|
+
)
|
|
13
22
|
|
|
14
23
|
const isHiddenByParam = useMemo(() => {
|
|
15
24
|
if (typeof window === 'undefined') return false
|
|
@@ -22,6 +31,7 @@ export default function BranchBar({ basePath }) {
|
|
|
22
31
|
return m ? m[1] : 'main'
|
|
23
32
|
}, [basePath])
|
|
24
33
|
|
|
34
|
+
const isLocalDev = checkLocalDev()
|
|
25
35
|
const isOnBranch = currentBranch !== 'main'
|
|
26
36
|
|
|
27
37
|
useEffect(() => {
|
|
@@ -32,7 +42,7 @@ export default function BranchBar({ basePath }) {
|
|
|
32
42
|
return () => observer.disconnect()
|
|
33
43
|
}, [])
|
|
34
44
|
|
|
35
|
-
if (!isOnBranch || hidden || isHiddenByParam) return null
|
|
45
|
+
if ((!isOnBranch && !isLocalDev) || hidden || isHiddenByParam) return null
|
|
36
46
|
|
|
37
47
|
function hideChrome() {
|
|
38
48
|
window.dispatchEvent(new KeyboardEvent('keydown', {
|
|
@@ -42,10 +52,14 @@ export default function BranchBar({ basePath }) {
|
|
|
42
52
|
|
|
43
53
|
return (
|
|
44
54
|
<div className={css.bar} data-branch-bar>
|
|
45
|
-
<div className={css.barInner}>
|
|
55
|
+
<div className={`${css.barInner}${isLocalDev ? '' : ` ${css.barProd}`}`}>
|
|
46
56
|
<span className={css.barLabel}>
|
|
47
57
|
<GitBranchIcon size={12} />
|
|
48
58
|
<span className={css.barBranchName}>{currentBranch}</span>
|
|
59
|
+
{isLocalDev && <>
|
|
60
|
+
<span className={css.barSeparator}>·</span>
|
|
61
|
+
<span>Local development</span>
|
|
62
|
+
</>}
|
|
49
63
|
</span>
|
|
50
64
|
<div className={css.barActions}>
|
|
51
65
|
<button className={css.barAction} onClick={hideChrome}>Hide</button>
|
|
@@ -16,12 +16,16 @@
|
|
|
16
16
|
justify-content: center;
|
|
17
17
|
gap: 8px;
|
|
18
18
|
height: 32px;
|
|
19
|
-
background:
|
|
20
|
-
color:
|
|
19
|
+
background: hsl(212, 92%, 45%);
|
|
20
|
+
color: #fff;
|
|
21
21
|
padding: 4px 12px;
|
|
22
22
|
position: relative;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
.barProd {
|
|
26
|
+
background: #1c2128;
|
|
27
|
+
}
|
|
28
|
+
|
|
25
29
|
.barTrigger {
|
|
26
30
|
display: flex;
|
|
27
31
|
align-items: center;
|
|
@@ -68,6 +72,11 @@
|
|
|
68
72
|
white-space: nowrap;
|
|
69
73
|
}
|
|
70
74
|
|
|
75
|
+
.barSeparator {
|
|
76
|
+
opacity: 0.6;
|
|
77
|
+
margin: 0 2px;
|
|
78
|
+
}
|
|
79
|
+
|
|
71
80
|
.barAction {
|
|
72
81
|
background: none;
|
|
73
82
|
border: none;
|
|
@@ -87,11 +96,11 @@
|
|
|
87
96
|
}
|
|
88
97
|
|
|
89
98
|
/* Push page content down */
|
|
90
|
-
:global(html:has([data-branch-bar])) {
|
|
99
|
+
:global(html:has([data-branch-bar]):not(.storyboard-chrome-hidden)) {
|
|
91
100
|
--sb-branch-bar-height: 32px;
|
|
92
101
|
}
|
|
93
102
|
|
|
94
|
-
:global(html:has([data-branch-bar]) body) {
|
|
103
|
+
:global(html:has([data-branch-bar]):not(.storyboard-chrome-hidden) body) {
|
|
95
104
|
padding-top: 32px;
|
|
96
105
|
}
|
|
97
106
|
|
|
@@ -15,27 +15,41 @@ export function useBranches(basePath) {
|
|
|
15
15
|
})
|
|
16
16
|
|
|
17
17
|
const [gitUser, setGitUser] = useState(null)
|
|
18
|
+
const branchBasePath = (basePath || '/').replace(/\/branch--[^/]*\/?$/, '/')
|
|
18
19
|
|
|
19
20
|
useEffect(() => {
|
|
20
21
|
const apiBase = (basePath || '/').replace(/\/$/, '')
|
|
22
|
+
const branchManifestUrl = `${branchBasePath.replace(/\/$/, '')}/branches.json`
|
|
21
23
|
|
|
22
24
|
fetch(`${apiBase}/_storyboard/git-user`).then(r => r.ok ? r.json() : null)
|
|
23
25
|
.then(data => { if (data?.name) setGitUser(data.name) })
|
|
24
26
|
.catch(() => {})
|
|
25
27
|
|
|
26
|
-
//
|
|
28
|
+
// Keep dev behavior: prefer live branch list from server API.
|
|
29
|
+
// On static deployments (GitHub Pages), fallback to branches.json.
|
|
27
30
|
fetch(`${apiBase}/_storyboard/worktrees`).then(r => r.ok ? r.json() : null)
|
|
28
|
-
.then(data => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
.then(data => {
|
|
32
|
+
if (Array.isArray(data) && data.length > 0) {
|
|
33
|
+
setBranches(data)
|
|
34
|
+
return true
|
|
35
|
+
}
|
|
36
|
+
return false
|
|
37
|
+
})
|
|
38
|
+
.catch(() => false)
|
|
39
|
+
.then((hasLiveData) => {
|
|
40
|
+
if (hasLiveData || isLocalDev) return
|
|
41
|
+
return fetch(branchManifestUrl)
|
|
42
|
+
.then(r => r.ok ? r.json() : null)
|
|
43
|
+
.then(data => { if (Array.isArray(data) && data.length > 0) setBranches(data) })
|
|
44
|
+
.catch(() => {})
|
|
45
|
+
})
|
|
46
|
+
}, [basePath, branchBasePath])
|
|
31
47
|
|
|
32
48
|
const currentBranch = useMemo(() => {
|
|
33
49
|
const m = (basePath || '').match(/\/branch--([^/]+)\/?$/)
|
|
34
50
|
return m ? m[1] : 'main'
|
|
35
51
|
}, [basePath])
|
|
36
52
|
|
|
37
|
-
const branchBasePath = (basePath || '/').replace(/\/branch--[^/]*\/$/, '/')
|
|
38
|
-
|
|
39
53
|
return { branches, currentBranch, branchBasePath, gitUser }
|
|
40
54
|
}
|
|
41
55
|
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { renderHook, waitFor } from '@testing-library/react'
|
|
2
|
+
import { useBranches } from './useBranches.js'
|
|
3
|
+
|
|
4
|
+
describe('useBranches', () => {
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
delete window.__SB_BRANCHES__
|
|
7
|
+
vi.unstubAllGlobals()
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('loads branches from branches.json when worktrees API is unavailable on branch deploys', async () => {
|
|
11
|
+
const branchesFromManifest = [
|
|
12
|
+
{ folder: 'branch--4.3.0-prompt-widget', branch: '4.3.0-prompt-widget' },
|
|
13
|
+
{ folder: 'branch--main', branch: 'main' },
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
const fetchMock = vi.fn((url) => {
|
|
17
|
+
if (url === '/storyboard/branch--4.3.0-prompt-widget/_storyboard/git-user') {
|
|
18
|
+
return Promise.resolve({ ok: false, json: async () => null })
|
|
19
|
+
}
|
|
20
|
+
if (url === '/storyboard/branch--4.3.0-prompt-widget/_storyboard/worktrees') {
|
|
21
|
+
return Promise.resolve({ ok: false, json: async () => null })
|
|
22
|
+
}
|
|
23
|
+
if (url === '/storyboard/branches.json') {
|
|
24
|
+
return Promise.resolve({ ok: true, json: async () => branchesFromManifest })
|
|
25
|
+
}
|
|
26
|
+
return Promise.reject(new Error(`Unexpected fetch URL: ${url}`))
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
vi.stubGlobal('fetch', fetchMock)
|
|
30
|
+
|
|
31
|
+
const { result } = renderHook(() => useBranches('/storyboard/branch--4.3.0-prompt-widget/'))
|
|
32
|
+
|
|
33
|
+
await waitFor(() => {
|
|
34
|
+
expect(result.current.branches).toEqual(branchesFromManifest)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
expect(fetchMock).toHaveBeenCalledWith('/storyboard/branches.json')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('resolves branches.json from repo root when basePath has no trailing slash', async () => {
|
|
41
|
+
const branchesFromManifest = [
|
|
42
|
+
{ folder: 'branch--4.3.0-prompt-widget', branch: '4.3.0-prompt-widget' },
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
const fetchMock = vi.fn((url) => {
|
|
46
|
+
if (url === '/storyboard/branch--4.3.0-prompt-widget/_storyboard/git-user') {
|
|
47
|
+
return Promise.resolve({ ok: false, json: async () => null })
|
|
48
|
+
}
|
|
49
|
+
if (url === '/storyboard/branch--4.3.0-prompt-widget/_storyboard/worktrees') {
|
|
50
|
+
return Promise.resolve({ ok: false, json: async () => null })
|
|
51
|
+
}
|
|
52
|
+
if (url === '/storyboard/branches.json') {
|
|
53
|
+
return Promise.resolve({ ok: true, json: async () => branchesFromManifest })
|
|
54
|
+
}
|
|
55
|
+
return Promise.reject(new Error(`Unexpected fetch URL: ${url}`))
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
vi.stubGlobal('fetch', fetchMock)
|
|
59
|
+
|
|
60
|
+
const { result } = renderHook(() => useBranches('/storyboard/branch--4.3.0-prompt-widget'))
|
|
61
|
+
|
|
62
|
+
await waitFor(() => {
|
|
63
|
+
expect(result.current.branches).toEqual(branchesFromManifest)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
expect(fetchMock).toHaveBeenCalledWith('/storyboard/branches.json')
|
|
67
|
+
})
|
|
68
|
+
})
|