@dfosco/storyboard-react 3.0.0 → 3.1.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/canvas/CanvasControls.jsx +123 -0
- package/src/canvas/CanvasControls.module.css +133 -0
- package/src/canvas/CanvasPage.jsx +306 -18
- package/src/canvas/CanvasPage.module.css +58 -0
- package/src/canvas/widgets/LinkPreview.jsx +34 -0
- package/src/canvas/widgets/LinkPreview.module.css +51 -0
- package/src/canvas/widgets/MarkdownBlock.jsx +8 -4
- package/src/canvas/widgets/PrototypeEmbed.jsx +160 -14
- package/src/canvas/widgets/PrototypeEmbed.module.css +217 -1
- package/src/canvas/widgets/StickyNote.jsx +12 -20
- package/src/canvas/widgets/StickyNote.module.css +14 -39
- package/src/canvas/widgets/WidgetWrapper.jsx +2 -15
- package/src/canvas/widgets/WidgetWrapper.module.css +0 -30
- package/src/canvas/widgets/index.js +3 -1
- package/src/canvas/widgets/widgetProps.js +7 -0
- package/src/vite/data-plugin.js +9 -7
|
@@ -4,50 +4,22 @@
|
|
|
4
4
|
|
|
5
5
|
.sticky {
|
|
6
6
|
background: var(--sticky-bg, #fff8c5);
|
|
7
|
-
border-radius:
|
|
7
|
+
border-radius: 6px;
|
|
8
|
+
border: 2px solid color-mix(in srgb, var(--sticky-bg) 80%, rgb(0, 0, 0) 10%);
|
|
8
9
|
min-width: 180px;
|
|
9
|
-
box-shadow: 2px 3px 8px rgba(0, 0, 0, 0.
|
|
10
|
+
box-shadow: 2px 3px 8px rgba(0, 0, 0, 0.04);
|
|
10
11
|
font-family: var(--tc-font-stack, system-ui, -apple-system, sans-serif);
|
|
11
12
|
resize: both;
|
|
12
13
|
overflow: auto;
|
|
13
14
|
position: relative;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
.removeBtn {
|
|
17
|
-
all: unset;
|
|
18
|
-
cursor: pointer;
|
|
19
|
-
position: absolute;
|
|
20
|
-
top: 4px;
|
|
21
|
-
right: 4px;
|
|
22
|
-
font-size: 16px;
|
|
23
|
-
line-height: 1;
|
|
24
|
-
width: 20px;
|
|
25
|
-
height: 20px;
|
|
26
|
-
display: flex;
|
|
27
|
-
align-items: center;
|
|
28
|
-
justify-content: center;
|
|
29
|
-
color: var(--sticky-border, #d4a72c);
|
|
30
|
-
border-radius: 2px;
|
|
31
|
-
opacity: 0;
|
|
32
|
-
transition: opacity 150ms;
|
|
33
|
-
z-index: 1;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
.sticky:hover .removeBtn {
|
|
37
|
-
opacity: 0.6;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
.removeBtn:hover {
|
|
41
|
-
opacity: 1;
|
|
42
|
-
color: var(--fgColor-danger, #d1242f);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
17
|
.text {
|
|
46
|
-
padding:
|
|
18
|
+
padding: 16px 20px;
|
|
47
19
|
margin: 0;
|
|
48
|
-
font-size:
|
|
20
|
+
font-size: 18px;
|
|
49
21
|
line-height: 1.5;
|
|
50
|
-
color:
|
|
22
|
+
color: color-mix(in srgb, var(--sticky-bg), black 70%);
|
|
51
23
|
white-space: pre-wrap;
|
|
52
24
|
word-break: break-word;
|
|
53
25
|
cursor: text;
|
|
@@ -55,21 +27,24 @@
|
|
|
55
27
|
}
|
|
56
28
|
|
|
57
29
|
.textarea {
|
|
58
|
-
|
|
30
|
+
position: absolute;
|
|
31
|
+
top: 0;
|
|
32
|
+
left: 0;
|
|
59
33
|
width: 100%;
|
|
60
34
|
height: 100%;
|
|
61
35
|
box-sizing: border-box;
|
|
62
|
-
padding:
|
|
36
|
+
padding: 16px 20px;
|
|
63
37
|
margin: 0;
|
|
64
38
|
border: none;
|
|
65
39
|
outline: none;
|
|
66
40
|
background: transparent;
|
|
67
41
|
font-family: inherit;
|
|
68
|
-
font-size:
|
|
42
|
+
font-size: 18px;
|
|
69
43
|
line-height: 1.5;
|
|
70
|
-
color:
|
|
44
|
+
color: color-mix(in srgb, var(--sticky-bg) 80%, rgb(0, 0, 0) 98%);
|
|
45
|
+
white-space: pre-wrap;
|
|
46
|
+
word-break: break-word;
|
|
71
47
|
resize: none;
|
|
72
|
-
min-height: 80px;
|
|
73
48
|
}
|
|
74
49
|
|
|
75
50
|
/* Color picker area — sits below the sticky */
|
|
@@ -2,24 +2,11 @@ import styles from './WidgetWrapper.module.css'
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Common wrapper for all canvas widgets.
|
|
5
|
-
* Provides shadow/border styling
|
|
5
|
+
* Provides shadow/border styling.
|
|
6
6
|
*/
|
|
7
|
-
export default function WidgetWrapper({ children,
|
|
7
|
+
export default function WidgetWrapper({ children, className }) {
|
|
8
8
|
return (
|
|
9
9
|
<section className={`${styles.wrapper} ${className || ''}`}>
|
|
10
|
-
{onRemove && (
|
|
11
|
-
<button
|
|
12
|
-
className={styles.removeBtn}
|
|
13
|
-
onClick={(e) => {
|
|
14
|
-
e.stopPropagation()
|
|
15
|
-
onRemove()
|
|
16
|
-
}}
|
|
17
|
-
title="Remove widget"
|
|
18
|
-
aria-label="Remove widget"
|
|
19
|
-
>
|
|
20
|
-
×
|
|
21
|
-
</button>
|
|
22
|
-
)}
|
|
23
10
|
<div className={styles.content}>
|
|
24
11
|
{children}
|
|
25
12
|
</div>
|
|
@@ -9,36 +9,6 @@
|
|
|
9
9
|
0 2px 8px rgba(0, 0, 0, 0.08);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
.removeBtn {
|
|
13
|
-
all: unset;
|
|
14
|
-
cursor: pointer;
|
|
15
|
-
position: absolute;
|
|
16
|
-
top: 6px;
|
|
17
|
-
right: 6px;
|
|
18
|
-
font-size: 16px;
|
|
19
|
-
line-height: 1;
|
|
20
|
-
width: 22px;
|
|
21
|
-
height: 22px;
|
|
22
|
-
display: flex;
|
|
23
|
-
align-items: center;
|
|
24
|
-
justify-content: center;
|
|
25
|
-
color: var(--fgColor-muted, #656d76);
|
|
26
|
-
border-radius: 6px;
|
|
27
|
-
background: var(--bgColor-default, #ffffff);
|
|
28
|
-
opacity: 0;
|
|
29
|
-
transition: opacity 150ms;
|
|
30
|
-
z-index: 1;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
.wrapper:hover .removeBtn {
|
|
34
|
-
opacity: 1;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
.removeBtn:hover {
|
|
38
|
-
color: var(--fgColor-danger, #d1242f);
|
|
39
|
-
background: var(--bgColor-danger-muted, #ffebe9);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
12
|
.content {
|
|
43
13
|
position: relative;
|
|
44
14
|
}
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import StickyNote from './StickyNote.jsx'
|
|
2
2
|
import MarkdownBlock from './MarkdownBlock.jsx'
|
|
3
3
|
import PrototypeEmbed from './PrototypeEmbed.jsx'
|
|
4
|
+
import LinkPreview from './LinkPreview.jsx'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Maps widget type strings to their React components.
|
|
7
|
-
* Each component receives: { id, props, onUpdate
|
|
8
|
+
* Each component receives: { id, props, onUpdate }
|
|
8
9
|
*/
|
|
9
10
|
export const widgetRegistry = {
|
|
10
11
|
'sticky-note': StickyNote,
|
|
11
12
|
'markdown': MarkdownBlock,
|
|
12
13
|
'prototype': PrototypeEmbed,
|
|
14
|
+
'link-preview': LinkPreview,
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
/**
|
|
@@ -130,10 +130,16 @@ export const markdownSchema = {
|
|
|
130
130
|
export const prototypeEmbedSchema = {
|
|
131
131
|
src: { type: 'url', label: 'URL', category: 'content', defaultValue: '' },
|
|
132
132
|
label: { type: 'text', label: 'Label', category: 'settings', defaultValue: '' },
|
|
133
|
+
zoom: { type: 'number', label: 'Zoom', category: 'settings', defaultValue: 100, min: 25, max: 200 },
|
|
133
134
|
width: { type: 'number', label: 'Width', category: 'size', defaultValue: 800, min: 200, max: 2000 },
|
|
134
135
|
height: { type: 'number', label: 'Height', category: 'size', defaultValue: 600, min: 200, max: 1500 },
|
|
135
136
|
}
|
|
136
137
|
|
|
138
|
+
export const linkPreviewSchema = {
|
|
139
|
+
url: { type: 'url', label: 'URL', category: 'content', defaultValue: '' },
|
|
140
|
+
title: { type: 'text', label: 'Title', category: 'content', defaultValue: '' },
|
|
141
|
+
}
|
|
142
|
+
|
|
137
143
|
/**
|
|
138
144
|
* Schema registry — maps widget type strings to their schemas.
|
|
139
145
|
*/
|
|
@@ -141,4 +147,5 @@ export const schemas = {
|
|
|
141
147
|
'sticky-note': stickyNoteSchema,
|
|
142
148
|
'markdown': markdownSchema,
|
|
143
149
|
'prototype': prototypeEmbedSchema,
|
|
150
|
+
'link-preview': linkPreviewSchema,
|
|
144
151
|
}
|
package/src/vite/data-plugin.js
CHANGED
|
@@ -3,7 +3,7 @@ import path from 'node:path'
|
|
|
3
3
|
import { execSync } from 'node:child_process'
|
|
4
4
|
import { globSync } from 'glob'
|
|
5
5
|
import { parse as parseJsonc } from 'jsonc-parser'
|
|
6
|
-
import { materializeFromText } from '
|
|
6
|
+
import { materializeFromText } from '@dfosco/storyboard-core/canvas/materializer'
|
|
7
7
|
|
|
8
8
|
const VIRTUAL_MODULE_ID = 'virtual:storyboard-data-index'
|
|
9
9
|
const RESOLVED_ID = '\0' + VIRTUAL_MODULE_ID
|
|
@@ -34,26 +34,28 @@ function parseDataFile(filePath) {
|
|
|
34
34
|
|
|
35
35
|
const name = canvasJsonlMatch[1]
|
|
36
36
|
let inferredRoute = null
|
|
37
|
-
const canvasFolderMatch = normalized.match(/(?:^|\/)src\/
|
|
37
|
+
const canvasFolderMatch = normalized.match(/(?:^|\/)src\/canvas\/([^/]+)\.folder\//)
|
|
38
38
|
const canvasFolderName = canvasFolderMatch ? canvasFolderMatch[1] : null
|
|
39
39
|
const folderDirMatch = normalized.match(/(?:^|\/)src\/prototypes\/([^/]+)\.folder\//)
|
|
40
40
|
const folderName = folderDirMatch ? folderDirMatch[1] : null
|
|
41
41
|
|
|
42
|
-
const canvasCheck = normalized.match(/(?:^|\/)src\/
|
|
42
|
+
const canvasCheck = normalized.match(/(?:^|\/)src\/canvas\//)
|
|
43
43
|
if (canvasCheck) {
|
|
44
44
|
const dirPath = normalized.substring(0, normalized.lastIndexOf('/'))
|
|
45
|
-
const routeBase = dirPath
|
|
46
|
-
.replace(/^.*?src\/
|
|
45
|
+
const routeBase = (dirPath + '/')
|
|
46
|
+
.replace(/^.*?src\/canvas\//, '')
|
|
47
47
|
.replace(/[^/]*\.folder\/?/g, '')
|
|
48
|
+
.replace(/\/$/, '')
|
|
48
49
|
inferredRoute = '/canvas/' + (routeBase ? routeBase + '/' : '') + name
|
|
49
50
|
inferredRoute = inferredRoute.replace(/\/+/g, '/').replace(/\/$/, '') || '/canvas'
|
|
50
51
|
}
|
|
51
52
|
const protoCheck = normalized.match(/(?:^|\/)src\/prototypes\//)
|
|
52
53
|
if (!canvasCheck && protoCheck) {
|
|
53
54
|
const dirPath = normalized.substring(0, normalized.lastIndexOf('/'))
|
|
54
|
-
const routeBase = dirPath
|
|
55
|
+
const routeBase = (dirPath + '/')
|
|
55
56
|
.replace(/^.*?src\/prototypes\//, '')
|
|
56
57
|
.replace(/[^/]*\.folder\/?/g, '')
|
|
58
|
+
.replace(/\/$/, '')
|
|
57
59
|
inferredRoute = '/canvas/' + (routeBase ? routeBase + '/' : '') + name
|
|
58
60
|
inferredRoute = inferredRoute.replace(/\/+/g, '/').replace(/\/$/, '') || '/canvas'
|
|
59
61
|
}
|
|
@@ -386,7 +388,7 @@ function generateModule({ index, protoFolders, flowRoutes, canvasRoutes }, root)
|
|
|
386
388
|
parsed = { ...parsed, _route: canvasRoutes[name] }
|
|
387
389
|
}
|
|
388
390
|
// Inject folder association
|
|
389
|
-
const folderDirMatch = path.relative(root, absPath).replace(/\\/g, '/').match(/(?:^|\/)src\/(?:prototypes|
|
|
391
|
+
const folderDirMatch = path.relative(root, absPath).replace(/\\/g, '/').match(/(?:^|\/)src\/(?:prototypes|canvas)\/([^/]+)\.folder\//)
|
|
390
392
|
if (folderDirMatch) {
|
|
391
393
|
parsed = { ...parsed, _folder: folderDirMatch[1] }
|
|
392
394
|
}
|