@sit-onyx/storybook-utils 1.0.0-beta.1 → 1.0.0-beta.100

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/src/theme.ts CHANGED
@@ -1,34 +1,40 @@
1
- import type { ThemeVars, ThemeVarsPartial } from "@storybook/theming";
2
- import { create } from "@storybook/theming/create";
3
- import onyxVariables from "sit-onyx/themes/onyx.json";
4
- import { ONYX_BREAKPOINTS as RAW_ONYX_BREAKPOINTS, type OnyxBreakpoint } from "sit-onyx/types";
5
- import onyxLogo from "./assets/logo-onyx.svg";
1
+ import {
2
+ ONYX_BREAKPOINTS as RAW_ONYX_BREAKPOINTS,
3
+ type OnyxBreakpoint,
4
+ } from "@sit-onyx/shared/breakpoints";
5
+ import { create, type ThemeVars } from "storybook/internal/theming";
6
+ import type { Viewport } from "storybook/internal/viewport";
7
+
8
+ export type BrandDetails = Pick<ThemeVars, "brandTitle" | "brandImage" | "brandUrl">;
9
+
10
+ /**
11
+ * Get the computed value for a CSS custom property.
12
+ * Per default the property value is taken from the body element.
13
+ */
14
+ export const getCustomProperty = (property: string, el: Element = document.body) =>
15
+ getComputedStyle(el).getPropertyValue(property);
6
16
 
7
17
  /**
8
18
  * Creates a custom theme for Storybook that uses onyx colors.
9
19
  *
10
20
  * @see https://storybook.js.org/docs/react/configure/theming#create-a-theme-quickstart
11
21
  */
