@dfosco/storyboard 0.7.1 → 0.8.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/dist/storyboard-ui.css +1 -1
- package/dist/storyboard-ui.js +13907 -10816
- package/dist/storyboard-ui.js.map +1 -1
- package/dist/tailwind.css +1 -1
- package/package.json +138 -128
- package/scaffold/AGENTS.md +9 -7
- package/scaffold/README.md +18 -0
- package/scaffold/deploy.yml +21 -0
- package/scaffold/gitignore +8 -14
- package/scaffold/preview.yml +40 -0
- package/scaffold/{manifest.json → scaffold.config.json} +73 -3
- package/scaffold/scripts/aggregate-artifacts.mjs +257 -0
- package/scaffold/skills/canvas/SKILL.md +1 -1
- package/scaffold/skills/migrate-0.5.0/SKILL.md +1 -1
- package/scaffold/skills/ship/SKILL.md +2 -2
- package/scaffold/skills/worktree/SKILL.md +1 -1
- package/scaffold/src/_prototype.jsx +17 -0
- package/scaffold/src/_story.jsx +13 -0
- package/scaffold/src/library/README.md +34 -0
- package/scaffold/src/library/_app.jsx +53 -0
- package/scaffold/src/library/_app.module.css +8 -0
- package/scaffold/src/library/create.jsx +10 -0
- package/scaffold/src/library/index.jsx +14 -0
- package/scaffold/src/library/mount.jsx +52 -0
- package/scaffold/src/library/prototypes-entry.jsx +57 -0
- package/scaffold/src/library/routes.jsx +205 -0
- package/scaffold/src/library/viewfinder.jsx +13 -0
- package/scaffold/src/library/workspace.jsx +17 -0
- package/scripts/aggregate-artifacts.mjs +257 -0
- package/src/core/artifact/operations.js +19 -13
- package/src/core/autosync/server.test.js +1 -1
- package/src/core/canvas/hot-pool.js +10 -11
- package/src/core/canvas/materializer.js +39 -13
- package/src/core/canvas/server.js +324 -69
- package/src/core/canvas/server.test.js +365 -2
- package/src/core/canvas/terminal-server.js +144 -57
- package/src/core/canvas/undoRedo.js +174 -0
- package/src/core/canvas/undoRedo.test.js +250 -0
- package/src/core/cli/buildManifest.js +34 -0
- package/src/core/cli/canvasRedo.js +44 -0
- package/src/core/cli/canvasUndo.js +40 -0
- package/src/core/cli/create.js +116 -18
- package/src/core/cli/dev.js +23 -35
- package/src/core/cli/index.js +21 -0
- package/src/core/cli/schemas.js +2 -3
- package/src/core/cli/serverUrl.js +24 -0
- package/src/core/cli/setup.js +14 -25
- package/src/core/cli/terminal-welcome.js +12 -6
- package/src/core/cli/updateVersion.js +42 -2
- package/src/core/cli/workleaf.js +140 -0
- package/src/core/comments/api.js +1 -1
- package/src/core/comments/queries.js +3 -0
- package/src/core/comments/ui/CommentWindow.jsx +3 -2
- package/src/core/data/artifactIndex.js +235 -0
- package/src/core/data/artifactIndex.test.js +203 -0
- package/src/core/data/artifactManifest.js +290 -0
- package/src/core/data/artifactManifest.test.js +207 -0
- package/src/core/data/viewfinder.js +47 -7
- package/src/core/index.js +6 -1
- package/src/core/lib/components/ui/avatar/avatar-fallback.jsx +2 -2
- package/src/core/lib/components/ui/avatar/avatar-image.jsx +2 -2
- package/src/core/lib/components/ui/avatar/avatar.jsx +2 -2
- package/src/core/lib/components/ui/checkbox/checkbox.jsx +12 -9
- package/src/core/lib/components/ui/dialog/dialog-content.jsx +45 -31
- package/src/core/lib/components/ui/dialog/dialog-trigger.jsx +9 -5
- package/src/core/lib/components/ui/dialog/dialog.jsx +12 -3
- package/src/core/lib/components/ui/dialog/dialog.module.css +118 -0
- package/src/core/lib/components/ui/dialog/index.js +4 -34
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.jsx +3 -3
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.jsx +19 -9
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-content.jsx +8 -8
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.jsx +2 -2
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-group.jsx +2 -2
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-item.jsx +15 -4
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-portal.jsx +2 -2
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.jsx +2 -2
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.jsx +14 -7
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-separator.jsx +2 -2
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.jsx +8 -4
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.jsx +4 -4
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub.jsx +2 -2
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-trigger.jsx +8 -2
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu.jsx +2 -2
- package/src/core/lib/components/ui/label/label.jsx +1 -2
- package/src/core/lib/components/ui/panel/panel-close.jsx +7 -4
- package/src/core/lib/components/ui/panel/panel-content.jsx +7 -9
- package/src/core/lib/components/ui/panel/panel-title.jsx +3 -3
- package/src/core/lib/components/ui/panel/panel.jsx +11 -2
- package/src/core/lib/components/ui/separator/separator.jsx +6 -4
- package/src/core/lib/components/ui/tooltip/tooltip-content.jsx +7 -6
- package/src/core/lib/components/ui/tooltip/tooltip-trigger.jsx +13 -6
- package/src/core/lib/components/ui/tooltip/tooltip.jsx +5 -5
- package/src/core/mountStoryboardCore.js +234 -74
- package/src/core/mountStoryboardCore.test.js +174 -0
- package/src/core/rename-watcher/watcher.js +329 -36
- package/src/core/rename-watcher/watcher.test.js +253 -0
- package/src/core/scaffold/fragments.js +287 -0
- package/src/core/scaffold/fragments.test.js +401 -0
- package/src/core/scaffold/index.js +291 -0
- package/src/core/scaffold/migrateLegacyGitignore.js +157 -0
- package/src/core/scaffold/scaffold-integration.test.js +247 -0
- package/src/core/scaffold.js +4 -97
- package/src/core/stores/configSchema.js +41 -2
- package/src/core/stores/recentArtifacts.js +49 -2
- package/src/core/stores/recentArtifacts.test.js +59 -6
- package/src/core/stores/themeBootstrap.js +264 -0
- package/src/core/stores/themeBootstrap.test.js +211 -0
- package/src/core/stores/themeStore.test.js +154 -0
- package/src/core/stores/themeStore.ts +235 -220
- package/src/core/styles/generateThemesCss.js +63 -0
- package/src/core/styles/tailwind.css +12 -4
- package/src/core/styles/workspace-chrome.css +30 -0
- package/src/core/tools/handlers/paletteTheme.js +11 -8
- package/src/core/ui/ActionMenuButton.jsx +2 -1
- package/src/core/ui/CanvasCreateMenu.jsx +1 -19
- package/src/core/ui/CoreUIBar.jsx +10 -7
- package/src/core/ui/ThemeMenuButton.jsx +45 -30
- package/src/core/vite/server-plugin.js +13 -0
- package/src/core/workleaf/cleanup.js +102 -0
- package/src/core/workleaf/create.js +199 -0
- package/src/core/workleaf/delete.js +101 -0
- package/src/core/workleaf/list.js +57 -0
- package/src/core/workleaf/merge.js +238 -0
- package/src/core/workleaf/paths.js +146 -0
- package/src/core/workleaf/paths.test.js +230 -0
- package/src/core/workshop/features/createCanvas/CreateCanvasForm.jsx +28 -21
- package/src/core/workshop/features/createFlow/server.js +5 -1
- package/src/core/workshop/features/createPrototype/server.js +5 -1
- package/src/core/workshop/features/templateIndex.js +28 -1
- package/src/core/worktree/port.js +48 -5
- package/src/core/worktree/port.test.js +42 -0
- package/src/internals/ArtifactForm/ArtifactForm.jsx +173 -112
- package/src/internals/ArtifactForm/ArtifactForm.module.css +48 -66
- package/src/internals/ArtifactForm/artifactSchemas.js +40 -5
- package/src/internals/BaseUiForm/BaseUiForm.jsx +293 -0
- package/src/internals/BaseUiForm/BaseUiForm.module.css +542 -0
- package/src/internals/BranchesDropdown/BranchesDropdown.jsx +69 -0
- package/src/internals/BranchesDropdown/BranchesDropdown.module.css +84 -0
- package/src/internals/CommandPalette/CommandPalette.jsx +25 -19
- package/src/internals/CommandPalette/CreateArtifactForm.jsx +150 -0
- package/src/internals/CommandPalette/CreateDialog.jsx +20 -110
- package/src/internals/CommandPalette/WidgetArtifactDialog.jsx +1 -1
- package/src/internals/CommandPalette/createHelpers.js +30 -0
- package/src/internals/CreatePage/CreatePage.jsx +57 -0
- package/src/internals/CreatePage/CreatePage.module.css +34 -0
- package/src/internals/Dialog.jsx +51 -0
- package/src/internals/SimpleWorkspace/SimpleWorkspace.jsx +533 -0
- package/src/internals/SimpleWorkspace/SimpleWorkspace.module.css +323 -0
- package/src/internals/Tooltip.jsx +39 -0
- package/src/internals/Viewfinder.jsx +196 -48
- package/src/internals/Viewfinder.module.css +72 -1
- package/src/internals/canvas/CanvasPage.jsx +267 -124
- package/src/internals/canvas/CanvasPage.multiselect.test.jsx +39 -26
- package/src/internals/canvas/PageSelector.jsx +15 -1
- package/src/internals/canvas/PageSelector.module.css +14 -0
- package/src/internals/canvas/canvasApi.js +40 -5
- package/src/internals/canvas/isolation/createIsolationMiddleware.js +68 -0
- package/src/internals/canvas/isolation/mountIsolationApp.jsx +88 -0
- package/src/internals/canvas/isolation/prototypeRoutes.jsx +131 -0
- package/src/internals/canvas/isolation/prototypesEntry.jsx +28 -0
- package/src/internals/canvas/isolation/storiesEntry.jsx +20 -0
- package/src/internals/canvas/isolation/storyRoutes.jsx +55 -0
- package/src/internals/canvas/useUndoRedo.js +75 -60
- package/src/internals/canvas/useUndoRedo.test.js +46 -178
- package/src/internals/canvas/widgets/ExpandedPaneTopBar.jsx +1 -1
- package/src/internals/canvas/widgets/FrozenTerminalOverlay.jsx +30 -21
- package/src/internals/canvas/widgets/ImageWidget.jsx +7 -2
- package/src/internals/canvas/widgets/LinkPreview.module.css +2 -2
- package/src/internals/canvas/widgets/PrototypeEmbed.jsx +69 -10
- package/src/internals/canvas/widgets/PrototypeEmbed.test.jsx +114 -0
- package/src/internals/canvas/widgets/StorySetWidget.jsx +8 -4
- package/src/internals/canvas/widgets/StoryWidget.jsx +21 -38
- package/src/internals/canvas/widgets/TerminalReadWidget.jsx +13 -15
- package/src/internals/canvas/widgets/WidgetChrome.jsx +1 -1
- package/src/internals/canvas/widgets/WidgetChrome.module.css +15 -0
- package/src/internals/canvas/widgets/embedTheme.js +2 -2
- package/src/internals/canvas/widgets/expandUtils.js +19 -19
- package/src/internals/canvas/widgets/normalizeLegacyEmbedSrc.js +74 -0
- package/src/internals/context.jsx +22 -1
- package/src/internals/context.test.jsx +47 -0
- package/src/internals/hooks/useArtifactIndex.js +31 -0
- package/src/internals/hooks/useArtifactIndex.test.js +45 -0
- package/src/internals/hooks/usePrototypeReloadGuard.js +14 -8
- package/src/internals/index.js +6 -0
- package/src/internals/loadConsumerWrapper.js +60 -0
- package/src/internals/story/ComponentSetPage.jsx +75 -34
- package/src/internals/story/StoryPage.jsx +16 -25
- package/src/internals/vite/data-plugin.js +313 -148
- package/src/internals/vite/data-plugin.test.js +11 -11
- package/{scaffold/storyboard.config.json → storyboard.config.json} +25 -0
- package/terminal.config.json +1 -1
- package/src/core/lib/components/ui/card/card-action.jsx +0 -11
- package/src/core/lib/components/ui/card/card-content.jsx +0 -11
- package/src/core/lib/components/ui/card/card-description.jsx +0 -11
- package/src/core/lib/components/ui/card/card-footer.jsx +0 -11
- package/src/core/lib/components/ui/card/card-header.jsx +0 -19
- package/src/core/lib/components/ui/card/card-title.jsx +0 -11
- package/src/core/lib/components/ui/card/card.jsx +0 -17
- package/src/core/lib/components/ui/card/index.js +0 -25
- package/src/core/lib/components/ui/collapsible/collapsible-content.jsx +0 -7
- package/src/core/lib/components/ui/collapsible/collapsible-trigger.jsx +0 -7
- package/src/core/lib/components/ui/collapsible/collapsible.jsx +0 -7
- package/src/core/lib/components/ui/collapsible/index.js +0 -13
- package/src/core/lib/components/ui/dialog/dialog-close.jsx +0 -7
- package/src/core/lib/components/ui/dialog/dialog-description.jsx +0 -15
- package/src/core/lib/components/ui/dialog/dialog-footer.jsx +0 -23
- package/src/core/lib/components/ui/dialog/dialog-header.jsx +0 -11
- package/src/core/lib/components/ui/dialog/dialog-overlay.jsx +0 -15
- package/src/core/lib/components/ui/dialog/dialog-portal.jsx +0 -4
- package/src/core/lib/components/ui/dialog/dialog-title.jsx +0 -15
- package/src/core/lib/components/ui/popover/index.js +0 -28
- package/src/core/lib/components/ui/popover/popover-close.jsx +0 -7
- package/src/core/lib/components/ui/popover/popover-content.jsx +0 -22
- package/src/core/lib/components/ui/popover/popover-description.jsx +0 -11
- package/src/core/lib/components/ui/popover/popover-header.jsx +0 -11
- package/src/core/lib/components/ui/popover/popover-portal.jsx +0 -4
- package/src/core/lib/components/ui/popover/popover-title.jsx +0 -11
- package/src/core/lib/components/ui/popover/popover-trigger.jsx +0 -8
- package/src/core/lib/components/ui/popover/popover.jsx +0 -4
- package/src/core/lib/components/ui/select/index.js +0 -37
- package/src/core/lib/components/ui/select/select-content.jsx +0 -30
- package/src/core/lib/components/ui/select/select-group-heading.jsx +0 -17
- package/src/core/lib/components/ui/select/select-group.jsx +0 -15
- package/src/core/lib/components/ui/select/select-item.jsx +0 -26
- package/src/core/lib/components/ui/select/select-label.jsx +0 -11
- package/src/core/lib/components/ui/select/select-portal.jsx +0 -4
- package/src/core/lib/components/ui/select/select-scroll-down-button.jsx +0 -18
- package/src/core/lib/components/ui/select/select-scroll-up-button.jsx +0 -18
- package/src/core/lib/components/ui/select/select-separator.jsx +0 -15
- package/src/core/lib/components/ui/select/select-trigger.jsx +0 -25
- package/src/core/lib/components/ui/select/select.jsx +0 -4
- package/src/core/lib/components/ui/sheet/index.js +0 -34
- package/src/core/lib/components/ui/sheet/sheet-close.jsx +0 -7
- package/src/core/lib/components/ui/sheet/sheet-content.jsx +0 -35
- package/src/core/lib/components/ui/sheet/sheet-description.jsx +0 -15
- package/src/core/lib/components/ui/sheet/sheet-footer.jsx +0 -11
- package/src/core/lib/components/ui/sheet/sheet-header.jsx +0 -11
- package/src/core/lib/components/ui/sheet/sheet-overlay.jsx +0 -15
- package/src/core/lib/components/ui/sheet/sheet-portal.jsx +0 -4
- package/src/core/lib/components/ui/sheet/sheet-title.jsx +0 -15
- package/src/core/lib/components/ui/sheet/sheet-trigger.jsx +0 -7
- package/src/core/lib/components/ui/sheet/sheet.jsx +0 -4
- package/src/core/lib/components/ui/toggle/index.js +0 -8
- package/src/core/lib/components/ui/toggle/toggle.jsx +0 -36
- package/src/core/lib/components/ui/toggle-group/index.js +0 -10
- package/src/core/lib/components/ui/toggle-group/toggle-group-item.jsx +0 -29
- package/src/core/lib/components/ui/toggle-group/toggle-group.jsx +0 -43
- package/src/internals/canvas/StoryErrorBoundary.jsx +0 -47
- package/src/internals/canvas/componentIsolate.jsx +0 -144
- package/src/internals/canvas/componentSetIsolate.jsx +0 -300
- package/src/internals/canvas/widgets/InlineStoryRenderer.jsx +0 -129
- package/src/internals/canvas/widgets/InlineStoryRenderer.test.jsx +0 -68
- package/src/primer/ThemeSync.jsx +0 -73
- package/src/primer/index.js +0 -1
|
@@ -1,300 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Canvas Component-Set Isolate — lightweight iframe entry point.
|
|
3
|
-
*
|
|
4
|
-
* Renders ALL named exports from a .story.jsx module in a grid layout,
|
|
5
|
-
* bypassing the full SPA bootstrap (router, StoryboardProvider, data index).
|
|
6
|
-
*
|
|
7
|
-
* This is the component-set equivalent of componentIsolate.jsx, which renders
|
|
8
|
-
* a single export. By avoiding the full app bootstrap, component-set widgets
|
|
9
|
-
* load significantly faster — especially important since each widget is an
|
|
10
|
-
* iframe that would otherwise need to initialize the entire app.
|
|
11
|
-
*
|
|
12
|
-
* Query params:
|
|
13
|
-
* module — absolute or base-relative path to the .story.jsx file
|
|
14
|
-
* layout — "horizontal" (default) | "vertical"
|
|
15
|
-
* selected — export name of the currently selected cell
|
|
16
|
-
* theme — canvas theme (light / dark / dark_dimmed)
|
|
17
|
-
*/
|
|
18
|
-
import { createElement, Component as ReactComponent, useState, useEffect, useLayoutEffect, useCallback, useRef } from 'react'
|
|
19
|
-
import { createRoot } from 'react-dom/client'
|
|
20
|
-
import { ThemeProvider, BaseStyles } from '@primer/react'
|
|
21
|
-
|
|
22
|
-
// ── Primer Primitives CSS (required for CSS variables) ──────────────
|
|
23
|
-
import '@primer/primitives/dist/css/base/size/size.css'
|
|
24
|
-
import '@primer/primitives/dist/css/base/typography/typography.css'
|
|
25
|
-
import '@primer/primitives/dist/css/base/motion/motion.css'
|
|
26
|
-
import '@primer/primitives/dist/css/functional/size/border.css'
|
|
27
|
-
import '@primer/primitives/dist/css/functional/size/breakpoints.css'
|
|
28
|
-
import '@primer/primitives/dist/css/functional/size/size-coarse.css'
|
|
29
|
-
import '@primer/primitives/dist/css/functional/size/size-fine.css'
|
|
30
|
-
import '@primer/primitives/dist/css/functional/size/size.css'
|
|
31
|
-
import '@primer/primitives/dist/css/functional/size/viewport.css'
|
|
32
|
-
import '@primer/primitives/dist/css/functional/typography/typography.css'
|
|
33
|
-
import '@primer/primitives/dist/css/functional/themes/light.css'
|
|
34
|
-
import '@primer/primitives/dist/css/functional/themes/light-colorblind.css'
|
|
35
|
-
import '@primer/primitives/dist/css/functional/themes/dark.css'
|
|
36
|
-
import '@primer/primitives/dist/css/functional/themes/dark-colorblind.css'
|
|
37
|
-
import '@primer/primitives/dist/css/functional/themes/dark-high-contrast.css'
|
|
38
|
-
import '@primer/primitives/dist/css/functional/themes/dark-dimmed.css'
|
|
39
|
-
|
|
40
|
-
import styles from '../story/ComponentSetPage.module.css'
|
|
41
|
-
|
|
42
|
-
// ── Error Boundary ──────────────────────────────────────────────────
|
|
43
|
-
class IsolateErrorBoundary extends ReactComponent {
|
|
44
|
-
constructor(props) {
|
|
45
|
-
super(props)
|
|
46
|
-
this.state = { error: null }
|
|
47
|
-
}
|
|
48
|
-
static getDerivedStateFromError(error) {
|
|
49
|
-
return { error }
|
|
50
|
-
}
|
|
51
|
-
render() {
|
|
52
|
-
if (this.state.error) {
|
|
53
|
-
return createElement('div', { style: errorStyle },
|
|
54
|
-
createElement('strong', null, this.props.name || 'Component'),
|
|
55
|
-
createElement('br'),
|
|
56
|
-
String(this.state.error.message || this.state.error),
|
|
57
|
-
)
|
|
58
|
-
}
|
|
59
|
-
return this.props.children
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const errorStyle = {
|
|
64
|
-
padding: '16px',
|
|
65
|
-
color: '#cf222e',
|
|
66
|
-
fontFamily: 'system-ui, -apple-system, sans-serif',
|
|
67
|
-
fontSize: '13px',
|
|
68
|
-
lineHeight: 1.5,
|
|
69
|
-
whiteSpace: 'pre-wrap',
|
|
70
|
-
wordBreak: 'break-word',
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// ── Resolve module path ─────────────────────────────────────────────
|
|
74
|
-
function resolveModulePath(raw) {
|
|
75
|
-
if (!raw) return raw
|
|
76
|
-
if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(raw)) return raw
|
|
77
|
-
if (!raw.startsWith('/')) return raw
|
|
78
|
-
const base = (import.meta.env?.BASE_URL || '/').replace(/\/$/, '')
|
|
79
|
-
if (!base) return raw
|
|
80
|
-
if (raw.startsWith(base)) return raw
|
|
81
|
-
return `${base}${raw}`
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// ── Component-Set Grid (mirrors ComponentSetPage UI) ────────────────
|
|
85
|
-
function ComponentSetGrid({ exports, layout, density, initialSelected }) {
|
|
86
|
-
const [selected, setSelected] = useState(initialSelected)
|
|
87
|
-
const gridRef = useRef(null)
|
|
88
|
-
|
|
89
|
-
const handleSelect = useCallback((exportName) => {
|
|
90
|
-
const next = exportName === selected ? '' : exportName
|
|
91
|
-
setSelected(next)
|
|
92
|
-
|
|
93
|
-
// Update URL without navigation
|
|
94
|
-
const params = new URLSearchParams(window.location.search)
|
|
95
|
-
if (next) params.set('selected', next)
|
|
96
|
-
else params.delete('selected')
|
|
97
|
-
window.history.replaceState(null, '', `${window.location.pathname}?${params}`)
|
|
98
|
-
|
|
99
|
-
// Notify parent widget
|
|
100
|
-
if (window.parent !== window) {
|
|
101
|
-
window.parent.postMessage({
|
|
102
|
-
type: 'storyboard:component-set:select',
|
|
103
|
-
exportName: next || null,
|
|
104
|
-
}, '*')
|
|
105
|
-
}
|
|
106
|
-
}, [selected])
|
|
107
|
-
|
|
108
|
-
// Post a sensible initial widget size to the parent. Each cell sizes to
|
|
109
|
-
// its own content via CSS; here we measure individual cells (which have
|
|
110
|
-
// intrinsic widths thanks to `minmax` + the iframe being narrow at first)
|
|
111
|
-
// and arrange them in a near-square grid to compute an initial widget size.
|
|
112
|
-
useLayoutEffect(() => {
|
|
113
|
-
const grid = gridRef.current
|
|
114
|
-
if (!grid || !exports) return
|
|
115
|
-
|
|
116
|
-
let posted = false
|
|
117
|
-
function measureAndPost() {
|
|
118
|
-
if (posted || window.parent === window) return
|
|
119
|
-
const cells = grid.querySelectorAll(`.${styles.cell}`)
|
|
120
|
-
if (cells.length === 0) return
|
|
121
|
-
let maxW = 0
|
|
122
|
-
let maxH = 0
|
|
123
|
-
cells.forEach((cell) => {
|
|
124
|
-
const r = cell.getBoundingClientRect()
|
|
125
|
-
if (r.width > maxW) maxW = r.width
|
|
126
|
-
if (r.height > maxH) maxH = r.height
|
|
127
|
-
})
|
|
128
|
-
if (maxW < 10 || maxH < 10) return
|
|
129
|
-
const count = cells.length
|
|
130
|
-
const cols = Math.max(1, Math.min(count, Math.ceil(Math.sqrt(count))))
|
|
131
|
-
const rows = Math.ceil(count / cols)
|
|
132
|
-
const gap = 16
|
|
133
|
-
const pad = 32 // 16px padding on both sides
|
|
134
|
-
const width = Math.ceil(maxW * cols + gap * (cols - 1) + pad)
|
|
135
|
-
const height = Math.ceil(maxH * rows + gap * (rows - 1) + pad)
|
|
136
|
-
posted = true
|
|
137
|
-
window.parent.postMessage({
|
|
138
|
-
type: 'storyboard:component-set:initial-size',
|
|
139
|
-
width,
|
|
140
|
-
height,
|
|
141
|
-
}, '*')
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Re-post on grid size changes too (content height changes as cells reflow).
|
|
145
|
-
// Measure from cell bounding boxes (not scrollWidth/scrollHeight) so the
|
|
146
|
-
// reported content size reflects the *natural* extent of the cells —
|
|
147
|
-
// scrollWidth always matches the container width when the widget is
|
|
148
|
-
// wider than needed, which would defeat width-snapping on resize end.
|
|
149
|
-
function postContentSize() {
|
|
150
|
-
if (window.parent === window) return
|
|
151
|
-
const cells = grid.querySelectorAll(`.${styles.cell}`)
|
|
152
|
-
if (!cells.length) return
|
|
153
|
-
const gridRect = grid.getBoundingClientRect()
|
|
154
|
-
let maxRight = 0
|
|
155
|
-
let maxBottom = 0
|
|
156
|
-
cells.forEach((cell) => {
|
|
157
|
-
const r = cell.getBoundingClientRect()
|
|
158
|
-
const right = r.right - gridRect.left
|
|
159
|
-
const bottom = r.bottom - gridRect.top
|
|
160
|
-
if (right > maxRight) maxRight = right
|
|
161
|
-
if (bottom > maxBottom) maxBottom = bottom
|
|
162
|
-
})
|
|
163
|
-
const pad = 16 // mirror grid padding
|
|
164
|
-
window.parent.postMessage({
|
|
165
|
-
type: 'storyboard:component-set:content-size',
|
|
166
|
-
width: Math.ceil(maxRight + pad),
|
|
167
|
-
height: Math.ceil(maxBottom + pad),
|
|
168
|
-
}, '*')
|
|
169
|
-
}
|
|
170
|
-
const ro = new ResizeObserver(postContentSize)
|
|
171
|
-
ro.observe(grid)
|
|
172
|
-
|
|
173
|
-
// Measure after layout + fonts settle
|
|
174
|
-
requestAnimationFrame(() => requestAnimationFrame(measureAndPost))
|
|
175
|
-
document.fonts.ready.then(() => requestAnimationFrame(measureAndPost))
|
|
176
|
-
|
|
177
|
-
return () => ro.disconnect()
|
|
178
|
-
}, [exports, layout, density])
|
|
179
|
-
|
|
180
|
-
// Signal snapshot-ready
|
|
181
|
-
useEffect(() => {
|
|
182
|
-
document.fonts.ready.then(() => {
|
|
183
|
-
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
184
|
-
window.__sbSnapshotReady?.()
|
|
185
|
-
}))
|
|
186
|
-
})
|
|
187
|
-
}, [exports])
|
|
188
|
-
|
|
189
|
-
const exportNames = Object.keys(exports)
|
|
190
|
-
|
|
191
|
-
// eslint-disable-next-line react-hooks/refs -- ref assigned to DOM element, not read during render
|
|
192
|
-
return createElement('div', {
|
|
193
|
-
ref: gridRef,
|
|
194
|
-
className: styles.grid,
|
|
195
|
-
'data-layout': layout,
|
|
196
|
-
'data-density': density,
|
|
197
|
-
},
|
|
198
|
-
exportNames.map((exportName) => {
|
|
199
|
-
const Component = exports[exportName]
|
|
200
|
-
const isSelected = exportName === selected
|
|
201
|
-
const cellStyle = typeof Component.minHeight === 'number'
|
|
202
|
-
? { '--cell-min-h': `${Component.minHeight}px` }
|
|
203
|
-
: undefined
|
|
204
|
-
return createElement('div', {
|
|
205
|
-
key: exportName,
|
|
206
|
-
className: styles.cell,
|
|
207
|
-
'data-selected': isSelected || undefined,
|
|
208
|
-
style: cellStyle,
|
|
209
|
-
},
|
|
210
|
-
createElement('button', {
|
|
211
|
-
className: styles.cellLabel,
|
|
212
|
-
onClick: () => handleSelect(exportName),
|
|
213
|
-
'data-selected': isSelected || undefined,
|
|
214
|
-
'aria-pressed': isSelected,
|
|
215
|
-
},
|
|
216
|
-
createElement('span', { className: styles.cellRadio, 'data-selected': isSelected || undefined }),
|
|
217
|
-
createElement('span', { className: styles.cellName }, exportName),
|
|
218
|
-
),
|
|
219
|
-
createElement('div', { className: styles.cellContent, 'data-cell-content': '' },
|
|
220
|
-
createElement(IsolateErrorBoundary, { name: exportName },
|
|
221
|
-
createElement(Component),
|
|
222
|
-
),
|
|
223
|
-
),
|
|
224
|
-
)
|
|
225
|
-
}),
|
|
226
|
-
)
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// ── Main ────────────────────────────────────────────────────────────
|
|
230
|
-
const params = new URLSearchParams(window.location.search)
|
|
231
|
-
const modulePath = params.get('module')
|
|
232
|
-
const layoutParam = params.get('layout') || 'auto'
|
|
233
|
-
const layout = layoutParam === 'horizontal' ? 'wide' : layoutParam === 'vertical' ? 'tall' : layoutParam
|
|
234
|
-
const density = params.get('density') || 'comfy'
|
|
235
|
-
const selected = params.get('selected') || ''
|
|
236
|
-
const theme = params.get('theme') || 'light'
|
|
237
|
-
|
|
238
|
-
const colorMode = theme.startsWith('dark') ? 'night' : 'day'
|
|
239
|
-
|
|
240
|
-
document.documentElement.setAttribute('data-color-mode', theme.startsWith('dark') ? 'dark' : 'light')
|
|
241
|
-
document.documentElement.setAttribute('data-dark-theme', theme.startsWith('dark') ? theme : '')
|
|
242
|
-
document.documentElement.setAttribute('data-light-theme', theme.startsWith('dark') ? '' : theme || 'light')
|
|
243
|
-
document.documentElement.setAttribute('data-sb-theme', theme || 'light')
|
|
244
|
-
|
|
245
|
-
// Note: we deliberately do NOT install the canvas-hmr-guard here. The guard
|
|
246
|
-
// drops BOTH `full-reload` and `update` payloads server-side, which means
|
|
247
|
-
// edits to the story source file never reach this iframe — no Fast Refresh,
|
|
248
|
-
// no reload, nothing. Story/component-set widgets are expected to refresh
|
|
249
|
-
// when the underlying .story.jsx is edited, so we let HMR through.
|
|
250
|
-
void import.meta.hot
|
|
251
|
-
|
|
252
|
-
const root = createRoot(document.getElementById('root'))
|
|
253
|
-
|
|
254
|
-
async function mount() {
|
|
255
|
-
if (!modulePath) {
|
|
256
|
-
root.render(createElement('div', { style: errorStyle }, 'Missing module param'))
|
|
257
|
-
return
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
if (!modulePath.match(/\.story\.(jsx|tsx)$/)) {
|
|
261
|
-
root.render(createElement('div', { style: errorStyle }, 'Invalid module path — only .story.jsx/.tsx files are allowed'))
|
|
262
|
-
return
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
try {
|
|
266
|
-
const resolved = resolveModulePath(modulePath)
|
|
267
|
-
const mod = await import(/* @vite-ignore */ resolved)
|
|
268
|
-
|
|
269
|
-
const namedExports = {}
|
|
270
|
-
for (const [key, value] of Object.entries(mod)) {
|
|
271
|
-
if (key !== 'default' && typeof value === 'function') {
|
|
272
|
-
// Opt-out: showcase exports that already render every variant.
|
|
273
|
-
if (value.componentSet === false) continue
|
|
274
|
-
namedExports[key] = value
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
if (Object.keys(namedExports).length === 0) {
|
|
279
|
-
throw new Error('No named exports found in story module')
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
root.render(
|
|
283
|
-
createElement(ThemeProvider, { colorMode },
|
|
284
|
-
createElement(BaseStyles, null,
|
|
285
|
-
createElement(ComponentSetGrid, { exports: namedExports, layout, density, initialSelected: selected }),
|
|
286
|
-
),
|
|
287
|
-
),
|
|
288
|
-
)
|
|
289
|
-
} catch (err) {
|
|
290
|
-
root.render(
|
|
291
|
-
createElement('div', { style: errorStyle },
|
|
292
|
-
createElement('strong', null, 'Component Set'),
|
|
293
|
-
createElement('br'),
|
|
294
|
-
String(err.message || err),
|
|
295
|
-
),
|
|
296
|
-
)
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
mount()
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* InlineStoryRenderer — renders a story's named export directly inside the
|
|
3
|
-
* canvas React tree (no iframe).
|
|
4
|
-
*
|
|
5
|
-
* Wrapped in:
|
|
6
|
-
* - <ThemeProvider colorMode> + <BaseStyles> so the story's Primer theme can
|
|
7
|
-
* differ from the canvas chrome theme.
|
|
8
|
-
* - <StoryErrorBoundary> so a thrown render error stays scoped to this widget.
|
|
9
|
-
*
|
|
10
|
-
* Re-imports the module on `storyboard:story-index-changed` events so HMR-driven
|
|
11
|
-
* story updates show without remounting the iframe shell.
|
|
12
|
-
*
|
|
13
|
-
* Note: stories using `useOverride` / `useFlowData` will share the canvas URL
|
|
14
|
-
* hash. This is a known limitation of the inline path; iframe path remains
|
|
15
|
-
* available via `canvas.inlineStories: false`.
|
|
16
|
-
*/
|
|
17
|
-
import { useState, useEffect, useMemo } from 'react'
|
|
18
|
-
import { ThemeProvider, BaseStyles } from '@primer/react'
|
|
19
|
-
import { getStoryData } from '../../../core/index.js'
|
|
20
|
-
import { StoryErrorBoundary } from '../StoryErrorBoundary.jsx'
|
|
21
|
-
|
|
22
|
-
function resolveColorMode() {
|
|
23
|
-
if (typeof document === 'undefined') return 'day'
|
|
24
|
-
const attr = document.documentElement.getAttribute('data-color-mode')
|
|
25
|
-
if (attr === 'dark') return 'night'
|
|
26
|
-
if (attr === 'light') return 'day'
|
|
27
|
-
if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {
|
|
28
|
-
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'night' : 'day'
|
|
29
|
-
}
|
|
30
|
-
return 'day'
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export default function InlineStoryRenderer({ storyId, exportName }) {
|
|
34
|
-
const [mod, setMod] = useState(null)
|
|
35
|
-
const [error, setError] = useState(null)
|
|
36
|
-
const [storyIndexKey, setStoryIndexKey] = useState(0)
|
|
37
|
-
const [colorMode, setColorMode] = useState(() => resolveColorMode())
|
|
38
|
-
|
|
39
|
-
// Track parent theme changes
|
|
40
|
-
useEffect(() => {
|
|
41
|
-
const onTheme = () => setColorMode(resolveColorMode())
|
|
42
|
-
document.addEventListener('storyboard:theme:changed', onTheme)
|
|
43
|
-
return () => document.removeEventListener('storyboard:theme:changed', onTheme)
|
|
44
|
-
}, [])
|
|
45
|
-
|
|
46
|
-
// Re-import on story index change (HMR / new exports added)
|
|
47
|
-
useEffect(() => {
|
|
48
|
-
const handler = () => setStoryIndexKey((k) => k + 1)
|
|
49
|
-
document.addEventListener('storyboard:story-index-changed', handler)
|
|
50
|
-
return () => document.removeEventListener('storyboard:story-index-changed', handler)
|
|
51
|
-
}, [])
|
|
52
|
-
|
|
53
|
-
// storyIndexKey forces re-lookup on HMR
|
|
54
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
55
|
-
const story = useMemo(() => getStoryData(storyId), [storyId, storyIndexKey])
|
|
56
|
-
const lookupError = useMemo(() => {
|
|
57
|
-
if (!story) return new Error(`Story "${storyId}" not found`)
|
|
58
|
-
if (typeof story._storyImport !== 'function') return new Error(`Story "${storyId}" has no import function`)
|
|
59
|
-
return null
|
|
60
|
-
}, [story, storyId])
|
|
61
|
-
|
|
62
|
-
useEffect(() => {
|
|
63
|
-
if (lookupError) return
|
|
64
|
-
let cancelled = false
|
|
65
|
-
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
66
|
-
setError(null)
|
|
67
|
-
story._storyImport()
|
|
68
|
-
.then((m) => { if (!cancelled) setMod(m) })
|
|
69
|
-
.catch((err) => { if (!cancelled) { setError(err); setMod(null) } })
|
|
70
|
-
return () => { cancelled = true }
|
|
71
|
-
}, [story, lookupError, storyIndexKey])
|
|
72
|
-
|
|
73
|
-
const displayedError = lookupError || error
|
|
74
|
-
|
|
75
|
-
const Component = useMemo(() => {
|
|
76
|
-
if (!mod) return null
|
|
77
|
-
if (exportName) {
|
|
78
|
-
const C = mod[exportName]
|
|
79
|
-
return typeof C === 'function' ? C : null
|
|
80
|
-
}
|
|
81
|
-
// No exportName — render the first named function export
|
|
82
|
-
for (const [k, v] of Object.entries(mod)) {
|
|
83
|
-
if (k !== 'default' && typeof v === 'function') return v
|
|
84
|
-
}
|
|
85
|
-
return null
|
|
86
|
-
}, [mod, exportName])
|
|
87
|
-
|
|
88
|
-
const errorStyle = {
|
|
89
|
-
padding: '16px',
|
|
90
|
-
color: '#cf222e',
|
|
91
|
-
fontFamily: 'system-ui, -apple-system, sans-serif',
|
|
92
|
-
fontSize: '13px',
|
|
93
|
-
lineHeight: 1.5,
|
|
94
|
-
whiteSpace: 'pre-wrap',
|
|
95
|
-
wordBreak: 'break-word',
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (displayedError) {
|
|
99
|
-
return (
|
|
100
|
-
<div style={errorStyle}>
|
|
101
|
-
<strong>{exportName || storyId}</strong>
|
|
102
|
-
<br />
|
|
103
|
-
{String(displayedError.message || displayedError)}
|
|
104
|
-
</div>
|
|
105
|
-
)
|
|
106
|
-
}
|
|
107
|
-
if (!mod) return null
|
|
108
|
-
if (!Component) {
|
|
109
|
-
return (
|
|
110
|
-
<div style={errorStyle}>
|
|
111
|
-
<strong>{exportName || storyId}</strong>
|
|
112
|
-
<br />
|
|
113
|
-
{exportName ? `Export "${exportName}" not found or is not a component` : 'No named exports found in story module'}
|
|
114
|
-
</div>
|
|
115
|
-
)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return (
|
|
119
|
-
<div style={{ contain: 'layout paint style', width: '100%', height: '100%', overflow: 'auto' }}>
|
|
120
|
-
<ThemeProvider colorMode={colorMode}>
|
|
121
|
-
<BaseStyles>
|
|
122
|
-
<StoryErrorBoundary name={exportName || storyId} resetKey={`${storyId}:${exportName}:${storyIndexKey}`}>
|
|
123
|
-
<Component />
|
|
124
|
-
</StoryErrorBoundary>
|
|
125
|
-
</BaseStyles>
|
|
126
|
-
</ThemeProvider>
|
|
127
|
-
</div>
|
|
128
|
-
)
|
|
129
|
-
}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
-
import { render, screen, act, waitFor } from '@testing-library/react'
|
|
3
|
-
import InlineStoryRenderer from './InlineStoryRenderer.jsx'
|
|
4
|
-
|
|
5
|
-
vi.mock('../../../core/index.js', () => ({
|
|
6
|
-
getStoryData: vi.fn(),
|
|
7
|
-
}))
|
|
8
|
-
|
|
9
|
-
import { getStoryData } from '../../../core/index.js'
|
|
10
|
-
|
|
11
|
-
function GoodComponent() { return <div data-testid="good">hello</div> }
|
|
12
|
-
function ThrowingComponent() { throw new Error('boom') }
|
|
13
|
-
|
|
14
|
-
describe('InlineStoryRenderer', () => {
|
|
15
|
-
beforeEach(() => {
|
|
16
|
-
vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
17
|
-
})
|
|
18
|
-
afterEach(() => {
|
|
19
|
-
vi.restoreAllMocks()
|
|
20
|
-
getStoryData.mockReset()
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
it('renders the named export when found', async () => {
|
|
24
|
-
getStoryData.mockReturnValue({
|
|
25
|
-
_storyImport: () => Promise.resolve({ Default: GoodComponent }),
|
|
26
|
-
})
|
|
27
|
-
render(<InlineStoryRenderer storyId="my-story" exportName="Default" />)
|
|
28
|
-
await waitFor(() => expect(screen.getByTestId('good')).toBeTruthy())
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('reports missing story', () => {
|
|
32
|
-
getStoryData.mockReturnValue(null)
|
|
33
|
-
render(<InlineStoryRenderer storyId="missing" exportName="Default" />)
|
|
34
|
-
expect(screen.getByText(/Story "missing" not found/)).toBeTruthy()
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
it('reports missing export', async () => {
|
|
38
|
-
getStoryData.mockReturnValue({
|
|
39
|
-
_storyImport: () => Promise.resolve({ Other: GoodComponent }),
|
|
40
|
-
})
|
|
41
|
-
render(<InlineStoryRenderer storyId="s" exportName="Default" />)
|
|
42
|
-
await waitFor(() => expect(screen.getByText(/Export "Default" not found/)).toBeTruthy())
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it('contains thrown render errors via error boundary', async () => {
|
|
46
|
-
getStoryData.mockReturnValue({
|
|
47
|
-
_storyImport: () => Promise.resolve({ Default: ThrowingComponent }),
|
|
48
|
-
})
|
|
49
|
-
render(<InlineStoryRenderer storyId="bad" exportName="Default" />)
|
|
50
|
-
await waitFor(() => expect(screen.getByText(/boom/)).toBeTruthy())
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
it('re-imports on storyboard:story-index-changed', async () => {
|
|
54
|
-
let calls = 0
|
|
55
|
-
getStoryData.mockImplementation(() => ({
|
|
56
|
-
_storyImport: () => {
|
|
57
|
-
calls += 1
|
|
58
|
-
return Promise.resolve({ Default: GoodComponent })
|
|
59
|
-
},
|
|
60
|
-
}))
|
|
61
|
-
render(<InlineStoryRenderer storyId="s" exportName="Default" />)
|
|
62
|
-
await waitFor(() => expect(calls).toBe(1))
|
|
63
|
-
act(() => {
|
|
64
|
-
document.dispatchEvent(new Event('storyboard:story-index-changed'))
|
|
65
|
-
})
|
|
66
|
-
await waitFor(() => expect(calls).toBe(2))
|
|
67
|
-
})
|
|
68
|
-
})
|
package/src/primer/ThemeSync.jsx
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ThemeSync — invisible React component that bridges the storyboard-core
|
|
3
|
-
* toolbar theme switcher with Primer's ThemeProvider context.
|
|
4
|
-
*
|
|
5
|
-
* Listens for `storyboard:theme:changed` custom events dispatched by the
|
|
6
|
-
* core theme store and calls setColorMode/setDayScheme/setNightScheme on
|
|
7
|
-
* Primer's useTheme() hook accordingly.
|
|
8
|
-
*
|
|
9
|
-
* On mount it reads localStorage to initialize Primer to the correct
|
|
10
|
-
* scheme before the Svelte CoreUIBar has loaded.
|
|
11
|
-
*
|
|
12
|
-
* When prototype sync is disabled (via "Apply theme to" settings), the
|
|
13
|
-
* prototype is forced to light mode regardless of the selected theme.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { useEffect } from 'react'
|
|
17
|
-
import { useTheme } from '@primer/react'
|
|
18
|
-
|
|
19
|
-
const THEME_STORAGE_KEY = 'sb-color-scheme'
|
|
20
|
-
const THEME_SYNC_STORAGE_KEY = 'sb-theme-sync'
|
|
21
|
-
|
|
22
|
-
const DEFAULT_SYNC = {
|
|
23
|
-
prototype: true,
|
|
24
|
-
toolbar: false,
|
|
25
|
-
codeBoxes: true,
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function readSyncTargets() {
|
|
29
|
-
try {
|
|
30
|
-
const raw = localStorage.getItem(THEME_SYNC_STORAGE_KEY)
|
|
31
|
-
if (!raw) return DEFAULT_SYNC
|
|
32
|
-
return { ...DEFAULT_SYNC, ...JSON.parse(raw) }
|
|
33
|
-
} catch {
|
|
34
|
-
return DEFAULT_SYNC
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function applyToPrimer(setColorMode, setDayScheme, setNightScheme, themeValue) {
|
|
39
|
-
if (themeValue === 'system' || !themeValue) {
|
|
40
|
-
setColorMode('auto')
|
|
41
|
-
setDayScheme('light')
|
|
42
|
-
setNightScheme('dark')
|
|
43
|
-
} else {
|
|
44
|
-
setColorMode('day')
|
|
45
|
-
setDayScheme(themeValue)
|
|
46
|
-
setNightScheme(themeValue)
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export default function ThemeSync() {
|
|
51
|
-
const { setColorMode, setDayScheme, setNightScheme } = useTheme()
|
|
52
|
-
|
|
53
|
-
// Restore saved theme on mount
|
|
54
|
-
useEffect(() => {
|
|
55
|
-
const saved = localStorage.getItem(THEME_STORAGE_KEY)
|
|
56
|
-
const syncTargets = readSyncTargets()
|
|
57
|
-
const prototypeTheme = syncTargets.prototype ? saved : 'light'
|
|
58
|
-
applyToPrimer(setColorMode, setDayScheme, setNightScheme, prototypeTheme)
|
|
59
|
-
}, []) // eslint-disable-line react-hooks/exhaustive-deps
|
|
60
|
-
|
|
61
|
-
// Listen for theme changes from the Svelte CoreUIBar
|
|
62
|
-
useEffect(() => {
|
|
63
|
-
function handleThemeChanged(e) {
|
|
64
|
-
const { prototypeTheme } = e.detail || {}
|
|
65
|
-
if (typeof prototypeTheme !== 'string') return
|
|
66
|
-
applyToPrimer(setColorMode, setDayScheme, setNightScheme, prototypeTheme)
|
|
67
|
-
}
|
|
68
|
-
document.addEventListener('storyboard:theme:changed', handleThemeChanged)
|
|
69
|
-
return () => document.removeEventListener('storyboard:theme:changed', handleThemeChanged)
|
|
70
|
-
}, [setColorMode, setDayScheme, setNightScheme])
|
|
71
|
-
|
|
72
|
-
return null
|
|
73
|
-
}
|
package/src/primer/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default as ThemeSync } from './ThemeSync.jsx'
|