@sit-onyx/storybook-utils 1.0.0-beta.99 → 1.0.0

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.99",
4
+ "version": "1.0.0",
5
5
  "type": "module",
6
6
  "author": "Schwarz IT KG",
7
7
  "license": "Apache-2.0",
@@ -28,20 +28,21 @@
28
28
  "peerDependencies": {
29
29
  "@storybook/vue3-vite": ">= 9.0.0",
30
30
  "@vueless/storybook-dark-mode": ">= 9.0.0",
31
+ "prettier": ">= 3.0.0",
31
32
  "storybook": ">= 9.0.0",
32
33
  "vue-component-type-helpers": ">= 2",
33
- "@sit-onyx/icons": "^1.0.0-beta.22",
34
- "@sit-onyx/shared": "^1.0.0-beta.4"
34
+ "@sit-onyx/flags": "^1.0.0",
35
+ "@sit-onyx/icons": "^1.0.0"
35
36
  },
36
37
  "dependencies": {
37
38
  "deepmerge-ts": "^7.1.5"
38
39
  },
39
40
  "devDependencies": {
40
- "storybook": "^9.0.18",
41
- "vue": "3.5.18",
42
- "vue-component-type-helpers": "^3.0.3",
43
- "@sit-onyx/icons": "^1.0.0-beta.22",
44
- "@sit-onyx/shared": "^1.0.0-beta.4"
41
+ "storybook": "^9.1.5",
42
+ "vue": "3.5.21",
43
+ "vue-component-type-helpers": "^3.0.6",
44
+ "@sit-onyx/icons": "^1.0.0",
45
+ "@sit-onyx/shared": "^0.1.0"
45
46
  },
