@gtkx/storybook 0.0.1 → 2.0.0-beta.12
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/LICENSE +373 -0
- package/README.md +17 -0
- package/dist/actions-panel.d.ts.map +1 -1
- package/dist/actions.d.ts +0 -3
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +3 -10
- package/dist/actions.js.map +1 -1
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +8 -5
- package/dist/catalog.js.map +1 -1
- package/dist/compose.d.ts.map +1 -1
- package/dist/compose.js +0 -5
- package/dist/compose.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/controls.d.ts.map +1 -1
- package/dist/controls.js +40 -21
- package/dist/controls.js.map +1 -1
- package/dist/explorer-navigator.d.ts.map +1 -1
- package/dist/explorer-navigator.js.map +1 -1
- package/dist/explorer-preview.d.ts.map +1 -1
- package/dist/explorer-preview.js +7 -10
- package/dist/explorer-preview.js.map +1 -1
- package/dist/explorer-view.d.ts.map +1 -1
- package/dist/explorer-view.js +1 -3
- package/dist/explorer-view.js.map +1 -1
- package/dist/explorer.d.ts.map +1 -1
- package/dist/types.d.ts +5 -19
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/upstream.d.ts.map +1 -1
- package/dist/upstream.js.map +1 -1
- package/package.json +74 -82
- package/src/actions.ts +29 -45
- package/src/catalog.ts +17 -10
- package/src/compose.ts +13 -13
- package/src/controls.tsx +49 -25
- package/src/explorer-navigator.tsx +18 -24
- package/src/explorer-preview.tsx +41 -36
- package/src/explorer-view.tsx +18 -18
- package/src/types.ts +28 -45
- package/src/upstream.ts +1 -7
package/src/controls.tsx
CHANGED
|
@@ -7,6 +7,9 @@ import type { Args, ArgTypes } from "./types.js";
|
|
|
7
7
|
|
|
8
8
|
type ControlsProps = { argTypes: ArgTypes; args: Args; onChange: (update: Args) => void };
|
|
9
9
|
|
|
10
|
+
const NO_SELECTION_ID = "no-selection";
|
|
11
|
+
const NO_SELECTION_LABEL = "Choose option...";
|
|
12
|
+
|
|
10
13
|
type ControlProps = {
|
|
11
14
|
argument: string;
|
|
12
15
|
title: string;
|
|
@@ -24,7 +27,7 @@ const isObject = (value: unknown): value is Args =>
|
|
|
24
27
|
const isWithinBoundsOrAbsent = (value: unknown, min: number, max: number): boolean =>
|
|
25
28
|
value === undefined || (typeof value === "number" && Number.isFinite(value) && value >= min && value <= max);
|
|
26
29
|
|
|
27
|
-
const controlTitle = (name: unknown, argument: string): string => typeof name === "string" ? name : argument;
|
|
30
|
+
const controlTitle = (name: unknown, argument: string): string => (typeof name === "string" ? name : argument);
|
|
28
31
|
|
|
29
32
|
const numberDigits = (step: number): number => {
|
|
30
33
|
const [coefficient = "", exponent = "0"] = String(step).split("e", 2);
|
|
@@ -51,9 +54,15 @@ const NumberControl = ({
|
|
|
51
54
|
const step = settings.step ?? 1;
|
|
52
55
|
|
|
53
56
|
if (
|
|
54
|
-
typeof min !== "number" ||
|
|
55
|
-
|
|
56
|
-
step
|
|
57
|
+
typeof min !== "number" ||
|
|
58
|
+
typeof max !== "number" ||
|
|
59
|
+
typeof step !== "number" ||
|
|
60
|
+
!Number.isFinite(min) ||
|
|
61
|
+
!Number.isFinite(max) ||
|
|
62
|
+
!Number.isFinite(step) ||
|
|
63
|
+
step <= 0 ||
|
|
64
|
+
min > max ||
|
|
65
|
+
!isWithinBoundsOrAbsent(value, min, max)
|
|
57
66
|
) {
|
|
58
67
|
return <UnsupportedControl title={title} description="Provide a finite number and valid bounds." />;
|
|
59
68
|
}
|
|
@@ -69,7 +78,7 @@ const NumberControl = ({
|
|
|
69
78
|
digits={numberDigits(step)}
|
|
70
79
|
numeric
|
|
71
80
|
widthChars={8}
|
|
72
|
-
adjustment={
|
|
81
|
+
adjustment={
|
|
73
82
|
<GtkAdjustment
|
|
74
83
|
lower={min}
|
|
75
84
|
upper={max}
|
|
@@ -77,12 +86,9 @@ const NumberControl = ({
|
|
|
77
86
|
pageIncrement={Math.min(Number.MAX_VALUE, step * 10)}
|
|
78
87
|
value={currentValue}
|
|
79
88
|
/>
|
|
80
|
-
|
|
89
|
+
}
|
|
81
90
|
onNotifyValue={(next) => {
|
|
82
|
-
if (
|
|
83
|
-
typeof next === "number" && Number.isFinite(next) &&
|
|
84
|
-
next >= min && next <= max && !Object.is(next, value)
|
|
85
|
-
) {
|
|
91
|
+
if (!Object.is(next, value)) {
|
|
86
92
|
onChange({ [argument]: next });
|
|
87
93
|
}
|
|
88
94
|
}}
|
|
@@ -92,19 +98,31 @@ const NumberControl = ({
|
|
|
92
98
|
|
|
93
99
|
const SelectControl = (props: Omit<ControlProps, "type">): ReactNode => {
|
|
94
100
|
const { argument, title, value, options, settings, isDisabled, onChange } = props;
|
|
95
|
-
if (
|
|
96
|
-
|
|
101
|
+
if (
|
|
102
|
+
!Array.isArray(options) ||
|
|
103
|
+
options.length === 0 ||
|
|
104
|
+
options.some(
|
|
105
|
+
(option: unknown) =>
|
|
106
|
+
option !== null &&
|
|
107
|
+
typeof option !== "string" &&
|
|
108
|
+
typeof option !== "number" &&
|
|
109
|
+
typeof option !== "boolean",
|
|
110
|
+
)
|
|
111
|
+
) {
|
|
97
112
|
return <UnsupportedControl title={title} description="Provide a list of text, number, or boolean options." />;
|
|
98
113
|
}
|
|
99
114
|
|
|
100
115
|
const values: unknown[] = options;
|
|
101
116
|
const selected = values.findIndex((option) => Object.is(option, value));
|
|
102
117
|
const labels = isObject(settings.labels) ? settings.labels : {};
|
|
103
|
-
const items =
|
|
104
|
-
|
|
118
|
+
const items = [
|
|
119
|
+
{ id: NO_SELECTION_ID, value: NO_SELECTION_LABEL },
|
|
120
|
+
...values.map((option, index) => {
|
|
121
|
+
const label = labels[String(option)];
|
|
105
122
|
|
|
106
|
-
|
|
107
|
-
|
|
123
|
+
return { id: String(index), value: typeof label === "string" ? label : String(option) };
|
|
124
|
+
}),
|
|
125
|
+
];
|
|
108
126
|
|
|
109
127
|
return (
|
|
110
128
|
<ComboRow
|
|
@@ -112,13 +130,19 @@ const SelectControl = (props: Omit<ControlProps, "type">): ReactNode => {
|
|
|
112
130
|
title={markupEscapeText(title, -1)}
|
|
113
131
|
sensitive={!isDisabled}
|
|
114
132
|
items={items}
|
|
115
|
-
selectedId={selected === -1 ?
|
|
133
|
+
selectedId={selected === -1 ? NO_SELECTION_ID : String(selected)}
|
|
116
134
|
onSelectionChanged={(id) => {
|
|
117
|
-
|
|
135
|
+
if (id === NO_SELECTION_ID) {
|
|
136
|
+
onChange({ [argument]: undefined });
|
|
118
137
|
|
|
119
|
-
|
|
120
|
-
onChange({ [argument]: values[index] });
|
|
138
|
+
return;
|
|
121
139
|
}
|
|
140
|
+
|
|
141
|
+
if (id === null) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
onChange({ [argument]: values[Number(id)] });
|
|
122
146
|
}}
|
|
123
147
|
/>
|
|
124
148
|
);
|
|
@@ -136,7 +160,7 @@ const Control = (props: ControlProps): ReactNode => {
|
|
|
136
160
|
sensitive={!isDisabled}
|
|
137
161
|
active={value === true}
|
|
138
162
|
onNotifyActive={(active) => {
|
|
139
|
-
if (
|
|
163
|
+
if (!Object.is(active, value)) {
|
|
140
164
|
onChange({ [argument]: active });
|
|
141
165
|
}
|
|
142
166
|
}}
|
|
@@ -151,7 +175,7 @@ const Control = (props: ControlProps): ReactNode => {
|
|
|
151
175
|
sensitive={!isDisabled}
|
|
152
176
|
text={typeof value === "string" ? value : ""}
|
|
153
177
|
onNotifyText={(text) => {
|
|
154
|
-
if (
|
|
178
|
+
if (!Object.is(text, value)) {
|
|
155
179
|
onChange({ [argument]: text });
|
|
156
180
|
}
|
|
157
181
|
}}
|
|
@@ -177,9 +201,9 @@ const Controls = ({ argTypes, args, onChange }: ControlsProps): ReactNode => (
|
|
|
177
201
|
<AdwPreferencesGroup title="Controls">
|
|
178
202
|
{Object.entries(argTypes).map(([argument, annotation]) => {
|
|
179
203
|
const control: unknown = annotation?.control;
|
|
180
|
-
const table
|
|
204
|
+
const table = isObject(annotation?.table) ? annotation.table : {};
|
|
181
205
|
|
|
182
|
-
if (control === undefined || control === false ||
|
|
206
|
+
if (control === undefined || control === false || table.disable === true) {
|
|
183
207
|
return null;
|
|
184
208
|
}
|
|
185
209
|
|
|
@@ -195,7 +219,7 @@ const Controls = ({ argTypes, args, onChange }: ControlsProps): ReactNode => (
|
|
|
195
219
|
type={typeof type === "string" ? type : "unknown"}
|
|
196
220
|
settings={settings}
|
|
197
221
|
options={annotation?.options}
|
|
198
|
-
isDisabled={settings.disable === true}
|
|
222
|
+
isDisabled={settings.disable === true || table.readonly === true}
|
|
199
223
|
onChange={onChange}
|
|
200
224
|
/>
|
|
201
225
|
);
|
|
@@ -20,14 +20,7 @@ type GroupProps = Pick<NavigatorProps, "selectedId" | "onSelect"> & {
|
|
|
20
20
|
|
|
21
21
|
const StoryGroup = ({ title, entries, selectedId, onSelect }: GroupProps): ReactNode => (
|
|
22
22
|
<GtkBox orientation={Gtk.Orientation.VERTICAL} spacing={6}>
|
|
23
|
-
<GtkLabel
|
|
24
|
-
label={title}
|
|
25
|
-
xalign={0}
|
|
26
|
-
marginStart={18}
|
|
27
|
-
marginEnd={18}
|
|
28
|
-
cssClasses={["heading"]}
|
|
29
|
-
wrap
|
|
30
|
-
/>
|
|
23
|
+
<GtkLabel label={title} xalign={0} marginStart={18} marginEnd={18} cssClasses={["heading"]} wrap />
|
|
31
24
|
<GtkListBox
|
|
32
25
|
cssClasses={["navigation-sidebar"]}
|
|
33
26
|
selectedIndex={entries.findIndex((entry) => entry.id === selectedId)}
|
|
@@ -54,22 +47,23 @@ const StoryGroup = ({ title, entries, selectedId, onSelect }: GroupProps): React
|
|
|
54
47
|
</GtkBox>
|
|
55
48
|
);
|
|
56
49
|
|
|
57
|
-
const LoadErrors = ({ errors }: Pick<NavigatorProps, "errors">): ReactNode =>
|
|
58
|
-
|
|
59
|
-
{
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
50
|
+
const LoadErrors = ({ errors }: Pick<NavigatorProps, "errors">): ReactNode =>
|
|
51
|
+
errors.length > 0 && (
|
|
52
|
+
<GtkBox name="storybook-load-errors" orientation={Gtk.Orientation.VERTICAL} spacing={6}>
|
|
53
|
+
{errors.map(({ source, error }, index) => (
|
|
54
|
+
<GtkLabel
|
|
55
|
+
key={`${source}-${String(index)}`}
|
|
56
|
+
label={`${source}\n${error.message.slice(0, 500)}`}
|
|
57
|
+
cssClasses={["error"]}
|
|
58
|
+
marginStart={18}
|
|
59
|
+
marginEnd={18}
|
|
60
|
+
wrap
|
|
61
|
+
selectable
|
|
62
|
+
xalign={0}
|
|
63
|
+
/>
|
|
64
|
+
))}
|
|
65
|
+
</GtkBox>
|
|
66
|
+
);
|
|
73
67
|
|
|
74
68
|
const StoryNavigator = ({ stories, errors, selectedId, onSelect }: NavigatorProps): ReactNode => {
|
|
75
69
|
const [search, setSearch] = useState("");
|
package/src/explorer-preview.tsx
CHANGED
|
@@ -4,6 +4,7 @@ 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
10
|
import type { Args, ComposedStory } from "./types.js";
|
|
@@ -32,23 +33,25 @@ class PreviewBoundary extends Component<BoundaryProps, BoundaryState> {
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
static getDerivedStateFromError(error: unknown): Pick<BoundaryState, "error"> {
|
|
35
|
-
return { error:
|
|
36
|
+
return { error: normalizeError(error) };
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
override state: BoundaryState = { error: null, args: this.props.args };
|
|
39
40
|
|
|
40
41
|
override render(): ReactNode {
|
|
41
|
-
return this.state.error === null
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
42
|
+
return this.state.error === null ? (
|
|
43
|
+
this.props.children
|
|
44
|
+
) : (
|
|
45
|
+
<AdwStatusPage
|
|
46
|
+
name="storybook-preview-error"
|
|
47
|
+
title="Preview failed"
|
|
48
|
+
iconName="dialog-error-symbolic"
|
|
49
|
+
description={
|
|
50
|
+
markupEscapeText(this.state.error.message.slice(0, 500), -1) +
|
|
51
|
+
"\n\nAdjust controls, reset the preview, or select another story to continue."
|
|
52
|
+
}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
52
55
|
}
|
|
53
56
|
}
|
|
54
57
|
|
|
@@ -108,11 +111,16 @@ const usePreviewState = ({ entry }: PreviewProps) => {
|
|
|
108
111
|
error: null,
|
|
109
112
|
actions: new ActionStore(),
|
|
110
113
|
}));
|
|
111
|
-
const onError = useCallback(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
const onError = useCallback(
|
|
115
|
+
(error: Error) => {
|
|
116
|
+
setState((current) =>
|
|
117
|
+
current.story === state.story && current.generation === state.generation
|
|
118
|
+
? { ...current, error }
|
|
119
|
+
: current,
|
|
120
|
+
);
|
|
121
|
+
},
|
|
122
|
+
[state.story, state.generation],
|
|
123
|
+
);
|
|
116
124
|
const boundArgs = useMemo(
|
|
117
125
|
() => bindActions(state.args, state.story.argTypes, state.actions, onError),
|
|
118
126
|
[state.args, state.story.argTypes, state.actions, onError],
|
|
@@ -148,12 +156,7 @@ const usePreviewState = ({ entry }: PreviewProps) => {
|
|
|
148
156
|
type InspectorProps = PreviewProps & { actions: ActionStore; args: Args; onChange: (args: Args) => void };
|
|
149
157
|
|
|
150
158
|
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
|
-
>
|
|
159
|
+
<GtkScrolledWindow name="storybook-inspector" widthRequest={280} hscrollbarPolicy={Gtk.PolicyType.NEVER} vexpand>
|
|
157
160
|
<GtkBox
|
|
158
161
|
orientation={Gtk.Orientation.VERTICAL}
|
|
159
162
|
spacing={24}
|
|
@@ -175,27 +178,29 @@ const StorybookPreview = ({ entry }: PreviewProps): ReactNode => {
|
|
|
175
178
|
<AdwToolbarView
|
|
176
179
|
hexpand
|
|
177
180
|
vexpand
|
|
178
|
-
topBar={
|
|
181
|
+
topBar={
|
|
179
182
|
<AdwHeaderBar
|
|
180
183
|
titleWidget={<AdwWindowTitle title={entry.name} subtitle={entry.title} />}
|
|
181
184
|
end={<GtkButton label="Reset story" onClicked={reset} />}
|
|
182
185
|
/>
|
|
183
|
-
|
|
186
|
+
}
|
|
184
187
|
>
|
|
185
188
|
<GtkBox>
|
|
186
189
|
<GtkBox orientation={Gtk.Orientation.VERTICAL} hexpand vexpand>
|
|
187
190
|
<PreviewBoundary key={state.generation} args={state.args}>
|
|
188
|
-
{state.error === null
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
191
|
+
{state.error === null ? (
|
|
192
|
+
<PreviewContent story={entry.story} args={boundArgs} />
|
|
193
|
+
) : (
|
|
194
|
+
<AdwStatusPage
|
|
195
|
+
name="storybook-preview-error"
|
|
196
|
+
title="Story action failed"
|
|
197
|
+
iconName="dialog-error-symbolic"
|
|
198
|
+
description={
|
|
199
|
+
markupEscapeText(state.error.message.slice(0, 500), -1) +
|
|
200
|
+
"\n\nReset the preview or select another story to continue."
|
|
201
|
+
}
|
|
202
|
+
/>
|
|
203
|
+
)}
|
|
199
204
|
</PreviewBoundary>
|
|
200
205
|
</GtkBox>
|
|
201
206
|
<StoryInspector entry={entry} actions={state.actions} args={state.args} onChange={onChange} />
|
package/src/explorer-view.tsx
CHANGED
|
@@ -42,15 +42,15 @@ const StorybookView = ({ catalog, title = "GTKX Storybook" }: StorybookViewProps
|
|
|
42
42
|
<AdwOverlaySplitView
|
|
43
43
|
minSidebarWidth={220}
|
|
44
44
|
maxSidebarWidth={300}
|
|
45
|
-
sidebar={
|
|
45
|
+
sidebar={
|
|
46
46
|
<AdwToolbarView
|
|
47
|
-
topBar={
|
|
47
|
+
topBar={
|
|
48
48
|
<AdwHeaderBar
|
|
49
|
-
titleWidget={
|
|
49
|
+
titleWidget={
|
|
50
50
|
<AdwWindowTitle title={title} subtitle={`${String(snapshot.stories.length)} stories`} />
|
|
51
|
-
|
|
51
|
+
}
|
|
52
52
|
/>
|
|
53
|
-
|
|
53
|
+
}
|
|
54
54
|
>
|
|
55
55
|
<StoryNavigator
|
|
56
56
|
stories={snapshot.stories}
|
|
@@ -59,20 +59,20 @@ const StorybookView = ({ catalog, title = "GTKX Storybook" }: StorybookViewProps
|
|
|
59
59
|
onSelect={setSelectedId}
|
|
60
60
|
/>
|
|
61
61
|
</AdwToolbarView>
|
|
62
|
-
|
|
62
|
+
}
|
|
63
63
|
>
|
|
64
|
-
{selected
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
76
|
</AdwOverlaySplitView>
|
|
77
77
|
);
|
|
78
78
|
};
|
package/src/types.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type * as Gtk from "@gtkx/gi/gtk";
|
|
2
2
|
import type { ComponentType, ReactNode } from "react";
|
|
3
3
|
import type {
|
|
4
|
+
ArgsFromMeta,
|
|
4
5
|
ComponentAnnotations,
|
|
5
6
|
GlobalTypes,
|
|
6
7
|
Renderer,
|
|
8
|
+
StoryAnnotations as StorybookAnnotations,
|
|
7
9
|
ArgTypes as StorybookArgTypes,
|
|
8
10
|
StoryContext as StorybookContext,
|
|
9
11
|
} from "storybook/internal/types";
|
|
@@ -69,51 +71,29 @@ type Annotations<TArgs extends object> = {
|
|
|
69
71
|
};
|
|
70
72
|
|
|
71
73
|
/** Default export metadata describing a component's stories and their shared annotations. */
|
|
72
|
-
type ComponentMeta<TArgs extends object> = Annotations<TArgs> &
|
|
73
|
-
ComponentAnnotations<NativeRenderer<TArgs>, TArgs>,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
globals?: Args;
|
|
80
|
-
};
|
|
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
|
+
};
|
|
81
81
|
|
|
82
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;
|
|
83
|
+
type ComponentOrProps<T> = T extends ComponentType<infer Props extends object> ? Props : T extends object ? T : Args;
|
|
86
84
|
|
|
87
85
|
/** Presents combined inferred args as a single object type. */
|
|
88
86
|
type Simplify<T> = { [Key in keyof T]: T[Key] };
|
|
89
87
|
|
|
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
88
|
/** Infers story args from component props, render functions, decorators, or declared defaults. */
|
|
111
|
-
type ArgsFrom<T> = T extends { component?: infer Component; render?:
|
|
89
|
+
type ArgsFrom<T> = T extends { component?: infer Component; render?: unknown; decorators?: unknown }
|
|
112
90
|
? Simplify<
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
: 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;
|
|
117
97
|
|
|
118
98
|
/** CSF3 default export metadata typed from a React component or an explicit args type. */
|
|
119
99
|
type Meta<T = Args> = ComponentMeta<ComponentOrProps<T>>;
|
|
@@ -130,14 +110,16 @@ type StoryAnnotations<TArgs extends object> = Annotations<TArgs> & {
|
|
|
130
110
|
type DefaultArgs<T> = T extends { args: infer Defaults } ? Defaults : Record<never, never>;
|
|
131
111
|
|
|
132
112
|
/** Makes args with metadata defaults optional while preserving other required args. */
|
|
133
|
-
type RequiredStoryArgs<T, TArgs extends object> =
|
|
134
|
-
|
|
113
|
+
type RequiredStoryArgs<T, TArgs extends object> = Omit<TArgs, keyof DefaultArgs<T>> &
|
|
114
|
+
Partial<Pick<TArgs, keyof TArgs & keyof DefaultArgs<T>>>;
|
|
135
115
|
|
|
136
116
|
/** CSF3 story object inferred from metadata, a React component, or an explicit args type. */
|
|
137
117
|
type StoryObj<T = Args> = T extends { component?: unknown; render?: unknown; decorators?: unknown; args?: unknown }
|
|
138
|
-
? StoryAnnotations<ArgsFrom<T>> &
|
|
139
|
-
|
|
140
|
-
|
|
118
|
+
? StoryAnnotations<ArgsFrom<T>> &
|
|
119
|
+
Pick<
|
|
120
|
+
StorybookAnnotations<NativeRenderer<ArgsFrom<T>>, ArgsFrom<T>, RequiredStoryArgs<T, ArgsFrom<T>>>,
|
|
121
|
+
"args"
|
|
122
|
+
>
|
|
141
123
|
: StoryAnnotations<ComponentOrProps<T>>;
|
|
142
124
|
|
|
143
125
|
/** Project annotations shared by all composed stories. */
|
|
@@ -177,9 +159,10 @@ type StoryKeys<TModule extends StoryModule> = {
|
|
|
177
159
|
[Key in keyof TModule]: Key extends "default" | "__esModule" | "__namedExportsOrder"
|
|
178
160
|
? never
|
|
179
161
|
: TModule[Key] extends StoryAnnotations<ArgsFrom<TModule["default"]>>
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}[keyof TModule] &
|
|
162
|
+
? Key
|
|
163
|
+
: never;
|
|
164
|
+
}[keyof TModule] &
|
|
165
|
+
string;
|
|
183
166
|
|
|
184
167
|
/** Composed stories keyed by their original named exports. */
|
|
185
168
|
type ComposedStories<TModule extends StoryModule> = Record<
|
package/src/upstream.ts
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import { composeStory } from "storybook/preview-api";
|
|
2
|
-
import type {
|
|
3
|
-
ComponentMeta,
|
|
4
|
-
ComposedStory,
|
|
5
|
-
Preview,
|
|
6
|
-
StoryAnnotations,
|
|
7
|
-
StoryRender,
|
|
8
|
-
} from "./types.js";
|
|
2
|
+
import type { ComponentMeta, ComposedStory, Preview, StoryAnnotations, StoryRender } from "./types.js";
|
|
9
3
|
|
|
10
4
|
type ComposeStory = <TArgs extends object>(
|
|
11
5
|
...annotations: [
|