12
- export const createTheme = (
13
- options?: Pick<ThemeVarsPartial, "base" | "brandTitle" | "brandImage" | "brandUrl">,
14
- ) => {
15
- const base = options?.base ?? "light";
16
- const primaryColor = onyxVariables["onyx-color-themed-primary-500"];
17
-
22
+ export const createTheme = (base: "light" | "dark" = "light", brandDetails?: BrandDetails) => {
23
+ const primaryColor = getCustomProperty("--onyx-color-onyx-500");
18
24
  return create({
19
- brandTitle: options?.brandTitle ?? "onyx Storybook",
20
- brandUrl: options?.brandUrl ?? "https://onyx.schwarz",
21
- brandImage: options?.brandImage ?? onyxLogo,
25
+ brandTitle: brandDetails?.brandTitle,
26
+ brandUrl: brandDetails?.brandUrl,
27
+ brandImage: brandDetails?.brandImage,
22
28
  brandTarget: "_blank",
23
- base: base,
29
+ base,
24
30
 
25
31
  // default theme values that are independent of the light/dark mode:
26
32
  colorPrimary: primaryColor,
27
- colorSecondary: onyxVariables["onyx-color-themed-secondary-500"],
33
+ colorSecondary: getCustomProperty("--onyx-color-themed-secondary-500"),
28
34
  barSelectedColor: primaryColor,
29
35
  barHoverColor: primaryColor,
30
- appBorderRadius: remToNumber(onyxVariables["onyx-number-radius-300"]),
31
- inputBorderRadius: remToNumber(onyxVariables["onyx-number-radius-200"]),
36
+ appBorderRadius: remToNumber(getCustomProperty("--onyx-number-radius-300")),
37
+ inputBorderRadius: remToNumber(getCustomProperty("--onyx-number-radius-200")),
32
38
 
33
39
  // custom colors depending on light/dark theme
34
40
  ...(base === "light" ? getLightTheme() : getDarkTheme()),
@@ -37,21 +43,21 @@ export const createTheme = (
37
43
 
38
44
  const getLightTheme = (): Partial<ThemeVars> => {
39
45
  return defineTheme({
40
- background: onyxVariables["onyx-color-universal-grayscale-white"],
41
- contentBackground: onyxVariables["onyx-color-themed-neutral-100"],
42
- text: onyxVariables["onyx-color-themed-neutral-700"],
43
- textMuted: onyxVariables["onyx-color-themed-neutral-600"],
44
- border: onyxVariables["onyx-color-themed-neutral-300"],
46
+ background: getCustomProperty("--onyx-color-universal-grayscale-white"),
47
+ contentBackground: getCustomProperty("--onyx-color-neutral-steel-100"),
48
+ text: getCustomProperty("--onyx-color-neutral-steel-700"),
49
+ textMuted: getCustomProperty("--onyx-color-neutral-steel-600"),
50
+ border: getCustomProperty("--onyx-color-neutral-steel-300"),
45
51
  });
46
52
  };
47
53
 
48
54
  const getDarkTheme = (): Partial<ThemeVars> => {
49
55
  return defineTheme({
50
- background: onyxVariables["onyx-color-themed-neutral-1100"],
51
- contentBackground: onyxVariables["onyx-color-themed-neutral-1200"],
52
- text: onyxVariables["onyx-color-themed-neutral-200"],
53
- textMuted: onyxVariables["onyx-color-themed-neutral-400"],
54
- border: onyxVariables["onyx-color-themed-neutral-900"],
56
+ background: getCustomProperty("--onyx-color-neutral-steel-1100"),
57
+ contentBackground: getCustomProperty("--onyx-color-neutral-steel-1200"),
58
+ text: getCustomProperty("--onyx-color-neutral-steel-200"),
59
+ textMuted: getCustomProperty("--onyx-color-neutral-steel-400"),
60
+ border: getCustomProperty("--onyx-color-neutral-steel-900"),
55
61
  });
56
62
  };
57
63
 
@@ -94,20 +100,26 @@ const defineTheme = (colors: {
94
100
  export const ONYX_BREAKPOINTS = Object.entries(RAW_ONYX_BREAKPOINTS).reduce(
95
101
  (obj, [name, width]) => {
96
102
  const breakpoint = name as OnyxBreakpoint;
97
- obj[breakpoint] = { name: breakpoint, styles: { width: `${width}px`, height: "100%" } };
103
+
104
+ const TYPES: Record<OnyxBreakpoint, Viewport["type"]> = {
105
+ "2xs": "mobile",
106
+ xs: "mobile",
107
+ sm: "tablet",
108
+ md: "tablet",
109
+ lg: "desktop",
110
+ xl: "desktop",
111
+ };
112
+
113
+ obj[breakpoint] = {
114
+ name: breakpoint,
115
+ styles: { width: `${width}px`, height: "100%" },
116
+ type: TYPES[breakpoint],
117
+ };
98
118
  return obj;
99
119
  },
100
- {} as Record<OnyxBreakpoint, StorybookBreakpoint>,
120
+ {} as Record<OnyxBreakpoint, Viewport>,
101
121
  );
102
122
 
103
- export type StorybookBreakpoint = {
104
- name: OnyxBreakpoint;
105
- styles: {
106
- width: string;
107
- height: string;
108
- };
109
- };
110
-
111
123
  /**
112
124
  * Converts a rem string into a numeric value with a rem base of 16.
113
125
  * @example "1rem" => 16
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
  };
@@ -1,51 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
- <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
3
- <svg width="100%" height="100%" viewBox="0 0 128 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
4
- <g transform="matrix(0.0550098,0,0,0.0550098,-13.9945,-10.7764)">
5
- <g transform="matrix(1.00003,0,0,1,-5.18689,1.4)">
6
- <path d="M1399.09,387.23L1457.51,302.52C1460.2,299.31 1462.88,297.7 1466.62,297.7L1526.12,297.7C1532.55,297.7 1534.17,301.99 1530.41,307.36L1437.8,438.74L1399.08,387.23L1399.09,387.23Z" style="fill:url(#_Linear1);fill-rule:nonzero;"/>
7
- </g>
8
- <g transform="matrix(1.00003,0,0,1,-5.18689,1.4)">
9
- <path d="M1438.22,438.15L1541.14,584.49C1544.35,589.85 1542.75,594.14 1536.32,594.14L1476.82,594.14C1473.61,594.14 1470.92,592.53 1468.77,589.32L1399.09,488.54L1329.4,589.32C1327.27,592.53 1324.58,594.14 1321.36,594.14L1261.86,594.14C1255.43,594.14 1243.69,589.85 1247.45,584.49L1359.96,438.15L1267.76,307.36C1264,301.99 1265.62,297.7 1272.04,297.7L1331.54,297.7C1335.3,297.7 1338.51,299.31 1340.65,302.52L1399.08,387.23L1438.21,438.15L1438.22,438.15Z" style="fill:rgb(50,184,198);fill-rule:nonzero;"/>
10
- </g>
11
- </g>
12
- <g transform="matrix(0.0550098,0,0,0.0550098,-13.9945,-10.7764)">
13
- <g transform="matrix(1.00003,0,0,1,-5.18689,1.4)">
14
- <path d="M520.64,448.86C520.64,569.47 497.06,599.49 389.85,599.49C282.63,599.49 259.58,569.47 259.58,448.86C259.58,327.71 282.63,297.69 389.85,297.69C497.05,297.69 520.64,327.71 520.64,448.86ZM324.45,448.86C324.45,524.44 332.49,543.21 389.85,543.21C447.2,543.21 455.24,524.44 455.24,448.86C455.24,372.74 447.19,353.98 389.85,353.98C332.48,353.98 324.45,372.74 324.45,448.86Z" style="fill:rgb(50,184,198);fill-rule:nonzero;"/>
15
- </g>
16
- <g transform="matrix(1.00003,0,0,1,-5.18689,1.4)">
17
- <g>
18
- <clipPath id="_clip2">
19
- <path d="M519.88,480.88C520.37,470.95 520.64,460.4 520.64,448.86C520.64,327.71 497.06,297.69 389.85,297.69C388.74,297.69 367.12,297.69 351.01,299.54C339.69,316.68 332.26,373.81 327.82,394.43C328.83,388.21 332.15,379.08 334.43,374.91C339.44,365.73 346.24,361.39 354.56,358.41C368.59,353.39 384.37,353.98 389.86,353.98C447.22,353.98 455.26,372.74 455.26,448.86"/>
20
- </clipPath>
21
- <g clip-path="url(#_clip2)">
22
- <g transform="matrix(0.999067,0,0,0.995598,327.82,297.69)">
23
- <use id="_Image3_" serif:id="_Image3" xlink:href="#_Image3" x="0" y="0" width="193px" height="184px"/>
24
- </g>
25
- </g>
26
- </g>
27
- </g>
28
- </g>
29
- <g transform="matrix(0.0550098,0,0,0.0550098,-13.9945,-10.7764)">
30
- <g transform="matrix(1.00003,0,0,1,-5.18689,1.4)">
31
- <path d="M662.36,353.99L724.46,353.99C774.85,353.99 787.16,357.2 787.16,436.54L787.16,588.77C787.16,594.14 789.85,596.82 795.21,596.82L843.46,596.82C848.82,596.82 852.03,594.14 852.03,588.77L852.03,424.75C852.03,312.71 824.69,297.7 725.52,297.7C707.79,297.7 685.61,297.9 662.72,299.33L662.36,353.99Z" style="fill:url(#_Linear4);fill-rule:nonzero;"/>
32
- </g>
33
- <g transform="matrix(1.00003,0,0,1,-5.18689,1.4)">
34
- <path d="M662.8,306.45L662.8,588.77C662.8,594.14 660.12,596.82 654.76,596.82L605.45,596.82C600.08,596.82 597.94,594.14 597.94,588.77L597.94,314.86C597.94,307.89 600.62,307.35 605.45,306.28C623.63,302.59 643.45,300.48 662.79,299.28L662.79,306.45L662.8,306.45Z" style="fill:rgb(50,184,198);fill-rule:nonzero;"/>
35
- </g>
36
- </g>
37
- <g transform="matrix(0.0550098,0,0,0.0550098,-13.9945,-10.7764)">
38
- <g transform="matrix(1.00003,0,0,1,-5.18689,1.4)">
39
- <path d="M1123.83,591.43C1100.93,595.16 1080.37,596.19 1055.33,596.19C956.8,596.19 931.78,562.64 931.78,455.06L931.78,307.02C931.78,301.69 934.44,299.03 939.77,299.03L988.23,299.03C993.56,299.03 996.23,301.69 996.23,307.02L996.23,446.01C996.23,522.16 1005.81,539.73 1065.46,539.73C1083.04,539.73 1104.87,539.2 1123.51,536.54L1123.84,591.43L1123.83,591.43Z" style="fill:url(#_Linear5);fill-rule:nonzero;"/>
40
- </g>
41
- <g transform="matrix(1.00003,0,0,1,-5.18689,1.4)">
42
- <path d="M1123.5,536.54L1123.5,307.02C1123.5,301.69 1126.16,299.03 1131.49,299.03L1179.96,299.03C1185.29,299.03 1187.95,301.69 1187.95,307.02L1187.95,576.49C1187.41,669.15 1169.31,702.7 1061.19,702.7C1025.52,702.7 1000.48,701.1 971.19,696.83C966.4,695.77 964.26,693.11 964.26,688.31L964.26,656.89C964.26,651.03 966.39,648.9 971.72,648.9L1057.46,648.9C1110.71,648.9 1123.49,636.12 1123.49,594.05L1123.49,536.54L1123.5,536.54Z" style="fill:rgb(50,184,198);fill-rule:nonzero;"/>
43
- </g>
44
- </g>
45
- <defs>
46
- <linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(133.32,0,0,133.32,1399.09,368.22)"><stop offset="0" style="stop-color:rgb(1,141,160);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(50,184,198);stop-opacity:1"/></linearGradient>
47
- <image id="_Image3" width="193px" height="184px" xlink:href="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCAC4AMEDAREAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAgAGAf/EABgQAQEBAQEAAAAAAAAAAAAAAAEAAhFh/8QAGQEBAQEBAQEAAAAAAAAAAAAAAAIBAwYH/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8Ayl7V8MUFBQUFBQUFAggQRphAwgQQIKQwgeSKMIGECCBhAggQWBhB3kUzFbkoKCgoKCgoEECCNMIEEDCBBSGEDCBhFGECCBhAggQWBhAgii5Bla3JQUFBQUFAggQRphAggYQIKQwgYQPJFGECCBhAggQWBhAgijCDvIMnW5KCgoKCgQQII0wgQQMIEFIYQMIHkijCBBAwgQQILAwgQRRhAggXIMhW5KCgoKBBAgjTCBBAwgQUhhAwijyQMIEEDCBBAgsDCBBFGECCBhB3nkGOrclBQUCCBBGmECCBhAgpDCBhFHkgYQIIGECCBBYGECCKMIEEDCBBAuUjF3VyUFAggQRphAggYQIKQwgYRR5IGECCBhAggQWBhAgijCBBAwgQQMKR3nkGJurkoEECCNMIEEDCBBSGEDCKPJAwgQQMIEECCwMIEEUYQIIGECCBhSGEHeQYa6uRBAgjTCBBAwgQUhhAwijyQMIEEDCBBAggYWBBFGECCBhAggYUhhAgg7yDDBdXIgjTCBBAwgQUhhAwijyQMIEEDCBBAggYWBBFGECCBhAggYUhhAggYQd5BhQurmYQIIGECCkMIGEUeSBhAggYQIIEEDCwIIowgQQMIEEDCkMIEEDCBBAuQYULq5kEDCBBSGEDCKPJAwgQQMIEECCBhYEEUYQIIGECCBhSEEDCBhAggYRpcgwgXVyMIEFIYQMIo8kDCBBAwgQQIIGFgQRRhAggYQIIGFIQQMIGECCBhGmEC5BhAurkQUhhA8kDCKMIEEDCBBAggYWBBFGECCBhAggYUhBAwgYQIIGEaYQIg7BhQrcjCBhAwiiCBhAggYQIIGFgQRRhAggYQIIGFIQQMIGECCBhGmECIOwUGICtyMIGEUQQMIEEDCBBYGECCKMIEEDCBBAwpCCBhAwgQQMI0wgRB2CgoMWFbkYRRBAwgQQMIEFgYQIIowgQQMIEEDCkIIGEDCBBAwjTCBEHYKCgoMcFaCCBhAggYQILAwgQRRhAggYQIIGFIQQMIGECCBhGmECIOwUFBQUGQCtBhAggYQILAwgQRRhAggYQIIGFIQQMIGECCBhGmECIOwUFBQUFBkwrQQQMIEFgYQIIowgQQMIEEDCkIIGEDCBBAwjTCBEHYKCgoKCgoMqFaDCBBYGECCKMIEEDCBBAwpCCBhAwgQQMI0wgRB2CgoKCgoKCgzAVoILAwgQRRhAggYQIIGFIQQMIGECCBhGmECIOwUFBQUFBQUFBmgqQYQIIowgQQMIEEDCkIIGECCB5IGEaYQIg7BQUFBQUFBQUFB//2Q=="/>
48
- <linearGradient id="_Linear4" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(35974.7,0,0,35974.7,126292,134082)"><stop offset="0" style="stop-color:rgb(1,141,160);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(50,184,198);stop-opacity:1"/></linearGradient>
49
- <linearGradient id="_Linear5" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(192.05,0,0,192.05,931.78,447.61)"><stop offset="0" style="stop-color:rgb(50,184,198);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(1,141,160);stop-opacity:1"/></linearGradient>
50
- </defs>
51
- </svg>
package/src/index.css DELETED
@@ -1,26 +0,0 @@
1
- @import url("sit-onyx/themes/onyx.css");
2
-
3
- .onyx-disclaimer {
4
- border-radius: 0.5rem;
5
- padding: 1rem;
6
- line-height: 1.5rem;
7
- margin: 1rem 0;
8
-
9
- /* same color as VitePress var(--vp-c-tip-soft) */
10
- background-color: color-mix(in srgb, var(--onyx-color-base-info-400) 25%, transparent);
11
- }
12
-
13
- .onyx-disclaimer__title {
14
- font-weight: 600;
15
- margin-bottom: 0.5rem;
16
- }
17
-
18
- .onyx-disclaimer p {
19
- margin: 0;
20
- }
21
-
22
- /* the Storybook table of content headline is always black so we need to manually set it for the dark mode */
23
- #storybook-docs .sbdocs-wrapper > div:nth-child(2) {
24
- /* same as Storybook color "textMuted" inside ./theme.ts */
25
- color: var(--onyx-color-text-icons-neutral-medium);
26
- }
@@ -1,258 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-unused-vars */
2
- //
3
- // This file is only a temporary copy of the improved source code generation for Storybook.
4
- // It is intended to be deleted once its officially released in Storybook itself, see:
5
- // https://github.com/storybookjs/storybook/pull/27194
6
- //
7
- import { expect, test } from "vitest";
8
- import { h } from "vue";
9
- import {
10
- generatePropsSourceCode,
11
- generateSlotSourceCode,
12
- generateSourceCode,
13
- getFunctionParamNames,
14
- parseDocgenInfo,
15
- type SourceCodeGeneratorContext,
16
- } from "./source-code-generator";
17
-
18
- test("should generate source code for props", () => {
19
- const ctx: SourceCodeGeneratorContext = {
20
- scriptVariables: {},
21
- imports: {},
22
- };
23
-
24
- const code = generatePropsSourceCode(
25
- {
26
- a: "foo",
27
- b: '"I am double quoted"',
28
- c: 42,
29
- d: true,
30
- e: false,
31
- f: [1, 2, 3],
32
- g: {
33
- g1: "foo",
34
- g2: 42,
35
- },
36
- h: undefined,
37
- i: null,
38
- j: "",
39
- k: BigInt(9007199254740991),
40
- l: Symbol(),
41
- m: Symbol("foo"),
42
- modelValue: "test-v-model",
43
- otherModelValue: 42,
44
- default: "default slot",
45
- testSlot: "test slot",
46
- },
47
- ["default", "testSlot"],
48
- ["update:modelValue", "update:otherModelValue"],
49
- ctx,
50
- );
51
-
52
- expect(code).toBe(
53
- `a="foo" b='"I am double quoted"' :c="42" d :e="false" :f="f" :g="g" :k="BigInt(9007199254740991)" :l="Symbol()" :m="Symbol('foo')" v-model="modelValue" v-model:otherModelValue="otherModelValue"`,
54
- );
55
-
56
- expect(ctx.scriptVariables).toStrictEqual({
57
- f: `[1,2,3]`,
58
- g: `{"g1":"foo","g2":42}`,
59
- modelValue: 'ref("test-v-model")',
60
- otherModelValue: "ref(42)",
61
- });
62
-
63
- expect(Array.from(ctx.imports.vue.values())).toStrictEqual(["ref"]);
64
- });
65
-
66
- test("should generate source code for slots", () => {
67
- // slot code generator should support primitive values (string, number etc.)
68
- // but also VNodes (e.g. created using h()) so custom Vue components can also be used
69
- // inside slots with proper generated code
70
-
71
- const slots = {
72
- default: "default content",
73
- a: "a content",
74
- b: 42,
75
- c: true,
76
- // single VNode without props
77
- d: h("div", "d content"),
78
- // VNode with props and single child
79
- e: h("div", { style: "color:red" }, "e content"),
80
- // VNode with props and single child returned as getter
81
- f: h("div", { style: "color:red" }, () => "f content"),
82
- // VNode with multiple children
83
- g: h("div", { style: "color:red" }, [
84
- "child 1",
85
- h("span", { style: "color:green" }, "child 2"),
86
- ]),
87
- // VNode multiple children but returned as getter
88
- h: h("div", { style: "color:red" }, () => [
89
- "child 1",
90
- h("span", { style: "color:green" }, "child 2"),
91
- ]),
92
- // VNode with multiple and nested children
93
- i: h("div", { style: "color:red" }, [
94
- "child 1",
95
- h("span", { style: "color:green" }, ["nested child 1", h("p", "nested child 2")]),
96
- ]),
97
- j: ["child 1", "child 2"],
98
- k: null,
99
- l: { foo: "bar" },
100
- m: BigInt(9007199254740991),
101
- };
102
-
103
- const expectedCode = `default content
104
-
105
- <template #a>a content</template>
106
-
107
- <template #b>42</template>
108
-
109
- <template #c>true</template>
110
-
111
- <template #d><div>d content</div></template>
112
-
113
- <template #e><div style="color:red">e content</div></template>
114
-
115
- <template #f><div style="color:red">f content</div></template>
116
-
117
- <template #g><div style="color:red">child 1
118
- <span style="color:green">child 2</span></div></template>
119
-
120
- <template #h><div style="color:red">child 1
121
- <span style="color:green">child 2</span></div></template>
122
-
123
- <template #i><div style="color:red">child 1
124
- <span style="color:green">nested child 1
125
- <p>nested child 2</p></span></div></template>
126
-
127
- <template #j>child 1
128
- child 2</template>
129
-
130
- <template #l>{"foo":"bar"}</template>
131
-
132
- <template #m>{{ BigInt(9007199254740991) }}</template>`;
133
-
134
- let actualCode = generateSlotSourceCode(slots, Object.keys(slots), {
135
- scriptVariables: {},
136
- imports: {},
137
- });
138
- expect(actualCode).toBe(expectedCode);
139
-
140
- // should generate the same code if getters/functions are used to return the slot content
141
- const slotsWithGetters = Object.entries(slots).reduce<
142
- Record<string, () => (typeof slots)[keyof typeof slots]>
143
- >((obj, [slotName, value]) => {
144
- obj[slotName] = () => value;
145
- return obj;
146
- }, {});
147
-
148
- actualCode = generateSlotSourceCode(slotsWithGetters, Object.keys(slotsWithGetters), {
149
- scriptVariables: {},
150
- imports: {},
151
- });
152
- expect(actualCode).toBe(expectedCode);
153
- });
154
-
155
- test("should generate source code for slots with bindings", () => {
156
- type TestBindings = {
157
- foo: string;
158
- bar?: number;
159
- };
160
-
161
- const slots = {
162
- a: ({ foo, bar }: TestBindings) => `Slot with bindings ${foo} and ${bar}`,
163
- b: ({ foo }: TestBindings) => h("a", { href: foo, target: foo }, `Test link: ${foo}`),
164
- };
165
-
166
- const expectedCode = `<template #a="{ foo, bar }">Slot with bindings {{ foo }} and {{ bar }}</template>
167
-
168
- <template #b="{ foo }"><a :href="foo" :target="foo">Test link: {{ foo }}</a></template>`;
169
-
170
- const actualCode = generateSlotSourceCode(slots, Object.keys(slots), {
171
- imports: {},
172
- scriptVariables: {},
173
- });
174
- expect(actualCode).toBe(expectedCode);
175
- });
176
-
177
- test("should generate source code with <script setup> block", () => {
178
- const actualCode = generateSourceCode({
179
- title: "MyComponent",
180
- component: {
181
- __docgenInfo: {
182
- slots: [{ name: "mySlot" }],
183
- events: [{ name: "update:c" }],
184
- },
185
- },
186
- args: {
187
- a: 42,
188
- b: "foo",
189
- c: [1, 2, 3],
190
- d: { bar: "baz" },
191
- mySlot: () => h("div", { test: [1, 2], d: { nestedProp: "foo" } }),
192
- },
193
- });
194
-
195
- expect(actualCode).toBe(`<script lang="ts" setup>
196
- import { ref } from "vue";
197
-
198
- const c = ref([1,2,3]);
199
-
200
- const d = {"bar":"baz"};
201
-
202
- const d1 = {"nestedProp":"foo"};
203
-
204
- const test = [1,2];
205
- </script>
206
-
207
- <template>
208
- <MyComponent :a="42" b="foo" v-model:c="c" :d="d"> <template #mySlot><div :d="d1" :test="test" /></template> </MyComponent>
209
- </template>`);
210
- });
211
-
212
- test.each([
213
- { __docgenInfo: "invalid-value", slotNames: [] },
214
- { __docgenInfo: {}, slotNames: [] },
215
- { __docgenInfo: { slots: "invalid-value" }, slotNames: [] },
216
- { __docgenInfo: { slots: ["invalid-value"] }, slotNames: [] },
217
- {
218
- __docgenInfo: { slots: [{ name: "slot-1" }, { name: "slot-2" }, { notName: "slot-3" }] },
219
- slotNames: ["slot-1", "slot-2"],
220
- },
221
- ])("should parse slots names from __docgenInfo", ({ __docgenInfo, slotNames }) => {
222
- const docgenInfo = parseDocgenInfo({ __docgenInfo });
223
- expect(docgenInfo.slotNames).toStrictEqual(slotNames);
224
- });
225
-
226
- test.each([
227
- { __docgenInfo: "invalid-value", eventNames: [] },
228
- { __docgenInfo: {}, eventNames: [] },
229
- { __docgenInfo: { events: "invalid-value" }, eventNames: [] },
230
- { __docgenInfo: { events: ["invalid-value"] }, eventNames: [] },
231
- {
232
- __docgenInfo: { events: [{ name: "event-1" }, { name: "event-2" }, { notName: "event-3" }] },
233
- eventNames: ["event-1", "event-2"],
234
- },
235
- ])("should parse event names from __docgenInfo", ({ __docgenInfo, eventNames }) => {
236
- const docgenInfo = parseDocgenInfo({ __docgenInfo });
237
- expect(docgenInfo.eventNames).toStrictEqual(eventNames);
238
- });
239
-
240
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
241
- test.each<{ fn: (...args: any[]) => unknown; expectedNames: string[] }>([
242
- { fn: () => ({}), expectedNames: [] },
243
- { fn: (a) => ({}), expectedNames: ["a"] },
244
- { fn: (a, b) => ({}), expectedNames: ["a", "b"] },
245
- { fn: (a, b, { c }) => ({}), expectedNames: ["a", "b", "{", "c", "}"] },
246
- { fn: ({ a, b }) => ({}), expectedNames: ["{", "a", "b", "}"] },
247
- {
248
- fn: {
249
- // simulate minified function after running "storybook build"
250
- toString: () => "({a:foo,b:bar})=>({})",
251
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
252
- } as (...args: any[]) => unknown,
253
- expectedNames: ["{", "a", "b", "}"],
254
- },
255
- ])("should extract function parameter names", ({ fn, expectedNames }) => {
256
- const paramNames = getFunctionParamNames(fn);
257
- expect(paramNames).toStrictEqual(expectedNames);
258
- });