@dfosco/storyboard-react 3.9.1 → 3.10.0-beta.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 +3 -3
- package/src/canvas/CanvasControls.jsx +2 -5
- package/src/canvas/CanvasPage.jsx +139 -20
- package/src/canvas/CanvasPage.module.css +1 -5
- package/src/canvas/CanvasToolbar.jsx +2 -5
- package/src/canvas/widgets/ComponentWidget.jsx +51 -3
- package/src/canvas/widgets/ComponentWidget.module.css +18 -0
- package/src/canvas/widgets/MarkdownBlock.module.css +1 -0
- package/src/canvas/widgets/PrototypeEmbed.jsx +36 -46
- package/src/canvas/widgets/ResizeHandle.jsx +56 -0
- package/src/canvas/widgets/ResizeHandle.module.css +29 -0
- package/src/canvas/widgets/StickyNote.jsx +21 -32
- package/src/canvas/widgets/StickyNote.module.css +1 -71
- package/src/canvas/widgets/StickyNote.test.jsx +96 -0
- package/src/canvas/widgets/WidgetChrome.jsx +244 -0
- package/src/canvas/widgets/WidgetChrome.module.css +209 -0
- package/src/canvas/widgets/widgetConfig.js +79 -0
- package/src/canvas/widgets/widgetProps.js +13 -35
- package/src/vite/data-plugin.js +8 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/* WidgetChrome — universal hover toolbar for canvas widgets */
|
|
2
|
+
|
|
3
|
+
.chromeContainer {
|
|
4
|
+
position: relative;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/* Widget slot — contains the actual widget; selection outline targets this */
|
|
8
|
+
.widgetSlot {
|
|
9
|
+
position: relative;
|
|
10
|
+
border-radius: 4px;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.widgetSlotSelected {
|
|
14
|
+
outline: 2px solid var(--bgColor-accent-emphasis, #2f81f7);
|
|
15
|
+
outline-offset: 2px;
|
|
16
|
+
border-radius: 4px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* Toolbar — absolutely positioned below the widget so it doesn't affect
|
|
20
|
+
the draggable box dimensions (tiny-canvas measures children for drag). */
|
|
21
|
+
.toolbar {
|
|
22
|
+
display: flex;
|
|
23
|
+
align-items: center;
|
|
24
|
+
justify-content: center;
|
|
25
|
+
height: 28px;
|
|
26
|
+
position: absolute;
|
|
27
|
+
left: 0;
|
|
28
|
+
right: 0;
|
|
29
|
+
top: calc(100% + 4px);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Trigger dot — centered, visible at rest */
|
|
33
|
+
.triggerDot {
|
|
34
|
+
width: 6px;
|
|
35
|
+
height: 6px;
|
|
36
|
+
border-radius: 50%;
|
|
37
|
+
background: var(--borderColor-muted, #d0d7de);
|
|
38
|
+
opacity: 0.5;
|
|
39
|
+
transition: opacity 120ms;
|
|
40
|
+
position: absolute;
|
|
41
|
+
left: 50%;
|
|
42
|
+
top: 50%;
|
|
43
|
+
transform: translate(-50%, -50%);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
:global([data-sb-canvas-theme^='dark']) .triggerDot {
|
|
47
|
+
background: var(--borderColor-muted, #373e47);
|
|
48
|
+
opacity: 0.6;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.triggerDotHidden {
|
|
52
|
+
opacity: 0;
|
|
53
|
+
pointer-events: none;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* Toolbar content — feature buttons + select handle */
|
|
57
|
+
.toolbarContent {
|
|
58
|
+
display: flex;
|
|
59
|
+
align-items: center;
|
|
60
|
+
justify-content: space-between;
|
|
61
|
+
width: 100%;
|
|
62
|
+
opacity: 0;
|
|
63
|
+
pointer-events: none;
|
|
64
|
+
transition: opacity 120ms;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.toolbarContentVisible {
|
|
68
|
+
opacity: 1;
|
|
69
|
+
pointer-events: auto;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Feature buttons — left-aligned group */
|
|
73
|
+
.featureButtons {
|
|
74
|
+
display: flex;
|
|
75
|
+
align-items: center;
|
|
76
|
+
gap: 3px;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Individual feature button */
|
|
80
|
+
.featureBtn {
|
|
81
|
+
all: unset;
|
|
82
|
+
cursor: pointer;
|
|
83
|
+
display: flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
justify-content: center;
|
|
86
|
+
width: 24px;
|
|
87
|
+
height: 24px;
|
|
88
|
+
border-radius: 12px;
|
|
89
|
+
border: 1.6px solid var(--borderColor-muted, #d0d7de);
|
|
90
|
+
background: var(--bgColor-default, #ffffff);
|
|
91
|
+
color: var(--fgColor-muted, #656d76);
|
|
92
|
+
font-size: 12px;
|
|
93
|
+
transition: background 100ms, color 100ms, border-color 100ms;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
:global([data-sb-canvas-theme^='dark']) .featureBtn {
|
|
97
|
+
background: var(--bgColor-muted, #161b22);
|
|
98
|
+
border-color: var(--borderColor-muted, #373e47);
|
|
99
|
+
color: var(--fgColor-muted, #8b949e);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.featureBtn:hover {
|
|
103
|
+
background: var(--bgColor-neutral-muted, #eaeef2);
|
|
104
|
+
color: var(--fgColor-default, #1f2328);
|
|
105
|
+
border-color: var(--borderColor-default, #d0d7de);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
:global([data-sb-canvas-theme^='dark']) .featureBtn:hover {
|
|
109
|
+
background: var(--bgColor-neutral-muted, #272c33);
|
|
110
|
+
color: var(--fgColor-default, #e6edf3);
|
|
111
|
+
border-color: var(--borderColor-default, #484f58);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* Select handle — right-aligned rounded rect */
|
|
115
|
+
.selectHandle {
|
|
116
|
+
all: unset;
|
|
117
|
+
cursor: grab;
|
|
118
|
+
width: 18px;
|
|
119
|
+
height: 12px;
|
|
120
|
+
border-radius: 4px;
|
|
121
|
+
border: 1.6px solid var(--borderColor-muted, #d0d7de);
|
|
122
|
+
background: var(--bgColor-default, #ffffff);
|
|
123
|
+
transition: background 100ms, border-color 100ms;
|
|
124
|
+
flex-shrink: 0;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
:global([data-sb-canvas-theme^='dark']) .selectHandle {
|
|
128
|
+
background: var(--bgColor-muted, #161b22);
|
|
129
|
+
border-color: var(--borderColor-muted, #373e47);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.selectHandle:hover {
|
|
133
|
+
border-color: var(--bgColor-accent-emphasis, #2f81f7);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.selectHandleActive {
|
|
137
|
+
background: var(--bgColor-accent-emphasis, #2f81f7);
|
|
138
|
+
border-color: var(--bgColor-accent-emphasis, #2f81f7);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.selectHandleActive:hover {
|
|
142
|
+
background: var(--bgColor-accent-emphasis, #388bfd);
|
|
143
|
+
border-color: var(--bgColor-accent-emphasis, #388bfd);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* Color picker feature */
|
|
147
|
+
.colorPickerWrapper {
|
|
148
|
+
position: relative;
|
|
149
|
+
display: flex;
|
|
150
|
+
align-items: center;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.colorDotInner {
|
|
154
|
+
width: 10px;
|
|
155
|
+
height: 10px;
|
|
156
|
+
border-radius: 50%;
|
|
157
|
+
display: block;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.colorPopup {
|
|
161
|
+
position: absolute;
|
|
162
|
+
bottom: calc(100% + 6px);
|
|
163
|
+
left: 50%;
|
|
164
|
+
transform: translateX(-50%);
|
|
165
|
+
display: flex;
|
|
166
|
+
gap: 5px;
|
|
167
|
+
padding: 6px 10px;
|
|
168
|
+
background: var(--bgColor-default, #ffffff);
|
|
169
|
+
border-radius: 20px;
|
|
170
|
+
box-shadow:
|
|
171
|
+
0 0 0 1px rgba(0, 0, 0, 0.08),
|
|
172
|
+
0 4px 12px rgba(0, 0, 0, 0.12);
|
|
173
|
+
opacity: 0;
|
|
174
|
+
pointer-events: none;
|
|
175
|
+
transition: opacity 150ms;
|
|
176
|
+
z-index: 10;
|
|
177
|
+
white-space: nowrap;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
:global([data-sb-canvas-theme^='dark']) .colorPopup {
|
|
181
|
+
background: var(--bgColor-muted, #161b22);
|
|
182
|
+
box-shadow:
|
|
183
|
+
0 0 0 1px rgba(255, 255, 255, 0.08),
|
|
184
|
+
0 4px 12px rgba(0, 0, 0, 0.45);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.colorPickerWrapper:hover .colorPopup {
|
|
188
|
+
opacity: 1;
|
|
189
|
+
pointer-events: auto;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.colorOption {
|
|
193
|
+
all: unset;
|
|
194
|
+
width: 20px;
|
|
195
|
+
height: 20px;
|
|
196
|
+
border-radius: 50%;
|
|
197
|
+
border: 2px solid transparent;
|
|
198
|
+
cursor: pointer;
|
|
199
|
+
transition: transform 100ms;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.colorOption:hover {
|
|
203
|
+
transform: scale(1.15);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.colorOptionActive {
|
|
207
|
+
border-color: currentColor;
|
|
208
|
+
box-shadow: 0 0 0 1px currentColor;
|
|
209
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Widget Config Loader
|
|
3
|
+
*
|
|
4
|
+
* Reads widgets.config.json from @dfosco/storyboard-core and builds
|
|
5
|
+
* schema objects compatible with the existing readProp/readAllProps/getDefaults API.
|
|
6
|
+
*
|
|
7
|
+
* The config is the single source of truth for widget definitions —
|
|
8
|
+
* prop schemas, feature lists, labels, and icons all come from here.
|
|
9
|
+
*/
|
|
10
|
+
import widgetsConfig from '@dfosco/storyboard-core/widgets.config.json'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Convert a config prop definition to the schema shape used by widgetProps.js.
|
|
14
|
+
* Config uses `"default"`, schema uses `"defaultValue"`.
|
|
15
|
+
*/
|
|
16
|
+
function configPropToSchema(propDef) {
|
|
17
|
+
const schema = {
|
|
18
|
+
type: propDef.type,
|
|
19
|
+
label: propDef.label,
|
|
20
|
+
category: propDef.category,
|
|
21
|
+
}
|
|
22
|
+
if (propDef.default !== undefined) schema.defaultValue = propDef.default
|
|
23
|
+
if (propDef.options) schema.options = propDef.options
|
|
24
|
+
if (propDef.min !== undefined) schema.min = propDef.min
|
|
25
|
+
if (propDef.max !== undefined) schema.max = propDef.max
|
|
26
|
+
return schema
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Build schema objects for all widget types from the config.
|
|
31
|
+
* Returns the same shape as the old hardcoded schemas in widgetProps.js.
|
|
32
|
+
*/
|
|
33
|
+
function buildSchemas() {
|
|
34
|
+
const result = {}
|
|
35
|
+
for (const [type, def] of Object.entries(widgetsConfig.widgets)) {
|
|
36
|
+
const schema = {}
|
|
37
|
+
for (const [key, propDef] of Object.entries(def.props || {})) {
|
|
38
|
+
schema[key] = configPropToSchema(propDef)
|
|
39
|
+
}
|
|
40
|
+
result[type] = schema
|
|
41
|
+
}
|
|
42
|
+
return result
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** All widget schemas, keyed by type string. */
|
|
46
|
+
export const schemas = buildSchemas()
|
|
47
|
+
|
|
48
|
+
/** Full widget config entries, keyed by type string. */
|
|
49
|
+
export const widgetTypes = widgetsConfig.widgets
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get the feature list for a widget type.
|
|
53
|
+
* @param {string} type — widget type string
|
|
54
|
+
* @returns {Array} features array from config, or empty array
|
|
55
|
+
*/
|
|
56
|
+
export function getFeatures(type) {
|
|
57
|
+
return widgetTypes[type]?.features ?? []
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get the display metadata (label, icon) for a widget type.
|
|
62
|
+
* @param {string} type — widget type string
|
|
63
|
+
* @returns {{ label: string, icon: string } | null}
|
|
64
|
+
*/
|
|
65
|
+
export function getWidgetMeta(type) {
|
|
66
|
+
const def = widgetTypes[type]
|
|
67
|
+
if (!def) return null
|
|
68
|
+
return { label: def.label, icon: def.icon }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get all widget types as an array of { type, label, icon } for menus.
|
|
73
|
+
* Excludes link-preview which is created via paste only.
|
|
74
|
+
*/
|
|
75
|
+
export function getMenuWidgetTypes() {
|
|
76
|
+
return Object.entries(widgetTypes)
|
|
77
|
+
.filter(([type]) => type !== 'link-preview')
|
|
78
|
+
.map(([type, def]) => ({ type, label: def.label, icon: def.icon }))
|
|
79
|
+
}
|
|
@@ -54,8 +54,9 @@
|
|
|
54
54
|
*
|
|
55
55
|
* ## Declaring Widget Props (Schema)
|
|
56
56
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
57
|
+
* Widget prop schemas are defined in widgets.config.json (packages/core)
|
|
58
|
+
* and loaded via widgetConfig.js. This module re-exports the generated
|
|
59
|
+
* schemas and provides utility functions for reading props with defaults.
|
|
59
60
|
*/
|
|
60
61
|
|
|
61
62
|
/**
|
|
@@ -71,6 +72,8 @@
|
|
|
71
72
|
* @property {number} [max] — maximum for 'number' type
|
|
72
73
|
*/
|
|
73
74
|
|
|
75
|
+
import { schemas as configSchemas } from './widgetConfig.js'
|
|
76
|
+
|
|
74
77
|
/**
|
|
75
78
|
* Read a prop value with fallback to schema default.
|
|
76
79
|
* @param {object} props — widget props object (may be null)
|
|
@@ -114,38 +117,13 @@ export function getDefaults(schema) {
|
|
|
114
117
|
return result
|
|
115
118
|
}
|
|
116
119
|
|
|
117
|
-
// ──
|
|
118
|
-
|
|
119
|
-
export const stickyNoteSchema = {
|
|
120
|
-
text: { type: 'text', label: 'Text', category: 'content', defaultValue: '' },
|
|
121
|
-
color: { type: 'select', label: 'Color', category: 'settings', defaultValue: 'yellow',
|
|
122
|
-
options: ['yellow', 'blue', 'green', 'pink', 'purple', 'orange'] },
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export const markdownSchema = {
|
|
126
|
-
content: { type: 'text', label: 'Content', category: 'content', defaultValue: '' },
|
|
127
|
-
width: { type: 'number', label: 'Width', category: 'size', defaultValue: 360, min: 200, max: 1200 },
|
|
128
|
-
}
|
|
120
|
+
// ── Config-driven schemas ───────────────────────────────────────────
|
|
129
121
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
label: { type: 'text', label: 'Label', category: 'settings', defaultValue: '' },
|
|
133
|
-
zoom: { type: 'number', label: 'Zoom', category: 'settings', defaultValue: 100, min: 25, max: 200 },
|
|
134
|
-
width: { type: 'number', label: 'Width', category: 'size', defaultValue: 800, min: 200, max: 2000 },
|
|
135
|
-
height: { type: 'number', label: 'Height', category: 'size', defaultValue: 600, min: 200, max: 1500 },
|
|
136
|
-
}
|
|
122
|
+
/** Schema registry — maps widget type strings to their schemas. */
|
|
123
|
+
export const schemas = configSchemas
|
|
137
124
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Schema registry — maps widget type strings to their schemas.
|
|
145
|
-
*/
|
|
146
|
-
export const schemas = {
|
|
147
|
-
'sticky-note': stickyNoteSchema,
|
|
148
|
-
'markdown': markdownSchema,
|
|
149
|
-
'prototype': prototypeEmbedSchema,
|
|
150
|
-
'link-preview': linkPreviewSchema,
|
|
151
|
-
}
|
|
125
|
+
// Named exports for backward compatibility with widget imports
|
|
126
|
+
export const stickyNoteSchema = schemas['sticky-note']
|
|
127
|
+
export const markdownSchema = schemas['markdown']
|
|
128
|
+
export const prototypeEmbedSchema = schemas['prototype']
|
|
129
|
+
export const linkPreviewSchema = schemas['link-preview']
|
package/src/vite/data-plugin.js
CHANGED
|
@@ -397,6 +397,14 @@ function generateModule({ index, protoFolders, flowRoutes, canvasRoutes }, root)
|
|
|
397
397
|
}
|
|
398
398
|
}
|
|
399
399
|
|
|
400
|
+
// Auto-fill gitAuthor for canvas metadata from git history
|
|
401
|
+
if (suffix === 'canvas' && parsed && !parsed.gitAuthor) {
|
|
402
|
+
const gitAuthor = getGitAuthor(root, absPath)
|
|
403
|
+
if (gitAuthor) {
|
|
404
|
+
parsed = { ...parsed, gitAuthor }
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
400
408
|
// Inject inferred route and resolve JSX companion for canvases
|
|
401
409
|
if (suffix === 'canvas') {
|
|
402
410
|
if (canvasRoutes[name]) {
|