@gtkx/storybook 0.0.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/actions-panel.d.ts +7 -0
- package/dist/actions-panel.d.ts.map +1 -0
- package/dist/actions-panel.js +11 -0
- package/dist/actions-panel.js.map +1 -0
- package/dist/actions.d.ts +35 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +161 -0
- package/dist/actions.js.map +1 -0
- package/dist/catalog.d.ts +71 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +158 -0
- package/dist/catalog.js.map +1 -0
- package/dist/compose.d.ts +27 -0
- package/dist/compose.d.ts.map +1 -0
- package/dist/compose.js +67 -0
- package/dist/compose.js.map +1 -0
- package/dist/config.d.ts +13 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +4 -0
- package/dist/config.js.map +1 -0
- package/dist/controls.d.ts +10 -0
- package/dist/controls.d.ts.map +1 -0
- package/dist/controls.js +94 -0
- package/dist/controls.js.map +1 -0
- package/dist/explorer-navigator.d.ts +11 -0
- package/dist/explorer-navigator.d.ts.map +1 -0
- package/dist/explorer-navigator.js +25 -0
- package/dist/explorer-navigator.js.map +1 -0
- package/dist/explorer-preview.d.ts +8 -0
- package/dist/explorer-preview.d.ts.map +1 -0
- package/dist/explorer-preview.js +89 -0
- package/dist/explorer-preview.js.map +1 -0
- package/dist/explorer-view.d.ts +15 -0
- package/dist/explorer-view.d.ts.map +1 -0
- package/dist/explorer-view.js +24 -0
- package/dist/explorer-view.js.map +1 -0
- package/dist/explorer.d.ts +22 -0
- package/dist/explorer.d.ts.map +1 -0
- package/dist/explorer.js +23 -0
- package/dist/explorer.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +143 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/upstream.d.ts +14 -0
- package/dist/upstream.d.ts.map +1 -0
- package/dist/upstream.js +5 -0
- package/dist/upstream.js.map +1 -0
- package/package.json +86 -0
- package/src/actions-panel.tsx +32 -0
- package/src/actions.ts +234 -0
- package/src/catalog.ts +263 -0
- package/src/compose.ts +94 -0
- package/src/config.ts +14 -0
- package/src/controls.tsx +206 -0
- package/src/explorer-navigator.tsx +120 -0
- package/src/explorer-preview.tsx +207 -0
- package/src/explorer-view.tsx +93 -0
- package/src/explorer.ts +44 -0
- package/src/index.ts +16 -0
- package/src/types.ts +205 -0
- package/src/upstream.ts +24 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { markupEscapeText } from "@gtkx/gi/glib";
|
|
3
|
+
import * as Gtk from "@gtkx/gi/gtk";
|
|
4
|
+
import { AdwHeaderBar, AdwStatusPage, AdwToolbarView, AdwWindowTitle } from "@gtkx/jsx/adw";
|
|
5
|
+
import { GtkBox, GtkButton, GtkScrolledWindow } from "@gtkx/jsx/gtk";
|
|
6
|
+
import { createPortal, rootElement } from "@gtkx/react";
|
|
7
|
+
import { Component, useCallback, useMemo, useState } from "react";
|
|
8
|
+
import type { StoryEntry } from "./catalog.js";
|
|
9
|
+
import type { Args, ComposedStory } from "./types.js";
|
|
10
|
+
import { ActionsPanel } from "./actions-panel.js";
|
|
11
|
+
import { ActionStore, bindActions } from "./actions.js";
|
|
12
|
+
import { Controls } from "./controls.js";
|
|
13
|
+
|
|
14
|
+
type PreviewProps = {
|
|
15
|
+
entry: StoryEntry;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type PreviewState = {
|
|
19
|
+
story: ComposedStory;
|
|
20
|
+
args: Args;
|
|
21
|
+
generation: number;
|
|
22
|
+
error: Error | null;
|
|
23
|
+
actions: ActionStore;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type BoundaryProps = { children: ReactNode; args: Args };
|
|
27
|
+
type BoundaryState = { error: Error | null; args: Args };
|
|
28
|
+
|
|
29
|
+
class PreviewBoundary extends Component<BoundaryProps, BoundaryState> {
|
|
30
|
+
static getDerivedStateFromProps(props: BoundaryProps, state: BoundaryState): BoundaryState | null {
|
|
31
|
+
return props.args === state.args ? null : { error: null, args: props.args };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static getDerivedStateFromError(error: unknown): Pick<BoundaryState, "error"> {
|
|
35
|
+
return { error: error instanceof Error ? error : new Error(String(error)) };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
override state: BoundaryState = { error: null, args: this.props.args };
|
|
39
|
+
|
|
40
|
+
override render(): ReactNode {
|
|
41
|
+
return this.state.error === null
|
|
42
|
+
? this.props.children
|
|
43
|
+
: (
|
|
44
|
+
<AdwStatusPage
|
|
45
|
+
name="storybook-preview-error"
|
|
46
|
+
title="Preview failed"
|
|
47
|
+
iconName="dialog-error-symbolic"
|
|
48
|
+
description={markupEscapeText(this.state.error.message.slice(0, 500), -1) +
|
|
49
|
+
"\n\nAdjust controls, reset the preview, or select another story to continue."}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const previewMode = (story: ComposedStory): "content" | "window" | "dialog" => {
|
|
56
|
+
const gtkx = story.parameters.gtkx;
|
|
57
|
+
const mode = typeof gtkx === "object" && gtkx !== null && "preview" in gtkx ? gtkx.preview : "content";
|
|
58
|
+
|
|
59
|
+
if (mode !== "content" && mode !== "window" && mode !== "dialog") {
|
|
60
|
+
throw new Error("Unsupported GTKX story preview mode");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return mode;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const PreviewContent = ({ story: Story, args }: { story: ComposedStory; args: Args }): ReactNode => {
|
|
67
|
+
const mode = previewMode(Story);
|
|
68
|
+
|
|
69
|
+
if (mode !== "content") {
|
|
70
|
+
return (
|
|
71
|
+
<>
|
|
72
|
+
<AdwStatusPage
|
|
73
|
+
title={mode === "window" ? "Window preview" : "Dialog preview"}
|
|
74
|
+
iconName="window-new-symbolic"
|
|
75
|
+
description="The story owns its presentation. Reset to open it again."
|
|
76
|
+
/>
|
|
77
|
+
{createPortal(<Story {...args} />, rootElement)}
|
|
78
|
+
</>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const margin = Story.parameters.layout === "fullscreen" ? 0 : 24;
|
|
83
|
+
const align = Story.parameters.layout === "centered" ? Gtk.Align.CENTER : Gtk.Align.FILL;
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<GtkBox
|
|
87
|
+
name="storybook-canvas"
|
|
88
|
+
orientation={Gtk.Orientation.VERTICAL}
|
|
89
|
+
hexpand
|
|
90
|
+
vexpand
|
|
91
|
+
halign={align}
|
|
92
|
+
valign={align}
|
|
93
|
+
marginTop={margin}
|
|
94
|
+
marginBottom={margin}
|
|
95
|
+
marginStart={margin}
|
|
96
|
+
marginEnd={margin}
|
|
97
|
+
>
|
|
98
|
+
<Story {...args} />
|
|
99
|
+
</GtkBox>
|
|
100
|
+
);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const usePreviewState = ({ entry }: PreviewProps) => {
|
|
104
|
+
const [state, setState] = useState<PreviewState>(() => ({
|
|
105
|
+
story: entry.story,
|
|
106
|
+
args: { ...entry.story.args },
|
|
107
|
+
generation: 0,
|
|
108
|
+
error: null,
|
|
109
|
+
actions: new ActionStore(),
|
|
110
|
+
}));
|
|
111
|
+
const onError = useCallback((error: Error) => {
|
|
112
|
+
setState((current) => current.story === state.story && current.generation === state.generation
|
|
113
|
+
? { ...current, error }
|
|
114
|
+
: current);
|
|
115
|
+
}, [state.story, state.generation]);
|
|
116
|
+
const boundArgs = useMemo(
|
|
117
|
+
() => bindActions(state.args, state.story.argTypes, state.actions, onError),
|
|
118
|
+
[state.args, state.story.argTypes, state.actions, onError],
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if (state.story !== entry.story) {
|
|
122
|
+
setState({
|
|
123
|
+
story: entry.story,
|
|
124
|
+
args: { ...entry.story.args },
|
|
125
|
+
generation: state.generation + 1,
|
|
126
|
+
error: null,
|
|
127
|
+
actions: new ActionStore(),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const reset = (): void => {
|
|
132
|
+
setState((current) => ({
|
|
133
|
+
story: entry.story,
|
|
134
|
+
args: { ...entry.story.args },
|
|
135
|
+
generation: current.generation + 1,
|
|
136
|
+
error: null,
|
|
137
|
+
actions: new ActionStore(),
|
|
138
|
+
}));
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const onChange = (args: Args): void => {
|
|
142
|
+
setState((current) => ({ ...current, args: { ...current.args, ...args } }));
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
return { state, boundArgs, reset, onChange };
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
type InspectorProps = PreviewProps & { actions: ActionStore; args: Args; onChange: (args: Args) => void };
|
|
149
|
+
|
|
150
|
+
const StoryInspector = ({ entry, actions, args, onChange }: InspectorProps): ReactNode => (
|
|
151
|
+
<GtkScrolledWindow
|
|
152
|
+
name="storybook-inspector"
|
|
153
|
+
widthRequest={280}
|
|
154
|
+
hscrollbarPolicy={Gtk.PolicyType.NEVER}
|
|
155
|
+
vexpand
|
|
156
|
+
>
|
|
157
|
+
<GtkBox
|
|
158
|
+
orientation={Gtk.Orientation.VERTICAL}
|
|
159
|
+
spacing={24}
|
|
160
|
+
marginTop={18}
|
|
161
|
+
marginBottom={18}
|
|
162
|
+
marginStart={12}
|
|
163
|
+
marginEnd={12}
|
|
164
|
+
>
|
|
165
|
+
<Controls argTypes={entry.controls} args={args} onChange={onChange} />
|
|
166
|
+
<ActionsPanel store={actions} />
|
|
167
|
+
</GtkBox>
|
|
168
|
+
</GtkScrolledWindow>
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
const StorybookPreview = ({ entry }: PreviewProps): ReactNode => {
|
|
172
|
+
const { state, boundArgs, reset, onChange } = usePreviewState({ entry });
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<AdwToolbarView
|
|
176
|
+
hexpand
|
|
177
|
+
vexpand
|
|
178
|
+
topBar={(
|
|
179
|
+
<AdwHeaderBar
|
|
180
|
+
titleWidget={<AdwWindowTitle title={entry.name} subtitle={entry.title} />}
|
|
181
|
+
end={<GtkButton label="Reset story" onClicked={reset} />}
|
|
182
|
+
/>
|
|
183
|
+
)}
|
|
184
|
+
>
|
|
185
|
+
<GtkBox>
|
|
186
|
+
<GtkBox orientation={Gtk.Orientation.VERTICAL} hexpand vexpand>
|
|
187
|
+
<PreviewBoundary key={state.generation} args={state.args}>
|
|
188
|
+
{state.error === null
|
|
189
|
+
? <PreviewContent story={entry.story} args={boundArgs} />
|
|
190
|
+
: (
|
|
191
|
+
<AdwStatusPage
|
|
192
|
+
name="storybook-preview-error"
|
|
193
|
+
title="Story action failed"
|
|
194
|
+
iconName="dialog-error-symbolic"
|
|
195
|
+
description={markupEscapeText(state.error.message.slice(0, 500), -1) +
|
|
196
|
+
"\n\nReset the preview or select another story to continue."}
|
|
197
|
+
/>
|
|
198
|
+
)}
|
|
199
|
+
</PreviewBoundary>
|
|
200
|
+
</GtkBox>
|
|
201
|
+
<StoryInspector entry={entry} actions={state.actions} args={state.args} onChange={onChange} />
|
|
202
|
+
</GtkBox>
|
|
203
|
+
</AdwToolbarView>
|
|
204
|
+
);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
export { StorybookPreview };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import * as Gio from "@gtkx/gi/gio";
|
|
3
|
+
import {
|
|
4
|
+
AdwApplication,
|
|
5
|
+
AdwApplicationWindow,
|
|
6
|
+
AdwHeaderBar,
|
|
7
|
+
AdwOverlaySplitView,
|
|
8
|
+
AdwStatusPage,
|
|
9
|
+
AdwToolbarView,
|
|
10
|
+
AdwWindowTitle,
|
|
11
|
+
} from "@gtkx/jsx/adw";
|
|
12
|
+
import { quit } from "@gtkx/react";
|
|
13
|
+
import { useCallback, useState, useSyncExternalStore } from "react";
|
|
14
|
+
import type { StoryCatalog } from "./catalog.js";
|
|
15
|
+
import { StoryNavigator } from "./explorer-navigator.js";
|
|
16
|
+
import { StorybookPreview } from "./explorer-preview.js";
|
|
17
|
+
|
|
18
|
+
/** Configuration for the native Storybook application shell. */
|
|
19
|
+
type StorybookProps = {
|
|
20
|
+
/** Source catalog whose stories and loading errors appear in the explorer. */
|
|
21
|
+
catalog: StoryCatalog;
|
|
22
|
+
/** Gio application identifier; defaults to org.gtkx.Storybook. */
|
|
23
|
+
applicationId?: string;
|
|
24
|
+
/** Explorer window and sidebar title; defaults to GTKX Storybook. */
|
|
25
|
+
title?: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type StorybookViewProps = Pick<StorybookProps, "catalog" | "title">;
|
|
29
|
+
|
|
30
|
+
const StorybookView = ({ catalog, title = "GTKX Storybook" }: StorybookViewProps): ReactNode => {
|
|
31
|
+
const subscribe = useCallback((listener: () => void) => catalog.subscribe(listener), [catalog]);
|
|
32
|
+
const getSnapshot = useCallback(() => catalog.getSnapshot(), [catalog]);
|
|
33
|
+
const snapshot = useSyncExternalStore(subscribe, getSnapshot);
|
|
34
|
+
const [selectedId, setSelectedId] = useState<string>();
|
|
35
|
+
const selected = snapshot.stories.find((entry) => entry.id === selectedId) ?? snapshot.stories[0];
|
|
36
|
+
|
|
37
|
+
if (selectedId !== selected?.id) {
|
|
38
|
+
setSelectedId(selected?.id);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<AdwOverlaySplitView
|
|
43
|
+
minSidebarWidth={220}
|
|
44
|
+
maxSidebarWidth={300}
|
|
45
|
+
sidebar={(
|
|
46
|
+
<AdwToolbarView
|
|
47
|
+
topBar={(
|
|
48
|
+
<AdwHeaderBar
|
|
49
|
+
titleWidget={(
|
|
50
|
+
<AdwWindowTitle title={title} subtitle={`${String(snapshot.stories.length)} stories`} />
|
|
51
|
+
)}
|
|
52
|
+
/>
|
|
53
|
+
)}
|
|
54
|
+
>
|
|
55
|
+
<StoryNavigator
|
|
56
|
+
stories={snapshot.stories}
|
|
57
|
+
errors={snapshot.errors}
|
|
58
|
+
selectedId={selected?.id}
|
|
59
|
+
onSelect={setSelectedId}
|
|
60
|
+
/>
|
|
61
|
+
</AdwToolbarView>
|
|
62
|
+
)}
|
|
63
|
+
>
|
|
64
|
+
{selected
|
|
65
|
+
? <StorybookPreview key={selected.id} entry={selected} />
|
|
66
|
+
: (
|
|
67
|
+
<AdwToolbarView topBar={<AdwHeaderBar />}>
|
|
68
|
+
<AdwStatusPage
|
|
69
|
+
name="storybook-empty"
|
|
70
|
+
title={snapshot.isLoading ? "Loading stories" : "Choose a story"}
|
|
71
|
+
iconName="applications-development-symbolic"
|
|
72
|
+
description="Stories from your project appear in the sidebar."
|
|
73
|
+
/>
|
|
74
|
+
</AdwToolbarView>
|
|
75
|
+
)}
|
|
76
|
+
</AdwOverlaySplitView>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/** Renders the Adwaita explorer application with navigation, story previews, controls, and actions. */
|
|
81
|
+
const Storybook = ({
|
|
82
|
+
catalog,
|
|
83
|
+
applicationId = "org.gtkx.Storybook",
|
|
84
|
+
title = "GTKX Storybook",
|
|
85
|
+
}: StorybookProps): ReactNode => (
|
|
86
|
+
<AdwApplication applicationId={applicationId} flags={Gio.ApplicationFlags.NON_UNIQUE}>
|
|
87
|
+
<AdwApplicationWindow title={title} defaultWidth={1200} defaultHeight={800} onCloseRequest={quit}>
|
|
88
|
+
<StorybookView catalog={catalog} title={title} />
|
|
89
|
+
</AdwApplicationWindow>
|
|
90
|
+
</AdwApplication>
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
export { Storybook };
|
package/src/explorer.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { createRoot } from "@gtkx/react";
|
|
2
|
+
import { createElement } from "react";
|
|
3
|
+
import type { StorySource } from "./catalog.js";
|
|
4
|
+
import type { Preview } from "./types.js";
|
|
5
|
+
import { StoryCatalog } from "./catalog.js";
|
|
6
|
+
import { Storybook } from "./explorer-view.js";
|
|
7
|
+
|
|
8
|
+
/** Controls a running native Storybook explorer and its source catalog. */
|
|
9
|
+
type StorybookSession = {
|
|
10
|
+
/** Replaces discovered sources and preview annotations, rejecting if any source fails to load. */
|
|
11
|
+
updateStories: (sources: StorySource[], preview?: Preview) => Promise<void>;
|
|
12
|
+
/** Displays a session failure while keeping successfully loaded stories available. */
|
|
13
|
+
reportStorybookError: (cause: unknown) => void;
|
|
14
|
+
/** Unmounts the explorer and its story windows or dialogs. */
|
|
15
|
+
stop: () => void;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/** Starts a native explorer and returns the handle used to load stories and stop it. */
|
|
19
|
+
const startStorybook = (options: {
|
|
20
|
+
/** Gio application identifier assigned to the explorer application. */
|
|
21
|
+
applicationId: string;
|
|
22
|
+
}): StorybookSession => {
|
|
23
|
+
const catalog = new StoryCatalog();
|
|
24
|
+
const root = createRoot(undefined, { onUncaughtError: catalog.reportError.bind(catalog) });
|
|
25
|
+
root.render(createElement(Storybook, { catalog, applicationId: options.applicationId }));
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
updateStories: (sources, preview) => catalog.load(sources, preview),
|
|
29
|
+
reportStorybookError: (cause) => {
|
|
30
|
+
catalog.reportError(cause);
|
|
31
|
+
},
|
|
32
|
+
stop: () => {
|
|
33
|
+
root.unmount();
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export { startStorybook };
|
|
39
|
+
export type { StorybookSession };
|
|
40
|
+
export type { StoryCatalogSnapshot, StoryEntry, StoryLoadError, StorySource } from "./catalog.js";
|
|
41
|
+
|
|
42
|
+
export { StoryCatalog } from "./catalog.js";
|
|
43
|
+
|
|
44
|
+
export { Storybook } from "./explorer-view.js";
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { action } from "./actions.js";
|
|
2
|
+
export type { ActionCallback } from "./actions.js";
|
|
3
|
+
export { composeStories, composeStory } from "./compose.js";
|
|
4
|
+
export type {
|
|
5
|
+
Args,
|
|
6
|
+
ArgTypes,
|
|
7
|
+
ComposedStories,
|
|
8
|
+
ComposedStory,
|
|
9
|
+
Decorator,
|
|
10
|
+
Meta,
|
|
11
|
+
Preview,
|
|
12
|
+
StoryContext,
|
|
13
|
+
StoryContextUpdate,
|
|
14
|
+
StoryObj,
|
|
15
|
+
StoryRender,
|
|
16
|
+
} from "./types.js";
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import type * as Gtk from "@gtkx/gi/gtk";
|
|
2
|
+
import type { ComponentType, ReactNode } from "react";
|
|
3
|
+
import type {
|
|
4
|
+
ComponentAnnotations,
|
|
5
|
+
GlobalTypes,
|
|
6
|
+
Renderer,
|
|
7
|
+
ArgTypes as StorybookArgTypes,
|
|
8
|
+
StoryContext as StorybookContext,
|
|
9
|
+
} from "storybook/internal/types";
|
|
10
|
+
|
|
11
|
+
/** Named values supplied to a story, including component props and custom render args. */
|
|
12
|
+
type Args = Record<string, unknown>;
|
|
13
|
+
|
|
14
|
+
/** Storybook renderer types for React content rendered as native GTK widgets. */
|
|
15
|
+
type NativeRenderer<TArgs extends object = Args> = Renderer & {
|
|
16
|
+
/** React component receiving the story's args. */
|
|
17
|
+
component: ComponentType<TArgs>;
|
|
18
|
+
/** React content returned by story rendering. */
|
|
19
|
+
storyResult: ReactNode;
|
|
20
|
+
/** Native widget type representing the preview canvas. */
|
|
21
|
+
canvasElement: Gtk.Widget;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** Explicit argument metadata used to configure native controls and named actions. */
|
|
25
|
+
type ArgTypes<TArgs extends object = Args> = Partial<StorybookArgTypes<TArgs>>;
|
|
26
|
+
|
|
27
|
+
/** Composed story identity, args, and annotations available to render functions and decorators. */
|
|
28
|
+
type StoryContext<TArgs extends object = Args> = Pick<
|
|
29
|
+
StorybookContext<NativeRenderer<TArgs>, TArgs>,
|
|
30
|
+
"id" | "name" | "title" | "args" | "initialArgs" | "argTypes" | "viewMode"
|
|
31
|
+
> & {
|
|
32
|
+
/** Parameters merged from preview, component metadata, and the story. */
|
|
33
|
+
parameters: Args;
|
|
34
|
+
/** Global values after applying preview defaults and metadata or story overrides. */
|
|
35
|
+
globals: Args;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/** Renders native React content from the current story args and composition context. */
|
|
39
|
+
type StoryRender<TArgs extends object = Args> = (args: TArgs, context: StoryContext<TArgs>) => ReactNode;
|
|
40
|
+
|
|
41
|
+
/** Values a decorator can override when invoking the decorated story. */
|
|
42
|
+
type StoryContextUpdate<TArgs extends object = Args> = {
|
|
43
|
+
/** Partial args merged into the current invocation without changing composed defaults. */
|
|
44
|
+
args?: Partial<TArgs>;
|
|
45
|
+
/** Global values overridden for the decorated invocation. */
|
|
46
|
+
globals?: Args;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/** Wraps story content and can override args or globals when invoking the supplied Story function. */
|
|
50
|
+
type Decorator<TArgs extends object = Args> = (
|
|
51
|
+
Story: (update?: StoryContextUpdate<TArgs>) => ReactNode,
|
|
52
|
+
context: StoryContext<TArgs>,
|
|
53
|
+
) => ReactNode;
|
|
54
|
+
|
|
55
|
+
/** Shared CSF annotations accepted at preview, component, and story scope. */
|
|
56
|
+
type Annotations<TArgs extends object> = {
|
|
57
|
+
/** Default args, with more specific annotation scopes taking precedence. */
|
|
58
|
+
args?: Partial<TArgs>;
|
|
59
|
+
/** Explicit argument descriptions, controls, and action registrations. */
|
|
60
|
+
argTypes?: ArgTypes<TArgs>;
|
|
61
|
+
/** Parameters merged across scopes, including native preview and layout settings. */
|
|
62
|
+
parameters?: Args;
|
|
63
|
+
/** Wrappers composed around the story's render function. */
|
|
64
|
+
decorators?: Decorator<TArgs>[];
|
|
65
|
+
/** Custom renderer used in place of the component or a less specific render annotation. */
|
|
66
|
+
render?: StoryRender<TArgs>;
|
|
67
|
+
/** Storybook tags composed with tags from other annotation scopes. */
|
|
68
|
+
tags?: string[];
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/** Default export metadata describing a component's stories and their shared annotations. */
|
|
72
|
+
type ComponentMeta<TArgs extends object> = Annotations<TArgs> & Pick<
|
|
73
|
+
ComponentAnnotations<NativeRenderer<TArgs>, TArgs>,
|
|
74
|
+
"title" | "id" | "includeStories" | "excludeStories"
|
|
75
|
+
> & {
|
|
76
|
+
/** Component rendered with story args when no custom render annotation applies. */
|
|
77
|
+
component?: ComponentType<TArgs>;
|
|
78
|
+
/** Global overrides shared by this component's stories. */
|
|
79
|
+
globals?: Args;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/** Resolves a component's props or accepts an explicitly supplied args object type. */
|
|
83
|
+
type ComponentOrProps<T> = T extends ComponentType<infer Props extends object>
|
|
84
|
+
? Props
|
|
85
|
+
: T extends object ? T : Args;
|
|
86
|
+
|
|
87
|
+
/** Presents combined inferred args as a single object type. */
|
|
88
|
+
type Simplify<T> = { [Key in keyof T]: T[Key] };
|
|
89
|
+
|
|
90
|
+
/** Combines the argument requirements of every member of a union. */
|
|
91
|
+
type UnionToIntersection<T> = (
|
|
92
|
+
T extends unknown ? (value: T) => void : never
|
|
93
|
+
) extends (value: infer Intersection) => void ? Intersection : never;
|
|
94
|
+
|
|
95
|
+
/** Retains explicitly named args while removing broad index signatures. */
|
|
96
|
+
type DeclaredArgs<T> = {
|
|
97
|
+
[Key in keyof T as string extends Key
|
|
98
|
+
? never
|
|
99
|
+
: number extends Key ? never : symbol extends Key ? never : Key]: T[Key];
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/** Extracts the args accepted by a custom render function. */
|
|
103
|
+
type RenderArgs<T> = T extends (args: infer Props extends object, context: never) => ReactNode ? Props : object;
|
|
104
|
+
|
|
105
|
+
/** Combines the args required by a metadata object's decorators. */
|
|
106
|
+
type DecoratorArgs<T> = T extends readonly (infer Item)[]
|
|
107
|
+
? UnionToIntersection<Item extends Decorator<infer Props extends object> ? Props : object>
|
|
108
|
+
: object;
|
|
109
|
+
|
|
110
|
+
/** Infers story args from component props, render functions, decorators, or declared defaults. */
|
|
111
|
+
type ArgsFrom<T> = T extends { component?: infer Component; render?: infer Render; decorators?: infer Decorators }
|
|
112
|
+
? Simplify<
|
|
113
|
+
(NonNullable<Component> extends ComponentType<infer Props extends object> ? Props : object) &
|
|
114
|
+
DeclaredArgs<RenderArgs<NonNullable<Render>> & DecoratorArgs<NonNullable<Decorators>>>
|
|
115
|
+
>
|
|
116
|
+
: T extends { args: infer Defaults extends object } ? Defaults : Args;
|
|
117
|
+
|
|
118
|
+
/** CSF3 default export metadata typed from a React component or an explicit args type. */
|
|
119
|
+
type Meta<T = Args> = ComponentMeta<ComponentOrProps<T>>;
|
|
120
|
+
|
|
121
|
+
/** Annotations for one CSF3 story object, including its display name and global overrides. */
|
|
122
|
+
type StoryAnnotations<TArgs extends object> = Annotations<TArgs> & {
|
|
123
|
+
/** Display name overriding the name derived from the story's named export. */
|
|
124
|
+
name?: string;
|
|
125
|
+
/** Global overrides applied specifically to this story. */
|
|
126
|
+
globals?: Args;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/** Extracts args already defaulted by component metadata. */
|
|
130
|
+
type DefaultArgs<T> = T extends { args: infer Defaults } ? Defaults : Record<never, never>;
|
|
131
|
+
|
|
132
|
+
/** Makes args with metadata defaults optional while preserving other required args. */
|
|
133
|
+
type RequiredStoryArgs<T, TArgs extends object> =
|
|
134
|
+
Omit<TArgs, keyof DefaultArgs<T>> & Partial<Pick<TArgs, keyof TArgs & keyof DefaultArgs<T>>>;
|
|
135
|
+
|
|
136
|
+
/** CSF3 story object inferred from metadata, a React component, or an explicit args type. */
|
|
137
|
+
type StoryObj<T = Args> = T extends { component?: unknown; render?: unknown; decorators?: unknown; args?: unknown }
|
|
138
|
+
? StoryAnnotations<ArgsFrom<T>> & (Record<never, never> extends RequiredStoryArgs<T, ArgsFrom<T>>
|
|
139
|
+
? { args?: RequiredStoryArgs<T, ArgsFrom<T>> }
|
|
140
|
+
: { args: RequiredStoryArgs<T, ArgsFrom<T>> })
|
|
141
|
+
: StoryAnnotations<ComponentOrProps<T>>;
|
|
142
|
+
|
|
143
|
+
/** Project annotations shared by all composed stories. */
|
|
144
|
+
type Preview = Annotations<Args> & {
|
|
145
|
+
/** Initial global values available in story rendering and decorators. */
|
|
146
|
+
initialGlobals?: Args;
|
|
147
|
+
/** Storybook metadata describing global values; native toolbar controls are not provided. */
|
|
148
|
+
globalTypes?: GlobalTypes;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/** Renderable story with merged annotations; props override its composed default args. */
|
|
152
|
+
type ComposedStory<TArgs extends object = Args> = ((args: Partial<TArgs>) => ReactNode) & {
|
|
153
|
+
/** Default args merged across preview, component metadata, and story annotations. */
|
|
154
|
+
args: Partial<TArgs>;
|
|
155
|
+
/** Composed argument metadata available to native controls and actions. */
|
|
156
|
+
argTypes: ArgTypes<TArgs>;
|
|
157
|
+
/** Composed parameters available to the renderer and explorer. */
|
|
158
|
+
parameters: Args;
|
|
159
|
+
/** Composed global values available in story context. */
|
|
160
|
+
globals: Args;
|
|
161
|
+
/** Storybook identifier derived from component identity and the story export. */
|
|
162
|
+
id: string;
|
|
163
|
+
/** Story name attached by Storybook's portable composition API. */
|
|
164
|
+
storyName: string;
|
|
165
|
+
/** Tags resulting from composition across annotation scopes. */
|
|
166
|
+
tags: string[];
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/** Imported CSF module containing default metadata and named story exports. */
|
|
170
|
+
type StoryModule = {
|
|
171
|
+
/** Component metadata validated when the module is composed. */
|
|
172
|
+
default: unknown;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
/** Named module exports whose types match CSF3 stories, excluding module bookkeeping. */
|
|
176
|
+
type StoryKeys<TModule extends StoryModule> = {
|
|
177
|
+
[Key in keyof TModule]: Key extends "default" | "__esModule" | "__namedExportsOrder"
|
|
178
|
+
? never
|
|
179
|
+
: TModule[Key] extends StoryAnnotations<ArgsFrom<TModule["default"]>>
|
|
180
|
+
? Key
|
|
181
|
+
: never;
|
|
182
|
+
}[keyof TModule] & string;
|
|
183
|
+
|
|
184
|
+
/** Composed stories keyed by their original named exports. */
|
|
185
|
+
type ComposedStories<TModule extends StoryModule> = Record<
|
|
186
|
+
StoryKeys<TModule>,
|
|
187
|
+
ComposedStory<ArgsFrom<TModule["default"]>>
|
|
188
|
+
>;
|
|
189
|
+
|
|
190
|
+
export type {
|
|
191
|
+
Args,
|
|
192
|
+
ArgTypes,
|
|
193
|
+
ComponentMeta,
|
|
194
|
+
ComposedStories,
|
|
195
|
+
ComposedStory,
|
|
196
|
+
Decorator,
|
|
197
|
+
Meta,
|
|
198
|
+
Preview,
|
|
199
|
+
StoryAnnotations,
|
|
200
|
+
StoryContext,
|
|
201
|
+
StoryContextUpdate,
|
|
202
|
+
StoryModule,
|
|
203
|
+
StoryObj,
|
|
204
|
+
StoryRender,
|
|
205
|
+
};
|
package/src/upstream.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { composeStory } from "storybook/preview-api";
|
|
2
|
+
import type {
|
|
3
|
+
ComponentMeta,
|
|
4
|
+
ComposedStory,
|
|
5
|
+
Preview,
|
|
6
|
+
StoryAnnotations,
|
|
7
|
+
StoryRender,
|
|
8
|
+
} from "./types.js";
|
|
9
|
+
|
|
10
|
+
type ComposeStory = <TArgs extends object>(
|
|
11
|
+
...annotations: [
|
|
12
|
+
story: StoryAnnotations<TArgs>,
|
|
13
|
+
meta: ComponentMeta<TArgs>,
|
|
14
|
+
preview: Preview,
|
|
15
|
+
renderer: { render: StoryRender<TArgs> },
|
|
16
|
+
exportName?: string,
|
|
17
|
+
]
|
|
18
|
+
) => ComposedStory<TArgs>;
|
|
19
|
+
|
|
20
|
+
const composeStoryAnnotations = composeStory as ComposeStory;
|
|
21
|
+
|
|
22
|
+
export { composeStoryAnnotations };
|
|
23
|
+
|
|
24
|
+
export { isExportStory, storyNameFromExport } from "storybook/internal/csf";
|