@sit-onyx/storybook-utils 1.0.0-beta.3 → 1.0.0-beta.31
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/package.json +5 -8
- package/src/actions.ts +1 -1
- package/src/index.ts +1 -0
- package/src/preview.ts +4 -5
- package/src/sbType.spec.ts +38 -0
- package/src/sbType.ts +35 -0
- package/src/source-code-generator.spec.ts +8 -4
- package/src/source-code-generator.ts +70 -17
- package/src/theme.ts +2 -3
- package/src/types.ts +1 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sit-onyx/storybook-utils",
|
|
3
3
|
"description": "Storybook utilities for Vue",
|
|
4
|
-
"version": "1.0.0-beta.
|
|
4
|
+
"version": "1.0.0-beta.31",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Schwarz IT KG",
|
|
7
7
|
"license": "Apache-2.0",
|
|
@@ -23,17 +23,14 @@
|
|
|
23
23
|
"url": "https://github.com/SchwarzIT/onyx/issues"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
|
-
"@storybook/
|
|
27
|
-
"
|
|
28
|
-
"@storybook/preview-api": ">= 8.0.0",
|
|
29
|
-
"@storybook/theming": ">= 8.0.0",
|
|
30
|
-
"@storybook/vue3": ">= 8.0.0",
|
|
26
|
+
"@storybook/vue3": ">= 8.2.0",
|
|
27
|
+
"storybook": ">= 8.2.0",
|
|
31
28
|
"storybook-dark-mode": ">= 4",
|
|
32
29
|
"@sit-onyx/icons": "^1.0.0-beta.0",
|
|
33
|
-
"sit-onyx": "^1.0.0-beta.
|
|
30
|
+
"sit-onyx": "^1.0.0-beta.30"
|
|
34
31
|
},
|
|
35
32
|
"dependencies": {
|
|
36
|
-
"deepmerge-ts": "^7.0
|
|
33
|
+
"deepmerge-ts": "^7.1.0"
|
|
37
34
|
},
|
|
38
35
|
"scripts": {
|
|
39
36
|
"build": "tsc --noEmit",
|
package/src/actions.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { useArgs } from "@storybook/preview-api";
|
|
2
1
|
import type { ArgTypes, Decorator, Meta } from "@storybook/vue3";
|
|
3
2
|
import { deepmerge } from "deepmerge-ts";
|
|
3
|
+
import { useArgs } from "storybook/internal/preview-api";
|
|
4
4
|
import { isReactive, reactive, watch } from "vue";
|
|
5
5
|
import type { DefineStorybookActionsAndVModelsOptions, ExtractVueEventNames } from ".";
|
|
6
6
|
|
package/src/index.ts
CHANGED
package/src/preview.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { addons } from "@storybook/preview-api";
|
|
3
|
-
import { type ThemeVars } from "@storybook/theming";
|
|
1
|
+
import { getIconImportName } from "@sit-onyx/icons";
|
|
4
2
|
import { type Preview, type StoryContext } from "@storybook/vue3";
|
|
5
3
|
import { deepmerge } from "deepmerge-ts";
|
|
6
4
|
import { DARK_MODE_EVENT_NAME } from "storybook-dark-mode";
|
|
7
|
-
|
|
8
|
-
import {
|
|
5
|
+
import { DOCS_RENDERED } from "storybook/internal/core-events";
|
|
6
|
+
import { addons } from "storybook/internal/preview-api";
|
|
7
|
+
import type { ThemeVars } from "storybook/internal/theming";
|
|
9
8
|
import { requiredGlobalType, withRequired } from "./required";
|
|
10
9
|
import { generateSourceCode } from "./source-code-generator";
|
|
11
10
|
import { ONYX_BREAKPOINTS, createTheme } from "./theme";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { SBType } from "storybook/internal/types";
|
|
2
|
+
import { describe, expect, test, vi } from "vitest";
|
|
3
|
+
import { walkTree } from "./sbType";
|
|
4
|
+
|
|
5
|
+
describe("walkTree", () => {
|
|
6
|
+
test.each<{ input: SBType; expected: SBType["name"][] }>([
|
|
7
|
+
{ input: { name: "array", value: { name: "number" } }, expected: ["array", "number"] },
|
|
8
|
+
{ input: { name: "object", value: { a: { name: "number" } } }, expected: ["object", "number"] },
|
|
9
|
+
{ input: { name: "enum", value: ["a"] }, expected: ["enum"] },
|
|
10
|
+
{
|
|
11
|
+
input: { name: "intersection", value: [{ name: "number" }] },
|
|
12
|
+
expected: ["intersection", "number"],
|
|
13
|
+
},
|
|
14
|
+
{ input: { name: "union", value: [{ name: "number" }] }, expected: ["union", "number"] },
|
|
15
|
+
{ input: { name: "other", value: "a" }, expected: ["other"] },
|
|
16
|
+
])("should execute cb for $input.name correctly", ({ input, expected }) => {
|
|
17
|
+
const spy = vi.fn<(p: SBType) => void>();
|
|
18
|
+
const result = walkTree(input, spy);
|
|
19
|
+
|
|
20
|
+
expect(result).toBeUndefined();
|
|
21
|
+
expect(spy).toHaveBeenCalledTimes(expected.length);
|
|
22
|
+
const nameCalls = spy.mock.calls.map(([{ name }]) => name);
|
|
23
|
+
expect(nameCalls).toMatchObject(expected);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("should return value if there is any returned", () => {
|
|
27
|
+
const target: SBType = { name: "number", raw: "here" };
|
|
28
|
+
const overshoot: SBType = { name: "boolean", raw: "here" };
|
|
29
|
+
const parent: SBType = { name: "intersection", value: [target, overshoot] };
|
|
30
|
+
const returned = 42;
|
|
31
|
+
const spy = vi.fn((p: SBType) => (p.raw === "here" ? returned : undefined));
|
|
32
|
+
const result = walkTree({ name: "union", value: [parent] }, spy);
|
|
33
|
+
|
|
34
|
+
expect(spy).toHaveBeenCalledTimes(3);
|
|
35
|
+
expect(spy).toHaveBeenLastCalledWith(target, parent);
|
|
36
|
+
expect(result).toBe(returned);
|
|
37
|
+
});
|
|
38
|
+
});
|
package/src/sbType.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { SBType } from "storybook/internal/types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Call a function `cb` for every type node in the storybook type tree.
|
|
5
|
+
* @param inputType the root type
|
|
6
|
+
* @param cb the function that is called for every type. If any non-nullish value is returned by `cb` the execution is stopped and this value is returned.
|
|
7
|
+
* @param parent optional, the parent type. Is only used as input for the `cb` function and provided when recursing.
|
|
8
|
+
* @returns the first non-nullish value that is returned by `cb`
|
|
9
|
+
*/
|
|
10
|
+
export const walkTree = <TValue>(
|
|
11
|
+
inputType: SBType,
|
|
12
|
+
cb: (sb: SBType, parent?: SBType) => TValue,
|
|
13
|
+
parent?: SBType,
|
|
14
|
+
): TValue | undefined => {
|
|
15
|
+
const shouldReturn = cb(inputType, parent);
|
|
16
|
+
if (shouldReturn) {
|
|
17
|
+
return shouldReturn;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (inputType.name === "union" || inputType.name === "intersection") {
|
|
21
|
+
return inputType.value.reduce<TValue | undefined>(
|
|
22
|
+
(prev, it) => prev ?? walkTree(it, cb, inputType),
|
|
23
|
+
undefined,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
if (inputType.name === "array") {
|
|
27
|
+
return walkTree(inputType.value, cb, inputType);
|
|
28
|
+
}
|
|
29
|
+
if (inputType.name === "object") {
|
|
30
|
+
return Object.values(inputType.value).reduce<TValue | undefined>(
|
|
31
|
+
(prev, it) => prev ?? walkTree(it, cb, inputType),
|
|
32
|
+
undefined,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
@@ -156,16 +156,20 @@ test("should generate source code for slots with bindings", () => {
|
|
|
156
156
|
type TestBindings = {
|
|
157
157
|
foo: string;
|
|
158
158
|
bar?: number;
|
|
159
|
+
boo: {
|
|
160
|
+
mimeType: string;
|
|
161
|
+
};
|
|
159
162
|
};
|
|
160
163
|
|
|
161
164
|
const slots = {
|
|
162
|
-
a: ({ foo, bar }: TestBindings) => `Slot with bindings ${foo} and ${
|
|
163
|
-
b: ({ foo }: TestBindings) =>
|
|
165
|
+
a: ({ foo, bar, boo }: TestBindings) => `Slot with bindings ${foo}, ${bar} and ${boo.mimeType}`,
|
|
166
|
+
b: ({ foo, boo }: TestBindings) =>
|
|
167
|
+
h("a", { href: foo, target: foo, type: boo.mimeType, ...boo }, `Test link: ${foo}`),
|
|
164
168
|
};
|
|
165
169
|
|
|
166
|
-
const expectedCode = `<template #a="{ foo, bar }">Slot with bindings {{ foo }} and {{
|
|
170
|
+
const expectedCode = `<template #a="{ foo, bar, boo }">Slot with bindings {{ foo }}, {{ bar }} and {{ boo.mimeType }}</template>
|
|
167
171
|
|
|
168
|
-
<template #b="{ foo }"><a :href="foo" :target="foo">Test link: {{ foo }}</a></template>`;
|
|
172
|
+
<template #b="{ foo, boo }"><a :href="foo" :target="foo" :type="boo.mimeType" v-bind="boo">Test link: {{ foo }}</a></template>`;
|
|
169
173
|
|
|
170
174
|
const actualCode = generateSlotSourceCode(slots, Object.keys(slots), {
|
|
171
175
|
imports: {},
|
|
@@ -3,11 +3,25 @@
|
|
|
3
3
|
// It is intended to be deleted once its officially released in Storybook itself, see:
|
|
4
4
|
// https://github.com/storybookjs/storybook/pull/27194
|
|
5
5
|
//
|
|
6
|
-
import { SourceType } from "@storybook/docs-tools";
|
|
7
6
|
import type { Args, StoryContext } from "@storybook/vue3";
|
|
7
|
+
import { SourceType } from "storybook/internal/docs-tools";
|
|
8
8
|
import { isVNode, type VNode } from "vue";
|
|
9
9
|
import { replaceAll } from "./preview";
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Used to get the tracking data from the proxy.
|
|
13
|
+
* A symbol is unique, so when using it as a key it can't be accidentally accessed.
|
|
14
|
+
*/
|
|
15
|
+
const TRACKING_SYMBOL = Symbol("DEEP_ACCESS_SYMBOL");
|
|
16
|
+
|
|
17
|
+
type TrackingProxy = {
|
|
18
|
+
[TRACKING_SYMBOL]: true;
|
|
19
|
+
toString: () => string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const isProxy = (obj: unknown): obj is TrackingProxy =>
|
|
23
|
+
!!(obj && typeof obj === "object" && TRACKING_SYMBOL in obj);
|
|
24
|
+
|
|
11
25
|
/**
|
|
12
26
|
* Context that is passed down to nested components/slots when generating the source code for a single story.
|
|
13
27
|
*/
|
|
@@ -184,6 +198,10 @@ export const generatePropsSourceCode = (
|
|
|
184
198
|
if (slotNames.includes(propName)) return;
|
|
185
199
|
if (value == undefined) return; // do not render undefined/null values
|
|
186
200
|
|
|
201
|
+
if (isProxy(value)) {
|
|
202
|
+
value = value!.toString();
|
|
203
|
+
}
|
|
204
|
+
|
|
187
205
|
switch (typeof value) {
|
|
188
206
|
case "string":
|
|
189
207
|
if (value === "") return; // do not render empty strings
|
|
@@ -225,7 +243,7 @@ export const generatePropsSourceCode = (
|
|
|
225
243
|
case "object": {
|
|
226
244
|
properties.push({
|
|
227
245
|
name: propName,
|
|
228
|
-
value: formatObject(value),
|
|
246
|
+
value: formatObject(value ?? {}),
|
|
229
247
|
// to follow Vue best practices, complex values like object and arrays are
|
|
230
248
|
// usually placed inside the <script setup> block instead of inlining them in the <template>
|
|
231
249
|
templateFn: undefined,
|
|
@@ -373,25 +391,60 @@ const generateSlotChildrenSourceCode = (
|
|
|
373
391
|
(param) => !["{", "}"].includes(param),
|
|
374
392
|
);
|
|
375
393
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
}, {});
|
|
380
|
-
|
|
381
|
-
const returnValue = child(parameters);
|
|
382
|
-
let slotSourceCode = generateSlotChildrenSourceCode([returnValue], ctx);
|
|
383
|
-
|
|
384
|
-
// if slot bindings are used for properties of other components, our {{ paramName }} is incorrect because
|
|
385
|
-
// it would generate e.g. my-prop="{{ paramName }}", therefore, we replace it here to e.g. :my-prop="paramName"
|
|
394
|
+
// We create proxy to track how and which properties of a parameter are accessed
|
|
395
|
+
const parameters: Record<string, string> = {};
|
|
396
|
+
const proxied: Record<string, TrackingProxy> = {};
|
|
386
397
|
paramNames.forEach((param) => {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
398
|
+
parameters[param] = `{{ ${param} }}`;
|
|
399
|
+
// TODO: we should be able to extend the proxy logic here and maybe get rid of the `generatePropsSourceCode` code
|
|
400
|
+
proxied[param] = new Proxy(
|
|
401
|
+
{
|
|
402
|
+
// we use the symbol to identify the proxy
|
|
403
|
+
[TRACKING_SYMBOL]: true,
|
|
404
|
+
} as TrackingProxy,
|
|
405
|
+
{
|
|
406
|
+
// getter is called when any prop of the parameter is read
|
|
407
|
+
get: (t, key) => {
|
|
408
|
+
if (key === TRACKING_SYMBOL) {
|
|
409
|
+
// allow retrieval of the tracking data
|
|
410
|
+
return t[TRACKING_SYMBOL];
|
|
411
|
+
}
|
|
412
|
+
if ([Symbol.toPrimitive, Symbol.toStringTag, "toString"].includes(key)) {
|
|
413
|
+
// when the parameter is used as a string we return the parameter name
|
|
414
|
+
// we use the double brace notation as we don't know if the parameter is used in text or in a binding
|
|
415
|
+
return () => `{{ ${param} }}`;
|
|
416
|
+
}
|
|
417
|
+
if (key === "v-bind") {
|
|
418
|
+
// if this key is returned we just return the parameter name
|
|
419
|
+
return `${param}`;
|
|
420
|
+
}
|
|
421
|
+
// otherwise a specific key of the parameter was accessed
|
|
422
|
+
// we use the double brace notation as we don't know if the parameter is used in text or in a binding
|
|
423
|
+
return `{{ ${param}.${key.toString()} }}`;
|
|
424
|
+
},
|
|
425
|
+
// ownKeys is called, among other uses, when an object is destructured
|
|
426
|
+
// in this case we assume the parameter is supposed to be bound using "v-bind"
|
|
427
|
+
// Therefore we only return one special key "v-bind" and the getter will be called afterwards with it
|
|
428
|
+
ownKeys: () => {
|
|
429
|
+
return [`v-bind`];
|
|
430
|
+
},
|
|
431
|
+
/** called when destructured */
|
|
432
|
+
getOwnPropertyDescriptor: () => ({
|
|
433
|
+
configurable: true,
|
|
434
|
+
enumerable: true,
|
|
435
|
+
value: param,
|
|
436
|
+
writable: true,
|
|
437
|
+
}),
|
|
438
|
+
},
|
|
391
439
|
);
|
|
392
440
|
});
|
|
393
441
|
|
|
394
|
-
|
|
442
|
+
const returnValue = child(proxied);
|
|
443
|
+
const slotSourceCode = generateSlotChildrenSourceCode([returnValue], ctx);
|
|
444
|
+
|
|
445
|
+
// if slot bindings are used for properties of other components, our {{ paramName }} is incorrect because
|
|
446
|
+
// it would generate e.g. my-prop="{{ paramName }}", therefore, we replace it here to e.g. :my-prop="paramName"
|
|
447
|
+
return replaceAll(slotSourceCode, / (\S+)="{{ (\S+) }}"/g, ` :$1="$2"`);
|
|
395
448
|
}
|
|
396
449
|
|
|
397
450
|
case "bigint":
|
package/src/theme.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { create } from "@storybook/theming/create";
|
|
1
|
+
import { ONYX_BREAKPOINTS as RAW_ONYX_BREAKPOINTS, type OnyxBreakpoint } from "sit-onyx";
|
|
3
2
|
import onyxVariables from "sit-onyx/themes/onyx.json";
|
|
4
|
-
import {
|
|
3
|
+
import { create, type ThemeVars, type ThemeVarsPartial } from "storybook/internal/theming";
|
|
5
4
|
import onyxLogo from "./assets/logo-onyx.svg";
|
|
6
5
|
|
|
7
6
|
/**
|