@sit-onyx/storybook-utils 1.0.0-beta.40 → 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 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.40",
4
+ "version": "1.0.0-beta.41",
5
5
  "type": "module",
6
6
  "author": "Schwarz IT KG",
7
7
  "license": "Apache-2.0",
package/src/actions.ts CHANGED
@@ -1,85 +1,61 @@
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";
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
- * 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
- * ```
7
+ * Adds actions for all argTypes of the 'event' category, so that they are logged via the actions plugin.
27
8
  */
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);
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
- * 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".
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.
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
- * import Input from './Input.vue';
41
+ * // .storybook/preview.ts
74
42
  *
75
43
  * {
76
- * decorators: [withVModelDecorator<typeof Input>(["update:modelValue"])]
44
+ * decorators: [withVModelDecorator()]
77
45
  * }
78
46
  * ```
79
47
  */
80
- export const withVModelDecorator = <T>(events: ExtractVueEventNames<T>[]): Decorator => {
48
+
49
+ export const withVModelDecorator = (options?: WithVModelDecoratorOptions): Decorator => {
81
50
  return (story, ctx) => {
82
- const vModelEvents = events.filter((event) => event.startsWith("update:"));
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/preview.ts CHANGED
@@ -5,6 +5,7 @@ import { DARK_MODE_EVENT_NAME } from "storybook-dark-mode";
5
5
  import { DOCS_RENDERED } from "storybook/internal/core-events";
6
6
  import { addons } from "storybook/internal/preview-api";
7
7
  import type { ThemeVars } from "storybook/internal/theming";
8
+ import { enhanceEventArgTypes } from "./actions";
8
9
  import { requiredGlobalType, withRequired } from "./required";
9
10
  import { generateSourceCode } from "./source-code-generator";
10
11
  import { ONYX_BREAKPOINTS, createTheme } from "./theme";
@@ -21,6 +22,7 @@ const themes = {
21
22
  * - Setup for dark mode (including docs page). Requires addon `storybook-dark-mode` to be enabled in .storybook/main.ts file
22
23
  * - Custom Storybook theme using onyx colors (light and dark mode)
23
24
  * - Configure viewports / breakpoints as defined by onyx
25
+ * - Logs Vue emits as Storybook events
24
26
  *
25
27
  * @param overrides Custom preview / overrides, will be deep merged with the default preview.
26
28
  *
@@ -41,6 +43,7 @@ const themes = {
41
43
  */
42
44
  export const createPreview = <T extends Preview = Preview>(overrides?: T) => {
43
45
  const defaultPreview = {
46
+ argTypesEnhancers: [enhanceEventArgTypes],
44
47
  globalTypes: {
45
48
  ...requiredGlobalType,
46
49
  },
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;