@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/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./actions";
2
2
  export * from "./preview";
3
+ export * from "./sbType";
3
4
  export * from "./theme";
4
5
  export * from "./types";
@@ -1,25 +1,19 @@
1
1
  import bellRing from "@sit-onyx/icons/bell-ring.svg?raw";
2
2
  import calendar from "@sit-onyx/icons/calendar.svg?raw";
3
3
  import placeholder from "@sit-onyx/icons/placeholder.svg?raw";
4
- import { describe, expect, test, vi } from "vitest";
4
+ import { describe, expect, test } from "vitest";
5
5
  import { replaceAll, sourceCodeTransformer } from "./preview";
6
- import * as sourceCodeGenerator from "./source-code-generator";
7
6
 
8
7
  describe("preview.ts", () => {
9
8
  test("should transform source code and add icon/onyx imports", () => {
10
- // ARRANGE
11
- const generatorSpy = vi.spyOn(sourceCodeGenerator, "generateSourceCode")
12
- .mockReturnValue(`<template>
9
+ // ACT
10
+ const sourceCode = sourceCodeTransformer(`<template>
13
11
  <OnyxTest icon='${placeholder}' test='${bellRing}' :obj="{foo:'${replaceAll(calendar, '"', "\\'")}'}" />
14
12
  <OnyxOtherComponent />
15
13
  <OnyxComp>Test</OnyxComp>
16
14
  </template>`);
17
15
 
18
- // ACT
19
- const sourceCode = sourceCodeTransformer("", { title: "OnyxTest", args: {} });
20
-
21
16
  // ASSERT
22
- expect(generatorSpy).toHaveBeenCalledOnce();
23
17
  expect(sourceCode).toBe(`<script lang="ts" setup>
24
18
  import { OnyxComp, OnyxOtherComponent, OnyxTest } from "sit-onyx";
25
19
  import bellRing from "@sit-onyx/icons/bell-ring.svg?raw";
package/src/preview.ts CHANGED
@@ -1,26 +1,22 @@
1
1
  import { getIconImportName } from "@sit-onyx/icons";
2
- import { type Preview, type StoryContext } from "@storybook/vue3";
2
+ import type { Preview } from "@storybook/vue3";
3
+ import { DARK_MODE_EVENT_NAME } from "@vueless/storybook-dark-mode";
3
4
  import { deepmerge } from "deepmerge-ts";
4
- 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
- import { generateSourceCode } from "./source-code-generator";
10
- import { ONYX_BREAKPOINTS, createTheme } from "./theme";
11
-
12
- const themes = {
13
- light: createTheme(),
14
- dark: createTheme({ base: "dark" }),
15
- } as const;
10
+ import { ONYX_BREAKPOINTS, createTheme, type BrandDetails } from "./theme";
16
11
 
17
12
  /**
18
13
  * Creates a default Storybook preview configuration for 'onyx' with the following features:
19
14
  * - Improved controls (sorting and expanded controls so descriptions etc. are also shown in a single story)
20
15
  * - Improved Vue-specific code highlighting (e.g. using `@` instead of `v-on:`)
21
- * - Setup for dark mode (including docs page). Requires addon `storybook-dark-mode` to be enabled in .storybook/main.ts file
16
+ * - Setup for dark mode (including docs page). Requires addon `@vueless/storybook-dark-mode` to be enabled in .storybook/main.ts file
22
17
  * - Custom Storybook theme using onyx colors (light and dark mode)
23
18
  * - Configure viewports / breakpoints as defined by onyx
19
+ * - Logs Vue emits as Storybook events
24
20
  *
25
21
  * @param overrides Custom preview / overrides, will be deep merged with the default preview.
26
22
  *
@@ -39,8 +35,20 @@ const themes = {
39
35
  * export default preview;
40
36
  * ```
41
37
  */
42
- export const createPreview = <T extends Preview = Preview>(overrides?: T) => {
38
+ export const createPreview = <T extends Preview = Preview>(
39
+ overrides?: T,
40
+ brandDetails?: BrandDetails,
41
+ ) => {
42
+ const themes = {
43
+ light: createTheme("light", brandDetails),
44
+ dark: createTheme("dark", brandDetails),
45
+ } as const;
46
+
43
47
  const defaultPreview = {
48
+ argTypesEnhancers: [enhanceEventArgTypes],
49
+ initialGlobals: {
50
+ ["requiredMode" satisfies keyof typeof requiredGlobalType]: "required",
51
+ },
44
52
  globalTypes: {
45
53
  ...requiredGlobalType,
46
54
  },
@@ -63,11 +71,9 @@ export const createPreview = <T extends Preview = Preview>(overrides?: T) => {
63
71
  if (isDark) {
64
72
  document.body.classList.remove("light");
65
73
  document.body.classList.add("dark");
66
- document.documentElement.style.colorScheme = "dark";
67
74
  } else {
68
75
  document.body.classList.remove("dark");
69
76
  document.body.classList.add("light");
70
- document.documentElement.style.colorScheme = "light";
71
77
  }
72
78
 
73
79
  return isDark ? themes.dark : themes.light;
@@ -123,16 +129,15 @@ export const createPreview = <T extends Preview = Preview>(overrides?: T) => {
123
129
  *
124
130
  * @see https://storybook.js.org/docs/react/api/doc-block-source
125
131
  */
126
- export const sourceCodeTransformer = (
127
- sourceCode: string,
128
- ctx: Pick<StoryContext, "title" | "component" | "args">,
129
- ): string => {
132
+ export const sourceCodeTransformer = (originalSourceCode: string): string => {
130
133
  const RAW_ICONS = import.meta.glob("../node_modules/@sit-onyx/icons/src/assets/*.svg", {
131
134
  query: "?raw",
132
135
  import: "default",
133
136
  eager: true,
134
137
  });
135
138
 
139
+ let code = originalSourceCode;
140
+
136
141
  /**
137
142
  * Mapping between icon SVG content (key) and icon name (value).
138
143
  * Needed to display a labelled dropdown list of all available icons.
@@ -145,8 +150,6 @@ export const sourceCodeTransformer = (
145
150
  {},
146
151
  );
147
152
 
148
- let code = generateSourceCode(ctx);
149
-
150
153
  const additionalImports: string[] = [];
151
154
 
152
155
  // add icon imports to the source code for all used onyx icons
@@ -156,7 +159,10 @@ export const sourceCodeTransformer = (
156
159
  const escapedIconContent = `"${replaceAll(iconContent, '"', '\\"')}"`;
157
160
 
158
161
  if (code.includes(iconContent)) {
159
- code = code.replace(new RegExp(` (\\S+)=['"]${iconContent}['"]`), ` :$1="${importName}"`);
162
+ code = code.replace(
163
+ new RegExp(` (\\S+)=['"]${escapeRegExp(iconContent)}['"]`),
164
+ ` :$1="${importName}"`,
165
+ );
160
166
  additionalImports.push(`import ${importName} from "@sit-onyx/icons/${iconName}.svg?raw";`);
161
167
  } else if (code.includes(singleQuotedIconContent)) {
162
168
  // support icons inside objects
@@ -206,3 +212,11 @@ ${code}`;
206
212
  export const replaceAll = (value: string, searchValue: string | RegExp, replaceValue: string) => {
207
213
  return value.replace(new RegExp(searchValue, "gi"), replaceValue);
208
214
  };
215
+
216
+ /**
217
+ * Escapes the given string value to be used in `new RegExp()`.
218
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions#escaping
219
+ */
220
+ export const escapeRegExp = (string: string) => {
221
+ return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
222
+ };
package/src/required.ts CHANGED
@@ -8,7 +8,6 @@ export const requiredGlobalType = {
8
8
  requiredMode: {
9
9
  name: "Required mode",
10
10
  description: "Switch between 'required' and 'optional' indicator",
11
- defaultValue: "required",
12
11
  toolbar: {
13
12
  icon: "flag",
14
13
  items: [
@@ -0,0 +1,38 @@
1
+ import type { SBType } from "storybook/internal/types";
2
+ import { describe, expect, test, vi } from "vitest";
3
+ import { walkTree } from "./sbType";
4
+
5
+ describe("walkTree", () => {
6
+ test.each<{ input: SBType; expected: SBType["name"][] }>([
7
+ { input: { name: "array", value: { name: "number" } }, expected: ["array", "number"] },
8
+ { input: { name: "object", value: { a: { name: "number" } } }, expected: ["object", "number"] },
9
+ { input: { name: "enum", value: ["a"] }, expected: ["enum"] },
10
+ {
11
+ input: { name: "intersection", value: [{ name: "number" }] },
12
+ expected: ["intersection", "number"],
13
+ },
14
+ { input: { name: "union", value: [{ name: "number" }] }, expected: ["union", "number"] },
15
+ { input: { name: "other", value: "a" }, expected: ["other"] },
16
+ ])("should execute cb for $input.name correctly", ({ input, expected }) => {
17
+ const spy = vi.fn<(p: SBType) => void>();
18
+ const result = walkTree(input, spy);
19
+
20
+ expect(result).toBeUndefined();
21
+ expect(spy).toHaveBeenCalledTimes(expected.length);
22
+ const nameCalls = spy.mock.calls.map(([{ name }]) => name);
23
+ expect(nameCalls).toMatchObject(expected);
24
+ });
25
+
26
+ test("should return value if there is any returned", () => {
27
+ const target: SBType = { name: "number", raw: "here" };
28
+ const overshoot: SBType = { name: "boolean", raw: "here" };
29
+ const parent: SBType = { name: "intersection", value: [target, overshoot] };
30
+ const returned = 42;
31
+ const spy = vi.fn((p: SBType) => (p.raw === "here" ? returned : undefined));
32
+ const result = walkTree({ name: "union", value: [parent] }, spy);
33
+
34
+ expect(spy).toHaveBeenCalledTimes(3);
35
+ expect(spy).toHaveBeenLastCalledWith(target, parent);
36
+ expect(result).toBe(returned);
37
+ });
38
+ });
package/src/sbType.ts ADDED
@@ -0,0 +1,104 @@
1
+ import type {
2
+ ArgTypesEnhancer,
3
+ InputType,
4
+ SBType,
5
+ StrictInputType,
6
+ } from "storybook/internal/types";
7
+
8
+ /**
9
+ * Call a function `cb` for every type node in the storybook type tree.
10
+ * @param inputType the root type
11
+ * @param cb the function that is called for every type. If any non-nullish value is returned by `cb` the execution is stopped and this value is returned.
12
+ * @param parent optional, the parent type. Is only used as input for the `cb` function and provided when recursing.
13
+ * @returns the first non-nullish value that is returned by `cb`
14
+ */
15
+ export const walkTree = <TValue>(
16
+ inputType: SBType,
17
+ cb: (sb: SBType, parent?: SBType) => TValue,
18
+ parent?: SBType,
19
+ ): TValue | undefined => {
20
+ const shouldReturn = cb(inputType, parent);
21
+ if (shouldReturn) {
22
+ return shouldReturn;
23
+ }
24
+
25
+ if (inputType.name === "union" || inputType.name === "intersection") {
26
+ return inputType.value.reduce<TValue | undefined>(
27
+ (prev, it) => prev ?? walkTree(it, cb, inputType),
28
+ undefined,
29
+ );
30
+ }
31
+ if (inputType.name === "array") {
32
+ return walkTree(inputType.value, cb, inputType);
33
+ }
34
+ if (inputType.name === "object") {
35
+ return Object.values(inputType.value).reduce<TValue | undefined>(
36
+ (prev, it) => prev ?? walkTree(it, cb, inputType),
37
+ undefined,
38
+ );
39
+ }
40
+ };
41
+
42
+ const SB_TYPE_CONTROL_MAP: Partial<Record<SBType["name"], InputType["control"]>> = {
43
+ boolean: { type: "boolean" },
44
+ string: { type: "text" },
45
+ number: { type: "number" },
46
+ };
47
+
48
+ const getFormInjectedParent = (symbol: string, inputType?: StrictInputType) => {
49
+ if (!inputType?.type || inputType.table?.defaultValue?.summary !== symbol) {
50
+ return undefined;
51
+ }
52
+
53
+ return walkTree(inputType.type, (elem, parent) =>
54
+ elem.name === "symbol" || (elem.name === "other" && elem.value === "unique symbol")
55
+ ? parent
56
+ : undefined,
57
+ );
58
+ };
59
+
60
+ /**
61
+ * Can be used to create an `ArgTypesEnhancer` which matches a Symbol that is used as default Prop.
62
+ * When it matches the passed description text will be set.
63
+ *
64
+ * @param symbol description of the symbol that should be matched.
65
+ * @param description the description text that should be shown in Storybook for this prop.
66
+ * @returns An `ArgTypesEnhancer` which can be passed to storybook.
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * import { createSymbolArgTypeEnhancer } from "@sit-onyx/storybook-utils";
71
+ *
72
+ * export const enhanceFormInjectedSymbol = createSymbolArgTypeEnhancer(
73
+ * "FORM_INJECTED_SYMBOL",
74
+ * "If no value (or `undefined`) is provided, `FORM_INJECTED_SYMBOL` is the internal default value for this prop.\n" +
75
+ * "In that case the props value will be derived from it's parent form (if it exists).\n",
76
+ * );
77
+ * ```
78
+ */
79
+ export const createSymbolArgTypeEnhancer = (
80
+ symbol: string,
81
+ description: string,
82
+ ): ArgTypesEnhancer => {
83
+ return (context) => {
84
+ Object.values(context.argTypes)
85
+ .map((argType) => {
86
+ const parent = getFormInjectedParent(symbol, argType);
87
+ return { argType, parent };
88
+ })
89
+ .filter(({ parent }) => parent)
90
+ .forEach(({ argType, parent }) => {
91
+ const firstAvailableControl = walkTree(
92
+ parent || argType.type!,
93
+ (sb) => SB_TYPE_CONTROL_MAP[sb.name],
94
+ );
95
+
96
+ if (firstAvailableControl && argType.table?.defaultValue) {
97
+ argType.control = firstAvailableControl;
98
+ argType.table.defaultValue.detail = description;
99
+ }
100
+ });
101
+
102
+ return context.argTypes;
103
+ };
104
+ };
package/src/style.css ADDED
@@ -0,0 +1,161 @@
1
+ .onyx-disclaimer {
2
+ border-radius: 0.5rem;
3
+ padding: 1rem;
4
+ line-height: var(--onyx-font-line-height-md);
5
+ margin: 1rem 0;
6
+
7
+ /* same color as VitePress var(--vp-c-tip-soft) */
8
+ background-color: color-mix(in srgb, var(--onyx-color-base-info-400) 25%, transparent);
9
+ }
10
+
11
+ .onyx-disclaimer__title {
12
+ font-weight: var(--onyx-font-weight-semibold);
13
+ margin-bottom: 0.5rem;
14
+ }
15
+
16
+ .onyx-disclaimer p {
17
+ margin: 0;
18
+ }
19
+
20
+ /* the Storybook table of content headline is always black so we need to manually set it for the dark mode */
21
+ #storybook-docs .sbdocs-wrapper > aside > nav > h2 {
22
+ /* same as Storybook color "textMuted" inside ./theme.ts */
23
+ color: var(--onyx-color-text-icons-neutral-medium);
24
+ }
25
+
26
+ /* To prevent bg flashing when changing between elements in darkmode */
27
+ .sb-preparing-story,
28
+ .sb-preparing-docs {
29
+ background-color: transparent;
30
+ }
31
+ /* removed placeholder for the same reason */
32
+ .sb-previewBlock,
33
+ .sb-argstableBlock {
34
+ display: none;
35
+ }
36
+
37
+ /*
38
+ Copy of ../sit-onyx/src/styles/variables/themes/onyx.css
39
+ TODO: Find a way to automate this
40
+ */
41
+ :where(:root),
42
+ .onyx-theme-default {
43
+ --onyx-color-steel-100: #fafbfc;
44
+ --onyx-color-steel-200: #e3eaf0;
45
+ --onyx-color-steel-300: #c9d6e0;
46
+ --onyx-color-steel-400: #9db3c4;
47
+ --onyx-color-steel-500: #7392aa;
48
+ --onyx-color-steel-600: #506e84;
49
+ --onyx-color-steel-700: #3e596e;
50
+ --onyx-color-steel-800: #31495c;
51
+ --onyx-color-steel-900: #22384a;
52
+ --onyx-color-steel-1000: #11212d;
53
+ --onyx-color-steel-1100: #081723;
54
+ --onyx-color-steel-1200: #000e19;
55
+ --onyx-color-themed-primary-100: #e8fcfc;
56
+ --onyx-color-themed-primary-200: #bbeaed;
57
+ --onyx-color-themed-primary-300: #79dde2;
58
+ --onyx-color-themed-primary-400: #3dd0d8;
59
+ --onyx-color-themed-primary-500: #00c3cd;
60
+ --onyx-color-themed-primary-600: #00adb5;
61
+ --onyx-color-themed-primary-700: #00969d;
62
+ --onyx-color-themed-primary-800: #008085;
63
+ --onyx-color-themed-primary-900: #00696e;
64
+ --onyx-color-themed-primary-1000: #005356;
65
+ --onyx-color-themed-primary-1100: #003c3e;
66
+ --onyx-color-themed-primary-1200: #002626;
67
+ --onyx-color-themed-secondary-100: var(--onyx-color-themed-primary-100);
68
+ --onyx-color-themed-secondary-200: var(--onyx-color-themed-primary-200);
69
+ --onyx-color-themed-secondary-300: var(--onyx-color-themed-primary-300);
70
+ --onyx-color-themed-secondary-400: var(--onyx-color-themed-primary-400);
71
+ --onyx-color-themed-secondary-500: var(--onyx-color-themed-primary-500);
72
+ --onyx-color-themed-secondary-600: var(--onyx-color-themed-primary-600);
73
+ --onyx-color-themed-secondary-700: var(--onyx-color-themed-primary-700);
74
+ --onyx-color-themed-secondary-800: var(--onyx-color-themed-primary-800);
75
+ --onyx-color-themed-secondary-900: var(--onyx-color-themed-primary-900);
76
+ --onyx-color-themed-secondary-1000: var(--onyx-color-themed-primary-1000);
77
+ --onyx-color-themed-secondary-1100: var(--onyx-color-themed-primary-1100);
78
+ --onyx-color-themed-secondary-1200: var(--onyx-color-themed-primary-1200);
79
+ --onyx-color-universal-grayscale-black: #000000;
80
+ --onyx-color-universal-grayscale-white: #ffffff;
81
+ --onyx-color-universal-green-100: #ecf8f2;
82
+ --onyx-color-universal-green-200: #c6e4d5;
83
+ --onyx-color-universal-green-300: #98d1b3;
84
+ --onyx-color-universal-green-400: #6ebe94;
85
+ --onyx-color-universal-green-500: #44aa75;
86
+ --onyx-color-universal-green-600: #3b9b69;
87
+ --onyx-color-universal-green-700: #328c5e;
88
+ --onyx-color-universal-green-800: #297d52;
89
+ --onyx-color-universal-green-900: #216d46;
90
+ --onyx-color-universal-green-1000: #185e3a;
91
+ --onyx-color-universal-green-1100: #064023;
92
+ --onyx-color-universal-green-1200: #064023;
93
+ --onyx-color-universal-orange-100: #faf6f2;
94
+ --onyx-color-universal-orange-200: #f8e7d8;
95
+ --onyx-color-universal-orange-300: #f6d1b1;
96
+ --onyx-color-universal-orange-400: #f4b57e;
97
+ --onyx-color-universal-orange-500: #f2994a;
98
+ --onyx-color-universal-orange-600: #d98841;
99
+ --onyx-color-universal-orange-700: #bf7737;
100
+ --onyx-color-universal-orange-800: #a6662e;
101
+ --onyx-color-universal-orange-900: #8c5625;
102
+ --onyx-color-universal-orange-1000: #73451c;
103
+ --onyx-color-universal-orange-1100: #593412;
104
+ --onyx-color-universal-orange-1200: #402309;
105
+ --onyx-color-universal-purple-100: #f4f1f8;
106
+ --onyx-color-universal-purple-200: #dbcfea;
107
+ --onyx-color-universal-purple-300: #c2addc;
108
+ --onyx-color-universal-purple-400: #a98ace;
109
+ --onyx-color-universal-purple-500: #9068c0;
110
+ --onyx-color-universal-purple-600: #805aae;
111
+ --onyx-color-universal-purple-700: #704c9b;
112
+ --onyx-color-universal-purple-800: #603e89;
113
+ --onyx-color-universal-purple-900: #513077;
114
+ --onyx-color-universal-purple-1000: #412265;
115
+ --onyx-color-universal-purple-1100: #311452;
116
+ --onyx-color-universal-purple-1200: #210640;
117
+ --onyx-color-universal-quantitatives-100: #005795;
118
+ --onyx-color-universal-quantitatives-200: #ff8a25;
119
+ --onyx-color-universal-quantitatives-300: #e51718;
120
+ --onyx-color-universal-quantitatives-400: #36b16b;
121
+ --onyx-color-universal-quantitatives-500: #56d8fc;
122
+ --onyx-color-universal-quantitatives-600: #ff9990;
123
+ --onyx-color-universal-quantitatives-700: #ff3fd1;
124
+ --onyx-color-universal-quantitatives-800: #3c6475;
125
+ --onyx-color-universal-quantitatives-900: #c4bc81;
126
+ --onyx-color-universal-quantitatives-1000: #c3143f;
127
+ --onyx-color-universal-quantitatives-1100: #a09dfa;
128
+ --onyx-color-universal-quantitatives-1200: #00bcc6;
129
+ --onyx-color-universal-red-100: #fbefee;
130
+ --onyx-color-universal-red-200: #f1d2d1;
131
+ --onyx-color-universal-red-300: #e6a7a5;
132
+ --onyx-color-universal-red-400: #dc716e;
133
+ --onyx-color-universal-red-500: #d1332f;
134
+ --onyx-color-universal-red-600: #bc2d2a;
135
+ --onyx-color-universal-red-700: #a82824;
136
+ --onyx-color-universal-red-800: #93221f;
137
+ --onyx-color-universal-red-900: #7e1d19;
138
+ --onyx-color-universal-red-1000: #691714;
139
+ --onyx-color-universal-red-1100: #400c09;
140
+ --onyx-color-universal-red-1200: #400c09;
141
+ --onyx-number-radius-100: 0.125rem;
142
+ --onyx-number-radius-200: 0.25rem;
143
+ --onyx-number-radius-300: 0.5rem;
144
+ --onyx-number-radius-400: 1rem;
145
+ --onyx-number-radius-500: 2rem;
146
+ --onyx-number-radius-600: 62.5rem;
147
+ --onyx-number-spacing-0: 0rem;
148
+ --onyx-number-spacing-100: 0.125rem;
149
+ --onyx-number-spacing-200: 0.25rem;
150
+ --onyx-number-spacing-250: 0.375rem;
151
+ --onyx-number-spacing-300: 0.5rem;
152
+ --onyx-number-spacing-325: 0.625rem;
153
+ --onyx-number-spacing-350: 0.75rem;
154
+ --onyx-number-spacing-400: 1rem;
155
+ --onyx-number-spacing-500: 1.5rem;
156
+ --onyx-number-spacing-600: 2rem;
157
+ --onyx-number-spacing-700: 3rem;
158
+ --onyx-number-spacing-800: 4rem;
159
+ --onyx-number-spacing-900: 6rem;
160
+ --onyx-number-spacing-950: 8rem;
161
+ }
package/src/theme.ts CHANGED
@@ -1,33 +1,40 @@
1
- import onyxVariables from "sit-onyx/themes/onyx.json";
2
- import { ONYX_BREAKPOINTS as RAW_ONYX_BREAKPOINTS, type OnyxBreakpoint } from "sit-onyx/types";
3
- import { create, type ThemeVars, type ThemeVarsPartial } from "storybook/internal/theming";
4
- 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
+
7
+ export type BrandDetails = Pick<ThemeVars, "brandTitle" | "brandImage" | "brandUrl">;
8
+
9
+ /**
10
+ * Get the computed value for a CSS custom property.
11
+ * Per default the property value is taken from the body element.
12
+ */
13
+ export const getCustomProperty = (property: string, el: Element = document.body) =>
14
+ getComputedStyle(el).getPropertyValue(property);
5
15
 
6
16
  /**
7
17
  * Creates a custom theme for Storybook that uses onyx colors.
8
18
  *
9
19
  * @see https://storybook.js.org/docs/react/configure/theming#create-a-theme-quickstart
10
20
  */
11
- export const createTheme = (
12
- options?: Pick<ThemeVarsPartial, "base" | "brandTitle" | "brandImage" | "brandUrl">,
13
- ) => {
14
- const base = options?.base ?? "light";
15
- const primaryColor = onyxVariables["onyx-color-themed-primary-500"];
21
+ export const createTheme = (base: "light" | "dark" = "light", brandDetails?: BrandDetails) => {
22
+ const primaryColor = getCustomProperty("--onyx-color-base-primary-500");
16
23
 
17
24
  return create({
18
- brandTitle: options?.brandTitle ?? "onyx Storybook",
19
- brandUrl: options?.brandUrl ?? "https://onyx.schwarz",
20
- brandImage: options?.brandImage ?? onyxLogo,
25
+ brandTitle: brandDetails?.brandTitle,
26
+ brandUrl: brandDetails?.brandUrl,
27
+ brandImage: brandDetails?.brandImage,
21
28
  brandTarget: "_blank",
22
- base: base,
29
+ base,
23
30
 
24
31
  // default theme values that are independent of the light/dark mode:
25
32
  colorPrimary: primaryColor,
26
- colorSecondary: onyxVariables["onyx-color-themed-secondary-500"],
33
+ colorSecondary: getCustomProperty("--onyx-color-themed-secondary-500"),
27
34
  barSelectedColor: primaryColor,
28
35
  barHoverColor: primaryColor,
29
- appBorderRadius: remToNumber(onyxVariables["onyx-number-radius-300"]),
30
- inputBorderRadius: remToNumber(onyxVariables["onyx-number-radius-200"]),
36
+ appBorderRadius: remToNumber(getCustomProperty("--onyx-number-radius-300")),
37
+ inputBorderRadius: remToNumber(getCustomProperty("--onyx-number-radius-200")),
31
38
 
32
39
  // custom colors depending on light/dark theme
33
40
  ...(base === "light" ? getLightTheme() : getDarkTheme()),
@@ -36,21 +43,21 @@ export const createTheme = (
36
43
 
37
44
  const getLightTheme = (): Partial<ThemeVars> => {
38
45
  return defineTheme({
39
- background: onyxVariables["onyx-color-universal-grayscale-white"],
40
- contentBackground: onyxVariables["onyx-color-themed-neutral-100"],
41
- text: onyxVariables["onyx-color-themed-neutral-700"],
42
- textMuted: onyxVariables["onyx-color-themed-neutral-600"],
43
- border: onyxVariables["onyx-color-themed-neutral-300"],
46
+ background: getCustomProperty("--onyx-color-universal-grayscale-white"),
47
+ contentBackground: getCustomProperty("--onyx-color-steel-100"),
48
+ text: getCustomProperty("--onyx-color-steel-700"),
49
+ textMuted: getCustomProperty("--onyx-color-steel-600"),
50
+ border: getCustomProperty("--onyx-color-steel-300"),
44
51
  });
45
52
  };
46
53
 
47
54
  const getDarkTheme = (): Partial<ThemeVars> => {
48
55
  return defineTheme({
49
- background: onyxVariables["onyx-color-themed-neutral-1100"],
50
- contentBackground: onyxVariables["onyx-color-themed-neutral-1200"],
51
- text: onyxVariables["onyx-color-themed-neutral-200"],
52
- textMuted: onyxVariables["onyx-color-themed-neutral-400"],
53
- border: onyxVariables["onyx-color-themed-neutral-900"],
56
+ background: getCustomProperty("--onyx-color-steel-1100"),
57
+ contentBackground: getCustomProperty("--onyx-color-steel-1200"),
58
+ text: getCustomProperty("--onyx-color-steel-200"),
59
+ textMuted: getCustomProperty("--onyx-color-steel-400"),
60
+ border: getCustomProperty("--onyx-color-steel-900"),
54
61
  });
55
62
  };
56
63
 
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
  };