@sit-onyx/storybook-utils 1.0.0-beta.9 → 1.0.0-beta.90
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -5
- package/package.json +18 -9
- package/src/actions.spec.ts +29 -0
- package/src/actions.ts +113 -54
- package/src/events.ts +777 -0
- package/src/index.ts +1 -0
- package/src/preview.spec.ts +3 -9
- package/src/preview.ts +34 -20
- package/src/required.ts +0 -1
- package/src/sbType.spec.ts +38 -0
- package/src/sbType.ts +104 -0
- package/src/style.css +161 -0
- package/src/theme.ts +33 -26
- package/src/types.ts +1 -49
- package/src/assets/logo-onyx.svg +0 -51
- package/src/index.css +0 -26
- package/src/source-code-generator.spec.ts +0 -258
- package/src/source-code-generator.ts +0 -539
package/README.md
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
<div align="center" style="text-align: center">
|
|
2
|
-
<
|
|
3
|
-
<source media="(prefers-color-scheme: dark)" type="image/svg+xml" srcset="https://raw.githubusercontent.com/SchwarzIT/onyx/main/.github/onyx-logo-light.svg">
|
|
4
|
-
<source media="(prefers-color-scheme: light)" type="image/svg+xml" srcset="https://raw.githubusercontent.com/SchwarzIT/onyx/main/.github/onyx-logo-dark.svg">
|
|
5
|
-
<img alt="onyx logo" src="https://raw.githubusercontent.com/SchwarzIT/onyx/main/.github/onyx-logo-dark.svg" width="160px">
|
|
6
|
-
</picture>
|
|
2
|
+
<img alt="onyx logo" src="https://raw.githubusercontent.com/SchwarzIT/onyx/main/.github/onyx-logo.svg" height="96px">
|
|
7
3
|
</div>
|
|
8
4
|
|
|
9
5
|
<br>
|
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.90",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Schwarz IT KG",
|
|
7
7
|
"license": "Apache-2.0",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"types": "./src/index.ts",
|
|
12
12
|
"exports": {
|
|
13
13
|
".": "./src/index.ts",
|
|
14
|
-
"./style.css": "./src/
|
|
14
|
+
"./style.css": "./src/style.css"
|
|
15
15
|
},
|
|
16
16
|
"homepage": "https://onyx.schwarz/development/packages/storybook-utils.html",
|
|
17
17
|
"repository": {
|
|
@@ -23,18 +23,27 @@
|
|
|
23
23
|
"url": "https://github.com/SchwarzIT/onyx/issues"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
|
-
"@storybook/vue3": ">=
|
|
27
|
-
"storybook": ">=
|
|
28
|
-
"storybook
|
|
29
|
-
"
|
|
30
|
-
"sit-onyx": "^1.0.0-beta.
|
|
26
|
+
"@storybook/vue3": ">= 9.0.0",
|
|
27
|
+
"@vueless/storybook-dark-mode": ">= 9.0.0",
|
|
28
|
+
"storybook": ">= 9.0.0",
|
|
29
|
+
"vue-component-type-helpers": ">= 2",
|
|
30
|
+
"@sit-onyx/icons": "^1.0.0-beta.18",
|
|
31
|
+
"@sit-onyx/shared": "^1.0.0-beta.3"
|
|
31
32
|
},
|
|
32
33
|
"dependencies": {
|
|
33
|
-
"deepmerge-ts": "^7.
|
|
34
|
+
"deepmerge-ts": "^7.1.5"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"storybook": "^9.0.12",
|
|
38
|
+
"vue": "3.5.16",
|
|
39
|
+
"vue-component-type-helpers": "^2.2.10",
|
|
40
|
+
"@sit-onyx/icons": "^1.0.0-beta.18",
|
|
41
|
+
"@sit-onyx/shared": "^1.0.0-beta.3"
|
|
34
42
|
},
|
|
35
43
|
"scripts": {
|
|
36
44
|
"build": "tsc --noEmit",
|
|
37
45
|
"test": "vitest",
|
|
38
|
-
"test:coverage": "vitest run --coverage"
|
|
46
|
+
"test:coverage": "vitest run --coverage",
|
|
47
|
+
"stylelint": "stylelint \"src/**/*.css\""
|
|
39
48
|
}
|
|
40
49
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { StoryContextForEnhancers } from "storybook/internal/types";
|
|
2
|
+
import { expect, test } from "vitest";
|
|
3
|
+
import { enhanceEventArgTypes } from "./actions";
|
|
4
|
+
|
|
5
|
+
test("should enhance event arg types", () => {
|
|
6
|
+
const argTypes = {
|
|
7
|
+
someProp: {
|
|
8
|
+
name: "someProp",
|
|
9
|
+
table: { category: "props" },
|
|
10
|
+
},
|
|
11
|
+
click: {
|
|
12
|
+
name: "click",
|
|
13
|
+
table: { category: "events" },
|
|
14
|
+
},
|
|
15
|
+
} satisfies StoryContextForEnhancers["argTypes"];
|
|
16
|
+
|
|
17
|
+
const result = enhanceEventArgTypes({
|
|
18
|
+
argTypes,
|
|
19
|
+
} as unknown as StoryContextForEnhancers);
|
|
20
|
+
|
|
21
|
+
expect(result).toStrictEqual({
|
|
22
|
+
...argTypes,
|
|
23
|
+
onClick: {
|
|
24
|
+
name: "onClick",
|
|
25
|
+
table: { disable: true },
|
|
26
|
+
action: "click",
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
});
|
package/src/actions.ts
CHANGED
|
@@ -1,85 +1,144 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
1
|
+
import type { Decorator } from "@storybook/vue3";
|
|
2
|
+
import { action } from "storybook/actions";
|
|
3
3
|
import { useArgs } from "storybook/internal/preview-api";
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
4
|
+
import type { ArgTypes, ArgTypesEnhancer, StrictInputType } from "storybook/internal/types";
|
|
5
|
+
import { h, isReactive, reactive, watch, type Component, type Events } from "vue";
|
|
6
|
+
import type { ComponentProps, ComponentSlots } from "vue-component-type-helpers";
|
|
7
|
+
import { EVENT_DOC_MAP } from "./events";
|
|
8
|
+
|
|
9
|
+
type ComponentEmits<Props extends ComponentProps<unknown>> = keyof {
|
|
10
|
+
[Key in keyof Props as Key extends `on${string}` ? Key : never]: true;
|
|
11
|
+
};
|
|
6
12
|
|
|
7
13
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
14
|
+
* Wraps the original component and adds [Storybook action logging](https://storybook.js.org/docs/essentials/actions).
|
|
15
|
+
* This is useful for slotted child components that emit relevant events.
|
|
16
|
+
*
|
|
17
|
+
* Returns a wrapped component, which can be used in place of the original component.
|
|
11
18
|
*
|
|
12
|
-
* @example
|
|
13
19
|
* ```ts
|
|
14
|
-
*
|
|
15
|
-
* import
|
|
16
|
-
*
|
|
17
|
-
*
|
|
20
|
+
* import { createActionLoggerWrapper } from "@sit-onyx/storybook-utils";
|
|
21
|
+
* import _ChildComponent from "./_ChildComponent.vue";
|
|
22
|
+
*
|
|
23
|
+
* // Usual story setup...
|
|
18
24
|
*
|
|
19
|
-
* const
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
25
|
+
* const ChildComponent = createActionLoggerWrapper(_ChildComponent, ["onChildEmit"]);
|
|
26
|
+
*
|
|
27
|
+
* export const Default = {
|
|
28
|
+
* args: {
|
|
29
|
+
* propName: 'Value'
|
|
30
|
+
* someSlot: () => h(ChildComponent, { label: "Item 1" }),
|
|
31
|
+
* },
|
|
32
|
+
* } satisfies Story;
|
|
26
33
|
* ```
|
|
27
34
|
*/
|
|
28
|
-
export const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
export const createActionLoggerWrapper =
|
|
36
|
+
<C extends Component>(component: C, emitsToLog: ComponentEmits<ComponentProps<C>>[]) =>
|
|
37
|
+
(props: ComponentProps<C>, ctx: { slots: ComponentSlots<C> }) => {
|
|
38
|
+
const entries = emitsToLog.map((emitName) => [
|
|
39
|
+
emitName,
|
|
40
|
+
// Log action in the format of `<component name> ~ <emit name>`
|
|
41
|
+
action(`${(component as { __name: string }).__name} ~ ${String(emitName)}`),
|
|
42
|
+
]);
|
|
43
|
+
const eventHandler = Object.fromEntries(entries);
|
|
44
|
+
return h(
|
|
45
|
+
component,
|
|
46
|
+
{
|
|
47
|
+
...eventHandler,
|
|
48
|
+
...props,
|
|
49
|
+
},
|
|
50
|
+
ctx.slots,
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Adds actions for all argTypes of the 'event' category, so that they are logged via the actions plugin.
|
|
56
|
+
*/
|
|
57
|
+
export const enhanceEventArgTypes: ArgTypesEnhancer = ({ argTypes }) => {
|
|
58
|
+
Object.values(argTypes)
|
|
59
|
+
.filter(({ table }) => table?.category === "events")
|
|
60
|
+
.forEach(({ name }) => {
|
|
61
|
+
const eventName = `on${capitalizeFirstLetter(name)}`;
|
|
62
|
+
if (eventName in argTypes) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
argTypes[eventName] = {
|
|
66
|
+
name: eventName,
|
|
67
|
+
table: { disable: true }, // do not add a second table entry for event name prefixed with "on"
|
|
68
|
+
action: name,
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
return argTypes;
|
|
40
72
|
};
|
|
41
73
|
|
|
42
74
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* with "on", e.g. "onClick".
|
|
46
|
-
*
|
|
47
|
-
* However in Vue, the event names are plain like "click" instead of "onClick" because
|
|
48
|
-
* otherwise we would use it like "@on-click="..."" which is redundant.
|
|
75
|
+
* Allows logging and documentation for the passed event listener names in Storybook.
|
|
76
|
+
* Will be documented in a extra "Relevant HTML events" section in the Storybook documentation.
|
|
49
77
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```typescript
|
|
80
|
+
* const meta: Meta<typeof OnyxButton> = {
|
|
81
|
+
* title: "Buttons/Button",
|
|
82
|
+
* component: OnyxButton,
|
|
83
|
+
* argTypes: {
|
|
84
|
+
* somethingElse: { ...someOtherArgType },
|
|
85
|
+
* ...withNativeEventLogging(["onClick"]),
|
|
86
|
+
* },
|
|
87
|
+
*};
|
|
88
|
+
* ```
|
|
52
89
|
*
|
|
53
|
-
* @
|
|
90
|
+
* @param relevantEvents a list of event names that should be logged
|
|
91
|
+
* @returns Storybook ArgTypes object
|
|
54
92
|
*/
|
|
55
|
-
export const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
93
|
+
export const withNativeEventLogging = (relevantEvents: (keyof Events)[]) =>
|
|
94
|
+
relevantEvents.reduce((argTypes, eventName) => {
|
|
95
|
+
const { constructor, event } = EVENT_DOC_MAP[eventName];
|
|
96
|
+
argTypes[eventName] = {
|
|
97
|
+
name: event.name,
|
|
98
|
+
control: false,
|
|
99
|
+
description: `The native HTML [${event.name}](${event.url}) event which dispatches an [${constructor.name}](${constructor.url}).`,
|
|
100
|
+
table: {
|
|
101
|
+
category: "Relevant HTML events",
|
|
102
|
+
type: { summary: constructor.name },
|
|
103
|
+
},
|
|
104
|
+
action: event.name,
|
|
60
105
|
};
|
|
61
|
-
|
|
62
|
-
argTypes[eventName] = { control: false };
|
|
63
106
|
return argTypes;
|
|
64
|
-
}, {});
|
|
107
|
+
}, {} as ArgTypes);
|
|
108
|
+
|
|
109
|
+
export type WithVModelDecoratorOptions = {
|
|
110
|
+
/**
|
|
111
|
+
* The matcher for the v-model events.
|
|
112
|
+
* @default /^update:/
|
|
113
|
+
*/
|
|
114
|
+
filter: (argType: StrictInputType, index: number, array: StrictInputType[]) => boolean;
|
|
65
115
|
};
|
|
66
116
|
|
|
67
117
|
/**
|
|
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
|
|
118
|
+
* Defines a custom decorator that will implement event handlers for all v-models,
|
|
119
|
+
* so that the Storybook controls are updated live when the user interacts with the component.
|
|
120
|
+
* This ensures that the story and component props stay in sync.
|
|
70
121
|
*
|
|
71
122
|
* @example
|
|
72
123
|
* ```ts
|
|
73
|
-
*
|
|
124
|
+
* // .storybook/preview.ts
|
|
74
125
|
*
|
|
75
126
|
* {
|
|
76
|
-
* decorators: [withVModelDecorator
|
|
127
|
+
* decorators: [withVModelDecorator()]
|
|
77
128
|
* }
|
|
78
129
|
* ```
|
|
79
130
|
*/
|
|
80
|
-
|
|
131
|
+
|
|
132
|
+
export const withVModelDecorator = (options?: WithVModelDecoratorOptions): Decorator => {
|
|
81
133
|
return (story, ctx) => {
|
|
82
|
-
const
|
|
134
|
+
const vModelFilter =
|
|
135
|
+
options?.filter ||
|
|
136
|
+
(({ table, name }) => table?.category === "events" && name.startsWith("update:"));
|
|
137
|
+
|
|
138
|
+
const vModelEvents = Object.values(ctx.argTypes)
|
|
139
|
+
.filter(vModelFilter)
|
|
140
|
+
.map(({ name }) => name);
|
|
141
|
+
|
|
83
142
|
if (!vModelEvents.length) return story();
|
|
84
143
|
|
|
85
144
|
const [args, updateArgs] = useArgs();
|