@usefragments/core 1.10.0 → 1.10.2
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/codes/index.d.ts +1 -1
- package/dist/compiled-types/index.d.ts +1 -1
- package/dist/generate/index.d.ts +1 -1
- package/dist/{governance-B5WWcmFY.d.ts → governance-CLk_wkP9.d.ts} +1 -1
- package/dist/index.d.ts +136 -357
- package/dist/index.js +7 -454
- package/dist/index.js.map +1 -1
- package/dist/preview-runtime.d.ts +48 -0
- package/dist/preview-runtime.js +91 -0
- package/dist/preview-runtime.js.map +1 -0
- package/dist/react-types.d.ts +3 -2
- package/dist/registry.d.ts +18 -18
- package/dist/schemas/index.d.ts +1 -1
- package/dist/storyAdapter.d.ts +187 -0
- package/dist/storyAdapter.js +371 -0
- package/dist/storyAdapter.js.map +1 -0
- package/dist/test-utils.d.ts +1 -1
- package/package.json +9 -1
- package/src/index.ts +0 -35
- package/src/react-create-element.test.ts +22 -0
- package/src/react-create-element.ts +12 -0
- package/src/react-types.ts +2 -1
- package/src/storyAdapter.ts +3 -2
- package/src/tokens/scss.test.ts +32 -1
- package/src/tokens/scss.ts +12 -7
- package/dist/{index-DtHxs0Pf.d.ts → index-_sxhUNqx.d.ts} +12 -12
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
// src/storybook-csf.ts
|
|
2
|
+
import {
|
|
3
|
+
toId as storybookToId,
|
|
4
|
+
storyNameFromExport as storybookStoryNameFromExport,
|
|
5
|
+
isExportStory as storybookIsExportStory
|
|
6
|
+
} from "@storybook/csf";
|
|
7
|
+
var toId = (...args) => storybookToId(...args);
|
|
8
|
+
var storyNameFromExport = (...args) => storybookStoryNameFromExport(...args);
|
|
9
|
+
var isExportStory = (...args) => storybookIsExportStory(...args);
|
|
10
|
+
|
|
11
|
+
// src/react-create-element.ts
|
|
12
|
+
import { createRequire } from "module";
|
|
13
|
+
function getCreateElement() {
|
|
14
|
+
try {
|
|
15
|
+
return createRequire(import.meta.url)("react").createElement;
|
|
16
|
+
} catch (error) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
'@usefragments/core: story rendering requires the optional peer dependency "react" (>=18). Install react to use storyModuleToFragment default render paths.',
|
|
19
|
+
{ cause: error instanceof Error ? error : void 0 }
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/storyAdapter.ts
|
|
25
|
+
var globalPreviewConfig = {};
|
|
26
|
+
function setPreviewConfig(config) {
|
|
27
|
+
globalPreviewConfig = config;
|
|
28
|
+
}
|
|
29
|
+
function getPreviewConfig() {
|
|
30
|
+
return globalPreviewConfig;
|
|
31
|
+
}
|
|
32
|
+
function storyModuleToFragment(storyModule, filePath) {
|
|
33
|
+
const meta = storyModule.default;
|
|
34
|
+
const component = meta.component;
|
|
35
|
+
if (!component) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const componentName = extractComponentName(meta, filePath);
|
|
39
|
+
const category = extractCategory(meta.title);
|
|
40
|
+
const props = convertArgTypes(meta.argTypes ?? {}, globalPreviewConfig.argTypes);
|
|
41
|
+
const variants = extractVariants(storyModule, component, meta);
|
|
42
|
+
const figmaUrl = extractFigmaUrl(meta.parameters);
|
|
43
|
+
const fragmentMeta = {
|
|
44
|
+
name: componentName,
|
|
45
|
+
description: meta.parameters?.docs?.description?.component ?? `${componentName} component`,
|
|
46
|
+
category,
|
|
47
|
+
tags: meta.tags?.filter((t) => t !== "autodocs"),
|
|
48
|
+
status: "stable",
|
|
49
|
+
figma: figmaUrl
|
|
50
|
+
};
|
|
51
|
+
const usage = {
|
|
52
|
+
when: [`Use ${componentName} for its intended purpose`],
|
|
53
|
+
whenNot: ["When a more specific component is available"]
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
component,
|
|
57
|
+
meta: fragmentMeta,
|
|
58
|
+
usage,
|
|
59
|
+
props,
|
|
60
|
+
variants
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function extractComponentName(meta, filePath) {
|
|
64
|
+
if (meta.title) {
|
|
65
|
+
const parts = meta.title.split("/");
|
|
66
|
+
return parts[parts.length - 1];
|
|
67
|
+
}
|
|
68
|
+
if (meta.component?.displayName) {
|
|
69
|
+
return meta.component.displayName;
|
|
70
|
+
}
|
|
71
|
+
if (meta.component?.name && meta.component.name !== "Component") {
|
|
72
|
+
return meta.component.name;
|
|
73
|
+
}
|
|
74
|
+
const match = filePath.match(/([^/\\]+)\.stories\.(tsx?|jsx?)$/);
|
|
75
|
+
return match?.[1] ?? "Unknown";
|
|
76
|
+
}
|
|
77
|
+
function extractCategory(title) {
|
|
78
|
+
if (!title) return "general";
|
|
79
|
+
const parts = title.split("/");
|
|
80
|
+
if (parts.length >= 3) {
|
|
81
|
+
return parts[parts.length - 2].toLowerCase();
|
|
82
|
+
}
|
|
83
|
+
return "general";
|
|
84
|
+
}
|
|
85
|
+
function extractFigmaUrl(parameters) {
|
|
86
|
+
if (!parameters) return void 0;
|
|
87
|
+
const design = parameters.design;
|
|
88
|
+
if (design?.url && typeof design.url === "string") {
|
|
89
|
+
return design.url;
|
|
90
|
+
}
|
|
91
|
+
if (typeof parameters.figma === "string") {
|
|
92
|
+
return parameters.figma;
|
|
93
|
+
}
|
|
94
|
+
return void 0;
|
|
95
|
+
}
|
|
96
|
+
function convertArgTypes(argTypes, globalArgTypes) {
|
|
97
|
+
const props = {};
|
|
98
|
+
const mergedArgTypes = { ...globalArgTypes, ...argTypes };
|
|
99
|
+
for (const [name, argType] of Object.entries(mergedArgTypes)) {
|
|
100
|
+
if (argType.table?.disable) continue;
|
|
101
|
+
if (argType.control === false && argType.action) continue;
|
|
102
|
+
const { controlType, controlOptions } = extractControlInfo(argType);
|
|
103
|
+
props[name] = {
|
|
104
|
+
type: inferPropType(argType),
|
|
105
|
+
description: argType.description ?? `${name} prop`,
|
|
106
|
+
...argType.options && { values: argType.options },
|
|
107
|
+
...argType.table?.defaultValue && {
|
|
108
|
+
default: argType.table.defaultValue.summary
|
|
109
|
+
},
|
|
110
|
+
...argType.defaultValue !== void 0 && {
|
|
111
|
+
default: argType.defaultValue
|
|
112
|
+
},
|
|
113
|
+
...argType.type?.required && { required: true },
|
|
114
|
+
...controlType && { controlType },
|
|
115
|
+
...controlOptions && Object.keys(controlOptions).length > 0 && { controlOptions }
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
return props;
|
|
119
|
+
}
|
|
120
|
+
function extractControlInfo(argType) {
|
|
121
|
+
if (argType.control === void 0 || argType.control === false) {
|
|
122
|
+
return {};
|
|
123
|
+
}
|
|
124
|
+
const control = typeof argType.control === "string" ? { type: argType.control } : argType.control;
|
|
125
|
+
const validControlTypes = [
|
|
126
|
+
"text",
|
|
127
|
+
"number",
|
|
128
|
+
"range",
|
|
129
|
+
"boolean",
|
|
130
|
+
"select",
|
|
131
|
+
"multi-select",
|
|
132
|
+
"radio",
|
|
133
|
+
"inline-radio",
|
|
134
|
+
"check",
|
|
135
|
+
"inline-check",
|
|
136
|
+
"object",
|
|
137
|
+
"file",
|
|
138
|
+
"color",
|
|
139
|
+
"date"
|
|
140
|
+
];
|
|
141
|
+
const controlType = validControlTypes.includes(control.type) ? control.type : void 0;
|
|
142
|
+
const controlOptions = {};
|
|
143
|
+
if (control.min !== void 0) controlOptions.min = control.min;
|
|
144
|
+
if (control.max !== void 0) controlOptions.max = control.max;
|
|
145
|
+
if (control.step !== void 0) controlOptions.step = control.step;
|
|
146
|
+
if (control.presetColors) controlOptions.presetColors = control.presetColors;
|
|
147
|
+
return {
|
|
148
|
+
controlType,
|
|
149
|
+
controlOptions: Object.keys(controlOptions).length > 0 ? controlOptions : void 0
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function inferPropType(argType) {
|
|
153
|
+
if (argType.action) return "function";
|
|
154
|
+
if (argType.options?.length) return "enum";
|
|
155
|
+
if (argType.type?.name) {
|
|
156
|
+
const typeMap = {
|
|
157
|
+
string: "string",
|
|
158
|
+
number: "number",
|
|
159
|
+
boolean: "boolean",
|
|
160
|
+
object: "object",
|
|
161
|
+
array: "array",
|
|
162
|
+
function: "function"
|
|
163
|
+
};
|
|
164
|
+
const mapped = typeMap[argType.type.name];
|
|
165
|
+
if (mapped) return mapped;
|
|
166
|
+
}
|
|
167
|
+
const control = typeof argType.control === "string" ? argType.control : argType.control ? argType.control.type : void 0;
|
|
168
|
+
if (control) {
|
|
169
|
+
const controlMap = {
|
|
170
|
+
// Text controls
|
|
171
|
+
text: "string",
|
|
172
|
+
// Number controls
|
|
173
|
+
number: "number",
|
|
174
|
+
range: "number",
|
|
175
|
+
// Boolean controls
|
|
176
|
+
boolean: "boolean",
|
|
177
|
+
check: "boolean",
|
|
178
|
+
"inline-check": "boolean",
|
|
179
|
+
// Enum/selection controls
|
|
180
|
+
select: "enum",
|
|
181
|
+
"multi-select": "enum",
|
|
182
|
+
radio: "enum",
|
|
183
|
+
"inline-radio": "enum",
|
|
184
|
+
// Object controls
|
|
185
|
+
object: "object",
|
|
186
|
+
file: "object",
|
|
187
|
+
// Special string controls
|
|
188
|
+
color: "string",
|
|
189
|
+
date: "string"
|
|
190
|
+
};
|
|
191
|
+
const mapped = controlMap[control];
|
|
192
|
+
if (mapped) return mapped;
|
|
193
|
+
}
|
|
194
|
+
return "string";
|
|
195
|
+
}
|
|
196
|
+
function isStory(value) {
|
|
197
|
+
if (typeof value === "object" && value !== null) {
|
|
198
|
+
const obj = value;
|
|
199
|
+
if ("args" in obj || "render" in obj || "play" in obj) return true;
|
|
200
|
+
}
|
|
201
|
+
if (typeof value === "function") {
|
|
202
|
+
const fn = value;
|
|
203
|
+
if ("args" in fn) return true;
|
|
204
|
+
}
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
function extractVariants(storyModule, component, meta) {
|
|
208
|
+
const variants = [];
|
|
209
|
+
for (const [exportName, exportValue] of Object.entries(storyModule)) {
|
|
210
|
+
if (exportName === "default") continue;
|
|
211
|
+
if (!isExportStory(exportName, meta)) continue;
|
|
212
|
+
if (!isStory(exportValue)) continue;
|
|
213
|
+
const story = exportValue;
|
|
214
|
+
const storyName = typeof story === "object" && story.name || typeof story === "object" && story.storyName || typeof story === "function" && story.storyName || storyNameFromExport(exportName);
|
|
215
|
+
const storyId = toId(meta.title || "Unknown", exportName);
|
|
216
|
+
let description = `${storyName} variant`;
|
|
217
|
+
if (typeof story === "object" && story.parameters?.docs?.description?.story) {
|
|
218
|
+
description = story.parameters.docs.description.story;
|
|
219
|
+
}
|
|
220
|
+
const storyPlayFn = typeof story === "object" ? story.play : story.play;
|
|
221
|
+
const hasPlayFunction = !!storyPlayFn;
|
|
222
|
+
const wrappedPlay = storyPlayFn ? async (context) => {
|
|
223
|
+
const args = {
|
|
224
|
+
...globalPreviewConfig.args,
|
|
225
|
+
...meta.args,
|
|
226
|
+
...typeof story === "function" ? story.args : story.args
|
|
227
|
+
};
|
|
228
|
+
const fullContext = buildStoryContext(meta, story, args, storyId, storyName);
|
|
229
|
+
const playContext = {
|
|
230
|
+
...fullContext,
|
|
231
|
+
canvasElement: context.canvasElement,
|
|
232
|
+
args: context.args,
|
|
233
|
+
step: context.step
|
|
234
|
+
};
|
|
235
|
+
await storyPlayFn(playContext);
|
|
236
|
+
} : void 0;
|
|
237
|
+
const storyTags = typeof story === "object" ? story.tags : void 0;
|
|
238
|
+
const loaders = collectLoaders(meta, story);
|
|
239
|
+
const variantArgs = {
|
|
240
|
+
...globalPreviewConfig.args,
|
|
241
|
+
...meta.args,
|
|
242
|
+
...typeof story === "function" ? story.args : story.args
|
|
243
|
+
};
|
|
244
|
+
const hasArgs = Object.keys(variantArgs).length > 0;
|
|
245
|
+
variants.push({
|
|
246
|
+
name: storyName,
|
|
247
|
+
description,
|
|
248
|
+
render: createRenderFunction(story, component, meta, storyId, storyName),
|
|
249
|
+
// Store Storybook-specific metadata
|
|
250
|
+
...hasPlayFunction && { hasPlayFunction: true },
|
|
251
|
+
...wrappedPlay && { play: wrappedPlay },
|
|
252
|
+
...storyId && { storyId },
|
|
253
|
+
...storyTags && { tags: storyTags },
|
|
254
|
+
...loaders.length > 0 && { loaders },
|
|
255
|
+
...hasArgs && { args: variantArgs }
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
return variants;
|
|
259
|
+
}
|
|
260
|
+
function collectLoaders(meta, story) {
|
|
261
|
+
const allLoaders = [
|
|
262
|
+
...globalPreviewConfig.loaders ?? [],
|
|
263
|
+
...meta.loaders ?? [],
|
|
264
|
+
...typeof story === "function" ? story.loaders ?? [] : story.loaders ?? []
|
|
265
|
+
];
|
|
266
|
+
if (allLoaders.length === 0) {
|
|
267
|
+
return [];
|
|
268
|
+
}
|
|
269
|
+
return allLoaders.map((loader) => {
|
|
270
|
+
return async () => {
|
|
271
|
+
const minimalContext = {
|
|
272
|
+
args: {},
|
|
273
|
+
argTypes: {},
|
|
274
|
+
globals: {},
|
|
275
|
+
parameters: {},
|
|
276
|
+
id: "",
|
|
277
|
+
kind: meta.title || "Unknown",
|
|
278
|
+
name: "",
|
|
279
|
+
story: "",
|
|
280
|
+
viewMode: "story",
|
|
281
|
+
loaded: {},
|
|
282
|
+
abortSignal: new AbortController().signal,
|
|
283
|
+
componentId: "",
|
|
284
|
+
title: meta.title || "Unknown"
|
|
285
|
+
};
|
|
286
|
+
return loader(minimalContext);
|
|
287
|
+
};
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
function buildStoryContext(meta, story, args, storyId, storyName, loadedData) {
|
|
291
|
+
const mergedArgs = {
|
|
292
|
+
...globalPreviewConfig.args,
|
|
293
|
+
...meta.args,
|
|
294
|
+
...typeof story === "object" ? story.args : story.args,
|
|
295
|
+
...args
|
|
296
|
+
};
|
|
297
|
+
const mergedArgTypes = {
|
|
298
|
+
...globalPreviewConfig.argTypes,
|
|
299
|
+
...meta.argTypes,
|
|
300
|
+
...typeof story === "object" ? story.argTypes : story.argTypes
|
|
301
|
+
};
|
|
302
|
+
const mergedParameters = {
|
|
303
|
+
...globalPreviewConfig.parameters,
|
|
304
|
+
...meta.parameters,
|
|
305
|
+
...typeof story === "object" ? story.parameters : story.parameters
|
|
306
|
+
};
|
|
307
|
+
return {
|
|
308
|
+
args: mergedArgs,
|
|
309
|
+
argTypes: mergedArgTypes ?? {},
|
|
310
|
+
globals: {},
|
|
311
|
+
parameters: mergedParameters ?? {},
|
|
312
|
+
id: storyId,
|
|
313
|
+
kind: meta.title || "Unknown",
|
|
314
|
+
name: storyName,
|
|
315
|
+
story: storyName,
|
|
316
|
+
viewMode: "story",
|
|
317
|
+
loaded: loadedData ?? {},
|
|
318
|
+
abortSignal: new AbortController().signal,
|
|
319
|
+
componentId: toId(meta.title || "Unknown", ""),
|
|
320
|
+
title: meta.title || "Unknown"
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
function createRenderFunction(story, component, meta, storyId, storyName) {
|
|
324
|
+
return (options) => {
|
|
325
|
+
const args = {
|
|
326
|
+
...globalPreviewConfig.args,
|
|
327
|
+
...meta.args,
|
|
328
|
+
...typeof story === "function" ? story.args : story.args,
|
|
329
|
+
...options?.args
|
|
330
|
+
// Runtime overrides from viewer props panel
|
|
331
|
+
};
|
|
332
|
+
const loadedData = options?.loadedData;
|
|
333
|
+
const context = buildStoryContext(meta, story, args, storyId, storyName, loadedData);
|
|
334
|
+
let renderFn;
|
|
335
|
+
if (typeof story === "function") {
|
|
336
|
+
renderFn = () => story(args);
|
|
337
|
+
} else if (story.render) {
|
|
338
|
+
renderFn = () => story.render.length >= 2 ? story.render(args, context) : story.render(args);
|
|
339
|
+
} else if (meta.render) {
|
|
340
|
+
renderFn = () => meta.render.length >= 2 ? meta.render(args, context) : meta.render(args);
|
|
341
|
+
} else {
|
|
342
|
+
renderFn = () => getCreateElement()(component, args);
|
|
343
|
+
}
|
|
344
|
+
const allDecorators = [
|
|
345
|
+
...globalPreviewConfig.decorators ?? [],
|
|
346
|
+
...meta.decorators ?? [],
|
|
347
|
+
...typeof story === "function" ? story.decorators ?? [] : story.decorators ?? []
|
|
348
|
+
].reverse();
|
|
349
|
+
if (allDecorators.length > 0) {
|
|
350
|
+
return applyDecorators(renderFn, allDecorators, context);
|
|
351
|
+
}
|
|
352
|
+
return renderFn();
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
function applyDecorators(renderFn, decorators, context) {
|
|
356
|
+
let storyFn = renderFn;
|
|
357
|
+
for (const decorator of decorators) {
|
|
358
|
+
const wrappedFn = storyFn;
|
|
359
|
+
storyFn = () => decorator(wrappedFn, context);
|
|
360
|
+
}
|
|
361
|
+
return storyFn();
|
|
362
|
+
}
|
|
363
|
+
export {
|
|
364
|
+
getPreviewConfig,
|
|
365
|
+
isExportStory,
|
|
366
|
+
setPreviewConfig,
|
|
367
|
+
storyModuleToFragment,
|
|
368
|
+
storyNameFromExport,
|
|
369
|
+
toId
|
|
370
|
+
};
|
|
371
|
+
//# sourceMappingURL=storyAdapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/storybook-csf.ts","../src/react-create-element.ts","../src/storyAdapter.ts"],"sourcesContent":["import {\n toId as storybookToId,\n storyNameFromExport as storybookStoryNameFromExport,\n isExportStory as storybookIsExportStory,\n} from \"@storybook/csf\";\n\nexport const toId: typeof storybookToId = (...args) => storybookToId(...args);\nexport const storyNameFromExport: typeof storybookStoryNameFromExport = (...args) =>\n storybookStoryNameFromExport(...args);\nexport const isExportStory: typeof storybookIsExportStory = (...args) =>\n storybookIsExportStory(...args);\n","import { createRequire } from \"node:module\";\n\nexport function getCreateElement(): typeof import(\"react\")[\"createElement\"] {\n try {\n return (createRequire(import.meta.url)(\"react\") as typeof import(\"react\")).createElement;\n } catch (error) {\n throw new Error(\n '@usefragments/core: story rendering requires the optional peer dependency \"react\" (>=18). Install react to use storyModuleToFragment default render paths.',\n { cause: error instanceof Error ? error : undefined },\n );\n }\n}\n","/**\n * Runtime adapter for converting Storybook CSF modules to Fragment definitions.\n *\n * This operates on IMPORTED modules at runtime, not source code parsing.\n * By leveraging Vite's module system, we get 100% accurate render functions\n * without any regex or AST parsing complexity.\n *\n * Supports Storybook 8.x with both CSF2 (Template.bind) and CSF3 (object stories).\n */\n\nimport type { ComponentType, ReactNode } from \"react\";\nimport { toId, storyNameFromExport, isExportStory } from \"./storybook-csf.js\";\nimport { getCreateElement } from \"./react-create-element.js\";\nimport type {\n FragmentDefinition,\n FragmentMeta,\n FragmentUsage,\n PropDefinition,\n FragmentVariant,\n ControlType,\n VariantLoader,\n PlayFunction,\n PlayFunctionContext,\n VariantRenderOptions,\n} from \"./types.js\";\n\n// Re-export @storybook/csf utilities for use in other modules\nexport { toId, storyNameFromExport, isExportStory };\n\n/**\n * Storybook decorator function signature\n */\nexport type Decorator = (\n Story: () => ReactNode,\n context: StoryContext\n) => ReactNode;\n\n/**\n * Storybook loader function signature\n */\nexport type Loader = (context: StoryContext) => Promise<Record<string, unknown>>;\n\n/**\n * Storybook play function signature (internal, extends StoryContext)\n */\ntype StorybookPlayFunction = (context: StorybookPlayFunctionContext) => Promise<void>;\n\n/**\n * Context passed to Storybook play functions (extends StoryContext for compatibility)\n */\ninterface StorybookPlayFunctionContext extends StoryContext {\n canvasElement: HTMLElement;\n step: (name: string, fn: () => Promise<void>) => Promise<void>;\n}\n\n/**\n * Context passed to decorators and render functions\n */\nexport interface StoryContext {\n args: Record<string, unknown>;\n argTypes: Record<string, StoryArgType>;\n globals: Record<string, unknown>;\n parameters: Record<string, unknown>;\n id: string;\n kind: string;\n name: string;\n story: string;\n viewMode: \"story\" | \"docs\";\n loaded: Record<string, unknown>;\n abortSignal: AbortSignal;\n componentId: string;\n title: string;\n}\n\n/**\n * Storybook Meta (default export)\n */\nexport interface StoryMeta {\n title?: string;\n component?: ComponentType<unknown>;\n subcomponents?: Record<string, ComponentType<unknown>>;\n tags?: string[];\n parameters?: Record<string, unknown> & {\n docs?: {\n description?: {\n component?: string;\n };\n };\n };\n argTypes?: Record<string, StoryArgType>;\n args?: Record<string, unknown>;\n decorators?: Decorator[];\n loaders?: Loader[];\n render?: (args: Record<string, unknown>, context?: StoryContext) => ReactNode;\n // Story filtering\n includeStories?: string[] | RegExp;\n excludeStories?: string[] | RegExp;\n}\n\n/**\n * Storybook argType definition\n */\nexport interface StoryArgType {\n control?:\n | string\n | false\n | { type: string; min?: number; max?: number; step?: number; presetColors?: string[] };\n options?: string[];\n description?: string;\n table?: {\n defaultValue?: { summary: string };\n type?: { summary: string };\n category?: string;\n subcategory?: string;\n disable?: boolean;\n };\n type?: { name: string; required?: boolean };\n name?: string;\n defaultValue?: unknown;\n if?: { arg?: string; exists?: boolean };\n mapping?: Record<string, unknown>;\n action?: string;\n}\n\n/**\n * Storybook Story export (CSF3)\n */\nexport interface Story {\n args?: Record<string, unknown>;\n argTypes?: Record<string, StoryArgType>;\n render?: (args: Record<string, unknown>, context?: StoryContext) => ReactNode;\n decorators?: Decorator[];\n loaders?: Loader[];\n play?: StorybookPlayFunction;\n parameters?: Record<string, unknown> & {\n docs?: {\n description?: {\n story?: string;\n };\n };\n };\n name?: string;\n storyName?: string; // Legacy CSF2\n tags?: string[];\n}\n\n/**\n * CSF2 story function (from Template.bind({})) with args attached\n */\nexport type CSF2Story = ((args: Record<string, unknown>) => ReactNode) & {\n args?: Record<string, unknown>;\n argTypes?: Record<string, StoryArgType>;\n decorators?: Decorator[];\n loaders?: Loader[];\n play?: StorybookPlayFunction;\n parameters?: Record<string, unknown>;\n storyName?: string;\n};\n\n/**\n * A complete Storybook module with default meta and named story exports\n */\nexport interface StoryModule {\n default: StoryMeta;\n [exportName: string]: Story | CSF2Story | StoryMeta | unknown;\n}\n\n/**\n * Global configuration from preview.tsx\n */\nexport interface PreviewConfig {\n decorators?: Decorator[];\n parameters?: Record<string, unknown>;\n globalTypes?: Record<string, unknown>;\n args?: Record<string, unknown>;\n argTypes?: Record<string, StoryArgType>;\n loaders?: Loader[];\n}\n\n// Store for global preview config (set by previewLoader)\nlet globalPreviewConfig: PreviewConfig = {};\n\n/**\n * Set the global preview configuration loaded from .storybook/preview.tsx\n */\nexport function setPreviewConfig(config: PreviewConfig): void {\n globalPreviewConfig = config;\n}\n\n/**\n * Get the current global preview configuration\n */\nexport function getPreviewConfig(): PreviewConfig {\n return globalPreviewConfig;\n}\n\n/**\n * Convert a Storybook module to a Fragment definition at runtime.\n *\n * @param storyModule - The imported Storybook module\n * @param filePath - File path for metadata extraction\n * @returns A complete FragmentDefinition ready for the viewer\n */\nexport function storyModuleToFragment(\n storyModule: StoryModule,\n filePath: string\n): FragmentDefinition | null {\n const meta = storyModule.default;\n const component = meta.component;\n\n // Stories without a component (e.g., documentation pages, icon galleries) are skipped\n if (!component) {\n return null;\n }\n\n const componentName = extractComponentName(meta, filePath);\n const category = extractCategory(meta.title);\n const props = convertArgTypes(meta.argTypes ?? {}, globalPreviewConfig.argTypes);\n const variants = extractVariants(storyModule, component, meta);\n\n // Extract Figma URL from parameters.design.url (storybook-addon-designs) or parameters.figma\n const figmaUrl = extractFigmaUrl(meta.parameters);\n\n const fragmentMeta: FragmentMeta = {\n name: componentName,\n description:\n meta.parameters?.docs?.description?.component ??\n `${componentName} component`,\n category,\n tags: meta.tags?.filter((t) => t !== \"autodocs\"),\n status: \"stable\",\n figma: figmaUrl,\n };\n\n const usage: FragmentUsage = {\n when: [`Use ${componentName} for its intended purpose`],\n whenNot: [\"When a more specific component is available\"],\n };\n\n return {\n component,\n meta: fragmentMeta,\n usage,\n props,\n variants,\n };\n}\n\n/**\n * Extract component name from meta or file path\n */\nfunction extractComponentName(meta: StoryMeta, filePath: string): string {\n // Try title (last fragment of path like \"Components/Forms/Button\" -> \"Button\")\n if (meta.title) {\n const parts = meta.title.split(\"/\");\n return parts[parts.length - 1];\n }\n\n // Try component displayName\n if (meta.component?.displayName) {\n return meta.component.displayName;\n }\n\n // Try component name\n if (meta.component?.name && meta.component.name !== \"Component\") {\n return meta.component.name;\n }\n\n // Fallback: extract from file path\n const match = filePath.match(/([^/\\\\]+)\\.stories\\.(tsx?|jsx?)$/);\n return match?.[1] ?? \"Unknown\";\n}\n\n/**\n * Extract category from Storybook title path\n */\nfunction extractCategory(title?: string): string {\n if (!title) return \"general\";\n\n const parts = title.split(\"/\");\n // \"Components/Forms/Button\" -> \"forms\" (need at least 3 parts for a subcategory)\n if (parts.length >= 3) {\n return parts[parts.length - 2].toLowerCase();\n }\n // \"Components/Button\" -> \"general\" (no subcategory specified)\n return \"general\";\n}\n\n/**\n * Extract Figma URL from Storybook parameters\n * Supports storybook-addon-designs format and custom figma parameter\n */\nfunction extractFigmaUrl(parameters?: Record<string, unknown>): string | undefined {\n if (!parameters) return undefined;\n\n // Try storybook-addon-designs format: parameters.design.url\n const design = parameters.design as { url?: string; type?: string } | undefined;\n if (design?.url && typeof design.url === \"string\") {\n return design.url;\n }\n\n // Try custom figma parameter: parameters.figma\n if (typeof parameters.figma === \"string\") {\n return parameters.figma;\n }\n\n return undefined;\n}\n\n/**\n * Convert Storybook argTypes to Fragment props\n * Merges global argTypes from preview config with meta argTypes\n */\nfunction convertArgTypes(\n argTypes: Record<string, StoryArgType>,\n globalArgTypes?: Record<string, StoryArgType>\n): Record<string, PropDefinition> {\n const props: Record<string, PropDefinition> = {};\n\n // Merge global and meta argTypes (meta takes precedence)\n const mergedArgTypes = { ...globalArgTypes, ...argTypes };\n\n for (const [name, argType] of Object.entries(mergedArgTypes)) {\n // Skip disabled argTypes\n if (argType.table?.disable) continue;\n // Skip action-only argTypes (no control)\n if (argType.control === false && argType.action) continue;\n\n // Extract control type and options\n const { controlType, controlOptions } = extractControlInfo(argType);\n\n props[name] = {\n type: inferPropType(argType),\n description: argType.description ?? `${name} prop`,\n ...(argType.options && { values: argType.options }),\n ...(argType.table?.defaultValue && {\n default: argType.table.defaultValue.summary,\n }),\n ...(argType.defaultValue !== undefined && {\n default: argType.defaultValue,\n }),\n ...(argType.type?.required && { required: true }),\n ...(controlType && { controlType }),\n ...(controlOptions && Object.keys(controlOptions).length > 0 && { controlOptions }),\n };\n }\n\n return props;\n}\n\n/**\n * Extract control type and options from a Storybook argType\n */\nfunction extractControlInfo(argType: StoryArgType): {\n controlType?: ControlType;\n controlOptions?: PropDefinition[\"controlOptions\"];\n} {\n // Handle no control or explicitly disabled control\n if (argType.control === undefined || argType.control === false) {\n return {};\n }\n\n const control = typeof argType.control === \"string\"\n ? { type: argType.control }\n : argType.control;\n\n // Map control type string to ControlType\n const validControlTypes: ControlType[] = [\n \"text\", \"number\", \"range\", \"boolean\", \"select\", \"multi-select\",\n \"radio\", \"inline-radio\", \"check\", \"inline-check\", \"object\", \"file\", \"color\", \"date\"\n ];\n\n const controlType = validControlTypes.includes(control.type as ControlType)\n ? (control.type as ControlType)\n : undefined;\n\n // Extract control options for controls that need them\n const controlOptions: PropDefinition[\"controlOptions\"] = {};\n\n if (control.min !== undefined) controlOptions.min = control.min;\n if (control.max !== undefined) controlOptions.max = control.max;\n if (control.step !== undefined) controlOptions.step = control.step;\n if (control.presetColors) controlOptions.presetColors = control.presetColors;\n\n return {\n controlType,\n controlOptions: Object.keys(controlOptions).length > 0 ? controlOptions : undefined,\n };\n}\n\n/**\n * Infer prop type from Storybook control/type\n * Handles all Storybook 8.x control types\n */\nfunction inferPropType(argType: StoryArgType): PropDefinition[\"type\"] {\n // Action argType → function\n if (argType.action) return \"function\";\n\n // If has options, it's an enum\n if (argType.options?.length) return \"enum\";\n\n // Check explicit type\n if (argType.type?.name) {\n const typeMap: Record<string, PropDefinition[\"type\"]> = {\n string: \"string\",\n number: \"number\",\n boolean: \"boolean\",\n object: \"object\",\n array: \"array\",\n function: \"function\",\n };\n const mapped = typeMap[argType.type.name];\n if (mapped) return mapped;\n }\n\n // Check control type\n const control =\n typeof argType.control === \"string\"\n ? argType.control\n : argType.control\n ? argType.control.type\n : undefined;\n\n if (control) {\n const controlMap: Record<string, PropDefinition[\"type\"]> = {\n // Text controls\n text: \"string\",\n\n // Number controls\n number: \"number\",\n range: \"number\",\n\n // Boolean controls\n boolean: \"boolean\",\n check: \"boolean\",\n \"inline-check\": \"boolean\",\n\n // Enum/selection controls\n select: \"enum\",\n \"multi-select\": \"enum\",\n radio: \"enum\",\n \"inline-radio\": \"enum\",\n\n // Object controls\n object: \"object\",\n file: \"object\",\n\n // Special string controls\n color: \"string\",\n date: \"string\",\n };\n const mapped = controlMap[control];\n if (mapped) return mapped;\n }\n\n return \"string\";\n}\n\n/**\n * Check if a value looks like a Storybook story\n * Handles both CSF 3 (objects) and CSF 2 (functions from Template.bind({}))\n */\nfunction isStory(value: unknown): value is Story | CSF2Story {\n // CSF 3: Story is an object with args/render/play\n if (typeof value === \"object\" && value !== null) {\n const obj = value as Record<string, unknown>;\n if (\"args\" in obj || \"render\" in obj || \"play\" in obj) return true;\n }\n\n // CSF 2: Story is a function (from Template.bind({})) with args attached\n if (typeof value === \"function\") {\n const fn = value as ((...args: unknown[]) => unknown) & { args?: unknown };\n if (\"args\" in fn) return true;\n }\n\n return false;\n}\n\n/**\n * Extract variants from story exports using @storybook/csf utilities\n */\nfunction extractVariants(\n storyModule: StoryModule,\n component: ComponentType<unknown>,\n meta: StoryMeta\n): FragmentVariant[] {\n const variants: FragmentVariant[] = [];\n\n for (const [exportName, exportValue] of Object.entries(storyModule)) {\n // Skip default export\n if (exportName === \"default\") continue;\n\n // Use isExportStory to filter based on includeStories/excludeStories\n if (!isExportStory(exportName, meta)) continue;\n\n // Check if it's a story\n if (!isStory(exportValue)) continue;\n\n const story = exportValue as Story | CSF2Story;\n\n // Get story name using storyNameFromExport\n const storyName =\n (typeof story === \"object\" && story.name) ||\n (typeof story === \"object\" && story.storyName) ||\n (typeof story === \"function\" && story.storyName) ||\n storyNameFromExport(exportName);\n\n // Generate story ID matching Storybook format\n const storyId = toId(meta.title || \"Unknown\", exportName);\n\n // Extract description based on story format\n let description = `${storyName} variant`;\n if (typeof story === \"object\" && story.parameters?.docs?.description?.story) {\n description = story.parameters.docs.description.story;\n }\n\n // Check for play function and capture it\n const storyPlayFn = typeof story === \"object\" ? story.play : story.play;\n const hasPlayFunction = !!storyPlayFn;\n\n // Create wrapped play function that adapts Storybook context to our PlayFunctionContext\n const wrappedPlay: PlayFunction | undefined = storyPlayFn\n ? async (context: PlayFunctionContext): Promise<void> => {\n // Build full Storybook context for compatibility\n const args = {\n ...globalPreviewConfig.args,\n ...meta.args,\n ...(typeof story === \"function\" ? story.args : story.args),\n };\n const fullContext = buildStoryContext(meta, story, args, storyId, storyName);\n\n // Merge our context with Storybook context\n const playContext = {\n ...fullContext,\n canvasElement: context.canvasElement,\n args: context.args,\n step: context.step,\n };\n\n await storyPlayFn(playContext as unknown as StorybookPlayFunctionContext);\n }\n : undefined;\n\n // Get story tags\n const storyTags = typeof story === \"object\" ? story.tags : undefined;\n\n // Collect loaders from global, meta, and story (in order)\n const loaders = collectLoaders(meta, story);\n\n // Compute the merged args for this variant (for code generation)\n const variantArgs = {\n ...globalPreviewConfig.args,\n ...meta.args,\n ...(typeof story === \"function\" ? story.args : story.args),\n };\n\n // Only include args if there are any defined\n const hasArgs = Object.keys(variantArgs).length > 0;\n\n variants.push({\n name: storyName,\n description,\n render: createRenderFunction(story, component, meta, storyId, storyName),\n // Store Storybook-specific metadata\n ...(hasPlayFunction && { hasPlayFunction: true }),\n ...(wrappedPlay && { play: wrappedPlay }),\n ...(storyId && { storyId }),\n ...(storyTags && { tags: storyTags }),\n ...(loaders.length > 0 && { loaders }),\n ...(hasArgs && { args: variantArgs }),\n });\n }\n\n return variants;\n}\n\n/**\n * Collect loaders from global, meta, and story levels\n * Returns wrapped loader functions that execute with context\n */\nfunction collectLoaders(\n meta: StoryMeta,\n story: Story | CSF2Story\n): VariantLoader[] {\n const allLoaders: Loader[] = [\n ...(globalPreviewConfig.loaders ?? []),\n ...(meta.loaders ?? []),\n ...(typeof story === \"function\" ? story.loaders ?? [] : story.loaders ?? []),\n ];\n\n if (allLoaders.length === 0) {\n return [];\n }\n\n // Wrap each loader to execute without requiring context at call time\n // The actual context will be built when the loader is executed\n return allLoaders.map((loader) => {\n return async (): Promise<Record<string, unknown>> => {\n // Create a minimal context for loader execution\n const minimalContext: StoryContext = {\n args: {},\n argTypes: {},\n globals: {},\n parameters: {},\n id: \"\",\n kind: meta.title || \"Unknown\",\n name: \"\",\n story: \"\",\n viewMode: \"story\",\n loaded: {},\n abortSignal: new AbortController().signal,\n componentId: \"\",\n title: meta.title || \"Unknown\",\n };\n return loader(minimalContext);\n };\n });\n}\n\n/**\n * Build a StoryContext for decorators and render functions\n */\nfunction buildStoryContext(\n meta: StoryMeta,\n story: Story | CSF2Story,\n args: Record<string, unknown>,\n storyId: string,\n storyName: string,\n loadedData?: Record<string, unknown>\n): StoryContext {\n const mergedArgs = {\n ...globalPreviewConfig.args,\n ...meta.args,\n ...(typeof story === \"object\" ? story.args : story.args),\n ...args,\n };\n\n const mergedArgTypes = {\n ...globalPreviewConfig.argTypes,\n ...meta.argTypes,\n ...(typeof story === \"object\" ? story.argTypes : story.argTypes),\n };\n\n const mergedParameters = {\n ...globalPreviewConfig.parameters,\n ...meta.parameters,\n ...(typeof story === \"object\" ? story.parameters : story.parameters),\n };\n\n return {\n args: mergedArgs,\n argTypes: mergedArgTypes ?? {},\n globals: {},\n parameters: mergedParameters ?? {},\n id: storyId,\n kind: meta.title || \"Unknown\",\n name: storyName,\n story: storyName,\n viewMode: \"story\",\n loaded: loadedData ?? {},\n abortSignal: new AbortController().signal,\n componentId: toId(meta.title || \"Unknown\", \"\"),\n title: meta.title || \"Unknown\",\n };\n}\n\n/**\n * Create a render function for a story\n * Handles both CSF 3 (objects) and CSF 2 (functions)\n * Applies decorators in correct order: story → meta → global (innermost first)\n * Accepts optional args overrides and loaded data from loaders\n */\nfunction createRenderFunction(\n story: Story | CSF2Story,\n component: ComponentType<unknown>,\n meta: StoryMeta,\n storyId: string,\n storyName: string\n): (options?: VariantRenderOptions) => ReactNode {\n return (options?: VariantRenderOptions) => {\n // Merge args: global → meta → story → runtime overrides\n const args = {\n ...globalPreviewConfig.args,\n ...meta.args,\n ...(typeof story === \"function\" ? story.args : story.args),\n ...options?.args, // Runtime overrides from viewer props panel\n };\n\n const loadedData = options?.loadedData;\n\n // Build the story context with loaded data\n const context = buildStoryContext(meta, story, args, storyId, storyName, loadedData);\n\n // Create the base render function\n let renderFn: () => ReactNode;\n\n if (typeof story === \"function\") {\n // CSF 2: Story is a function (from Template.bind({}))\n renderFn = () => story(args);\n } else if (story.render) {\n // CSF 3: Story has custom render function\n // Support both render(args) and render(args, context) signatures\n renderFn = () =>\n story.render!.length >= 2\n ? story.render!(args, context)\n : story.render!(args);\n } else if (meta.render) {\n // CSF 3: Meta has default render function\n renderFn = () =>\n meta.render!.length >= 2\n ? meta.render!(args, context)\n : meta.render!(args);\n } else {\n // Default: render component with args\n renderFn = () => getCreateElement()(component, args);\n }\n\n // Collect decorators in Storybook order\n // story → meta → global, then reverse to apply innermost first\n const allDecorators = [\n ...(globalPreviewConfig.decorators ?? []),\n ...(meta.decorators ?? []),\n ...(typeof story === \"function\" ? story.decorators ?? [] : story.decorators ?? []),\n ].reverse();\n\n // Apply decorators if any\n if (allDecorators.length > 0) {\n return applyDecorators(renderFn, allDecorators, context);\n }\n\n return renderFn();\n };\n}\n\n/**\n * Apply decorators in the correct order\n * Decorators wrap from innermost to outermost\n */\nfunction applyDecorators(\n renderFn: () => ReactNode,\n decorators: Decorator[],\n context: StoryContext\n): ReactNode {\n // Start with the base render function\n let storyFn: () => ReactNode = renderFn;\n\n // Each decorator wraps the previous one\n for (const decorator of decorators) {\n const wrappedFn = storyFn;\n storyFn = () => decorator(wrappedFn, context);\n }\n\n return storyFn();\n}\n\n/**\n * Convert PascalCase to Title Case\n * @deprecated Use storyNameFromExport from @storybook/csf instead\n */\nfunction pascalToTitle(name: string): string {\n return name.replace(/([A-Z])/g, \" $1\").trim();\n}\n"],"mappings":"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","names":[]}
|
package/dist/test-utils.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usefragments/core",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Core types, schemas, and runtime API for Fragments component definitions",
|
|
6
6
|
"author": "Conan McNicholl",
|
|
@@ -71,6 +71,14 @@
|
|
|
71
71
|
"./test-utils": {
|
|
72
72
|
"types": "./dist/test-utils.d.ts",
|
|
73
73
|
"import": "./dist/test-utils.js"
|
|
74
|
+
},
|
|
75
|
+
"./story": {
|
|
76
|
+
"types": "./dist/storyAdapter.d.ts",
|
|
77
|
+
"import": "./dist/storyAdapter.js"
|
|
78
|
+
},
|
|
79
|
+
"./preview-runtime": {
|
|
80
|
+
"types": "./dist/preview-runtime.d.ts",
|
|
81
|
+
"import": "./dist/preview-runtime.js"
|
|
74
82
|
}
|
|
75
83
|
},
|
|
76
84
|
"files": [
|
package/src/index.ts
CHANGED
|
@@ -501,28 +501,6 @@ export {
|
|
|
501
501
|
} from "./policy-exclude.js";
|
|
502
502
|
export type { PolicyExclude, PolicyExcludeInput } from "./policy-exclude.js";
|
|
503
503
|
|
|
504
|
-
// Story adapter (runtime conversion of Storybook modules)
|
|
505
|
-
export {
|
|
506
|
-
storyModuleToFragment,
|
|
507
|
-
setPreviewConfig,
|
|
508
|
-
getPreviewConfig,
|
|
509
|
-
// Re-export @storybook/csf utilities
|
|
510
|
-
toId,
|
|
511
|
-
storyNameFromExport,
|
|
512
|
-
isExportStory,
|
|
513
|
-
} from "./storyAdapter.js";
|
|
514
|
-
export type {
|
|
515
|
-
StoryModule,
|
|
516
|
-
StoryMeta,
|
|
517
|
-
Story,
|
|
518
|
-
StoryArgType,
|
|
519
|
-
StoryContext,
|
|
520
|
-
Decorator,
|
|
521
|
-
Loader,
|
|
522
|
-
PreviewConfig,
|
|
523
|
-
CSF2Story,
|
|
524
|
-
} from "./storyAdapter.js";
|
|
525
|
-
|
|
526
504
|
// Storybook adapter filtering
|
|
527
505
|
export {
|
|
528
506
|
checkStoryExclusion,
|
|
@@ -640,19 +618,6 @@ export type {
|
|
|
640
618
|
CompositionGuideline,
|
|
641
619
|
} from "./composition.js";
|
|
642
620
|
|
|
643
|
-
// Shared preview runtime
|
|
644
|
-
export {
|
|
645
|
-
executeVariantLoaders,
|
|
646
|
-
resolvePreviewRuntimeState,
|
|
647
|
-
usePreviewVariantRuntime,
|
|
648
|
-
PreviewVariantRuntime,
|
|
649
|
-
} from "./preview-runtime.js";
|
|
650
|
-
export type {
|
|
651
|
-
PreviewVariantLike,
|
|
652
|
-
PreviewRuntimeState,
|
|
653
|
-
PreviewRuntimeOptions,
|
|
654
|
-
} from "./preview-runtime.js";
|
|
655
|
-
|
|
656
621
|
// Component discovery
|
|
657
622
|
export { discoverComponents, isReactComponent } from "./component-discovery.js";
|
|
658
623
|
export type { DiscoverComponentsOptions } from "./component-discovery.js";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
describe("getCreateElement", () => {
|
|
4
|
+
afterEach(() => {
|
|
5
|
+
vi.resetModules();
|
|
6
|
+
vi.doUnmock("node:module");
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("throws a clear error when react cannot be resolved", async () => {
|
|
10
|
+
vi.doMock("node:module", () => ({
|
|
11
|
+
createRequire: () => () => {
|
|
12
|
+
throw new Error("Cannot find module 'react'");
|
|
13
|
+
},
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
const { getCreateElement } = await import("./react-create-element.js");
|
|
17
|
+
|
|
18
|
+
expect(() => getCreateElement()).toThrow(
|
|
19
|
+
'@usefragments/core: story rendering requires the optional peer dependency "react" (>=18). Install react to use storyModuleToFragment default render paths.',
|
|
20
|
+
);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
|
|
3
|
+
export function getCreateElement(): typeof import("react")["createElement"] {
|
|
4
|
+
try {
|
|
5
|
+
return (createRequire(import.meta.url)("react") as typeof import("react")).createElement;
|
|
6
|
+
} catch (error) {
|
|
7
|
+
throw new Error(
|
|
8
|
+
'@usefragments/core: story rendering requires the optional peer dependency "react" (>=18). Install react to use storyModuleToFragment default render paths.',
|
|
9
|
+
{ cause: error instanceof Error ? error : undefined },
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
}
|
package/src/react-types.ts
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Import from '@usefragments/core/react' when you need React-typed
|
|
5
5
|
* component definitions. The main '@usefragments/core' entrypoint
|
|
6
|
-
* works without React installed
|
|
6
|
+
* works without React installed — story and preview-runtime APIs live on
|
|
7
|
+
* `@usefragments/core/story` and `@usefragments/core/preview-runtime`.
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
import type { ComponentType, ReactNode, JSX } from 'react';
|
package/src/storyAdapter.ts
CHANGED
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
* Supports Storybook 8.x with both CSF2 (Template.bind) and CSF3 (object stories).
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import type { ComponentType, ReactNode } from "react";
|
|
12
12
|
import { toId, storyNameFromExport, isExportStory } from "./storybook-csf.js";
|
|
13
|
+
import { getCreateElement } from "./react-create-element.js";
|
|
13
14
|
import type {
|
|
14
15
|
FragmentDefinition,
|
|
15
16
|
FragmentMeta,
|
|
@@ -711,7 +712,7 @@ function createRenderFunction(
|
|
|
711
712
|
: meta.render!(args);
|
|
712
713
|
} else {
|
|
713
714
|
// Default: render component with args
|
|
714
|
-
renderFn = () =>
|
|
715
|
+
renderFn = () => getCreateElement()(component, args);
|
|
715
716
|
}
|
|
716
717
|
|
|
717
718
|
// Collect decorators in Storybook order
|
package/src/tokens/scss.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
|
|
3
3
|
import { normalizeTokenGroupComment } from "./categories.js";
|
|
4
|
-
import { parseScssTokens } from "./scss.js";
|
|
4
|
+
import { parseScssTokens, parseScssVariables } from "./scss.js";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* FRICTION #7d — stop prose SCSS comments from being slugified into token
|
|
@@ -92,3 +92,34 @@ describe("parseScssTokens category derivation (#7d)", () => {
|
|
|
92
92
|
expect(out.categories.colors?.some((t) => t.name === "--fui-color-accent")).toBe(true);
|
|
93
93
|
});
|
|
94
94
|
});
|
|
95
|
+
|
|
96
|
+
describe("parseScssVariables", () => {
|
|
97
|
+
it("retains multiline generated adapter expressions as one variable value", () => {
|
|
98
|
+
const variables = parseScssVariables(`
|
|
99
|
+
$fui-font-weight-normal: map.get(
|
|
100
|
+
measurements.$legacy,
|
|
101
|
+
"typography",
|
|
102
|
+
"fontWeight",
|
|
103
|
+
"normal"
|
|
104
|
+
) !default;
|
|
105
|
+
`);
|
|
106
|
+
|
|
107
|
+
expect(variables.get("$fui-font-weight-normal")).toBe(
|
|
108
|
+
'map.get( measurements.$legacy, "typography", "fontWeight", "normal" )'
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("keeps Sass maps available for resolution without emitting the map container as a token", () => {
|
|
113
|
+
const scss = `
|
|
114
|
+
$spacing: (
|
|
115
|
+
xs: 4px,
|
|
116
|
+
sm: 8px,
|
|
117
|
+
);
|
|
118
|
+
`;
|
|
119
|
+
|
|
120
|
+
expect(parseScssVariables(scss).get("$spacing")).toBe("( xs: 4px, sm: 8px, )");
|
|
121
|
+
|
|
122
|
+
const tokens = Object.values(parseScssTokens(scss).categories).flat();
|
|
123
|
+
expect(tokens.map((token) => token.name)).not.toContain("$spacing");
|
|
124
|
+
});
|
|
125
|
+
});
|