@gtkx/storybook 2.0.0-beta.11 → 2.0.0-beta.13
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/README.md +5 -5
- package/dist/actions-panel.d.ts.map +1 -1
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +2 -1
- package/dist/actions.js.map +1 -1
- package/dist/catalog.d.ts +1 -1
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +11 -5
- package/dist/catalog.js.map +1 -1
- package/dist/compose.d.ts.map +1 -1
- package/dist/compose.js.map +1 -1
- package/dist/config.d.ts +4 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/controls.d.ts +2 -1
- package/dist/controls.d.ts.map +1 -1
- package/dist/controls.js +30 -12
- package/dist/controls.js.map +1 -1
- package/dist/explorer-navigator.d.ts.map +1 -1
- package/dist/explorer-navigator.js +29 -2
- package/dist/explorer-navigator.js.map +1 -1
- package/dist/explorer-preview.d.ts.map +1 -1
- package/dist/explorer-preview.js +29 -13
- package/dist/explorer-preview.js.map +1 -1
- package/dist/explorer-view.d.ts +5 -1
- package/dist/explorer-view.d.ts.map +1 -1
- package/dist/explorer-view.js +17 -9
- package/dist/explorer-view.js.map +1 -1
- package/dist/explorer.d.ts +4 -0
- package/dist/explorer.d.ts.map +1 -1
- package/dist/explorer.js +6 -1
- package/dist/explorer.js.map +1 -1
- package/dist/types.d.ts +8 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/upstream.d.ts +4 -2
- package/dist/upstream.d.ts.map +1 -1
- package/dist/upstream.js +3 -1
- package/dist/upstream.js.map +1 -1
- package/package.json +14 -21
- package/src/actions.ts +28 -33
- package/src/catalog.ts +29 -19
- package/src/compose.ts +13 -7
- package/src/config.ts +4 -1
- package/src/controls.tsx +42 -18
- package/src/explorer-navigator.tsx +63 -28
- package/src/explorer-preview.tsx +95 -54
- package/src/explorer-view.tsx +56 -27
- package/src/explorer.ts +12 -1
- package/src/types.ts +42 -30
- package/src/upstream.ts +6 -8
package/src/explorer-preview.tsx
CHANGED
|
@@ -4,12 +4,14 @@ import * as Gtk from "@gtkx/gi/gtk";
|
|
|
4
4
|
import { AdwHeaderBar, AdwStatusPage, AdwToolbarView, AdwWindowTitle } from "@gtkx/jsx/adw";
|
|
5
5
|
import { GtkBox, GtkButton, GtkScrolledWindow } from "@gtkx/jsx/gtk";
|
|
6
6
|
import { createPortal, rootElement } from "@gtkx/react";
|
|
7
|
+
import { normalizeError } from "@gtkx/utils";
|
|
7
8
|
import { Component, useCallback, useMemo, useState } from "react";
|
|
8
9
|
import type { StoryEntry } from "./catalog.js";
|
|
9
|
-
import type { Args, ComposedStory } from "./types.js";
|
|
10
|
+
import type { Args, ArgTypes, ComposedStory } from "./types.js";
|
|
10
11
|
import { ActionsPanel } from "./actions-panel.js";
|
|
11
12
|
import { ActionStore, bindActions } from "./actions.js";
|
|
12
13
|
import { Controls } from "./controls.js";
|
|
14
|
+
import { isConditionalArgIncluded } from "./upstream.js";
|
|
13
15
|
|
|
14
16
|
type PreviewProps = {
|
|
15
17
|
entry: StoryEntry;
|
|
@@ -32,23 +34,25 @@ class PreviewBoundary extends Component<BoundaryProps, BoundaryState> {
|
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
static getDerivedStateFromError(error: unknown): Pick<BoundaryState, "error"> {
|
|
35
|
-
return { error:
|
|
37
|
+
return { error: normalizeError(error) };
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
override state: BoundaryState = { error: null, args: this.props.args };
|
|
39
41
|
|
|
40
42
|
override render(): ReactNode {
|
|
41
|
-
return this.state.error === null
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
43
|
+
return this.state.error === null ? (
|
|
44
|
+
this.props.children
|
|
45
|
+
) : (
|
|
46
|
+
<AdwStatusPage
|
|
47
|
+
name="storybook-preview-error"
|
|
48
|
+
title="Preview failed"
|
|
49
|
+
iconName="dialog-error-symbolic"
|
|
50
|
+
description={
|
|
51
|
+
markupEscapeText(this.state.error.message.slice(0, 500), -1) +
|
|
52
|
+
"\n\nAdjust controls, reset the preview, or select another story to continue."
|
|
53
|
+
}
|
|
54
|
+
/>
|
|
55
|
+
);
|
|
52
56
|
}
|
|
53
57
|
}
|
|
54
58
|
|
|
@@ -63,6 +67,36 @@ const previewMode = (story: ComposedStory): "content" | "window" | "dialog" => {
|
|
|
63
67
|
return mode;
|
|
64
68
|
};
|
|
65
69
|
|
|
70
|
+
const mapValue = (value: unknown, mapping: Record<string, unknown>): unknown => {
|
|
71
|
+
const key = String(value);
|
|
72
|
+
|
|
73
|
+
return Object.hasOwn(mapping, key) ? mapping[key] : value;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const mapArgs = (args: Args, argTypes: ArgTypes): Args =>
|
|
77
|
+
Object.fromEntries(
|
|
78
|
+
Object.entries(args).map(([argument, value]) => {
|
|
79
|
+
const mapping = argTypes[argument]?.mapping;
|
|
80
|
+
|
|
81
|
+
if (mapping === undefined) {
|
|
82
|
+
return [argument, value];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return [
|
|
86
|
+
argument,
|
|
87
|
+
Array.isArray(value) ? value.map((item) => mapValue(item, mapping)) : mapValue(value, mapping),
|
|
88
|
+
];
|
|
89
|
+
}),
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const includedArgs = (args: Args, argTypes: ArgTypes, globals: Args): Args =>
|
|
93
|
+
Object.fromEntries(
|
|
94
|
+
Object.entries(args).map(([argument, value]) => [
|
|
95
|
+
argument,
|
|
96
|
+
isConditionalArgIncluded(argTypes[argument] ?? {}, args, globals) ? value : undefined,
|
|
97
|
+
]),
|
|
98
|
+
);
|
|
99
|
+
|
|
66
100
|
const PreviewContent = ({ story: Story, args }: { story: ComposedStory; args: Args }): ReactNode => {
|
|
67
101
|
const mode = previewMode(Story);
|
|
68
102
|
|
|
@@ -83,20 +117,20 @@ const PreviewContent = ({ story: Story, args }: { story: ComposedStory; args: Ar
|
|
|
83
117
|
const align = Story.parameters.layout === "centered" ? Gtk.Align.CENTER : Gtk.Align.FILL;
|
|
84
118
|
|
|
85
119
|
return (
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
</
|
|
120
|
+
<GtkScrolledWindow hexpand vexpand propagateNaturalWidth propagateNaturalHeight>
|
|
121
|
+
<GtkBox
|
|
122
|
+
name="storybook-canvas"
|
|
123
|
+
orientation={Gtk.Orientation.VERTICAL}
|
|
124
|
+
halign={align}
|
|
125
|
+
valign={align}
|
|
126
|
+
marginTop={margin}
|
|
127
|
+
marginBottom={margin}
|
|
128
|
+
marginStart={margin}
|
|
129
|
+
marginEnd={margin}
|
|
130
|
+
>
|
|
131
|
+
<Story {...args} />
|
|
132
|
+
</GtkBox>
|
|
133
|
+
</GtkScrolledWindow>
|
|
100
134
|
);
|
|
101
135
|
};
|
|
102
136
|
|
|
@@ -108,14 +142,24 @@ const usePreviewState = ({ entry }: PreviewProps) => {
|
|
|
108
142
|
error: null,
|
|
109
143
|
actions: new ActionStore(),
|
|
110
144
|
}));
|
|
111
|
-
const onError = useCallback(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
145
|
+
const onError = useCallback(
|
|
146
|
+
(error: Error) => {
|
|
147
|
+
setState((current) =>
|
|
148
|
+
current.story === state.story && current.generation === state.generation
|
|
149
|
+
? { ...current, error }
|
|
150
|
+
: current,
|
|
151
|
+
);
|
|
152
|
+
},
|
|
153
|
+
[state.story, state.generation],
|
|
154
|
+
);
|
|
116
155
|
const boundArgs = useMemo(
|
|
117
|
-
() =>
|
|
118
|
-
|
|
156
|
+
() =>
|
|
157
|
+
includedArgs(
|
|
158
|
+
bindActions(mapArgs(state.args, state.story.argTypes), state.story.argTypes, state.actions, onError),
|
|
159
|
+
state.story.argTypes,
|
|
160
|
+
state.story.globals,
|
|
161
|
+
),
|
|
162
|
+
[state.args, state.story.argTypes, state.story.globals, state.actions, onError],
|
|
119
163
|
);
|
|
120
164
|
|
|
121
165
|
if (state.story !== entry.story) {
|
|
@@ -148,12 +192,7 @@ const usePreviewState = ({ entry }: PreviewProps) => {
|
|
|
148
192
|
type InspectorProps = PreviewProps & { actions: ActionStore; args: Args; onChange: (args: Args) => void };
|
|
149
193
|
|
|
150
194
|
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
|
-
>
|
|
195
|
+
<GtkScrolledWindow name="storybook-inspector" widthRequest={280} hscrollbarPolicy={Gtk.PolicyType.NEVER} vexpand>
|
|
157
196
|
<GtkBox
|
|
158
197
|
orientation={Gtk.Orientation.VERTICAL}
|
|
159
198
|
spacing={24}
|
|
@@ -162,7 +201,7 @@ const StoryInspector = ({ entry, actions, args, onChange }: InspectorProps): Rea
|
|
|
162
201
|
marginStart={12}
|
|
163
202
|
marginEnd={12}
|
|
164
203
|
>
|
|
165
|
-
<Controls argTypes={entry.controls} args={args} onChange={onChange} />
|
|
204
|
+
<Controls argTypes={entry.controls} args={args} globals={entry.story.globals} onChange={onChange} />
|
|
166
205
|
<ActionsPanel store={actions} />
|
|
167
206
|
</GtkBox>
|
|
168
207
|
</GtkScrolledWindow>
|
|
@@ -175,27 +214,29 @@ const StorybookPreview = ({ entry }: PreviewProps): ReactNode => {
|
|
|
175
214
|
<AdwToolbarView
|
|
176
215
|
hexpand
|
|
177
216
|
vexpand
|
|
178
|
-
|
|
217
|
+
top={
|
|
179
218
|
<AdwHeaderBar
|
|
180
219
|
titleWidget={<AdwWindowTitle title={entry.name} subtitle={entry.title} />}
|
|
181
220
|
end={<GtkButton label="Reset story" onClicked={reset} />}
|
|
182
221
|
/>
|
|
183
|
-
|
|
222
|
+
}
|
|
184
223
|
>
|
|
185
224
|
<GtkBox>
|
|
186
225
|
<GtkBox orientation={Gtk.Orientation.VERTICAL} hexpand vexpand>
|
|
187
226
|
<PreviewBoundary key={state.generation} args={state.args}>
|
|
188
|
-
{state.error === null
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
227
|
+
{state.error === null ? (
|
|
228
|
+
<PreviewContent story={entry.story} args={boundArgs} />
|
|
229
|
+
) : (
|
|
230
|
+
<AdwStatusPage
|
|
231
|
+
name="storybook-preview-error"
|
|
232
|
+
title="Story action failed"
|
|
233
|
+
iconName="dialog-error-symbolic"
|
|
234
|
+
description={
|
|
235
|
+
markupEscapeText(state.error.message.slice(0, 500), -1) +
|
|
236
|
+
"\n\nReset the preview or select another story to continue."
|
|
237
|
+
}
|
|
238
|
+
/>
|
|
239
|
+
)}
|
|
199
240
|
</PreviewBoundary>
|
|
200
241
|
</GtkBox>
|
|
201
242
|
<StoryInspector entry={entry} actions={state.actions} args={state.args} onChange={onChange} />
|
package/src/explorer-view.tsx
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
AdwWindowTitle,
|
|
11
11
|
} from "@gtkx/jsx/adw";
|
|
12
12
|
import { quit } from "@gtkx/react";
|
|
13
|
-
import { useCallback, useState, useSyncExternalStore } from "react";
|
|
13
|
+
import { useCallback, useEffect, useEffectEvent, useState, useSyncExternalStore } from "react";
|
|
14
14
|
import type { StoryCatalog } from "./catalog.js";
|
|
15
15
|
import { StoryNavigator } from "./explorer-navigator.js";
|
|
16
16
|
import { StorybookPreview } from "./explorer-preview.js";
|
|
@@ -23,56 +23,78 @@ type StorybookProps = {
|
|
|
23
23
|
applicationId?: string;
|
|
24
24
|
/** Explorer window and sidebar title; defaults to GTKX Storybook. */
|
|
25
25
|
title?: string;
|
|
26
|
+
/** Story selected once the catalog lists it; the first story is selected otherwise. */
|
|
27
|
+
initialStoryId?: string | undefined;
|
|
28
|
+
/** Called with the identifier of each story the explorer selects. */
|
|
29
|
+
onStorySelected?: ((id: string) => void) | undefined;
|
|
26
30
|
};
|
|
27
31
|
|
|
28
|
-
type StorybookViewProps = Pick<StorybookProps, "catalog" | "title">;
|
|
32
|
+
type StorybookViewProps = Pick<StorybookProps, "catalog" | "title" | "initialStoryId" | "onStorySelected">;
|
|
29
33
|
|
|
30
|
-
const
|
|
34
|
+
const storyCount = (count: number): string => (count === 1 ? "1 story" : `${String(count)} stories`);
|
|
35
|
+
|
|
36
|
+
const StorybookView = ({
|
|
37
|
+
catalog,
|
|
38
|
+
title = "GTKX Storybook",
|
|
39
|
+
initialStoryId,
|
|
40
|
+
onStorySelected,
|
|
41
|
+
}: StorybookViewProps): ReactNode => {
|
|
31
42
|
const subscribe = useCallback((listener: () => void) => catalog.subscribe(listener), [catalog]);
|
|
32
43
|
const getSnapshot = useCallback(() => catalog.getSnapshot(), [catalog]);
|
|
33
44
|
const snapshot = useSyncExternalStore(subscribe, getSnapshot);
|
|
34
|
-
const [selectedId, setSelectedId] = useState
|
|
45
|
+
const [selectedId, setSelectedId] = useState(initialStoryId);
|
|
35
46
|
const selected = snapshot.stories.find((entry) => entry.id === selectedId) ?? snapshot.stories[0];
|
|
36
47
|
|
|
37
|
-
if (selectedId !== selected
|
|
38
|
-
setSelectedId(selected
|
|
48
|
+
if (selected !== undefined && selectedId !== selected.id) {
|
|
49
|
+
setSelectedId(selected.id);
|
|
39
50
|
}
|
|
40
51
|
|
|
52
|
+
const selectedStoryId = selected?.id;
|
|
53
|
+
const reportSelection = useEffectEvent((id: string) => {
|
|
54
|
+
onStorySelected?.(id);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (selectedStoryId !== undefined) {
|
|
59
|
+
reportSelection(selectedStoryId);
|
|
60
|
+
}
|
|
61
|
+
}, [selectedStoryId]);
|
|
62
|
+
|
|
41
63
|
return (
|
|
42
64
|
<AdwOverlaySplitView
|
|
43
65
|
minSidebarWidth={220}
|
|
44
66
|
maxSidebarWidth={300}
|
|
45
|
-
sidebar={
|
|
67
|
+
sidebar={
|
|
46
68
|
<AdwToolbarView
|
|
47
|
-
|
|
69
|
+
top={
|
|
48
70
|
<AdwHeaderBar
|
|
49
|
-
titleWidget={
|
|
50
|
-
<AdwWindowTitle title={title} subtitle={
|
|
51
|
-
|
|
71
|
+
titleWidget={
|
|
72
|
+
<AdwWindowTitle title={title} subtitle={storyCount(snapshot.stories.length)} />
|
|
73
|
+
}
|
|
52
74
|
/>
|
|
53
|
-
|
|
75
|
+
}
|
|
54
76
|
>
|
|
55
77
|
<StoryNavigator
|
|
56
78
|
stories={snapshot.stories}
|
|
57
79
|
errors={snapshot.errors}
|
|
58
|
-
selectedId={
|
|
80
|
+
selectedId={selectedStoryId}
|
|
59
81
|
onSelect={setSelectedId}
|
|
60
82
|
/>
|
|
61
83
|
</AdwToolbarView>
|
|
62
|
-
|
|
84
|
+
}
|
|
63
85
|
>
|
|
64
|
-
{selected
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
86
|
+
{selected ? (
|
|
87
|
+
<StorybookPreview key={selected.id} entry={selected} />
|
|
88
|
+
) : (
|
|
89
|
+
<AdwToolbarView top={<AdwHeaderBar />}>
|
|
90
|
+
<AdwStatusPage
|
|
91
|
+
name="storybook-empty"
|
|
92
|
+
title={snapshot.isLoading ? "Loading stories" : "Choose a story"}
|
|
93
|
+
iconName="applications-development-symbolic"
|
|
94
|
+
description="Stories from your project appear in the sidebar."
|
|
95
|
+
/>
|
|
96
|
+
</AdwToolbarView>
|
|
97
|
+
)}
|
|
76
98
|
</AdwOverlaySplitView>
|
|
77
99
|
);
|
|
78
100
|
};
|
|
@@ -82,10 +104,17 @@ const Storybook = ({
|
|
|
82
104
|
catalog,
|
|
83
105
|
applicationId = "org.gtkx.Storybook",
|
|
84
106
|
title = "GTKX Storybook",
|
|
107
|
+
initialStoryId,
|
|
108
|
+
onStorySelected,
|
|
85
109
|
}: StorybookProps): ReactNode => (
|
|
86
110
|
<AdwApplication applicationId={applicationId} flags={Gio.ApplicationFlags.NON_UNIQUE}>
|
|
87
111
|
<AdwApplicationWindow title={title} defaultWidth={1200} defaultHeight={800} onCloseRequest={quit}>
|
|
88
|
-
<StorybookView
|
|
112
|
+
<StorybookView
|
|
113
|
+
catalog={catalog}
|
|
114
|
+
title={title}
|
|
115
|
+
initialStoryId={initialStoryId}
|
|
116
|
+
onStorySelected={onStorySelected}
|
|
117
|
+
/>
|
|
89
118
|
</AdwApplicationWindow>
|
|
90
119
|
</AdwApplication>
|
|
91
120
|
);
|
package/src/explorer.ts
CHANGED
|
@@ -19,10 +19,21 @@ type StorybookSession = {
|
|
|
19
19
|
const startStorybook = (options: {
|
|
20
20
|
/** Gio application identifier assigned to the explorer application. */
|
|
21
21
|
applicationId: string;
|
|
22
|
+
/** Story to select once it is loaded, such as the one a restarted explorer was showing. */
|
|
23
|
+
initialStoryId?: string | undefined;
|
|
24
|
+
/** Called with the identifier of each story the explorer selects. */
|
|
25
|
+
onStorySelected?: ((id: string) => void) | undefined;
|
|
22
26
|
}): StorybookSession => {
|
|
23
27
|
const catalog = new StoryCatalog();
|
|
24
28
|
const root = createRoot(undefined, { onUncaughtError: catalog.reportError.bind(catalog) });
|
|
25
|
-
root.render(
|
|
29
|
+
root.render(
|
|
30
|
+
createElement(Storybook, {
|
|
31
|
+
catalog,
|
|
32
|
+
applicationId: options.applicationId,
|
|
33
|
+
initialStoryId: options.initialStoryId,
|
|
34
|
+
onStorySelected: options.onStorySelected,
|
|
35
|
+
}),
|
|
36
|
+
);
|
|
26
37
|
|
|
27
38
|
return {
|
|
28
39
|
updateStories: (sources, preview) => catalog.load(sources, preview),
|
package/src/types.ts
CHANGED
|
@@ -71,20 +71,16 @@ type Annotations<TArgs extends object> = {
|
|
|
71
71
|
};
|
|
72
72
|
|
|
73
73
|
/** Default export metadata describing a component's stories and their shared annotations. */
|
|
74
|
-
type ComponentMeta<TArgs extends object> = Annotations<TArgs> &
|
|
75
|
-
ComponentAnnotations<NativeRenderer<TArgs>, TArgs>,
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
globals?: Args;
|
|
82
|
-
};
|
|
74
|
+
type ComponentMeta<TArgs extends object> = Annotations<TArgs> &
|
|
75
|
+
Pick<ComponentAnnotations<NativeRenderer<TArgs>, TArgs>, "title" | "id" | "includeStories" | "excludeStories"> & {
|
|
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
|
+
};
|
|
83
81
|
|
|
84
82
|
/** Resolves a component's props or accepts an explicitly supplied args object type. */
|
|
85
|
-
type ComponentOrProps<T> = T extends ComponentType<infer Props extends object>
|
|
86
|
-
? Props
|
|
87
|
-
: T extends object ? T : Args;
|
|
83
|
+
type ComponentOrProps<T> = T extends ComponentType<infer Props extends object> ? Props : T extends object ? T : Args;
|
|
88
84
|
|
|
89
85
|
/** Presents combined inferred args as a single object type. */
|
|
90
86
|
type Simplify<T> = { [Key in keyof T]: T[Key] };
|
|
@@ -92,10 +88,12 @@ type Simplify<T> = { [Key in keyof T]: T[Key] };
|
|
|
92
88
|
/** Infers story args from component props, render functions, decorators, or declared defaults. */
|
|
93
89
|
type ArgsFrom<T> = T extends { component?: infer Component; render?: unknown; decorators?: unknown }
|
|
94
90
|
? Simplify<
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
: T extends { args: infer Defaults extends object }
|
|
91
|
+
(NonNullable<Component> extends ComponentType<infer Props extends object> ? Props : object) &
|
|
92
|
+
ArgsFromMeta<NativeRenderer, T>
|
|
93
|
+
>
|
|
94
|
+
: T extends { args: infer Defaults extends object }
|
|
95
|
+
? Defaults
|
|
96
|
+
: Args;
|
|
99
97
|
|
|
100
98
|
/** CSF3 default export metadata typed from a React component or an explicit args type. */
|
|
101
99
|
type Meta<T = Args> = ComponentMeta<ComponentOrProps<T>>;
|
|
@@ -112,15 +110,16 @@ type StoryAnnotations<TArgs extends object> = Annotations<TArgs> & {
|
|
|
112
110
|
type DefaultArgs<T> = T extends { args: infer Defaults } ? Defaults : Record<never, never>;
|
|
113
111
|
|
|
114
112
|
/** Makes args with metadata defaults optional while preserving other required args. */
|
|
115
|
-
type RequiredStoryArgs<T, TArgs extends object> =
|
|
116
|
-
|
|
113
|
+
type RequiredStoryArgs<T, TArgs extends object> = Omit<TArgs, keyof DefaultArgs<T>> &
|
|
114
|
+
Partial<Pick<TArgs, keyof TArgs & keyof DefaultArgs<T>>>;
|
|
117
115
|
|
|
118
116
|
/** CSF3 story object inferred from metadata, a React component, or an explicit args type. */
|
|
119
117
|
type StoryObj<T = Args> = T extends { component?: unknown; render?: unknown; decorators?: unknown; args?: unknown }
|
|
120
|
-
? StoryAnnotations<ArgsFrom<T>> &
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
118
|
+
? StoryAnnotations<ArgsFrom<T>> &
|
|
119
|
+
Pick<
|
|
120
|
+
StorybookAnnotations<NativeRenderer<ArgsFrom<T>>, ArgsFrom<T>, RequiredStoryArgs<T, ArgsFrom<T>>>,
|
|
121
|
+
"args"
|
|
122
|
+
>
|
|
124
123
|
: StoryAnnotations<ComponentOrProps<T>>;
|
|
125
124
|
|
|
126
125
|
/** Project annotations shared by all composed stories. */
|
|
@@ -155,20 +154,33 @@ type StoryModule = {
|
|
|
155
154
|
default: unknown;
|
|
156
155
|
};
|
|
157
156
|
|
|
157
|
+
/** Whether a story's declared args name only args of the component metadata, as stories typed from the metadata do. */
|
|
158
|
+
type IsMetaArgs<TArgs, TMetaArgs> = [Exclude<keyof TArgs, keyof TMetaArgs>] extends [never] ? true : false;
|
|
159
|
+
|
|
160
|
+
/** Args of a story export: the component metadata's args, or the args type the story declares itself. */
|
|
161
|
+
type StoryArgs<TModule extends StoryModule, TStory> =
|
|
162
|
+
TStory extends StoryAnnotations<infer TArgs extends object>
|
|
163
|
+
? IsMetaArgs<TArgs, ArgsFrom<TModule["default"]>> extends true
|
|
164
|
+
? TStory extends StoryAnnotations<ArgsFrom<TModule["default"]>>
|
|
165
|
+
? ArgsFrom<TModule["default"]>
|
|
166
|
+
: TArgs
|
|
167
|
+
: TArgs
|
|
168
|
+
: never;
|
|
169
|
+
|
|
158
170
|
/** Named module exports whose types match CSF3 stories, excluding module bookkeeping. */
|
|
159
171
|
type StoryKeys<TModule extends StoryModule> = {
|
|
160
172
|
[Key in keyof TModule]: Key extends "default" | "__esModule" | "__namedExportsOrder"
|
|
161
173
|
? never
|
|
162
|
-
: TModule[Key] extends
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}[keyof TModule] &
|
|
174
|
+
: [StoryArgs<TModule, TModule[Key]>] extends [never]
|
|
175
|
+
? never
|
|
176
|
+
: Key;
|
|
177
|
+
}[keyof TModule] &
|
|
178
|
+
string;
|
|
166
179
|
|
|
167
180
|
/** Composed stories keyed by their original named exports. */
|
|
168
|
-
type ComposedStories<TModule extends StoryModule> =
|
|
169
|
-
StoryKeys<TModule
|
|
170
|
-
|
|
171
|
-
>;
|
|
181
|
+
type ComposedStories<TModule extends StoryModule> = {
|
|
182
|
+
[Key in StoryKeys<TModule>]: ComposedStory<StoryArgs<TModule, TModule[Key]>>;
|
|
183
|
+
};
|
|
172
184
|
|
|
173
185
|
export type {
|
|
174
186
|
Args,
|
package/src/upstream.ts
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
|
+
import type { InputType } from "storybook/internal/types";
|
|
2
|
+
import { includeConditionalArg } from "storybook/internal/csf";
|
|
1
3
|
import { composeStory } from "storybook/preview-api";
|
|
2
|
-
import type {
|
|
3
|
-
ComponentMeta,
|
|
4
|
-
ComposedStory,
|
|
5
|
-
Preview,
|
|
6
|
-
StoryAnnotations,
|
|
7
|
-
StoryRender,
|
|
8
|
-
} from "./types.js";
|
|
4
|
+
import type { Args, ComponentMeta, ComposedStory, Preview, StoryAnnotations, StoryRender } from "./types.js";
|
|
9
5
|
|
|
10
6
|
type ComposeStory = <TArgs extends object>(
|
|
11
7
|
...annotations: [
|
|
@@ -19,6 +15,8 @@ type ComposeStory = <TArgs extends object>(
|
|
|
19
15
|
|
|
20
16
|
const composeStoryAnnotations = composeStory as ComposeStory;
|
|
21
17
|
|
|
22
|
-
|
|
18
|
+
const isConditionalArgIncluded: (argType: InputType, args: Args, globals: Args) => boolean = includeConditionalArg;
|
|
19
|
+
|
|
20
|
+
export { composeStoryAnnotations, isConditionalArgIncluded };
|
|
23
21
|
|
|
24
22
|
export { isExportStory, storyNameFromExport } from "storybook/internal/csf";
|