@storystick/react 0.2.0-rc.0 → 0.2.0-rc.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/PUBLIC-API.md +4 -2
- package/dist/.source-hash +1 -1
- package/dist/authoring-skeleton.d.ts +5 -0
- package/dist/authoring-skeleton.js +29 -0
- package/dist/preview-host/preview-authoring-client.d.ts +2 -2
- package/dist/preview-host/preview-authoring-client.js +9 -1
- package/dist/preview-host/preview-standalone-controls.js +22 -12
- package/dist/preview-page-state.d.ts +12 -0
- package/dist/preview-page-state.js +11 -0
- package/dist/preview-panel-state.js +1 -1
- package/dist/preview-toolbar-panel.d.ts +2 -1
- package/dist/preview-toolbar-panel.js +2 -2
- package/dist/preview-toolbar-shell.d.ts +1 -0
- package/dist/preview-toolbar-shell.js +22 -16
- package/dist/storystick-mark.js +3 -2
- package/package.json +13 -5
package/PUBLIC-API.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @storystick/react API contract
|
|
2
2
|
|
|
3
|
-
Install this candidate explicitly with `@storystick/react@0.2.0-rc.
|
|
3
|
+
Install this candidate explicitly with `@storystick/react@0.2.0-rc.1` after registry bootstrap and publication. This repository alone does not make the candidate or beta channel available on npm.
|
|
4
4
|
|
|
5
|
-
Version 0.2.0-rc.
|
|
5
|
+
Version 0.2.0-rc.1, cohort `sdk-0.2.0-rc.1`. ESM distribution; no CommonJS or universal Edge compatibility is promised.
|
|
6
6
|
|
|
7
7
|
This package is a product integration facade. Reserved contracts are required between SDK packages and must be upgraded with the same cohort. Their public export paths do not promise independent stability.
|
|
8
8
|
|
|
@@ -26,6 +26,8 @@ This package is a product integration facade. Reserved contracts are required be
|
|
|
26
26
|
| `./internal/use-momentary-preview-navigate-mode` | Reserved SDK contract |
|
|
27
27
|
| `./internal/resize-edge` | Reserved SDK contract |
|
|
28
28
|
| `./internal/preview-toolbar-shell` | Reserved SDK contract |
|
|
29
|
+
| `./internal/preview-page-state` | Reserved SDK contract |
|
|
30
|
+
| `./internal/authoring-skeleton` | Reserved SDK contract |
|
|
29
31
|
| `./internal/preview-panel-state` | Reserved SDK contract |
|
|
30
32
|
| `./internal/preview-host` | Reserved SDK contract |
|
|
31
33
|
|
package/dist/.source-hash
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
4cb09a292c5e314adfbbfa1a1bdf1cad0ae376072cfd5a37269e4be8e69570da
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** The same outline covers admission and module loading, before either app's
|
|
2
|
+
* styles/providers exist. Only the owned panel receives these styles. */
|
|
3
|
+
export declare function AuthoringPanelSkeleton({ surface }: {
|
|
4
|
+
surface: 'editor' | 'assistant';
|
|
5
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/** The same outline covers admission and module loading, before either app's
|
|
3
|
+
* styles/providers exist. Only the owned panel receives these styles. */
|
|
4
|
+
export function AuthoringPanelSkeleton({ surface }) {
|
|
5
|
+
return _jsxs("div", { "data-ss-panel-skeleton": surface, role: "status", "aria-label": surface === 'assistant' ? 'Loading assistant' : 'Loading document content', children: [_jsx("style", { children: STYLES }), _jsx("div", { "data-ss-panel-skeleton-body": "", "aria-hidden": "true", children: surface === 'assistant' ? _jsxs(_Fragment, { children: [_jsxs("div", { "data-ss-panel-skeleton-greeting": "", children: [_jsx("i", {}), _jsx("i", {})] }), _jsxs("div", { "data-ss-panel-skeleton-composer": "", children: [_jsx("i", {}), _jsx("i", {})] })] }) : _jsxs(_Fragment, { children: [_jsxs("div", { "data-ss-panel-skeleton-title": "", children: [_jsx("i", {}), _jsx("i", {})] }), _jsxs("div", { "data-ss-panel-skeleton-tabs": "", children: [_jsx("i", {}), _jsx("i", {}), _jsx("i", {})] }), _jsxs("div", { "data-ss-panel-skeleton-fields": "", children: [_jsx("i", {}), _jsx("i", {}), _jsx("i", {})] })] }) })] });
|
|
6
|
+
}
|
|
7
|
+
const STYLES = `
|
|
8
|
+
[data-ss-panel-skeleton] { box-sizing: border-box; width: 100%; height: 100%; min-height: 240px; background: var(--ss-surface-page, #fafafa); }
|
|
9
|
+
[data-ss-panel-skeleton-body] { box-sizing: border-box; display: flex; flex-direction: column; height: 100%; min-height: inherit; gap: 32px; padding: 24px 16px 16px; }
|
|
10
|
+
[data-ss-panel-skeleton] i { display: block; flex-shrink: 0; background: var(--ss-surface-widget, #f0f0f0); border-radius: var(--ss-radius-field, 18px); }
|
|
11
|
+
[data-ss-panel-skeleton-title] { display: grid; gap: 12px; padding-top: 12px; }
|
|
12
|
+
[data-ss-panel-skeleton-title] i { height: 24px; width: 85%; }
|
|
13
|
+
[data-ss-panel-skeleton-title] i:last-child { width: 60%; }
|
|
14
|
+
[data-ss-panel-skeleton-tabs] { display: flex; gap: 12px; }
|
|
15
|
+
[data-ss-panel-skeleton-tabs] i { width: 64px; height: 32px; border-radius: 999px; }
|
|
16
|
+
[data-ss-panel-skeleton-fields] { display: grid; gap: 12px; }
|
|
17
|
+
[data-ss-panel-skeleton-fields] i { height: 88px; }
|
|
18
|
+
[data-ss-panel-skeleton-fields] i:last-child { height: 132px; }
|
|
19
|
+
[data-ss-panel-skeleton-greeting] { display: grid; gap: 12px; width: 75%; margin: auto; }
|
|
20
|
+
[data-ss-panel-skeleton-greeting] i { height: 16px; }
|
|
21
|
+
[data-ss-panel-skeleton-greeting] i:last-child { width: 70%; margin-inline: auto; }
|
|
22
|
+
[data-ss-panel-skeleton-composer] { display: flex; align-items: center; justify-content: space-between; height: 60px; flex-shrink: 0; padding: 0 16px; border-radius: 30px; background: var(--ss-surface-card, #fff); box-shadow: 0 0 0 1px var(--ss-border-card, #e9e9e9); }
|
|
23
|
+
[data-ss-panel-skeleton-composer] i { width: 55%; height: 12px; }
|
|
24
|
+
[data-ss-panel-skeleton-composer] i:last-child { width: 32px; height: 32px; border-radius: 50%; }
|
|
25
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
26
|
+
[data-ss-panel-skeleton] i { animation: ss-panel-skeleton-pulse 1.8s ease-in-out infinite; }
|
|
27
|
+
}
|
|
28
|
+
@keyframes ss-panel-skeleton-pulse { 50% { opacity: .5; } }
|
|
29
|
+
`;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { type PreviewAuthoringAdmission, type PreviewAuthoringRuntimeModule } from '@storystick/preview/authoring/runtime-contract';
|
|
1
|
+
import { type PreviewAuthoringAdmission, type PreviewAuthoringRuntimeModule, type PreviewAuthoringSurface } from '@storystick/preview/authoring/runtime-contract';
|
|
2
2
|
declare global {
|
|
3
3
|
interface Window {
|
|
4
4
|
StorystickAuthoringRuntime?: PreviewAuthoringRuntimeModule;
|
|
5
5
|
}
|
|
6
6
|
}
|
|
7
7
|
export declare function authoringRuntimeUrl(studioUrl: string): string | null;
|
|
8
|
-
export declare function loadPreviewAuthoringRuntime(source: string): Promise<PreviewAuthoringRuntimeModule>;
|
|
8
|
+
export declare function loadPreviewAuthoringRuntime(source: string, surface?: PreviewAuthoringSurface | null): Promise<PreviewAuthoringRuntimeModule>;
|
|
9
9
|
export declare function requestPreviewAuthoringAdmission(args: {
|
|
10
10
|
url: string;
|
|
11
11
|
previous?: PreviewAuthoringAdmission | null;
|
|
@@ -13,7 +13,15 @@ export function authoringRuntimeUrl(studioUrl) {
|
|
|
13
13
|
return null;
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
|
-
export function loadPreviewAuthoringRuntime(source) {
|
|
16
|
+
export function loadPreviewAuthoringRuntime(source, surface) {
|
|
17
|
+
const pending = loadRuntimeModule(source);
|
|
18
|
+
// Code preparation never holds admission/authentication behind a panel's
|
|
19
|
+
// dependencies. Rendering uses the same imports and its own Suspense boundary.
|
|
20
|
+
if (surface)
|
|
21
|
+
void pending.then(runtime => runtime.preload(surface)).catch(() => undefined);
|
|
22
|
+
return pending;
|
|
23
|
+
}
|
|
24
|
+
function loadRuntimeModule(source) {
|
|
17
25
|
const installed = readRuntime();
|
|
18
26
|
if (installed)
|
|
19
27
|
return Promise.resolve(installed);
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useMomentaryPreviewInteractionOverride } from '@storystick/react/internal/use-momentary-preview-navigate-mode';
|
|
4
4
|
import { PreviewPanelAction, PreviewPanelState } from '@storystick/react/internal/preview-panel-state';
|
|
5
|
+
import { AuthoringPanelSkeleton } from '@storystick/react/internal/authoring-skeleton';
|
|
5
6
|
import { useStandalonePresentation } from './use-standalone-presentation.js';
|
|
6
7
|
import { PreviewInteractionControl } from '@storystick/react/internal/preview-interaction-control';
|
|
7
8
|
import { PreviewToolbarShell, PreviewEditIcon as EditIcon, PreviewAssistantIcon as ChatIcon, PreviewStudioIcon as StudioIcon, } from '@storystick/react/internal/preview-toolbar-shell';
|
|
@@ -28,6 +29,7 @@ export function PreviewStandaloneControls({ state, projectId, recovery, basePath
|
|
|
28
29
|
const host = useRef(null);
|
|
29
30
|
const editorContainer = useRef(null);
|
|
30
31
|
const [headerLeading, setHeaderLeading] = useState(null);
|
|
32
|
+
const [headerTitle, setHeaderTitle] = useState(null);
|
|
31
33
|
const [headerTrailing, setHeaderTrailing] = useState(null);
|
|
32
34
|
const [contextContainer, setContextContainer] = useState(null);
|
|
33
35
|
const [interactionMode, setInteractionMode] = useState(() => readStandaloneInteractionMode(projectId, state.sessionId));
|
|
@@ -148,14 +150,18 @@ export function PreviewStandaloneControls({ state, projectId, recovery, basePath
|
|
|
148
150
|
? 'Element selection is unavailable because some elements do not match their editing context.'
|
|
149
151
|
: 'Element selection is unavailable because this page has incomplete or conflicting editing information.'
|
|
150
152
|
: null;
|
|
151
|
-
const
|
|
153
|
+
const shouldPreloadRuntime = canEdit && (interactionMode === 'edit' || menuOpen);
|
|
152
154
|
useEffect(() => {
|
|
153
|
-
if (!
|
|
155
|
+
if (!shouldPreloadRuntime || !runtimeSource)
|
|
154
156
|
return;
|
|
155
|
-
// Warm
|
|
156
|
-
//
|
|
157
|
+
// Warm the common runtime; panel dependencies follow a specific intent.
|
|
158
|
+
// Admission and mounting stay separate from this shared code preparation.
|
|
157
159
|
void loadPreviewAuthoringRuntime(runtimeSource).catch(() => undefined);
|
|
158
|
-
}, [
|
|
160
|
+
}, [shouldPreloadRuntime, runtimeSource]);
|
|
161
|
+
const preloadPanel = useCallback((surface) => {
|
|
162
|
+
if (runtimeSource)
|
|
163
|
+
void loadPreviewAuthoringRuntime(runtimeSource, surface).catch(() => undefined);
|
|
164
|
+
}, [runtimeSource]);
|
|
159
165
|
const currentPage = useRef(page);
|
|
160
166
|
currentPage.current = page;
|
|
161
167
|
const presentationRef = useRef(presentation);
|
|
@@ -271,7 +277,7 @@ export function PreviewStandaloneControls({ state, projectId, recovery, basePath
|
|
|
271
277
|
(surface === 'editor' && !targetRequest) ||
|
|
272
278
|
root === null ||
|
|
273
279
|
editorContainer.current === null ||
|
|
274
|
-
contextContainer === null || headerLeading === null || headerTrailing === null ||
|
|
280
|
+
contextContainer === null || headerLeading === null || headerTitle === null || headerTrailing === null ||
|
|
275
281
|
runtimeSource === null ||
|
|
276
282
|
runtimeHandle.current !== null)
|
|
277
283
|
return;
|
|
@@ -285,7 +291,7 @@ export function PreviewStandaloneControls({ state, projectId, recovery, basePath
|
|
|
285
291
|
setError(null);
|
|
286
292
|
try {
|
|
287
293
|
const [runtime, initialAdmission] = await Promise.all([
|
|
288
|
-
loadPreviewAuthoringRuntime(runtimeSource),
|
|
294
|
+
loadPreviewAuthoringRuntime(runtimeSource, surface),
|
|
289
295
|
requestPreviewAuthoringAdmission({ url: authoringUrl }),
|
|
290
296
|
]);
|
|
291
297
|
if (cancelled)
|
|
@@ -306,9 +312,9 @@ export function PreviewStandaloneControls({ state, projectId, recovery, basePath
|
|
|
306
312
|
const handle = runtime.mount({
|
|
307
313
|
root,
|
|
308
314
|
container,
|
|
309
|
-
header: { leading: headerLeading, trailing: headerTrailing },
|
|
315
|
+
header: { leading: headerLeading, title: headerTitle, trailing: headerTrailing },
|
|
310
316
|
target: targetRef.current ?? undefined,
|
|
311
|
-
surface
|
|
317
|
+
surface,
|
|
312
318
|
assistantThreadId: assistantThreadId.current,
|
|
313
319
|
onSurface: next => { if (runtimeOwner.current === owner) {
|
|
314
320
|
setMenuOpen(false);
|
|
@@ -423,7 +429,7 @@ export function PreviewStandaloneControls({ state, projectId, recovery, basePath
|
|
|
423
429
|
return () => {
|
|
424
430
|
cancelled = true;
|
|
425
431
|
};
|
|
426
|
-
}, [authoringUrl, connectAttempt, targetRequest, root, contextContainer, headerLeading, headerTrailing, runtimeSource, surface, wantsRuntime, connected]);
|
|
432
|
+
}, [authoringUrl, connectAttempt, targetRequest, root, contextContainer, headerLeading, headerTitle, headerTrailing, runtimeSource, surface, wantsRuntime, connected]);
|
|
427
433
|
useEffect(() => {
|
|
428
434
|
if (!targetRequest || !runtimeHandle.current)
|
|
429
435
|
return;
|
|
@@ -466,12 +472,16 @@ export function PreviewStandaloneControls({ state, projectId, recovery, basePath
|
|
|
466
472
|
dispose();
|
|
467
473
|
};
|
|
468
474
|
}, []);
|
|
469
|
-
return _jsx(PreviewToolbarShell, { hostRef: host, onRoot: setRoot, menuOpen: menuOpen, onMenuOpenChange: setMenuOpen, panelOpen: surface !== null, onPanelClose: closePanel, panelLeading: _jsx("div", { ref: setHeaderLeading, "data-ss-surface": "" }), panelTrailing: _jsx("div", { ref: setHeaderTrailing, "data-ss-surface": "" }), panelLabel: surface === "assistant" ? "Assistant" : "Edit content", menu: _jsxs(_Fragment, { children: [_jsxs("div", { className: "menu-group", role: "group", "aria-label": "Site view", children: [_jsx("span", { className: "menu-group-label", children: "Site view" }), _jsxs("div", { className: "menu-group-content", children: [_jsx("div", { ref: setContextContainer, "data-ss-surface": "" }), editorStatus === 'connecting' ?
|
|
475
|
+
return _jsx(PreviewToolbarShell, { hostRef: host, onRoot: setRoot, menuOpen: menuOpen, onMenuOpenChange: setMenuOpen, panelOpen: surface !== null, onPanelClose: closePanel, panelTitle: _jsx("span", { ref: setHeaderTitle, className: "panel-title", "data-placeholder": surface === "assistant" ? "Assistant" : "Edit content" }), panelLeading: _jsx("div", { ref: setHeaderLeading, "data-ss-surface": "" }), panelTrailing: _jsx("div", { ref: setHeaderTrailing, "data-ss-surface": "" }), panelLabel: surface === "assistant" ? "Assistant" : "Edit content", menu: _jsxs(_Fragment, { children: [_jsxs("div", { className: "menu-group", role: "group", "aria-label": "Site view", children: [_jsx("span", { className: "menu-group-label", children: "Site view" }), _jsxs("div", { className: "menu-group-content", children: [_jsx("div", { ref: setContextContainer, "data-ss-surface": "" }), editorStatus === 'connecting' ? _jsxs("div", { className: "menu-context-loading", role: "status", "aria-label": "Loading site view", children: [_jsx("span", {}), _jsx("span", {})] }) : null] })] }), _jsx("div", { className: "menu-separator", "aria-hidden": "true" }), _jsxs("div", { className: "menu-group", role: "group", "aria-label": "Interaction", children: [_jsx("span", { className: "menu-group-label", children: "Interaction" }), _jsx("div", { className: "menu-group-content", children: _jsx(PreviewInteractionControl, { interactionMode: effectiveMode, onChange: chooseInteractionMode }) })] }), _jsx("div", { className: "menu-separator", "aria-hidden": "true" }), _jsxs("button", { type: "button", className: "menu-action", disabled: !canEdit, onMouseEnter: () => { if (canEdit)
|
|
476
|
+
preloadPanel('editor'); }, onFocus: () => { if (canEdit)
|
|
477
|
+
preloadPanel('editor'); }, onClick: () => openEditor(currentPageTarget), children: [_jsx(EditIcon, {}), _jsx("span", { children: "Edit content" })] }), _jsxs("button", { type: "button", className: "menu-action", disabled: !connected || !runtimeSource, onMouseEnter: () => { if (connected)
|
|
478
|
+
preloadPanel('assistant'); }, onFocus: () => { if (connected)
|
|
479
|
+
preloadPanel('assistant'); }, onClick: () => { setError(null); setMenuOpen(false); setSurface('assistant'); }, children: [_jsx(ChatIcon, {}), _jsx("span", { children: "Assistant" })] }), destination && (_jsxs("a", { className: "menu-action", href: destination.href, children: [_jsx(StudioIcon, {}), _jsx("span", { children: "Open in Studio" })] })), (connectionNotice || notice ||
|
|
470
480
|
overlayNotice ||
|
|
471
481
|
(!rendered && connected) ||
|
|
472
482
|
recovery?.error ||
|
|
473
483
|
error) && (_jsxs("div", { className: "notices", children: [connectionNotice && _jsx("p", { role: "status", children: connectionNotice }), notice && _jsx("p", { children: notice }), overlayNotice && _jsx("p", { role: "status", children: overlayNotice }), connected && !rendered && (_jsxs("p", { children: ["This page has not confirmed its rendered perspective.", ' ', _jsx("button", { type: "button", onClick: () => window.location.reload(), children: "Reload" })] })), recovery?.error && (_jsxs("p", { role: "alert", children: [recovery.error, ' ', _jsx("button", { type: "button", onClick: recovery.retry, disabled: false, children: "Retry" })] })), error && _jsxs("p", { role: "alert", children: [error, ' ', editorStatus === 'error' && !runtimeHandle.current ? (_jsx("button", { type: "button", onClick: () => setConnectAttempt(value => value + 1), children: "Retry" })) : null] })] }))] }), panel: _jsxs(_Fragment, { children: [surface === 'editor' && target === null && committedTarget === null && !continuation && (_jsxs("div", { className: "editor-empty", children: [_jsx(EditIcon, {}), _jsx("strong", { children: "Select an element on the page" }), _jsx("p", { children: "The matching field will open here." })] })), (editorStatus === 'idle' || editorStatus === 'connecting') &&
|
|
474
|
-
(surface === 'assistant' || target || continuation) && (_jsx("div", { className: "editor-loading",
|
|
484
|
+
(surface === 'assistant' || target || continuation) && (_jsx("div", { className: "editor-loading", children: _jsx(AuthoringPanelSkeleton, { surface: surface === 'assistant' ? 'assistant' : 'editor' }) })), editorStatus === 'error' && error && (_jsx("div", { className: "panel-state-overlay", children: _jsx(PreviewPanelState, { title: surface === 'assistant' ? 'Assistant unavailable' : 'Editor unavailable', description: error, actions: _jsx(PreviewPanelAction, { onClick: () => {
|
|
475
485
|
setError(null);
|
|
476
486
|
if (runtimeHandle.current && (committedTarget ?? target))
|
|
477
487
|
requestEditorTarget({ ...(committedTarget ?? target) });
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
/** One page composition for launch, activation and terminal Preview feedback.
|
|
3
|
+
* Actions and progress belong to the caller's existing lifecycle. */
|
|
4
|
+
export declare function PreviewPageState({ title, description, role, indicator, actions, children }: {
|
|
5
|
+
title: string;
|
|
6
|
+
description: ReactNode;
|
|
7
|
+
role: 'status' | 'alert';
|
|
8
|
+
indicator?: ReactNode;
|
|
9
|
+
actions?: ReactNode;
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
}): import("react").JSX.Element;
|
|
12
|
+
export declare function PreviewPageProgress(): import("react").JSX.Element;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { PREVIEW_PAGE_STYLES } from '@storystick/preview/page-presentation';
|
|
3
|
+
import { StorystickMark } from './storystick-mark.js';
|
|
4
|
+
/** One page composition for launch, activation and terminal Preview feedback.
|
|
5
|
+
* Actions and progress belong to the caller's existing lifecycle. */
|
|
6
|
+
export function PreviewPageState({ title, description, role, indicator, actions, children }) {
|
|
7
|
+
return _jsxs(_Fragment, { children: [_jsx("style", { children: PREVIEW_PAGE_STYLES }), _jsx("main", { "data-ss-preview-page": "", children: _jsxs("div", { "data-ss-preview-page-content": "", children: [_jsxs("div", { "data-ss-preview-page-brand": "", children: [_jsx(StorystickMark, {}), _jsx("span", { children: "Storystick" })] }), _jsxs("div", { role: role, children: [_jsxs("div", { "data-ss-preview-page-heading": "", children: [indicator, _jsx("h1", { "data-ss-preview-page-title": "", children: title })] }), _jsx("p", { "data-ss-preview-page-description": "", children: description })] }), actions ? _jsx("div", { "data-ss-preview-page-actions": "", children: actions }) : null, children] }) })] });
|
|
8
|
+
}
|
|
9
|
+
export function PreviewPageProgress() {
|
|
10
|
+
return _jsx("span", { "data-ss-preview-page-progress": "", "aria-hidden": "true" });
|
|
11
|
+
}
|
|
@@ -12,7 +12,7 @@ export function PreviewPanelAction(props) {
|
|
|
12
12
|
return _jsx("button", { type: "button", "data-ss-panel-action": "", ...props });
|
|
13
13
|
}
|
|
14
14
|
const MESSAGE_STYLES = `
|
|
15
|
-
[data-ss-panel-state] { box-sizing: border-box; display: flex; flex-direction: column; width: 100%; height: 100%; min-height: 0; min-width: 0; overflow: auto; padding: 24px; background: #fff; }
|
|
15
|
+
[data-ss-panel-state] { box-sizing: border-box; display: flex; flex-direction: column; width: 100%; height: 100%; min-height: 0; min-width: 0; overflow: auto; padding: 24px; background: var(--ss-surface-page, #fff); }
|
|
16
16
|
[data-ss-panel-state] > [data-ss-panel-message] { margin: auto; padding: 0; }
|
|
17
17
|
[data-ss-panel-message] { box-sizing: border-box; flex: none; width: 100%; max-width: 360px; margin-inline: auto; padding: 16px; color: #52525b; font: 400 13px/1.5 "Storystick Inter", "Inter Variable", ui-sans-serif, system-ui, sans-serif; text-align: center; overflow-wrap: anywhere; }
|
|
18
18
|
[data-ss-panel-message-copy] h2 { margin: 0; color: #18181b; font-size: 15px; font-weight: 500; line-height: 1.4; text-wrap: balance; }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ComponentProps, ReactNode, RefObject } from 'react';
|
|
2
2
|
/** The same movable frame hosts the retained editor and assistant. */
|
|
3
|
-
export declare function PreviewToolbarPanel({ open, label, onClose, panelRef, handleProps, resizeHandles, leading, trailing, children, }: {
|
|
3
|
+
export declare function PreviewToolbarPanel({ open, label, onClose, panelRef, handleProps, resizeHandles, leading, trailing, title, children, }: {
|
|
4
4
|
open: boolean;
|
|
5
5
|
label: string;
|
|
6
6
|
onClose(): void;
|
|
@@ -9,5 +9,6 @@ export declare function PreviewToolbarPanel({ open, label, onClose, panelRef, ha
|
|
|
9
9
|
resizeHandles: ReactNode;
|
|
10
10
|
leading?: ReactNode;
|
|
11
11
|
trailing?: ReactNode;
|
|
12
|
+
title?: ReactNode;
|
|
12
13
|
children: ReactNode;
|
|
13
14
|
}): import("react").JSX.Element;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { X } from './preview-icons.js';
|
|
3
3
|
/** The same movable frame hosts the retained editor and assistant. */
|
|
4
|
-
export function PreviewToolbarPanel({ open, label, onClose, panelRef, handleProps, resizeHandles, leading, trailing, children, }) {
|
|
5
|
-
return (_jsxs("section", { ref: panelRef, className: "editor-panel", "aria-label": label, hidden: !open, inert: !open, children: [_jsxs("div", { className: "panel-contents", children: [_jsxs("header", { className: "panel-header", children: [leading, _jsx("button", { type: "button", className: "panel-drag-handle", "aria-label": `Move ${label} panel`, title: "Drag to move. Arrow keys move; Alt + Up/Down fills the height; Home and End dock to a side.", ...handleProps, children: label }), trailing, _jsx("button", { type: "button", className: "panel-close", "aria-label": `Close ${label}`, onClick: onClose, children: _jsx(X, { size: 16, strokeWidth: 1.25, absoluteStrokeWidth: true, "aria-hidden": "true" }) })] }), _jsx("div", { className: "panel-body", children: children })] }), resizeHandles] }));
|
|
4
|
+
export function PreviewToolbarPanel({ open, label, onClose, panelRef, handleProps, resizeHandles, leading, trailing, title, children, }) {
|
|
5
|
+
return (_jsxs("section", { ref: panelRef, className: "editor-panel", "aria-label": label, hidden: !open, inert: !open, children: [_jsxs("div", { className: "panel-contents", children: [_jsxs("header", { className: "panel-header", children: [leading, _jsx("button", { type: "button", className: "panel-drag-handle", "aria-label": `Move ${label} panel`, title: "Drag to move. Arrow keys move; Alt + Up/Down fills the height; Home and End dock to a side.", ...handleProps, children: title ?? label }), trailing, _jsx("button", { type: "button", className: "panel-close", "aria-label": `Close ${label}`, onClick: onClose, children: _jsx(X, { size: 16, strokeWidth: 1.25, absoluteStrokeWidth: true, "aria-hidden": "true" }) })] }), _jsx("div", { className: "panel-body", children: children })] }), resizeHandles] }));
|
|
6
6
|
}
|
|
@@ -23,7 +23,7 @@ export function PreviewToolbarShell(props) {
|
|
|
23
23
|
}, [host, props.onRoot]);
|
|
24
24
|
return _jsx("div", { ref: host, "data-storystick-preview-controls": "", style: { all: "initial", position: "fixed", zIndex: 2147483647, top: 0, left: 0, width: 0, height: 0 }, children: root ? createPortal(_jsx(ToolbarContents, { ...props, root: root }), root) : null });
|
|
25
25
|
}
|
|
26
|
-
function ToolbarContents({ root, menuOpen, panelOpen, menu, panel, panelLabel, panelLeading, panelTrailing, onMenuOpenChange, onPanelClose }) {
|
|
26
|
+
function ToolbarContents({ root, menuOpen, panelOpen, menu, panel, panelLabel, panelTitle, panelLeading, panelTrailing, onMenuOpenChange, onPanelClose }) {
|
|
27
27
|
const layout = usePreviewToolbarLayout(() => onMenuOpenChange(false));
|
|
28
28
|
const menuRef = useRef(null);
|
|
29
29
|
const [shell, setShell] = useState(null);
|
|
@@ -44,7 +44,7 @@ function ToolbarContents({ root, menuOpen, panelOpen, menu, panel, panelLabel, p
|
|
|
44
44
|
onPanelClose();
|
|
45
45
|
layout.bubble.current?.focus({ preventScroll: true });
|
|
46
46
|
}
|
|
47
|
-
return _jsxs(_Fragment, { children: [_jsx("style", { children: CONTROL_STYLES }), _jsxs("div", { ref: setShell, className: "storystick-shell", "data-ss-surface": "", dir: "ltr", children: [_jsx(PreviewToolbarPanel, { open: panelOpen, label: panelLabel, onClose: closePanel, leading: panelLeading, trailing: panelTrailing, panelRef: layout.panel, handleProps: { ...layout.panelDrag, onKeyDown: layout.onPanelKeyDown }, resizeHandles: _jsx(PreviewToolbarResizeHandles, { label: panelLabel, edges: layout.resizeEdges, size: layout.panelBounds, handleProps: layout.resizeHandleProps }), children: panel }), _jsxs(Popover.Root, { open: menuOpen, modal: false, onOpenChange: (open, details) => {
|
|
47
|
+
return _jsxs(_Fragment, { children: [_jsx("style", { children: CONTROL_STYLES }), _jsxs("div", { ref: setShell, className: "storystick-shell", "data-ss-surface": "", dir: "ltr", children: [_jsx(PreviewToolbarPanel, { open: panelOpen, label: panelLabel, onClose: closePanel, leading: panelLeading, title: panelTitle, trailing: panelTrailing, panelRef: layout.panel, handleProps: { ...layout.panelDrag, onKeyDown: layout.onPanelKeyDown }, resizeHandles: _jsx(PreviewToolbarResizeHandles, { label: panelLabel, edges: layout.resizeEdges, size: layout.panelBounds, handleProps: layout.resizeHandleProps }), children: panel }), _jsxs(Popover.Root, { open: menuOpen, modal: false, onOpenChange: (open, details) => {
|
|
48
48
|
// Runtime menus live in a second React root, but remain part of this
|
|
49
49
|
// dropdown interaction. Their owned portals must not dismiss its parent.
|
|
50
50
|
const portals = root.querySelector('[data-storystick-authoring-portals]');
|
|
@@ -82,33 +82,42 @@ const CONTROL_STYLES = `
|
|
|
82
82
|
.storystick-brand { width: 14px; height: 13px; transform: rotate(90deg); }
|
|
83
83
|
.menu-positioner { z-index: 3; pointer-events: auto; }
|
|
84
84
|
.menu { width: min(240px, calc(100vw - ${TOOLBAR_GAP * 2}px)); max-height: min(var(--available-height), calc(100dvh - ${TOOLBAR_GAP * 2}px)); overflow: auto; overscroll-behavior: contain; padding: 5px; }
|
|
85
|
-
|
|
86
|
-
.
|
|
87
|
-
.menu, .editor-panel {
|
|
85
|
+
/* Keep the bubble's relief stable before and after the authoring styles load. */
|
|
86
|
+
.bubble { box-shadow: 0 0 1px rgb(0 0 0 / 0.34), 0 2px 6px -1px rgb(0 0 0 / 0.12), 0 8px 20px -6px rgb(0 0 0 / 0.18); }
|
|
87
|
+
.menu, .editor-panel { box-shadow: 0 0 0 1px rgb(0 0 0 / 0.07), 0 4px 12px -2px rgb(0 0 0 / 0.1), 0 16px 40px -10px rgb(0 0 0 / 0.2); border: 0; }
|
|
88
|
+
.menu { background: var(--ss-surface-card, #fff); border-radius: var(--ss-radius-2xl, 19px); }
|
|
88
89
|
.menu-group { display: flex; flex-direction: column; gap: 2px; }
|
|
89
90
|
.menu-group-label { display: flex; align-items: center; min-height: 25px; padding: 4px 10px; color: var(--ss-text-tertiary, oklch(0.515 0.006 258.364)); font-size: 13px; line-height: 17px; font-weight: 500; }
|
|
90
91
|
.menu-group-content { padding: 0 10px 5px; }
|
|
91
|
-
.menu-
|
|
92
|
+
.menu-context-loading { display: flex; gap: 8px; height: 32px; }
|
|
93
|
+
.menu-context-loading span { width: 88px; border-radius: 999px; background: var(--ss-surface-widget, #f0f0f0); }
|
|
94
|
+
.menu-context-loading span:last-child { width: 56px; }
|
|
92
95
|
.menu-separator { margin: 5px 10px; border-top: 1px solid var(--ss-border-separator, oklch(0.958 0.001 255)); }
|
|
93
96
|
.menu-action { width: 100%; min-height: 28px; display: flex; align-items: center; gap: 8px; padding: 5px 10px; color: inherit; text-decoration: none; text-align: left; font-size: 14px; line-height: 18px; font-weight: 400; background: transparent; border: 0; border-radius: var(--ss-radius-md, 7px); }
|
|
97
|
+
.menu-action + .menu-action { margin-block-start: 2px; }
|
|
94
98
|
.menu-action:hover { background: var(--ss-surface-field, oklch(0.958 0.001 255)); }
|
|
95
99
|
.menu-action:disabled { opacity: .45; cursor: default; }
|
|
96
100
|
.menu-action:disabled:hover { background: transparent; }
|
|
97
101
|
.notices { margin-top: 5px; padding: 5px 10px; color: var(--ss-text-tertiary, oklch(0.515 0.006 258.364)); border-top: 1px solid var(--ss-border-separator, oklch(0.958 0.001 255)); font-size: 12px; line-height: 16px; }
|
|
98
102
|
.notices p { margin: 4px 0; }
|
|
99
103
|
.notices button { padding: 0; color: inherit; background: none; border: 0; text-decoration: underline; }
|
|
100
|
-
.editor-panel { pointer-events: auto; position: fixed; top: 0; left: 0; z-index: 1; }
|
|
101
|
-
|
|
104
|
+
.editor-panel { --ss-surface-page: var(--ss-surface-workspace, #fafafa); background: var(--ss-surface-page); border-radius: var(--ss-radius-bubble, 26px); pointer-events: auto; position: fixed; top: 0; left: 0; z-index: 1; }
|
|
105
|
+
/* Runtime roots carry theme defaults; their page ground belongs to this host. */
|
|
106
|
+
.editor-panel [data-ss-surface] { --ss-surface-page: inherit; }
|
|
107
|
+
.panel-contents { height: 100%; overflow: hidden; border-radius: inherit; display: grid; grid-template-rows: calc(2 * var(--ss-radius-bubble, 26px)) minmax(0, 1fr); }
|
|
102
108
|
.editor-panel[hidden] { display: none; }
|
|
103
|
-
|
|
109
|
+
/* The close control has an 18px radius; its inset keeps both curves concentric. */
|
|
110
|
+
.panel-header { display: flex; align-items: center; gap: 4px; padding: 0 calc(var(--ss-radius-bubble, 26px) - 18px); }
|
|
104
111
|
.panel-header > [data-ss-surface] { display: flex; flex: none; }
|
|
105
112
|
.panel-header > [data-ss-surface]:empty { display: none; }
|
|
106
113
|
.panel-drag-handle { min-width: 0; align-self: stretch; flex: 1; text-align: left; font-weight: 500; color: var(--ss-text-secondary, #52525b); background: none; border: 0; padding: 0; border-radius: 7px; touch-action: none; user-select: none; cursor: grab; }
|
|
114
|
+
.panel-title { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
115
|
+
.panel-title:empty::before { content: attr(data-placeholder); }
|
|
107
116
|
.panel-drag-handle:first-child, .panel-header > :empty + .panel-drag-handle { padding-inline-start: 10px; }
|
|
108
117
|
.panel-close { flex: none; width: 36px; height: 36px; display: grid; place-items: center; border: 0; border-radius: 999px; color: var(--ss-text-secondary, #52525b); background: none; }
|
|
109
118
|
.panel-close:hover { background: var(--ss-surface-field, #f4f4f5); }
|
|
110
119
|
.panel-body { position: relative; min-height: 0; display: grid; grid-template-rows: minmax(0, 1fr); }
|
|
111
|
-
.panel-resize-handle { --ss-resize-edge-inset: max(0px, calc(var(--ss-radius-
|
|
120
|
+
.panel-resize-handle { --ss-resize-edge-inset: max(0px, calc(var(--ss-radius-bubble, 26px) - 12px)); position: absolute; z-index: 3; border: 0; padding: 0; background: transparent; touch-action: none; user-select: none; }
|
|
112
121
|
.panel-resize-handle:focus-visible { outline: 2px solid var(--ss-focus-color, oklch(0.614 0.194 257)); outline-offset: -2px; border-radius: 4px; }
|
|
113
122
|
.panel-resize-handle:is([data-resize-edge="n"], [data-resize-edge="s"]) { left: 12px; right: 12px; height: 8px; cursor: ns-resize; }
|
|
114
123
|
.panel-resize-handle:is([data-resize-edge="e"], [data-resize-edge="w"]) { top: 12px; bottom: 12px; width: 8px; cursor: ew-resize; }
|
|
@@ -123,16 +132,13 @@ const CONTROL_STYLES = `
|
|
|
123
132
|
.panel-resize-handle[data-resize-edge="nw"] { top: -4px; left: -4px; cursor: nwse-resize; }
|
|
124
133
|
.bubble[data-dragging], .editor-panel[data-dragging] .panel-drag-handle { cursor: grabbing; }
|
|
125
134
|
.bubble[data-dragging] { transition: none; }
|
|
126
|
-
.editor-mount { min-height: 0; overflow: hidden; background:
|
|
135
|
+
.editor-mount { min-height: 0; overflow: hidden; background: var(--ss-surface-page); }
|
|
127
136
|
.editor-mount > [data-storystick-authoring-root] { height: 100%; }
|
|
128
|
-
.editor-empty, .editor-loading { position: absolute; inset: 0; z-index: 2; display: grid; place-content: center; justify-items: center; padding: 24px; text-align: center; color: #52525b; background:
|
|
137
|
+
.editor-empty, .editor-loading { position: absolute; inset: 0; z-index: 2; display: grid; place-content: center; justify-items: center; padding: 24px; text-align: center; color: #52525b; background: var(--ss-surface-page); }
|
|
129
138
|
.panel-state-overlay { position: absolute; inset: 0; z-index: 2; min-height: 0; }
|
|
130
139
|
.editor-empty svg { width: 28px; height: 28px; margin-bottom: 12px; }
|
|
131
140
|
.editor-empty strong { color: #18181b; }
|
|
132
141
|
.editor-empty p { margin: 4px 0 0; }
|
|
133
|
-
.editor-loading { display: block; padding:
|
|
134
|
-
.editor-loading > div { display: grid; gap: 16px; }
|
|
135
|
-
.editor-loading > div > div { height: 80px; border-radius: 6px; background: #f4f4f5; }
|
|
136
|
-
.editor-loading > div > div:last-child { height: 128px; }
|
|
142
|
+
.editor-loading { display: block; padding: 0; }
|
|
137
143
|
@media (prefers-reduced-motion: reduce) { .bubble { transition: none; } }
|
|
138
144
|
`;
|
package/dist/storystick-mark.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { STORYSTICK_MARK_RECTS, STORYSTICK_MARK_VIEW_BOX } from '@storystick/preview/page-presentation';
|
|
2
3
|
/** Canonical brand geometry, shared by Studio and the lightweight preview host. */
|
|
3
4
|
export function StorystickMark(props) {
|
|
4
|
-
return (
|
|
5
|
+
return (_jsx("svg", { ...props, "data-slot": "storystick-mark", viewBox: STORYSTICK_MARK_VIEW_BOX, fill: "currentColor", focusable: "false", "aria-hidden": "true", children: STORYSTICK_MARK_RECTS.map(rect => _jsx("rect", { ...rect }, rect.x)) }));
|
|
5
6
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@storystick/react",
|
|
3
|
-
"version": "0.2.0-rc.
|
|
3
|
+
"version": "0.2.0-rc.1",
|
|
4
4
|
"license": "FSL-1.1-MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -80,6 +80,14 @@
|
|
|
80
80
|
"types": "./dist/preview-toolbar-shell.d.ts",
|
|
81
81
|
"import": "./dist/preview-toolbar-shell.js"
|
|
82
82
|
},
|
|
83
|
+
"./internal/preview-page-state": {
|
|
84
|
+
"types": "./dist/preview-page-state.d.ts",
|
|
85
|
+
"import": "./dist/preview-page-state.js"
|
|
86
|
+
},
|
|
87
|
+
"./internal/authoring-skeleton": {
|
|
88
|
+
"types": "./dist/authoring-skeleton.d.ts",
|
|
89
|
+
"import": "./dist/authoring-skeleton.js"
|
|
90
|
+
},
|
|
83
91
|
"./internal/preview-panel-state": {
|
|
84
92
|
"types": "./dist/preview-panel-state.d.ts",
|
|
85
93
|
"import": "./dist/preview-panel-state.js"
|
|
@@ -104,10 +112,10 @@
|
|
|
104
112
|
"dependencies": {
|
|
105
113
|
"@tanstack/query-core": "5.101.4",
|
|
106
114
|
"@base-ui/react": "1.3.0",
|
|
107
|
-
"@storystick/
|
|
108
|
-
"@storystick/media": "0.2.0-rc.
|
|
109
|
-
"@storystick/types": "0.1.0-rc.
|
|
110
|
-
"@storystick/
|
|
115
|
+
"@storystick/client": "0.2.0-rc.1",
|
|
116
|
+
"@storystick/media": "0.2.0-rc.1",
|
|
117
|
+
"@storystick/types": "0.1.0-rc.1",
|
|
118
|
+
"@storystick/preview": "0.2.0-rc.1"
|
|
111
119
|
},
|
|
112
120
|
"peerDependencies": {
|
|
113
121
|
"react": "^19.2.8",
|