@dfosco/storyboard 0.8.1 → 0.9.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/dist/storyboard-ui.css +1 -1
- package/dist/storyboard-ui.js +6420 -6336
- package/dist/storyboard-ui.js.map +1 -1
- package/dist/tailwind.css +1 -1
- package/dist/tiny-canvas.css +1 -1
- package/package.json +1 -1
- package/scaffold/src/library/_app.jsx +2 -14
- package/scaffold/src/library/_app.module.css +2 -8
- package/scaffold/src/library/mount.jsx +0 -1
- package/src/canvas/style.css +16 -16
- package/src/core/artifact/operations.js +35 -2
- package/src/core/artifact/schemas/canvas.schema.json +7 -1
- package/src/core/canvas/hot-pool.js +11 -22
- package/src/core/canvas/selectedWidgets.js +1 -1
- package/src/core/canvas/server.js +52 -31
- package/src/core/canvas/server.test.js +107 -0
- package/src/core/cli/artifact.js +2 -0
- package/src/core/cli/create.js +35 -5
- package/src/core/cli/schemas.js +6 -1
- package/src/core/comments/ui/comment-layout.css +14 -14
- package/src/core/index.js +18 -0
- package/src/core/inspector/highlighter.js +1 -1
- package/src/core/inspector/mouseMode.js +2 -2
- package/src/core/lib/components/ui/dialog/dialog.module.css +8 -8
- package/src/core/modes/modes.css +4 -4
- package/src/core/mountStoryboardCore.js +53 -9
- package/src/core/mountStoryboardCore.test.js +25 -1
- package/src/core/rename-watcher/config.json +1 -1
- package/src/core/rename-watcher/watcher.js +1 -1
- package/src/core/stores/canvasInteractionStore.js +120 -0
- package/src/core/stores/canvasInteractionStore.test.js +126 -0
- package/src/core/stores/configSchema.js +23 -6
- package/src/core/stores/configSchema.test.js +3 -0
- package/src/core/stores/customerModeConfig.js +24 -3
- package/src/core/stores/customerModeConfig.test.js +46 -0
- package/src/core/stores/presentationStore.js +118 -0
- package/src/core/stores/presentationStore.test.js +114 -0
- package/src/core/stores/uiConfig.js +10 -2
- package/src/core/styles/tailwind.css +138 -90
- package/src/core/styles/workspace-chrome.css +4 -21
- package/src/core/ui/AutosyncMenuButton.css +7 -7
- package/src/core/ui/BranchSelect.module.css +5 -5
- package/src/core/ui/ChromeSlot.jsx +92 -0
- package/src/core/ui/ChromeSlot.test.jsx +140 -0
- package/src/core/ui/CoreUIBar.css +5 -5
- package/src/core/ui/CoreUIBar.jsx +161 -136
- package/src/core/ui/InspectorPanel.css +5 -5
- package/src/core/ui/InspectorPanel.jsx +9 -9
- package/src/core/ui/PwaInstallBanner.css +5 -5
- package/src/core/ui/PwaInstallBanner.jsx +5 -5
- package/src/core/ui/core-ui-colors.css +28 -66
- package/src/core/ui/sidepanel.css +12 -12
- package/src/core/workshop/features/createCanvas/CreateCanvasForm.jsx +102 -13
- package/src/internals/ArtifactForm/ArtifactForm.module.css +7 -7
- package/src/internals/AuthModal/AuthModal.module.css +34 -34
- package/src/internals/BaseUiForm/BaseUiForm.module.css +85 -85
- package/src/internals/BranchBar/BranchBar.module.css +4 -4
- package/src/internals/BranchesDropdown/BranchesDropdown.module.css +9 -9
- package/src/internals/CommandPalette/CommandPalette.jsx +16 -10
- package/src/internals/CommandPalette/WidgetArtifactDialog.css +16 -16
- package/src/internals/CommandPalette/command-palette.css +14 -14
- package/src/internals/CreatePage/CreatePage.module.css +1 -1
- package/src/internals/FlowError.module.css +5 -5
- package/src/internals/PrototypeErrorBoundary.module.css +15 -15
- package/src/internals/SimpleWorkspace/SimpleWorkspace.jsx +5 -4
- package/src/internals/SimpleWorkspace/SimpleWorkspace.module.css +38 -38
- package/src/internals/Viewfinder.jsx +33 -16
- package/src/internals/Viewfinder.module.css +189 -189
- package/src/internals/canvas/CanvasControls.module.css +11 -11
- package/src/internals/canvas/CanvasPage.bridge.test.jsx +11 -37
- package/src/internals/canvas/CanvasPage.jsx +91 -93
- package/src/internals/canvas/CanvasPage.module.css +20 -19
- package/src/internals/canvas/CanvasToolbar.module.css +11 -11
- package/src/internals/canvas/ConnectorLayer.jsx +39 -2
- package/src/internals/canvas/ConnectorLayer.module.css +10 -10
- package/src/internals/canvas/PageSelector.module.css +28 -28
- package/src/internals/canvas/canvasTheme.js +22 -111
- package/src/internals/canvas/isolation/prototypeRoutes.jsx +12 -3
- package/src/internals/canvas/isolation/storyRoutes.jsx +1 -1
- package/src/internals/canvas/widgets/CodePenEmbed.jsx +16 -36
- package/src/internals/canvas/widgets/CodePenEmbed.module.css +12 -12
- package/src/internals/canvas/widgets/CropOverlay.module.css +23 -23
- package/src/internals/canvas/widgets/ExpandedPane.module.css +8 -8
- package/src/internals/canvas/widgets/FigmaEmbed.jsx +15 -36
- package/src/internals/canvas/widgets/FigmaEmbed.module.css +15 -15
- package/src/internals/canvas/widgets/FrozenTerminalOverlay.module.css +5 -5
- package/src/internals/canvas/widgets/ImageWidget.module.css +3 -3
- package/src/internals/canvas/widgets/LinkPreview.module.css +77 -78
- package/src/internals/canvas/widgets/MarkdownBlock.module.css +31 -31
- package/src/internals/canvas/widgets/PromptWidget.jsx +2 -16
- package/src/internals/canvas/widgets/PromptWidget.module.css +33 -33
- package/src/internals/canvas/widgets/PrototypeEmbed.jsx +23 -7
- package/src/internals/canvas/widgets/PrototypeEmbed.module.css +58 -56
- package/src/internals/canvas/widgets/PrototypeEmbed.test.jsx +4 -4
- package/src/internals/canvas/widgets/ResizeHandle.module.css +4 -4
- package/src/internals/canvas/widgets/StorySetWidget.jsx +45 -21
- package/src/internals/canvas/widgets/StoryWidget.jsx +53 -21
- package/src/internals/canvas/widgets/StoryWidget.module.css +22 -20
- package/src/internals/canvas/widgets/TerminalReadWidget.module.css +4 -4
- package/src/internals/canvas/widgets/TerminalWidget.jsx +56 -45
- package/src/internals/canvas/widgets/TerminalWidget.module.css +10 -10
- package/src/internals/canvas/widgets/TilesWidget.module.css +17 -17
- package/src/internals/canvas/widgets/WidgetChrome.module.css +61 -61
- package/src/internals/canvas/widgets/WidgetWrapper.module.css +2 -2
- package/src/internals/canvas/widgets/embedOverlay.module.css +14 -4
- package/src/internals/canvas/widgets/embedTheme.js +10 -102
- package/src/internals/canvas/widgets/widgetConfig.js +5 -5
- package/src/internals/story/ComponentSetPage.module.css +14 -14
- package/src/internals/story/StoryPage.module.css +2 -2
- package/src/internals/vite/data-plugin.js +5 -2
- package/src/internals/canvas/WebGLContextPool.jsx +0 -291
- package/src/internals/canvas/WebGLContextPool.test.jsx +0 -165
- package/src/internals/canvas/widgets/ComponentSetWidget.module.css +0 -89
- package/src/internals/canvas/widgets/StorySetWidget.module.css +0 -89
|
@@ -509,18 +509,18 @@ export default function InspectorPanel() {
|
|
|
509
509
|
{/* Empty state */}
|
|
510
510
|
{!hasSelection && !inspecting && (
|
|
511
511
|
<div className="flex flex-col items-center justify-center h-full gap-3 px-6 py-12 text-center">
|
|
512
|
-
<span style={{ color: 'var(--
|
|
512
|
+
<span style={{ color: 'var(--color-foreground-muted)', opacity: 0.4 }}>
|
|
513
513
|
<IconoirSquareDashed size={48} />
|
|
514
514
|
</span>
|
|
515
|
-
<p className="text-sm font-medium m-0" style={{ color: 'var(--
|
|
515
|
+
<p className="text-sm font-medium m-0" style={{ color: 'var(--color-foreground)' }}>
|
|
516
516
|
Select an element to start
|
|
517
517
|
</p>
|
|
518
|
-
<p className="text-xs m-0" style={{ color: 'var(--
|
|
518
|
+
<p className="text-xs m-0" style={{ color: 'var(--color-foreground-muted)' }}>
|
|
519
519
|
Click the inspect button to enter selection mode
|
|
520
520
|
</p>
|
|
521
521
|
<button
|
|
522
522
|
className="mt-2 px-4 py-1.5 text-xs font-medium rounded-md border-none cursor-pointer transition-colors"
|
|
523
|
-
style={{ background: 'var(--
|
|
523
|
+
style={{ background: 'var(--color-foreground-accent, #7655a4)', color: '#fff' }}
|
|
524
524
|
onClick={startInspecting}
|
|
525
525
|
>
|
|
526
526
|
Start inspecting
|
|
@@ -533,7 +533,7 @@ export default function InspectorPanel() {
|
|
|
533
533
|
<div className="flex flex-col items-center justify-center h-full gap-3 px-6 py-12 text-center">
|
|
534
534
|
<div className="flex items-center gap-2">
|
|
535
535
|
<span className="inspector-pulse-dot" />
|
|
536
|
-
<p className="text-sm m-0" style={{ color: 'var(--
|
|
536
|
+
<p className="text-sm m-0" style={{ color: 'var(--color-foreground)' }}>
|
|
537
537
|
Click any element on the page to inspect it
|
|
538
538
|
</p>
|
|
539
539
|
</div>
|
|
@@ -541,8 +541,8 @@ export default function InspectorPanel() {
|
|
|
541
541
|
className="mt-2 px-4 py-1.5 text-xs font-medium rounded-md border cursor-pointer transition-colors"
|
|
542
542
|
style={{
|
|
543
543
|
background: 'transparent',
|
|
544
|
-
color: 'var(--
|
|
545
|
-
borderColor: 'var(--
|
|
544
|
+
color: 'var(--color-foreground-muted)',
|
|
545
|
+
borderColor: 'var(--color-border, #d1d9e0)',
|
|
546
546
|
}}
|
|
547
547
|
onClick={stopInspecting}
|
|
548
548
|
>
|
|
@@ -556,7 +556,7 @@ export default function InspectorPanel() {
|
|
|
556
556
|
<div className="flex flex-col flex-1 min-h-0 p-3 pt-0 gap-3">
|
|
557
557
|
{/* Component name */}
|
|
558
558
|
<div>
|
|
559
|
-
<h3 className="text-base font-bold m-0 inspector-mono" style={{ color: 'var(--
|
|
559
|
+
<h3 className="text-base font-bold m-0 inspector-mono" style={{ color: 'var(--color-foreground-accent, #7655a4)' }}>
|
|
560
560
|
{componentInfo.name}
|
|
561
561
|
</h3>
|
|
562
562
|
</div>
|
|
@@ -640,7 +640,7 @@ export default function InspectorPanel() {
|
|
|
640
640
|
{/* Re-select button */}
|
|
641
641
|
<button
|
|
642
642
|
className="flex items-center justify-center gap-1.5 w-full px-3 py-1.5 text-xs font-medium rounded-md border-none cursor-pointer transition-colors shrink-0"
|
|
643
|
-
style={{ background: 'var(--
|
|
643
|
+
style={{ background: 'var(--color-foreground-accent, #7655a4)', color: '#fff' }}
|
|
644
644
|
onClick={startInspecting}
|
|
645
645
|
>
|
|
646
646
|
<OcticonSvg name="search" size={12} />
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
align-items: center;
|
|
9
9
|
gap: 0.5rem;
|
|
10
10
|
padding: 0.75rem 1rem;
|
|
11
|
-
background: var(--
|
|
12
|
-
color: var(--
|
|
13
|
-
border: 1px solid var(--
|
|
11
|
+
background: var(--color-background, #0d1117);
|
|
12
|
+
color: var(--color-foreground, #e6edf3);
|
|
13
|
+
border: 1px solid var(--color-border, #30363d);
|
|
14
14
|
border-radius: 0.75rem;
|
|
15
15
|
font-family: "Mona Sans", -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
|
|
16
16
|
font-size: 0.8125rem;
|
|
@@ -31,12 +31,12 @@
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
.pwa-banner-btn.install {
|
|
34
|
-
background: var(--
|
|
34
|
+
background: var(--color-background-success-emphasis, #238636);
|
|
35
35
|
color: #fff;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
.pwa-banner-btn.dismiss {
|
|
39
39
|
background: transparent;
|
|
40
|
-
color: var(--
|
|
40
|
+
color: var(--color-foreground-muted, #8d96a0);
|
|
41
41
|
padding: 0.375rem;
|
|
42
42
|
}
|
|
@@ -33,9 +33,9 @@ const bannerStyle = {
|
|
|
33
33
|
alignItems: 'center',
|
|
34
34
|
gap: '0.5rem',
|
|
35
35
|
padding: '0.75rem 1rem',
|
|
36
|
-
background: 'var(--
|
|
37
|
-
color: 'var(--
|
|
38
|
-
border: '1px solid var(--
|
|
36
|
+
background: 'var(--color-background, #0d1117)',
|
|
37
|
+
color: 'var(--color-foreground, #e6edf3)',
|
|
38
|
+
border: '1px solid var(--color-border, #30363d)',
|
|
39
39
|
borderRadius: '0.75rem',
|
|
40
40
|
fontFamily: '"Mona Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif',
|
|
41
41
|
fontSize: '0.8125rem',
|
|
@@ -53,14 +53,14 @@ const btnBase = {
|
|
|
53
53
|
|
|
54
54
|
const installBtnStyle = {
|
|
55
55
|
...btnBase,
|
|
56
|
-
background: 'var(--
|
|
56
|
+
background: 'var(--color-background-success-emphasis, #238636)',
|
|
57
57
|
color: '#fff',
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
const dismissBtnStyle = {
|
|
61
61
|
...btnBase,
|
|
62
62
|
background: 'transparent',
|
|
63
|
-
color: 'var(--
|
|
63
|
+
color: 'var(--color-foreground-muted, #8d96a0)',
|
|
64
64
|
padding: '0.375rem',
|
|
65
65
|
}
|
|
66
66
|
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
*
|
|
17
17
|
* TriggerButton uses the `trigger` button variant (no Tailwind dark:
|
|
18
18
|
* classes) so its scoped CSS has full control over background/color.
|
|
19
|
+
*
|
|
20
|
+
* Concrete color values are pinned (not aliased) so toolbar styling
|
|
21
|
+
* stays locked to its own scheme regardless of the global theme cascade
|
|
22
|
+
* defined in `core/styles/tailwind.css`.
|
|
19
23
|
*/
|
|
20
24
|
|
|
21
25
|
/* Default: light tokens */
|
|
@@ -27,43 +31,23 @@
|
|
|
27
31
|
[data-slot="dialog-overlay"] {
|
|
28
32
|
color-scheme: light;
|
|
29
33
|
|
|
30
|
-
|
|
31
|
-
--color-
|
|
32
|
-
--color-foreground:
|
|
33
|
-
--color-popover:
|
|
34
|
-
--color-popover-foreground:
|
|
35
|
-
--color-primary:
|
|
36
|
-
--color-primary-foreground:
|
|
37
|
-
--color-secondary:
|
|
38
|
-
--color-secondary-foreground:
|
|
39
|
-
--color-muted:
|
|
40
|
-
--color-muted-foreground:
|
|
41
|
-
--color-accent:
|
|
42
|
-
--color-accent-foreground:
|
|
43
|
-
--color-border:
|
|
44
|
-
--color-
|
|
45
|
-
|
|
46
|
-
--
|
|
47
|
-
--sb--color-foreground: hsl(222.2 84% 4.9%);
|
|
48
|
-
--sb--color-popover: hsl(0 0% 100%);
|
|
49
|
-
--sb--color-popover-foreground: hsl(222.2 84% 4.9%);
|
|
50
|
-
--sb--color-primary: hsl(222.2 47.4% 11.2%);
|
|
51
|
-
--sb--color-primary-foreground: hsl(210 40% 98%);
|
|
52
|
-
--sb--color-secondary: hsl(210 40% 96.1%);
|
|
53
|
-
--sb--color-secondary-foreground: hsl(222.2 47.4% 11.2%);
|
|
54
|
-
--sb--color-muted: hsl(210 40% 96.1%);
|
|
55
|
-
--sb--color-muted-foreground: hsl(215.4 16.3% 46.9%);
|
|
56
|
-
--sb--color-accent: hsl(210 40% 96.1%);
|
|
57
|
-
--sb--color-accent-foreground: hsl(222.2 47.4% 11.2%);
|
|
58
|
-
--sb--color-border: hsl(214.3 31.8% 91.4%);
|
|
59
|
-
--sb--color-input: hsl(214.3 31.8% 91.4%);
|
|
60
|
-
|
|
61
|
-
/* Primer token aliases used in side panel + inspector chrome */
|
|
62
|
-
--bgColor-default: #ffffff;
|
|
63
|
-
--fgColor-default: #1f2328;
|
|
64
|
-
--fgColor-muted: #656d76;
|
|
65
|
-
--borderColor-default: #d1d9e0;
|
|
66
|
-
--bgColor-neutral-muted: rgba(175, 184, 193, 0.2);
|
|
34
|
+
--color-background: #ffffff;
|
|
35
|
+
--color-foreground: #1f2328;
|
|
36
|
+
--color-foreground-muted: #59636e;
|
|
37
|
+
--color-popover: #ffffff;
|
|
38
|
+
--color-popover-foreground: #1f2328;
|
|
39
|
+
--color-primary: #1f2328;
|
|
40
|
+
--color-primary-foreground: #ffffff;
|
|
41
|
+
--color-secondary: #f6f8fa;
|
|
42
|
+
--color-secondary-foreground: #1f2328;
|
|
43
|
+
--color-muted: #f6f8fa;
|
|
44
|
+
--color-muted-foreground: #59636e;
|
|
45
|
+
--color-accent: #ddf4ff;
|
|
46
|
+
--color-accent-foreground: #0969da;
|
|
47
|
+
--color-border: #d1d9e0;
|
|
48
|
+
--color-border-muted: #d1d9e0b3;
|
|
49
|
+
--color-background-neutral-muted: rgba(175, 184, 193, 0.2);
|
|
50
|
+
--color-input: #d1d9e0;
|
|
67
51
|
|
|
68
52
|
/* Slate palette tokens — used by trigger buttons and canvas toolbar */
|
|
69
53
|
--color-slate-100: oklch(96.8% .007 247.896);
|
|
@@ -72,10 +56,7 @@
|
|
|
72
56
|
--color-slate-600: oklch(44.6% .043 257.281);
|
|
73
57
|
}
|
|
74
58
|
|
|
75
|
-
/* Dark tokens — applied when toolbar syncs with a dark theme.
|
|
76
|
-
* Uses Primer-aligned colors (matching base.css --sb-* dark tokens)
|
|
77
|
-
* instead of generic shadcn dark values.
|
|
78
|
-
*/
|
|
59
|
+
/* Dark tokens — applied when toolbar syncs with a dark theme. */
|
|
79
60
|
:root[data-sb-toolbar-theme^="dark"] [data-core-ui-bar],
|
|
80
61
|
:root[data-sb-toolbar-theme^="dark"] [data-sidepanel],
|
|
81
62
|
:root[data-sb-toolbar-theme^="dark"] [data-slot="dropdown-menu-content"],
|
|
@@ -86,6 +67,7 @@
|
|
|
86
67
|
|
|
87
68
|
--color-background: #161b22;
|
|
88
69
|
--color-foreground: #e6edf3;
|
|
70
|
+
--color-foreground-muted: #8b949e;
|
|
89
71
|
--color-popover: #161b22;
|
|
90
72
|
--color-popover-foreground: #e6edf3;
|
|
91
73
|
--color-primary: #e6edf3;
|
|
@@ -94,36 +76,16 @@
|
|
|
94
76
|
--color-secondary-foreground: #e6edf3;
|
|
95
77
|
--color-muted: #21262d;
|
|
96
78
|
--color-muted-foreground: #8b949e;
|
|
97
|
-
--color-accent: #
|
|
98
|
-
--color-accent-foreground: #
|
|
79
|
+
--color-accent: #388bfd1a;
|
|
80
|
+
--color-accent-foreground: #4493f8;
|
|
99
81
|
--color-border: #30363d;
|
|
82
|
+
--color-border-muted: #30363db3;
|
|
83
|
+
--color-background-neutral-muted: rgba(110, 118, 129, 0.2);
|
|
100
84
|
--color-input: #30363d;
|
|
101
85
|
|
|
102
|
-
--sb--color-background: #161b22;
|
|
103
|
-
--sb--color-foreground: #e6edf3;
|
|
104
|
-
--sb--color-popover: #161b22;
|
|
105
|
-
--sb--color-popover-foreground: #e6edf3;
|
|
106
|
-
--sb--color-primary: #e6edf3;
|
|
107
|
-
--sb--color-primary-foreground: #161b22;
|
|
108
|
-
--sb--color-secondary: #21262d;
|
|
109
|
-
--sb--color-secondary-foreground: #e6edf3;
|
|
110
|
-
--sb--color-muted: #21262d;
|
|
111
|
-
--sb--color-muted-foreground: #8b949e;
|
|
112
|
-
--sb--color-accent: #21262d;
|
|
113
|
-
--sb--color-accent-foreground: #e6edf3;
|
|
114
|
-
--sb--color-border: #30363d;
|
|
115
|
-
--sb--color-input: #30363d;
|
|
116
|
-
|
|
117
|
-
/* Primer token aliases used in side panel + inspector chrome */
|
|
118
|
-
--bgColor-default: #161b22;
|
|
119
|
-
--fgColor-default: #e6edf3;
|
|
120
|
-
--fgColor-muted: #8b949e;
|
|
121
|
-
--borderColor-default: #30363d;
|
|
122
|
-
--bgColor-neutral-muted: rgba(110, 118, 129, 0.2);
|
|
123
|
-
|
|
124
86
|
/* Slate palette tokens — dark equivalents */
|
|
125
87
|
--color-slate-100: oklch(20.8% .042 265.755);
|
|
126
88
|
--color-slate-300: oklch(29.3% .042 264.531);
|
|
127
89
|
--color-slate-400: oklch(44.6% .043 257.281);
|
|
128
|
-
--color-slate-600: oklch(
|
|
90
|
+
--color-slate-600: oklch(44.6% .043 257.281);
|
|
129
91
|
}
|
|
@@ -99,11 +99,11 @@ html.sb-sidepanel-open.sb-sidepanel-bottom .sb-mode-switch {
|
|
|
99
99
|
z-index: 9998;
|
|
100
100
|
display: flex;
|
|
101
101
|
flex-direction: column;
|
|
102
|
-
background-color: var(--
|
|
103
|
-
border-left: 1px solid var(--
|
|
102
|
+
background-color: var(--color-background, #ffffff);
|
|
103
|
+
border-left: 1px solid var(--color-border, #d0d7de);
|
|
104
104
|
box-shadow: -4px 0 24px rgba(0, 0, 0, 0.15);
|
|
105
105
|
font-family: "Mona Sans", -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
|
106
|
-
color: var(--
|
|
106
|
+
color: var(--color-foreground, #1f2328);
|
|
107
107
|
animation: sb-sidepanel-slide-in 0.25s ease;
|
|
108
108
|
}
|
|
109
109
|
|
|
@@ -115,7 +115,7 @@ html.sb-sidepanel-open.sb-sidepanel-bottom .sb-mode-switch {
|
|
|
115
115
|
width: 100% !important;
|
|
116
116
|
height: var(--sb--sidepanel-height, 300px);
|
|
117
117
|
border-left: none;
|
|
118
|
-
border-top: 1px solid var(--
|
|
118
|
+
border-top: 1px solid var(--color-border, #d0d7de);
|
|
119
119
|
box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.15);
|
|
120
120
|
animation: sb-sidepanel-slide-up 0.25s ease;
|
|
121
121
|
}
|
|
@@ -199,7 +199,7 @@ html.sb-sidepanel-open.sb-sidepanel-bottom .sb-mode-switch {
|
|
|
199
199
|
font-weight: 600;
|
|
200
200
|
text-transform: uppercase;
|
|
201
201
|
letter-spacing: 0.05em;
|
|
202
|
-
color: var(--
|
|
202
|
+
color: var(--color-foreground-muted, #656d76);
|
|
203
203
|
padding-left: 4px;
|
|
204
204
|
}
|
|
205
205
|
|
|
@@ -213,7 +213,7 @@ html.sb-sidepanel-open.sb-sidepanel-bottom .sb-mode-switch {
|
|
|
213
213
|
appearance: none;
|
|
214
214
|
border: none;
|
|
215
215
|
background: transparent;
|
|
216
|
-
color: var(--
|
|
216
|
+
color: var(--color-foreground-muted, #656d76);
|
|
217
217
|
cursor: pointer;
|
|
218
218
|
padding: 6px;
|
|
219
219
|
border-radius: 6px;
|
|
@@ -224,8 +224,8 @@ html.sb-sidepanel-open.sb-sidepanel-bottom .sb-mode-switch {
|
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
.sb-sidepanel-action-btn:hover {
|
|
227
|
-
background: var(--
|
|
228
|
-
color: var(--
|
|
227
|
+
background: var(--color-background-neutral-muted, rgba(110, 118, 129, 0.1));
|
|
228
|
+
color: var(--color-foreground, #1f2328);
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
/* Body */
|
|
@@ -246,8 +246,8 @@ html.sb-sidepanel-open.sb-sidepanel-bottom .sb-mode-switch {
|
|
|
246
246
|
.sb-sidepanel-spinner {
|
|
247
247
|
width: 20px;
|
|
248
248
|
height: 20px;
|
|
249
|
-
border: 2px solid var(--
|
|
250
|
-
border-top-color: var(--
|
|
249
|
+
border: 2px solid var(--color-border, #d0d7de);
|
|
250
|
+
border-top-color: var(--color-foreground-muted, #656d76);
|
|
251
251
|
border-radius: 50%;
|
|
252
252
|
animation: sb-spin 0.6s linear infinite;
|
|
253
253
|
}
|
|
@@ -263,7 +263,7 @@ html.storyboard-mode-inspect .sb-sidepanel:not(.sb-sidepanel--bottom) {
|
|
|
263
263
|
top: 12px;
|
|
264
264
|
right: 12px;
|
|
265
265
|
bottom: 12px;
|
|
266
|
-
border-radius: var(--
|
|
266
|
+
border-radius: var(--radius);
|
|
267
267
|
border-left: none;
|
|
268
268
|
}
|
|
269
269
|
|
|
@@ -275,7 +275,7 @@ html.storyboard-mode-inspect .sb-sidepanel--bottom {
|
|
|
275
275
|
right: 12px;
|
|
276
276
|
bottom: 12px;
|
|
277
277
|
width: auto !important;
|
|
278
|
-
border-radius: var(--
|
|
278
|
+
border-radius: var(--radius);
|
|
279
279
|
border-top: none;
|
|
280
280
|
}
|
|
281
281
|
|
|
@@ -22,8 +22,15 @@ export default function CreateCanvasForm({ onClose }) {
|
|
|
22
22
|
const [titleTouched, setTitleTouched] = useState(false)
|
|
23
23
|
const [description, setDescription] = useState('')
|
|
24
24
|
const [folder, setFolder] = useState('')
|
|
25
|
+
// Special folder select value indicating the user wants to create a new
|
|
26
|
+
// folder rather than pick an existing one. Triggers the new-folder input.
|
|
27
|
+
const NEW_FOLDER_VALUE = '__new__'
|
|
28
|
+
const [folderSelectValue, setFolderSelectValue] = useState('')
|
|
29
|
+
const [newFolderName, setNewFolderName] = useState('')
|
|
30
|
+
const [newFolderKind, setNewFolderKind] = useState('workspace')
|
|
25
31
|
const [grid, setGrid] = useState(true)
|
|
26
|
-
|
|
32
|
+
// folderEntries: [{ name, kind: 'workspace' | 'pages' }, ...]
|
|
33
|
+
const [folderEntries, setFolderEntries] = useState([])
|
|
27
34
|
const [loading, setLoading] = useState(true)
|
|
28
35
|
const [submitting, setSubmitting] = useState(false)
|
|
29
36
|
const [error, setError] = useState(null)
|
|
@@ -59,8 +66,13 @@ export default function CreateCanvasForm({ onClose }) {
|
|
|
59
66
|
)
|
|
60
67
|
|
|
61
68
|
const routePreview = useMemo(
|
|
62
|
-
() =>
|
|
63
|
-
|
|
69
|
+
() => {
|
|
70
|
+
if (!kebabName) return ''
|
|
71
|
+
// Both `.folder/` and plain dir produce the same URL — `.folder` is
|
|
72
|
+
// stripped by the data plugin's route resolver.
|
|
73
|
+
return folder ? `/canvas/${folder}/${kebabName}` : `/canvas/${kebabName}`
|
|
74
|
+
},
|
|
75
|
+
[kebabName, folder],
|
|
64
76
|
)
|
|
65
77
|
|
|
66
78
|
const nameError = useMemo(() => {
|
|
@@ -71,10 +83,14 @@ export default function CreateCanvasForm({ onClose }) {
|
|
|
71
83
|
return null
|
|
72
84
|
}, [name, kebabName])
|
|
73
85
|
|
|
74
|
-
const canSubmit = useMemo(
|
|
75
|
-
(
|
|
76
|
-
|
|
77
|
-
|
|
86
|
+
const canSubmit = useMemo(() => {
|
|
87
|
+
if (!kebabName || nameError || submitting) return false
|
|
88
|
+
// When the user picked "+ Create new folder" they must also provide a
|
|
89
|
+
// valid (kebab-case) folder name. Other selections (existing folder or
|
|
90
|
+
// "No folder") always have a usable `folder` value already.
|
|
91
|
+
if (folderSelectValue === NEW_FOLDER_VALUE && !folder) return false
|
|
92
|
+
return true
|
|
93
|
+
}, [kebabName, nameError, submitting, folderSelectValue, folder])
|
|
78
94
|
|
|
79
95
|
useEffect(() => {
|
|
80
96
|
// Restore success state after Vite's full-reload on file creation
|
|
@@ -93,7 +109,13 @@ export default function CreateCanvasForm({ onClose }) {
|
|
|
93
109
|
const res = await fetch(getApiUrl() + '/folders')
|
|
94
110
|
if (res.ok) {
|
|
95
111
|
const data = await res.json()
|
|
96
|
-
|
|
112
|
+
// Prefer the new tagged `entries` shape; fall back to legacy
|
|
113
|
+
// `folders` string array (treat as 'pages' for back-compat).
|
|
114
|
+
if (Array.isArray(data.entries)) {
|
|
115
|
+
setFolderEntries(data.entries.filter((e) => e && e.name))
|
|
116
|
+
} else if (Array.isArray(data.folders)) {
|
|
117
|
+
setFolderEntries(data.folders.map((name) => ({ name, kind: 'pages' })))
|
|
118
|
+
}
|
|
97
119
|
}
|
|
98
120
|
} catch { /* empty */ } finally {
|
|
99
121
|
setLoading(false)
|
|
@@ -102,6 +124,38 @@ export default function CreateCanvasForm({ onClose }) {
|
|
|
102
124
|
fetchFolders()
|
|
103
125
|
}, [])
|
|
104
126
|
|
|
127
|
+
function handleFolderSelectChange(value) {
|
|
128
|
+
setFolderSelectValue(value)
|
|
129
|
+
if (value === NEW_FOLDER_VALUE) {
|
|
130
|
+
// Defer folder commit until the user types a name.
|
|
131
|
+
setFolder('')
|
|
132
|
+
} else {
|
|
133
|
+
setFolder(value)
|
|
134
|
+
setNewFolderName('')
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Whenever the user types into the new-folder input, mirror it into `folder`
|
|
139
|
+
// so the submit handler picks it up. Kebab-case to match canvas folder
|
|
140
|
+
// naming conventions (data plugin / route resolution assumes lowercase).
|
|
141
|
+
const kebabNewFolder = useMemo(
|
|
142
|
+
() =>
|
|
143
|
+
newFolderName
|
|
144
|
+
.replace(/[^a-zA-Z0-9\s_-]/g, '')
|
|
145
|
+
.trim()
|
|
146
|
+
.replace(/[\s_]+/g, '-')
|
|
147
|
+
.toLowerCase()
|
|
148
|
+
.replace(/-+/g, '-')
|
|
149
|
+
.replace(/^-|-$/g, ''),
|
|
150
|
+
[newFolderName],
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
if (folderSelectValue === NEW_FOLDER_VALUE) {
|
|
155
|
+
setFolder(kebabNewFolder)
|
|
156
|
+
}
|
|
157
|
+
}, [folderSelectValue, kebabNewFolder])
|
|
158
|
+
|
|
105
159
|
function handleTitleInput(e) {
|
|
106
160
|
setTitle(e.target.value)
|
|
107
161
|
setTitleTouched(true)
|
|
@@ -126,6 +180,10 @@ export default function CreateCanvasForm({ onClose }) {
|
|
|
126
180
|
title: displayTitle,
|
|
127
181
|
description: description || undefined,
|
|
128
182
|
folder: folder || undefined,
|
|
183
|
+
// Only meaningful when the folder doesn't exist yet — the server
|
|
184
|
+
// resolves existing folders by inspecting the filesystem and
|
|
185
|
+
// ignores `folderKind` in that case.
|
|
186
|
+
folderKind: folder && folderSelectValue === NEW_FOLDER_VALUE ? newFolderKind : undefined,
|
|
129
187
|
grid,
|
|
130
188
|
}),
|
|
131
189
|
})
|
|
@@ -209,19 +267,50 @@ export default function CreateCanvasForm({ onClose }) {
|
|
|
209
267
|
<select
|
|
210
268
|
id="canvas-folder"
|
|
211
269
|
className={selectClass}
|
|
212
|
-
value={
|
|
213
|
-
onChange={(e) =>
|
|
270
|
+
value={folderSelectValue}
|
|
271
|
+
onChange={(e) => handleFolderSelectChange(e.target.value)}
|
|
214
272
|
disabled={loading}
|
|
215
273
|
>
|
|
216
274
|
<option value="">No folder</option>
|
|
217
|
-
{
|
|
218
|
-
<option key={f} value={f}>
|
|
219
|
-
{f}
|
|
275
|
+
{folderEntries.map((f) => (
|
|
276
|
+
<option key={`${f.kind}:${f.name}`} value={f.name}>
|
|
277
|
+
{f.name} {f.kind === 'workspace' ? '— workspace folder' : '— canvas pages'}
|
|
220
278
|
</option>
|
|
221
279
|
))}
|
|
280
|
+
<option value={NEW_FOLDER_VALUE}>+ Create new folder…</option>
|
|
222
281
|
</select>
|
|
223
282
|
</div>
|
|
224
283
|
|
|
284
|
+
{folderSelectValue === NEW_FOLDER_VALUE && (
|
|
285
|
+
<div className="space-y-2 pl-4 border-l-2 border-muted">
|
|
286
|
+
<div className="space-y-1">
|
|
287
|
+
<Label htmlFor="canvas-new-folder-name">New folder name</Label>
|
|
288
|
+
<Input
|
|
289
|
+
id="canvas-new-folder-name"
|
|
290
|
+
value={newFolderName}
|
|
291
|
+
onChange={(e) => setNewFolderName(e.target.value)}
|
|
292
|
+
placeholder="kebab-case-folder"
|
|
293
|
+
/>
|
|
294
|
+
</div>
|
|
295
|
+
<div className="space-y-1">
|
|
296
|
+
<Label htmlFor="canvas-new-folder-kind">Folder type</Label>
|
|
297
|
+
<select
|
|
298
|
+
id="canvas-new-folder-kind"
|
|
299
|
+
className={selectClass}
|
|
300
|
+
value={newFolderKind}
|
|
301
|
+
onChange={(e) => setNewFolderKind(e.target.value)}
|
|
302
|
+
>
|
|
303
|
+
<option value="workspace">
|
|
304
|
+
Workspace folder — sibling canvases grouped in the workspace
|
|
305
|
+
</option>
|
|
306
|
+
<option value="pages">
|
|
307
|
+
Canvas pages — multi-page canvas, canvases become pages
|
|
308
|
+
</option>
|
|
309
|
+
</select>
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
)}
|
|
313
|
+
|
|
225
314
|
<div className="flex items-center gap-2">
|
|
226
315
|
<Checkbox
|
|
227
316
|
id="canvas-grid"
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
.formShell {
|
|
6
6
|
max-width: 480px;
|
|
7
7
|
padding: 28px;
|
|
8
|
-
background: var(--
|
|
9
|
-
border: 1px solid var(--
|
|
8
|
+
background: var(--color-background, #ffffff);
|
|
9
|
+
border: 1px solid var(--color-border, #d0d7de);
|
|
10
10
|
border-radius: 14px;
|
|
11
11
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
12
12
|
}
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
font-size: 20px;
|
|
39
39
|
font-weight: 700;
|
|
40
40
|
line-height: 1.2;
|
|
41
|
-
color: var(--
|
|
41
|
+
color: var(--color-foreground, #1a1a1a);
|
|
42
42
|
letter-spacing: -0.01em;
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
.description {
|
|
50
50
|
margin: 0;
|
|
51
51
|
font-size: 14px;
|
|
52
|
-
color: var(--
|
|
52
|
+
color: var(--color-foreground-muted, #656d76);
|
|
53
53
|
line-height: 1.45;
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
/* ─── Code field override (textarea variant) ─── */
|
|
71
71
|
|
|
72
72
|
.codeField {
|
|
73
|
-
font-family: var(--
|
|
73
|
+
font-family: var(--font-mono, ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace);
|
|
74
74
|
font-size: 13px;
|
|
75
75
|
line-height: 1.5;
|
|
76
76
|
}
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
gap: 8px;
|
|
85
85
|
margin-top: 4px;
|
|
86
86
|
padding-top: 16px;
|
|
87
|
-
border-top: 1px solid var(--
|
|
87
|
+
border-top: 1px solid var(--color-border-muted, #eaeef2);
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
/* ─── Advanced toggle ─── */
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
.advancedToggle {
|
|
93
93
|
align-self: flex-start;
|
|
94
94
|
margin-top: 2px;
|
|
95
|
-
color: var(--
|
|
95
|
+
color: var(--color-foreground-muted, #656d76);
|
|
96
96
|
padding-left: 10px;
|
|
97
97
|
padding-right: 10px;
|
|
98
98
|
}
|