@dfosco/storyboard-react 4.1.0 → 4.2.0-beta.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 +3 -3
- package/src/CommandPalette/CommandPalette.jsx +69 -5
- package/src/CommandPalette/command-palette.css +5 -0
- package/src/canvas/CanvasPage.jsx +412 -10
- package/src/canvas/CanvasPage.module.css +26 -4
- package/src/canvas/ConnectorLayer.jsx +252 -0
- package/src/canvas/ConnectorLayer.module.css +60 -0
- package/src/canvas/PageSelector.jsx +376 -37
- package/src/canvas/PageSelector.module.css +93 -6
- package/src/canvas/canvasApi.js +35 -0
- package/src/canvas/widgets/MarkdownBlock.jsx +27 -6
- package/src/canvas/widgets/MarkdownBlock.module.css +11 -1
- package/src/canvas/widgets/StickyNote.module.css +3 -0
- package/src/canvas/widgets/TerminalWidget.jsx +274 -0
- package/src/canvas/widgets/TerminalWidget.module.css +158 -0
- package/src/canvas/widgets/WidgetChrome.jsx +86 -1
- package/src/canvas/widgets/WidgetChrome.module.css +72 -0
- package/src/canvas/widgets/index.js +2 -0
- package/src/canvas/widgets/widgetConfig.js +78 -0
- package/src/canvas/widgets/widgetProps.js +1 -0
- package/src/context.jsx +15 -0
|
@@ -4,6 +4,78 @@
|
|
|
4
4
|
position: relative;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
/* Connector anchor ports — positioned at widget edge centers */
|
|
8
|
+
.anchorPort {
|
|
9
|
+
position: absolute;
|
|
10
|
+
width: 12px;
|
|
11
|
+
height: 12px;
|
|
12
|
+
border-radius: 50%;
|
|
13
|
+
background: var(--bgColor-accent-emphasis, #2f81f7);
|
|
14
|
+
border: 3px solid var(--bgColor-default, #fff);
|
|
15
|
+
opacity: 0;
|
|
16
|
+
transition: opacity 0.15s ease, width 0.1s ease, height 0.1s ease, margin 0.1s ease;
|
|
17
|
+
cursor: crosshair;
|
|
18
|
+
z-index: 100;
|
|
19
|
+
pointer-events: auto;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.chromeContainer:hover .anchorPort {
|
|
23
|
+
opacity: 0.6;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.anchorPort:hover {
|
|
27
|
+
opacity: 1;
|
|
28
|
+
width: 18px;
|
|
29
|
+
height: 18px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.anchorPortTop {
|
|
33
|
+
top: -6px;
|
|
34
|
+
left: 50%;
|
|
35
|
+
margin-left: -6px;
|
|
36
|
+
}
|
|
37
|
+
.anchorPortTop:hover {
|
|
38
|
+
margin-left: -9px;
|
|
39
|
+
top: -9px;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.anchorPortBottom {
|
|
43
|
+
bottom: -6px;
|
|
44
|
+
left: 50%;
|
|
45
|
+
margin-left: -6px;
|
|
46
|
+
}
|
|
47
|
+
.anchorPortBottom:hover {
|
|
48
|
+
margin-left: -9px;
|
|
49
|
+
bottom: -9px;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.anchorPortLeft {
|
|
53
|
+
left: -6px;
|
|
54
|
+
top: 50%;
|
|
55
|
+
margin-top: -6px;
|
|
56
|
+
}
|
|
57
|
+
.anchorPortLeft:hover {
|
|
58
|
+
margin-top: -9px;
|
|
59
|
+
left: -9px;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.anchorPortRight {
|
|
63
|
+
right: -6px;
|
|
64
|
+
top: 50%;
|
|
65
|
+
margin-top: -6px;
|
|
66
|
+
}
|
|
67
|
+
.anchorPortRight:hover {
|
|
68
|
+
margin-top: -9px;
|
|
69
|
+
right: -9px;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.anchorPortDisabled {
|
|
73
|
+
background: var(--fgColor-muted, #8b949e);
|
|
74
|
+
opacity: 0;
|
|
75
|
+
cursor: not-allowed;
|
|
76
|
+
pointer-events: none;
|
|
77
|
+
}
|
|
78
|
+
|
|
7
79
|
/* Widget slot — contains the actual widget; selection outline targets this */
|
|
8
80
|
.widgetSlot {
|
|
9
81
|
position: relative;
|
|
@@ -6,6 +6,7 @@ import ImageWidget from './ImageWidget.jsx'
|
|
|
6
6
|
import FigmaEmbed from './FigmaEmbed.jsx'
|
|
7
7
|
import CodePenEmbed from './CodePenEmbed.jsx'
|
|
8
8
|
import StoryWidget from './StoryWidget.jsx'
|
|
9
|
+
import TerminalWidget from './TerminalWidget.jsx'
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Maps widget type strings to their React components.
|
|
@@ -20,6 +21,7 @@ export const widgetRegistry = {
|
|
|
20
21
|
'figma-embed': FigmaEmbed,
|
|
21
22
|
'codepen-embed': CodePenEmbed,
|
|
22
23
|
'story': StoryWidget,
|
|
24
|
+
'terminal': TerminalWidget,
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
/**
|
|
@@ -151,6 +151,19 @@ export function getWidgetMeta(type) {
|
|
|
151
151
|
return { label: def.label, icon: def.icon }
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Get the interact gate config for a widget type.
|
|
156
|
+
* @returns {{ enabled: boolean, label: string }}
|
|
157
|
+
*/
|
|
158
|
+
export function getInteractGate(type) {
|
|
159
|
+
const def = widgetTypes[type]
|
|
160
|
+
if (!def || !def.interactGate) return { enabled: false, label: 'Click to interact' }
|
|
161
|
+
return {
|
|
162
|
+
enabled: true,
|
|
163
|
+
label: def.interactGateLabel || 'Click to interact',
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
154
167
|
/**
|
|
155
168
|
* Get all widget types as an array of { type, label, icon } for menus.
|
|
156
169
|
* Excludes link-preview, image, and figma-embed which are created via paste only.
|
|
@@ -160,3 +173,68 @@ export function getMenuWidgetTypes() {
|
|
|
160
173
|
.filter(([type]) => type !== 'link-preview' && type !== 'image' && type !== 'figma-embed' && type !== 'codepen-embed' && type !== 'story')
|
|
161
174
|
.map(([type, def]) => ({ type, label: def.label, icon: def.icon }))
|
|
162
175
|
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Get the connector configuration for a widget type.
|
|
179
|
+
* @param {string} type — widget type string
|
|
180
|
+
* @returns {{ anchors: Record<string, string>, accept: string[], exclude: string[], defaults: Object|undefined }}
|
|
181
|
+
*/
|
|
182
|
+
export function getConnectorConfig(type) {
|
|
183
|
+
const def = widgetTypes[type]?.connectors
|
|
184
|
+
return {
|
|
185
|
+
anchors: def?.anchors ?? { top: 'available', bottom: 'available', left: 'available', right: 'available' },
|
|
186
|
+
accept: def?.accept ?? ['*'],
|
|
187
|
+
exclude: def?.exclude ?? [],
|
|
188
|
+
defaults: def?.defaults,
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Check if a specific anchor is available on a widget type.
|
|
194
|
+
* @param {string} type — widget type string
|
|
195
|
+
* @param {string} anchor — anchor name (top/bottom/left/right)
|
|
196
|
+
* @returns {'available' | 'disabled' | 'unavailable'}
|
|
197
|
+
*/
|
|
198
|
+
export function getAnchorState(type, anchor) {
|
|
199
|
+
const config = getConnectorConfig(type)
|
|
200
|
+
return config.anchors[anchor] ?? 'available'
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Get the connector styling defaults from config.
|
|
205
|
+
* @returns {Object} connector default styles
|
|
206
|
+
*/
|
|
207
|
+
export function getConnectorDefaults() {
|
|
208
|
+
const defaults = widgetsConfig.connectorDefaults ?? {}
|
|
209
|
+
return {
|
|
210
|
+
controlOffset: defaults.controlOffset ?? 80,
|
|
211
|
+
stroke: defaults.stroke ?? 'var(--fgColor-accent, #0969da)',
|
|
212
|
+
strokeWidth: defaults.strokeWidth ?? 4,
|
|
213
|
+
hoverStroke: defaults.hoverStroke ?? 'var(--fgColor-danger, #cf222e)',
|
|
214
|
+
hoverStrokeWidth: defaults.hoverStrokeWidth ?? 5,
|
|
215
|
+
endpointRadius: defaults.endpointRadius ?? 6,
|
|
216
|
+
endpointFill: defaults.endpointFill ?? 'var(--fgColor-accent, #0969da)',
|
|
217
|
+
endpointStroke: defaults.endpointStroke ?? 'var(--bgColor-default, #ffffff)',
|
|
218
|
+
endpointStrokeWidth: defaults.endpointStrokeWidth ?? 3,
|
|
219
|
+
hitAreaStrokeWidth: defaults.hitAreaStrokeWidth ?? 16,
|
|
220
|
+
dragStroke: defaults.dragStroke ?? 'var(--fgColor-accent, #0969da)',
|
|
221
|
+
dragStrokeWidth: defaults.dragStrokeWidth ?? 2,
|
|
222
|
+
dragDasharray: defaults.dragDasharray ?? '6 4',
|
|
223
|
+
dragOpacity: defaults.dragOpacity ?? 0.7,
|
|
224
|
+
startEndpoint: defaults.startEndpoint ?? 'circle',
|
|
225
|
+
endEndpoint: defaults.endEndpoint ?? 'circle',
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Check if a connection from sourceType to targetType is allowed.
|
|
231
|
+
* @param {string} targetType — widget type receiving the connection
|
|
232
|
+
* @param {string} sourceType — widget type initiating the connection
|
|
233
|
+
* @returns {boolean}
|
|
234
|
+
*/
|
|
235
|
+
export function canAcceptConnection(targetType, sourceType) {
|
|
236
|
+
const config = getConnectorConfig(targetType)
|
|
237
|
+
if (config.exclude.includes(sourceType)) return false
|
|
238
|
+
if (config.accept.includes('*')) return true
|
|
239
|
+
return config.accept.includes(sourceType)
|
|
240
|
+
}
|
|
@@ -129,3 +129,4 @@ export const prototypeEmbedSchema = schemas['prototype']
|
|
|
129
129
|
export const linkPreviewSchema = schemas['link-preview']
|
|
130
130
|
export const imageSchema = schemas['image']
|
|
131
131
|
export const figmaEmbedSchema = schemas['figma-embed']
|
|
132
|
+
export const terminalSchema = schemas['terminal']
|
package/src/context.jsx
CHANGED
|
@@ -30,6 +30,21 @@ for (const [name, data] of Object.entries(canvases || {})) {
|
|
|
30
30
|
})
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
+
// Sort each group's pages by pageOrder from .meta.json (if available)
|
|
34
|
+
for (const [, pages] of canvasGroupMap) {
|
|
35
|
+
const pageOrder = pages[0]?._canvasMeta?.pageOrder
|
|
36
|
+
if (Array.isArray(pageOrder)) {
|
|
37
|
+
const orderMap = new Map()
|
|
38
|
+
pageOrder.forEach((entry, idx) => {
|
|
39
|
+
if (typeof entry === 'string' && !entry.startsWith('sep-')) orderMap.set(entry, idx)
|
|
40
|
+
})
|
|
41
|
+
pages.sort((a, b) => {
|
|
42
|
+
const ai = orderMap.has(a.name) ? orderMap.get(a.name) : Infinity
|
|
43
|
+
const bi = orderMap.has(b.name) ? orderMap.get(b.name) : Infinity
|
|
44
|
+
return ai - bi
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
}
|
|
33
48
|
|
|
34
49
|
// Build a map from story route paths → story names at module load time
|
|
35
50
|
const storyRouteMap = new Map()
|