@sit-onyx/storybook-utils 1.0.0-beta.4 → 1.0.0-beta.41
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 +6 -9
- package/src/actions.ts +40 -64
- package/src/index.ts +1 -0
- package/src/preview.ts +19 -6
- 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 -49
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.41",
|
|
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
|
-
"@sit-onyx/icons": "^1.0.0-beta.
|
|
33
|
-
"sit-onyx": "^1.0.0-beta.
|
|
29
|
+
"@sit-onyx/icons": "^1.0.0-beta.1",
|
|
30
|
+
"sit-onyx": "^1.0.0-beta.39"
|
|
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,85 +1,61 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
1
|
+
import type { Decorator } from "@storybook/vue3";
|
|
2
|
+
import { useArgs } from "storybook/internal/preview-api";
|
|
3
|
+
import type { ArgTypesEnhancer, StrictInputType } from "storybook/internal/types";
|
|
4
4
|
import { isReactive, reactive, watch } from "vue";
|
|
5
|
-
import type { DefineStorybookActionsAndVModelsOptions, ExtractVueEventNames } from ".";
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
|
-
*
|
|
9
|
-
* the given events as well as implementing v-model handlers so that the Storybook controls are updated when you interact with the component.
|
|
10
|
-
* Should be preferred over manually defining argTypes for *.stories.ts files.
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```ts
|
|
14
|
-
* // Input.stories.ts
|
|
15
|
-
* import { defineStorybookActionsAndVModels } from '@sit-onyx/storybook-utils';
|
|
16
|
-
* import type { Meta } from '@storybook/vue3';
|
|
17
|
-
* import Input from './Input.vue';
|
|
18
|
-
*
|
|
19
|
-
* const meta: Meta<typeof Input> = {
|
|
20
|
-
* title: 'components/Input',
|
|
21
|
-
* ...defineStorybookActionsAndVModels({
|
|
22
|
-
* component: Input,
|
|
23
|
-
* events: ['update:modelValue', 'change'],
|
|
24
|
-
* }),
|
|
25
|
-
* };
|
|
26
|
-
* ```
|
|
7
|
+
* Adds actions for all argTypes of the 'event' category, so that they are logged via the actions plugin.
|
|
27
8
|
*/
|
|
28
|
-
export const
|
|
29
|
-
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
9
|
+
export const enhanceEventArgTypes: ArgTypesEnhancer = ({ argTypes }) => {
|
|
10
|
+
Object.values(argTypes)
|
|
11
|
+
.filter(({ table }) => table?.category === "events")
|
|
12
|
+
.forEach(({ name }) => {
|
|
13
|
+
const eventName = `on${capitalizeFirstLetter(name)}`;
|
|
14
|
+
if (eventName in argTypes) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
argTypes[eventName] = {
|
|
18
|
+
name: eventName,
|
|
19
|
+
table: { disable: true },
|
|
20
|
+
action: eventName,
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
return argTypes;
|
|
40
24
|
};
|
|
41
25
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
* otherwise we would use it like "@on-click="..."" which is redundant.
|
|
49
|
-
*
|
|
50
|
-
* So this utility will remove the on[eventName] entry from the Storybook panel/table
|
|
51
|
-
* and register the correct eventName as action so it is logged in the "Actions" tab.
|
|
52
|
-
*
|
|
53
|
-
* @example defineActions(["click", "input"])
|
|
54
|
-
*/
|
|
55
|
-
export const defineActions = <T>(events: ExtractVueEventNames<T>[]): ArgTypes => {
|
|
56
|
-
return events.reduce<ArgTypes>((argTypes, eventName) => {
|
|
57
|
-
argTypes[`on${capitalizeFirstLetter(eventName)}`] = {
|
|
58
|
-
table: { disable: true },
|
|
59
|
-
action: eventName,
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
argTypes[eventName] = { control: false };
|
|
63
|
-
return argTypes;
|
|
64
|
-
}, {});
|
|
26
|
+
export type WithVModelDecoratorOptions = {
|
|
27
|
+
/**
|
|
28
|
+
* The matcher for the v-model events.
|
|
29
|
+
* @default /^update:/
|
|
30
|
+
*/
|
|
31
|
+
filter: (argType: StrictInputType) => boolean;
|
|
65
32
|
};
|
|
66
33
|
|
|
67
34
|
/**
|
|
68
|
-
* Defines a custom decorator that will implement event handlers for all v-models
|
|
69
|
-
* so that the Storybook controls are updated live when the user interacts with the component
|
|
35
|
+
* Defines a custom decorator that will implement event handlers for all v-models,
|
|
36
|
+
* so that the Storybook controls are updated live when the user interacts with the component.
|
|
37
|
+
* This ensures that the story and component props stay in sync.
|
|
70
38
|
*
|
|
71
39
|
* @example
|
|
72
40
|
* ```ts
|
|
73
|
-
*
|
|
41
|
+
* // .storybook/preview.ts
|
|
74
42
|
*
|
|
75
43
|
* {
|
|
76
|
-
* decorators: [withVModelDecorator
|
|
44
|
+
* decorators: [withVModelDecorator()]
|
|
77
45
|
* }
|
|
78
46
|
* ```
|
|
79
47
|
*/
|
|
80
|
-
|
|
48
|
+
|
|
49
|
+
export const withVModelDecorator = (options?: WithVModelDecoratorOptions): Decorator => {
|
|
81
50
|
return (story, ctx) => {
|
|
82
|
-
const
|
|
51
|
+
const vModelFilter =
|
|
52
|
+
options?.filter ||
|
|
53
|
+
(({ table, name }) => table?.category === "events" && name.startsWith("update:"));
|
|
54
|
+
|
|
55
|
+
const vModelEvents = Object.values(ctx.argTypes)
|
|
56
|
+
.filter(vModelFilter)
|
|
57
|
+
.map(({ name }) => name);
|
|
58
|
+
|
|
83
59
|
if (!vModelEvents.length) return story();
|
|
84
60
|
|
|
85
61
|
const [args, updateArgs] = useArgs();
|
package/src/index.ts
CHANGED
package/src/preview.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
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";
|
|
8
|
+
import { enhanceEventArgTypes } from "./actions";
|
|
9
9
|
import { requiredGlobalType, withRequired } from "./required";
|
|
10
10
|
import { generateSourceCode } from "./source-code-generator";
|
|
11
11
|
import { ONYX_BREAKPOINTS, createTheme } from "./theme";
|
|
@@ -22,6 +22,7 @@ const themes = {
|
|
|
22
22
|
* - Setup for dark mode (including docs page). Requires addon `storybook-dark-mode` to be enabled in .storybook/main.ts file
|
|
23
23
|
* - Custom Storybook theme using onyx colors (light and dark mode)
|
|
24
24
|
* - Configure viewports / breakpoints as defined by onyx
|
|
25
|
+
* - Logs Vue emits as Storybook events
|
|
25
26
|
*
|
|
26
27
|
* @param overrides Custom preview / overrides, will be deep merged with the default preview.
|
|
27
28
|
*
|
|
@@ -42,6 +43,7 @@ const themes = {
|
|
|
42
43
|
*/
|
|
43
44
|
export const createPreview = <T extends Preview = Preview>(overrides?: T) => {
|
|
44
45
|
const defaultPreview = {
|
|
46
|
+
argTypesEnhancers: [enhanceEventArgTypes],
|
|
45
47
|
globalTypes: {
|
|
46
48
|
...requiredGlobalType,
|
|
47
49
|
},
|
|
@@ -157,7 +159,10 @@ export const sourceCodeTransformer = (
|
|
|
157
159
|
const escapedIconContent = `"${replaceAll(iconContent, '"', '\\"')}"`;
|
|
158
160
|
|
|
159
161
|
if (code.includes(iconContent)) {
|
|
160
|
-
code = code.replace(
|
|
162
|
+
code = code.replace(
|
|
163
|
+
new RegExp(` (\\S+)=['"]${escapeRegExp(iconContent)}['"]`),
|
|
164
|
+
` :$1="${importName}"`,
|
|
165
|
+
);
|
|
161
166
|
additionalImports.push(`import ${importName} from "@sit-onyx/icons/${iconName}.svg?raw";`);
|
|
162
167
|
} else if (code.includes(singleQuotedIconContent)) {
|
|
163
168
|
// support icons inside objects
|
|
@@ -207,3 +212,11 @@ ${code}`;
|
|
|
207
212
|
export const replaceAll = (value: string, searchValue: string | RegExp, replaceValue: string) => {
|
|
208
213
|
return value.replace(new RegExp(searchValue, "gi"), replaceValue);
|
|
209
214
|
};
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Escapes the given string value to be used in `new RegExp()`.
|
|
218
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions#escaping
|
|
219
|
+
*/
|
|
220
|
+
export const escapeRegExp = (string: string) => {
|
|
221
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
|
|
222
|
+
};
|
|
@@ -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
|
/**
|
package/src/types.ts
CHANGED
|
@@ -1,52 +1,3 @@
|
|
|
1
|
-
import type { ComponentPropsAndSlots, Meta } from "@storybook/vue3";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Extracts all event names defined by e.g. `defineEmits()` from the given Vue component.
|
|
5
|
-
*
|
|
6
|
-
* @example
|
|
7
|
-
* ```ts
|
|
8
|
-
* import Input from "./Input.vue";
|
|
9
|
-
* type InputEvents = ExtractVueEventNames<typeof Input>; // e.g. "input" | "change"
|
|
10
|
-
* ```
|
|
11
|
-
*/
|
|
12
|
-
export type ExtractVueEventNames<VueComponent> =
|
|
13
|
-
Extract<
|
|
14
|
-
// extract all props/events of the vue component that are functions
|
|
15
|
-
ExtractKeysByValueType<
|
|
16
|
-
// this generic type will extract ALL props and events from the given Vue component
|
|
17
|
-
ComponentPropsAndSlots<VueComponent>,
|
|
18
|
-
// emits are declared as functions, so we only take props/events that are functions and ignore the rest
|
|
19
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- We must use any here to match the type defined by Vue
|
|
20
|
-
((...args: any) => any) | undefined
|
|
21
|
-
>,
|
|
22
|
-
// filter out potential function properties by just picking events that start with "on"
|
|
23
|
-
`on${string}`
|
|
24
|
-
> extends `on${infer EventName}`
|
|
25
|
-
? // until now the extracted event names still start with "on" but we want to have the plain event name
|
|
26
|
-
// so we will remove the "on" prefix and uncapitalized the first letter so e.g. "onClick" becomes "click"
|
|
27
|
-
Uncapitalize<EventName>
|
|
28
|
-
: never;
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Extracts only the keys from T whose value type satisfies U.
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* ```ts
|
|
35
|
-
* type Test = ExtractKeysByValueType<{ a: boolean, b: number, c: boolean }, boolean>
|
|
36
|
-
* // result: "a" | "c"
|
|
37
|
-
* ```
|
|
38
|
-
*/
|
|
39
|
-
export type ExtractKeysByValueType<T, U> = { [P in keyof T]: T[P] extends U ? P : never }[keyof T] &
|
|
40
|
-
keyof T;
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Options for defining Storybook actions and v-models.
|
|
44
|
-
*/
|
|
45
|
-
export type DefineStorybookActionsAndVModelsOptions<T> = Meta<T> & {
|
|
46
|
-
component: NonNullable<T>;
|
|
47
|
-
events: ExtractVueEventNames<T>[];
|
|
48
|
-
};
|
|
49
|
-
|
|
50
1
|
export type StorybookGlobalType<TValue> = {
|
|
51
2
|
name: string;
|
|
52
3
|
description: string;
|
|
@@ -55,5 +6,6 @@ export type StorybookGlobalType<TValue> = {
|
|
|
55
6
|
icon: string;
|
|
56
7
|
items: { value: TValue; right?: string; title: string }[];
|
|
57
8
|
title?: string;
|
|
9
|
+
dynamicTitle?: boolean;
|
|
58
10
|
};
|
|
59
11
|
};
|