46
47
  "scripts": {
47
48
  "build": "tsc --noEmit",
@@ -0,0 +1,17 @@
1
+ /**
2
+ * All available onyx breakpoints / viewports.
3
+ * Key = breakpoint name, value = width in pixels.
4
+ */
5
+ export const ONYX_BREAKPOINTS = {
6
+ "2xs": 320,
7
+ xs: 577,
8
+ sm: 769,
9
+ md: 993,
10
+ lg: 1441,
11
+ xl: 1921,
12
+ } as const;
13
+
14
+ // "string &" is needed to fix a current Vue issue where a warning is logged for invalid property types
15
+ // when this types is used in a union, see:
16
+ // https://github.com/SchwarzIT/onyx/issues/3290
17
+ export type OnyxBreakpoint = string & keyof typeof ONYX_BREAKPOINTS;
package/src/events.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { Events } from "vue";
2
2
 
3
3
  // prettier-ignore
4
- type EventName<T extends Event> =
4
+ type EventName<T extends Event> =
5
5
  T extends ClipboardEvent ? "ClipboardEvent"
6
6
  : T extends WheelEvent ? "WheelEvent"
7
7
  : T extends PointerEvent ? "PointerEvent"
@@ -311,8 +311,8 @@ export const EVENT_DOC_MAP: EventDocMap = {
311
311
  },
312
312
  onAuxclick: {
313
313
  constructor: {
314
- name: "MouseEvent",
315
- url: "https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent",
314
+ name: "PointerEvent",
315
+ url: "https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent",
316
316
  },
317
317
  event: {
318
318
  name: "auxclick",
@@ -321,8 +321,8 @@ export const EVENT_DOC_MAP: EventDocMap = {
321
321
  },
322
322
  onClick: {
323
323
  constructor: {
324
- name: "MouseEvent",
325
- url: "https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent",
324
+ name: "PointerEvent",
325
+ url: "https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent",
326
326
  },
327
327
  event: {
328
328
  name: "click",
@@ -331,8 +331,8 @@ export const EVENT_DOC_MAP: EventDocMap = {
331
331
  },
332
332
  onContextmenu: {
333
333
  constructor: {
334
- name: "MouseEvent",
335
- url: "https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent",
334
+ name: "PointerEvent",
335
+ url: "https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent",
336
336
  },
337
337
  event: {
338
338
  name: "contextmenu",
@@ -1,14 +1,13 @@
1
- import bellRing from "@sit-onyx/icons/bell-ring.svg?raw";
2
- import calendar from "@sit-onyx/icons/calendar.svg?raw";
3
- import placeholder from "@sit-onyx/icons/placeholder.svg?raw";
1
+ import { flagDE } from "@sit-onyx/flags";
2
+ import { iconBellRing, iconCalendar, iconPlaceholder } from "@sit-onyx/icons";
4
3
  import { describe, expect, test } from "vitest";
5
4
  import { replaceAll, sourceCodeTransformer } from "./preview.js";
6
5
 
7
6
  describe("preview.ts", () => {
8
- test("should transform source code and add icon/onyx imports", () => {
7
+ test("should transform source code and add icon/onyx imports", async () => {
9
8
  // ACT
10
- const sourceCode = sourceCodeTransformer(`<template>
11
- <OnyxTest icon='${placeholder}' test='${bellRing}' :obj="{foo:'${replaceAll(calendar, '"', "\\'")}'}" />
9
+ const sourceCode = await sourceCodeTransformer(`<template>
10
+ <OnyxTest icon='${iconPlaceholder}' test='${iconBellRing}' :obj="{foo:'${replaceAll(iconCalendar, '"', "\\'")}'}" flag='${flagDE}' />
12
11
  <OnyxOtherComponent />
13
12
  <OnyxComp>Test</OnyxComp>
14
13
  </template>`);
@@ -16,15 +15,19 @@ describe("preview.ts", () => {
16
15
  // ASSERT
17
16
  expect(sourceCode).toBe(`<script lang="ts" setup>
18
17
  import { OnyxComp, OnyxOtherComponent, OnyxTest } from "sit-onyx";
19
- import bellRing from "@sit-onyx/icons/bell-ring.svg?raw";
20
- import calendar from "@sit-onyx/icons/calendar.svg?raw";
21
- import placeholder from "@sit-onyx/icons/placeholder.svg?raw";
18
+ import { iconBellRing, iconCalendar, iconPlaceholder } from "@sit-onyx/icons";
19
+ import { flagDE } from "@sit-onyx/flags";
22
20
  </script>
23
21
 
24
22
  <template>
25
- <OnyxTest :icon="placeholder" :test="bellRing" :obj="{foo:calendar}" />
26
- <OnyxOtherComponent />
27
- <OnyxComp>Test</OnyxComp>
23
+ <OnyxTest
24
+ :icon="iconPlaceholder"
25
+ :test="iconBellRing"
26
+ :obj="{foo:iconCalendar}"
27
+ :flag="flagDE"
28
+ />
29
+ <OnyxOtherComponent />
30
+ <OnyxComp>Test</OnyxComp>
28
31
  </template>`);
29
32
  });
30
33
  });
package/src/preview.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { getIconImportName } from "@sit-onyx/icons/utils";
2
1
  import type { Preview } from "@storybook/vue3-vite";
3
2
  import { DARK_MODE_EVENT_NAME } from "@vueless/storybook-dark-mode";
4
3
  import { deepmerge } from "deepmerge-ts";
@@ -132,80 +131,116 @@ export const createPreview = <T extends Preview = Preview>(
132
131
  *
133
132
  * @see https://storybook.js.org/docs/react/api/doc-block-source
134
133
  */
135
- export const sourceCodeTransformer = (originalSourceCode: string): string => {
136
- const RAW_ICONS = import.meta.glob("../node_modules/@sit-onyx/icons/src/assets/*.svg", {
137
- query: "?raw",
138
- import: "default",
139
- eager: true,
140
- });
141
-
134
+ export const sourceCodeTransformer = async (originalSourceCode: string): Promise<string> => {
142
135
  let code = originalSourceCode;
143
136
 
144
137
  /**
145
- * Mapping between icon SVG content (key) and icon name (value).
146
- * Needed to display a labelled dropdown list of all available icons.
138
+ * A list of additional JavaScript imports to be added at the top of the source code.
139
+ *
140
+ * key = module/package name to import from, value: set of imports to import from the package
147
141
  */
148
- const ALL_ICONS = Object.entries(RAW_ICONS).reduce<Record<string, string>>(
149
- (obj, [filePath, content]) => {
150
- obj[filePath.split("/").at(-1)!.replace(".svg", "")] = content as string;
151
- return obj;
152
- },
153
- {},
154
- );
155
-
156
- const additionalImports: string[] = [];
157
-
158
- // add icon imports to the source code for all used onyx icons
159
- Object.entries(ALL_ICONS).forEach(([iconName, iconContent]) => {
160
- const importName = getIconImportName(iconName);
161
- const singleQuotedIconContent = `'${replaceAll(iconContent, '"', "\\'")}'`;
162
- const escapedIconContent = `"${replaceAll(iconContent, '"', '\\"')}"`;
163
-
164
- if (code.includes(iconContent)) {
165
- code = code.replace(
166
- new RegExp(` (\\S+)=['"]${escapeRegExp(iconContent)}['"]`),
167
- ` :$1="${importName}"`,
168
- );
169
- additionalImports.push(`import ${importName} from "@sit-onyx/icons/${iconName}.svg?raw";`);
170
- } else if (code.includes(singleQuotedIconContent)) {
171
- // support icons inside objects
172
- code = code.replace(singleQuotedIconContent, importName);
173
- additionalImports.push(`import ${importName} from "@sit-onyx/icons/${iconName}.svg?raw";`);
174
- } else if (code.includes(escapedIconContent)) {
175
- // support icons inside objects
176
- code = code.replace(escapedIconContent, importName);
177
- additionalImports.push(`import ${importName} from "@sit-onyx/icons/${iconName}.svg?raw";`);
178
- }
179
- });
142
+ const additionalImports = new Map<string, Set<string>>();
180
143
 
181
144
  // add imports for all used onyx components
182
145
  // Set is used here to only include unique components if they are used multiple times
183
- const usedOnyxComponents = [
184
- ...new Set(Array.from(code.matchAll(/<(Onyx\w+)(?:\s*\/?)/g)).map((match) => match[1])),
185
- ].sort();
146
+ const usedOnyxComponents = new Set(
147
+ Array.from(code.matchAll(/<(Onyx\w+)(?:\s*\/?)/g))
148
+ .map((match) => match[1])
149
+ .filter((i) => i != undefined),
150
+ );
151
+ additionalImports.set("sit-onyx", usedOnyxComponents);
186
152
 
187
- if (usedOnyxComponents.length > 0) {
188
- additionalImports.unshift(`import { ${usedOnyxComponents.join(", ")} } from "sit-onyx";`);
189
- }
153
+ /**
154
+ * List of npm packages to replace the source code with.
155
+ * The source code will be checked for any import of the package and (if its used), the code will be replaced by the corresponding import.
156
+ */
157
+ const packagesToReplace = [
158
+ { name: "@sit-onyx/icons", data: await import("@sit-onyx/icons") },
159
+ { name: "@sit-onyx/flags", data: await import("@sit-onyx/flags") },
160
+ ];
161
+
162
+ packagesToReplace.forEach((_package) => {
163
+ Object.entries(_package.data).forEach(([name, content]) => {
164
+ const singleQuotedContent = `'${replaceAll(content, '"', "\\'")}'`;
165
+ const escapedContent = `"${replaceAll(content, '"', '\\"')}"`;
190
166
 
191
- if (additionalImports.length === 0) return code;
167
+ const imports = additionalImports.get(_package.name) ?? new Set<string>();
192
168
 
193
- if (code.startsWith("<script")) {
194
- const index = code.indexOf("\n");
195
- const hasOtherImports = code.includes("import {");
196
- return (
197
- code.slice(0, index) +
198
- additionalImports.join("\n") +
199
- (!hasOtherImports ? "\n" : "") +
200
- code.slice(index)
169
+ if (code.includes(content)) {
170
+ imports.add(name);
171
+
172
+ code = code.replace(
173
+ new RegExp(` (\\S+)=['"]${escapeRegExp(content)}['"]`),
174
+ ` :$1="${name}"`,
175
+ );
176
+ } else if (code.includes(singleQuotedContent)) {
177
+ // support values inside objects
178
+ imports.add(name);
179
+ code = code.replace(singleQuotedContent, name);
180
+ } else if (code.includes(escapedContent)) {
181
+ // support values inside objects
182
+ imports.add(name);
183
+ code = code.replace(escapedContent, name);
184
+ }
185
+
186
+ additionalImports.set(_package.name, imports);
187
+ });
188
+ });
189
+
190
+ // remove imports without any data so we don't add empty imports
191
+ additionalImports.forEach((value, key) => {
192
+ if (!value.size) additionalImports.delete(key);
193
+ });
194
+
195
+ // generate the source code for the additional imports and add them to the top of the code snippet
196
+ if (additionalImports.size > 0) {
197
+ const additionalImportsCode = Array.from(additionalImports.entries()).reduce(
198
+ (code, [packageName, imports]) => {
199
+ if (imports.size) {
200
+ code.push(
201
+ `import { ${Array.from(imports.values()).sort().join(", ")} } from "${packageName}";`,
202
+ );
203
+ }
204
+ return code;
205
+ },
206
+ [] as string[],
201
207
  );
202
- }
203
208
 
204
- return `<script lang="ts" setup>
205
- ${additionalImports.join("\n")}
209
+ if (code.startsWith("<script")) {
210
+ const index = code.indexOf("\n");
211
+ const hasOtherImports = code.includes("import {");
212
+ code =
213
+ code.slice(0, index) +
214
+ additionalImportsCode.join("\n") +
215
+ (!hasOtherImports ? "\n" : "") +
216
+ code.slice(index);
217
+ } else {
218
+ code = `<script lang="ts" setup>
219
+ ${additionalImportsCode.join("\n")}
206
220
  </script>
207
221
 
208
222
  ${code}`;
223
+ }
224
+ }
225
+
226
+ try {
227
+ const { format } = await import("prettier/standalone");
228
+ const parserHtml = await import("prettier/parser-html");
229
+
230
+ code = await format(code, {
231
+ parser: "vue",
232
+ plugins: [parserHtml],
233
+ htmlWhitespaceSensitivity: "ignore",
234
+ });
235
+
236
+ // trim code to remove trailing newlines that are added by prettier
237
+ code = code.trim();
238
+ } catch (e) {
239
+ // eslint-disable-next-line no-console -- if the formatting fails, there is usually an issue with our code so we want to inform the user that the formatting failed
240
+ console.error("Error while formatting Storybook code snippet:", e);
241
+ }
242
+
243
+ return code;
209
244
  };
210
245
 
211
246
  /**
package/src/theme.ts CHANGED
@@ -1,9 +1,6 @@
1
- import {
2
- ONYX_BREAKPOINTS as RAW_ONYX_BREAKPOINTS,
3
- type OnyxBreakpoint,
4
- } from "@sit-onyx/shared/breakpoints";
5
1
  import { create, type ThemeVars } from "storybook/internal/theming";
6
2
  import type { Viewport } from "storybook/internal/viewport";
3
+ import { ONYX_BREAKPOINTS as RAW_ONYX_BREAKPOINTS, type OnyxBreakpoint } from "./breakpoints.js";
7
4
 
8
5
  export type BrandDetails = Pick<ThemeVars, "brandTitle" | "brandImage" | "brandUrl">;
9
6