@dfosco/storyboard-react 4.0.0-beta.41 → 4.0.0-beta.43

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.
@@ -9,14 +9,19 @@ import styles from './PageSelector.module.css'
9
9
  *
10
10
  * @param {{ currentName: string, pages: Array<{ name: string, route: string, title: string }>, isLocalDev?: boolean }} props
11
11
  */
12
- export default function PageSelector({ currentName, pages, isLocalDev = false }) {
12
+ export default function PageSelector({ currentName, pages: initialPages, isLocalDev = false }) {
13
13
  const [open, setOpen] = useState(false)
14
14
  const [adding, setAdding] = useState(false)
15
15
  const [newName, setNewName] = useState('')
16
16
  const [creating, setCreating] = useState(false)
17
+ const [pages, setPages] = useState(initialPages)
18
+ const [successMsg, setSuccessMsg] = useState(null)
17
19
  const containerRef = useRef(null)
18
20
  const inputRef = useRef(null)
19
21
 
22
+ // Sync pages when prop changes (e.g. HMR reload)
23
+ useEffect(() => { setPages(initialPages) }, [initialPages])
24
+
20
25
  const currentPage = pages.find((p) => p.name === currentName)
21
26
  const currentLabel = currentPage?.title || currentName.split('/').pop()
22
27
  const currentIndex = pages.findIndex((p) => p.name === currentName)
@@ -46,18 +51,32 @@ export default function PageSelector({ currentName, pages, isLocalDev = false })
46
51
  setCreating(false)
47
52
  return
48
53
  }
49
- // Navigate to the new page once Vite picks it up
50
- const kebab = trimmed
51
- .replace(/[^a-zA-Z0-9\s_-]/g, '')
52
- .trim()
53
- .replace(/[\s_]+/g, '-')
54
- .toLowerCase()
55
- .replace(/-+/g, '-')
56
- .replace(/^-|-$/g, '')
57
- const route = folder ? `/${folder}/${kebab}` : `/${kebab}`
54
+ const route = result.route
58
55
  const base = (import.meta.env?.BASE_URL || '/').replace(/\/$/, '')
59
- // Small delay to let Vite detect the new file
60
- setTimeout(() => { window.location.href = base + route }, 600)
56
+ const targetUrl = base + route
57
+
58
+ // Optimistically add the new page to the bottom of the list
59
+ const pageName = result.name || trimmed
60
+ setPages(prev => [...prev, { name: pageName, route, title: trimmed }])
61
+
62
+ // Show success confirmation and reset form
63
+ setSuccessMsg(`"${trimmed}" created`)
64
+ setAdding(false)
65
+ setNewName('')
66
+ setCreating(false)
67
+
68
+ // Navigate to the new page after Vite picks up the new file
69
+ if (import.meta.hot) {
70
+ const timer = setTimeout(() => {
71
+ window.location.href = targetUrl
72
+ }, 3000)
73
+ import.meta.hot.on('vite:beforeFullReload', () => {
74
+ clearTimeout(timer)
75
+ sessionStorage.setItem('sb-pending-navigate', targetUrl)
76
+ })
77
+ } else {
78
+ setTimeout(() => { window.location.href = targetUrl }, 1000)
79
+ }
61
80
  } catch (err) {
62
81
  console.error('Failed to create canvas page:', err)
63
82
  setCreating(false)
@@ -77,6 +96,7 @@ export default function PageSelector({ currentName, pages, isLocalDev = false })
77
96
  setOpen(false)
78
97
  setAdding(false)
79
98
  setNewName('')
99
+ setSuccessMsg(null)
80
100
  }
81
101
  }
82
102
  document.addEventListener('mousedown', handleClick)
@@ -100,7 +120,8 @@ export default function PageSelector({ currentName, pages, isLocalDev = false })
100
120
  return () => document.removeEventListener('keydown', handleKey)
101
121
  }, [open, adding])
102
122
 
103
- if (!pages || pages.length < 2) return null
123
+ // Show selector when there are multiple pages, or in dev mode (to allow adding pages)
124
+ if (!pages || (pages.length < 2 && !isLocalDev)) return null
104
125
 
105
126
  return (
106
127
  <nav ref={containerRef} className={styles.container} aria-label="Canvas pages">
@@ -189,6 +210,9 @@ export default function PageSelector({ currentName, pages, isLocalDev = false })
189
210
  + Add new page
190
211
  </li>
191
212
  )}
213
+ {successMsg && (
214
+ <li className={styles.successMsg}>✓ {successMsg}</li>
215
+ )}
192
216
  </>
193
217
  )}
194
218
  </ul>
@@ -156,3 +156,10 @@
156
156
  opacity: 0.5;
157
157
  cursor: default;
158
158
  }
159
+
160
+ .successMsg {
161
+ padding: 4px 12px 6px;
162
+ font-size: 11px;
163
+ color: #1a7f37;
164
+ font-weight: 500;
165
+ }
package/src/context.jsx CHANGED
@@ -232,7 +232,11 @@ export default function StoryboardProvider({ flowName, sceneName, recordName, re
232
232
  if (canvasId) {
233
233
  const canvasData = canvases?.[canvasId]
234
234
  const group = canvasData?._group
235
- const siblingPages = group ? canvasGroupMap.get(group) || [] : []
235
+ // Include the current canvas as a sibling even if it's the only page in its group,
236
+ // so the PageSelector can render and allow adding new pages.
237
+ const siblingPages = group
238
+ ? canvasGroupMap.get(group) || []
239
+ : [{ name: canvasId, route: canvasData?._route || `/canvas/${canvasId}`, title: canvasData?.title || canvasId.split('/').pop() }]
236
240
  const canvasMeta = canvasData?._canvasMeta || null
237
241
  const canvasValue = {
238
242
  data: null,