@sit-onyx/storybook-utils 1.0.0-beta.5 → 1.0.0-beta.51

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 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.5",
4
+ "version": "1.0.0-beta.51",
5
5
  "type": "module",
6
6
  "author": "Schwarz IT KG",
7
7
  "license": "Apache-2.0",
@@ -23,14 +23,17 @@
23
23
  "url": "https://github.com/SchwarzIT/onyx/issues"
24
24
  },
25
25
  "peerDependencies": {
26
- "@storybook/vue3": ">= 8.2.0",
27
- "storybook": ">= 8.2.0",
26
+ "@storybook/vue3": ">= 8.3.0",
27
+ "storybook": ">= 8.3.0",
28
28
  "storybook-dark-mode": ">= 4",
29
- "@sit-onyx/icons": "^1.0.0-beta.0",
30
- "sit-onyx": "^1.0.0-beta.4"
29
+ "@sit-onyx/icons": "^1.0.0-beta.4",
30
+ "sit-onyx": "^1.0.0-beta.45"
31
31
  },
32
32
  "dependencies": {
33
- "deepmerge-ts": "^7.0.3"
33
+ "deepmerge-ts": "^7.1.0"
34
+ },
35
+ "devDependencies": {
36
+ "vue": "^3.5.0"
34
37
  },
35
38
  "scripts": {
36
39
  "build": "tsc --noEmit",
@@ -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,97 @@
1
- import type { ArgTypes, Decorator, Meta } from "@storybook/vue3";
2
- import { deepmerge } from "deepmerge-ts";
1
+ import type { Decorator } from "@storybook/vue3";
3
2
  import { useArgs } from "storybook/internal/preview-api";
4
- import { isReactive, reactive, watch } from "vue";
5
- import type { DefineStorybookActionsAndVModelsOptions, ExtractVueEventNames } from ".";
3
+ import type { ArgTypes, ArgTypesEnhancer, StrictInputType } from "storybook/internal/types";
4
+ import { isReactive, reactive, watch, type Events } from "vue";
5
+ import { EVENT_DOC_MAP } from "./events";
6
6
 
7
7
  /**
8
- * Utility to define Storybook meta for a given Vue component which will take care of defining argTypes for
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
- * ```
8
+ * Adds actions for all argTypes of the 'event' category, so that they are logged via the actions plugin.
27
9
  */
28
- export const defineStorybookActionsAndVModels = <T>(
29
- options: DefineStorybookActionsAndVModelsOptions<T>,
30
- ): Meta => {
31
- const defaultMeta = {
32
- argTypes: {
33
- ...defineActions(options.events),
34
- ...{}, // this is needed to fix a type issue
35
- },
36
- decorators: [withVModelDecorator(options.events)],
37
- } satisfies Meta;
38
-
39
- return deepmerge(options, defaultMeta);
10
+ export const enhanceEventArgTypes: ArgTypesEnhancer = ({ argTypes }) => {
11
+ Object.values(argTypes)
12
+ .filter(({ table }) => table?.category === "events")
13
+ .forEach(({ name }) => {
14
+ const eventName = `on${capitalizeFirstLetter(name)}`;
15
+ if (eventName in argTypes) {
16
+ return;
17
+ }
18
+ argTypes[eventName] = {
19
+ name: eventName,
20
+ table: { disable: true }, // do not add a second table entry for event name prefixed with "on"
21
+ action: name,
22
+ };
23
+ });
24
+ return argTypes;
40
25
  };
41
26
 
42
27
  /**
43
- * Defines Storybook actions ("argTypes") for the given events.
44
- * Reason for this wrapper function is that Storybook expects event names to be prefixed
45
- * with "on", e.g. "onClick".
28
+ * Allows logging and documentation for the passed event listener names in Storybook.
29
+ * Will be documented in a extra "Relevant HTML events" section in the Storybook documentation.
46
30
  *
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.
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.
31
+ * @example
32
+ * ```typescript
33
+ * const meta: Meta<typeof OnyxButton> = {
34
+ * title: "Buttons/Button",
35
+ * component: OnyxButton,
36
+ * argTypes: {
37
+ * somethingElse: { ...someOtherArgType },
38
+ * ...withNativeEventLogging(["onClick"]),
39
+ * },
40
+ *};
41
+ * ```
52
42
  *
53
- * @example defineActions(["click", "input"])
43
+ * @param relevantEvents a list of event names that should be logged
44
+ * @returns Storybook ArgTypes object
54
45
  */
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,
46
+ export const withNativeEventLogging = (relevantEvents: (keyof Events)[]) =>
47
+ relevantEvents.reduce((argTypes, eventName) => {
48
+ const { constructor, event } = EVENT_DOC_MAP[eventName];
49
+ argTypes[eventName] = {
50
+ name: event.name,
51
+ control: false,
52
+ description: `The native HTML [${event.name}](${event.url}) event which dispatches an [${constructor.name}](${constructor.url}).`,
53
+ table: {
54
+ category: "Relevant HTML events",
55
+ type: { summary: constructor.name },
56
+ },
57
+ action: event.name,
60
58
  };
61
-
62
- argTypes[eventName] = { control: false };
63
59
  return argTypes;
64
- }, {});
60
+ }, {} as ArgTypes);
61
+
62
+ export type WithVModelDecoratorOptions = {
63
+ /**
64
+ * The matcher for the v-model events.
65
+ * @default /^update:/
66
+ */
67
+ filter: (argType: StrictInputType) => boolean;
65
68
  };
66
69
 
67
70
  /**
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
71
+ * Defines a custom decorator that will implement event handlers for all v-models,
72
+ * so that the Storybook controls are updated live when the user interacts with the component.
73
+ * This ensures that the story and component props stay in sync.
70
74
  *
71
75
  * @example
72
76
  * ```ts
73
- * import Input from './Input.vue';
77
+ * // .storybook/preview.ts
74
78
  *
75
79
  * {
76
- * decorators: [withVModelDecorator<typeof Input>(["update:modelValue"])]
80
+ * decorators: [withVModelDecorator()]
77
81
  * }
78
82
  * ```
79
83
  */
80
- export const withVModelDecorator = <T>(events: ExtractVueEventNames<T>[]): Decorator => {
84
+
85
+ export const withVModelDecorator = (options?: WithVModelDecoratorOptions): Decorator => {
81
86
  return (story, ctx) => {
82
- const vModelEvents = events.filter((event) => event.startsWith("update:"));
87
+ const vModelFilter =
88
+ options?.filter ||
89
+ (({ table, name }) => table?.category === "events" && name.startsWith("update:"));
90
+
91
+ const vModelEvents = Object.values(ctx.argTypes)
92
+ .filter(vModelFilter)
93
+ .map(({ name }) => name);
94
+
83
95
  if (!vModelEvents.length) return story();
84
96
 
85
97
  const [args, updateArgs] = useArgs();