@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.
@@ -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
- * Each widget type exports a `schema` describing its props.
58
- * This is used by the toolbar, canvas settings, and future widget inspectors.
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
- // ── Widget Schemas ──────────────────────────────────────────────────
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
- export const prototypeEmbedSchema = {
131
- src: { type: 'url', label: 'URL', category: 'content', defaultValue: '' },
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
- export const linkPreviewSchema = {
139
- url: { type: 'url', label: 'URL', category: 'content', defaultValue: '' },
140
- title: { type: 'text', label: 'Title', category: 'content', defaultValue: '' },
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']
@@ -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]) {