@repobuddy/storybook 2.2.2 → 2.4.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/esm/index.d.ts CHANGED
@@ -5,6 +5,7 @@ import * as react_jsx_runtime0 from "react/jsx-runtime";
5
5
  import { ClassNameProps, StyleProps } from "@just-web/css";
6
6
  import { Args, DecoratorFunction, Renderer } from "storybook/internal/csf";
7
7
  import { Decorator, Meta, StoryContext, StoryObj, StrictArgs } from "@storybook/react-vite";
8
+ import { IsStringLiteral } from "type-plus";
8
9
  export * from "@repobuddy/test";
9
10
 
10
11
  //#region src/components/show_html.d.ts
@@ -32,9 +33,11 @@ declare function ShowHtml({
32
33
  * A decorator that shows the source code of a story above the rendered story.
33
34
  * The source code is taken from the story's `parameters.docs.source.code`.
34
35
  */
35
- declare function showDocSource<TRenderer extends Renderer = Renderer, TArgs = Args>(): DecoratorFunction<TRenderer, TArgs>;
36
+ declare function showDocSource<TRenderer extends Renderer = Renderer, TArgs = Args>(options?: {
37
+ showOriginalSource?: boolean | undefined;
38
+ }): DecoratorFunction<TRenderer, TArgs>;
36
39
  //#endregion
37
- //#region src/decorators/with_story_card.d.ts
40
+ //#region src/components/story_card.d.ts
38
41
  type StoryCardProps = {
39
42
  /**
40
43
  * Optional title displayed as a heading in the card.
@@ -55,7 +58,26 @@ type StoryCardProps = {
55
58
  * If a function is provided, it receives the card state and default className,
56
59
  * and should return the final className string.
57
60
  */
58
- className?: ((state: Required<Pick<StoryCardProps, 'status'>> & {
61
+ className?: ((state: Pick<StoryCardProps, 'status'> & {
62
+ defaultClassName: string;
63
+ }) => string) | string | undefined;
64
+ /**
65
+ * Content to display in the card body.
66
+ * Can be any React node (string, JSX, etc.).
67
+ */
68
+ children?: ReactNode | undefined;
69
+ };
70
+ //#endregion
71
+ //#region src/decorators/with_story_card.d.ts
72
+ type WithStoryCardProps = Omit<StoryCardProps, 'children' | 'className'> & {
73
+ /**
74
+ * Additional CSS classes or a function to compute classes.
75
+ *
76
+ * If a string is provided, it will be merged with the default classes.
77
+ * If a function is provided, it receives the card state and default className,
78
+ * and should return the final className string.
79
+ */
80
+ className?: ((state: Pick<StoryCardProps, 'status'> & {
59
81
  defaultClassName: string;
60
82
  }) => string) | string | undefined;
61
83
  /**
@@ -139,7 +161,7 @@ declare function withStoryCard<TRenderer extends Renderer = Renderer>({
139
161
  status,
140
162
  content: contentProp,
141
163
  ...rest
142
- }?: StoryCardProps): DecoratorFunction<TRenderer>;
164
+ }?: WithStoryCardProps): DecoratorFunction<TRenderer>;
143
165
  //#endregion
144
166
  //#region src/parameters/define_actions_param.d.ts
145
167
  interface ActionsParam {
@@ -645,6 +667,8 @@ declare function whenRunningInTest<TArgs = StrictArgs>(decoratorOrHandler: ((...
645
667
  * @template M - The base Meta type
646
668
  * @template E - The extension type containing tagType
647
669
  *
670
+ * @deprecated use `import { ExtendsMeta } from '@repobuddy/storybook'` instead.
671
+ *
648
672
  * @example
649
673
  * ```ts
650
674
  * // Create a generic Meta type for a project
@@ -665,6 +689,8 @@ type ExtendMeta<TCmpOrArgs, M extends Meta<TCmpOrArgs>, E extends {
665
689
  * @template S - The base StoryObj type
666
690
  * @template E - The extension type containing tagType
667
691
  *
692
+ * @deprecated use `import { ExtendsStoryObj } from '@repobuddy/storybook'` instead.
693
+ *
668
694
  * @example
669
695
  * ```ts
670
696
  * // Create a generic StoryObj type for a project
@@ -680,4 +706,79 @@ type ExtendStoryObj<TMetaOrCmpOrArgs, S extends StoryObj<TMetaOrCmpOrArgs>, E ex
680
706
  tags?: Array<E['tag'] | (string & {})> | undefined;
681
707
  };
682
708
  //#endregion
683
- export { ActionsParam, BackgroundsParam, DocsParam, ExtendMeta, ExtendStoryObj, GlobalApiBackgroundsParam, GlobalApiViewportParam, LayoutParam, ShowHtml, ShowHtmlProps, SourceProps, StoryCardProps, StorySortParam, StorybookBuiltInParams, TestParam, Viewport, ViewportParam, defineActionsParam, defineBackgroundsParam, defineDocsParam, defineLayoutParam, defineParameters, defineTestParam, defineViewportParam, showDocSource, whenRunningInTest, withStoryCard };
709
+ //#region src/types/_extract_string_literals.d.ts
710
+ type ExtractStringLiterals<T> = T extends any ? (string extends T ? never : T) : never;
711
+ //#endregion
712
+ //#region src/types/extends_meta.d.ts
713
+ /**
714
+ * Extends the Storybook Meta type with custom tag types.
715
+ *
716
+ * This utility type allows you to extend the `tags` property of a Storybook Meta type
717
+ * with custom string literal types while preserving existing tag types from the base Meta.
718
+ *
719
+ * @template M - The base Meta type to extend
720
+ * @template E - The extension type containing a `tag` property with the custom tag types
721
+ *
722
+ * @example
723
+ * ```ts
724
+ * import type { ExtendsMeta } from '@repobuddy/storybook'
725
+ * import type { Args, Meta as M } from '@storybook/your-framework'
726
+ *
727
+ * // Create a generic Meta type for your project
728
+ * type Meta<TCmpOrArgs = Args> = ExtendsMeta<
729
+ * M<TCmpOrArgs>,
730
+ * { tag: 'new' | 'beta' | 'deprecated' }
731
+ * >
732
+ *
733
+ * // Use in component stories
734
+ * const meta: Meta<typeof Component> = {
735
+ * tags: ['new'], // <--- gets auto-completion for 'new' | 'beta' | 'deprecated'
736
+ * // ...
737
+ * }
738
+ * ```
739
+ */
740
+ type ExtendsMeta<M extends {
741
+ tags?: string[] | undefined;
742
+ }, E extends {
743
+ tag: string;
744
+ }> = Omit<M, 'tags'> & {
745
+ tags?: ExtractStringLiterals<NonNullable<M['tags']>[number]> extends infer MT ? IsStringLiteral<MT> extends true ? Array<(string & {}) | MT | E['tag']> | undefined : Array<(string & {}) | E['tag']> | undefined : never;
746
+ };
747
+ //#endregion
748
+ //#region src/types/extends_story_obj.d.ts
749
+ /**
750
+ * Extends the Storybook StoryObj type with custom tag types.
751
+ *
752
+ * This utility type allows you to extend the `tags` property of a Storybook StoryObj type
753
+ * with custom string literal types while preserving existing tag types from the base StoryObj.
754
+ *
755
+ * @template S - The base StoryObj type to extend (must have an optional `tags` property)
756
+ * @template E - The extension type containing a `tag` property with the custom tag types
757
+ *
758
+ * @example
759
+ * ```ts
760
+ * import type { ExtendsStoryObj } from '@repobuddy/storybook'
761
+ * import type { Args, StoryObj as S } from '@storybook/your-framework'
762
+ *
763
+ * // Create a generic StoryObj type for your project
764
+ * type StoryObj<TCmpOrArgs = Args> = ExtendsStoryObj<
765
+ * S<TCmpOrArgs>,
766
+ * { tag: 'new' | 'beta' | 'deprecated' }
767
+ * >
768
+ *
769
+ * // Use in component stories
770
+ * const story: StoryObj<typeof Component> = {
771
+ * tags: ['new'], // <--- gets auto-completion for 'new' | 'beta' | 'deprecated'
772
+ * // ...
773
+ * }
774
+ * ```
775
+ */
776
+ type ExtendsStoryObj<S extends {
777
+ tags?: string[] | undefined;
778
+ }, E extends {
779
+ tag: string;
780
+ }> = Omit<S, 'tags'> & {
781
+ tags?: ExtractStringLiterals<NonNullable<S['tags']>[number]> extends infer MT ? IsStringLiteral<MT> extends true ? Array<(string & {}) | MT | E['tag']> | undefined : Array<(string & {}) | E['tag']> | undefined : never;
782
+ };
783
+ //#endregion
784
+ export { ActionsParam, BackgroundsParam, DocsParam, ExtendMeta, ExtendStoryObj, ExtendsMeta, ExtendsStoryObj, GlobalApiBackgroundsParam, GlobalApiViewportParam, LayoutParam, ShowHtml, ShowHtmlProps, SourceProps, StorySortParam, StorybookBuiltInParams, TestParam, Viewport, ViewportParam, WithStoryCardProps, defineActionsParam, defineBackgroundsParam, defineDocsParam, defineLayoutParam, defineParameters, defineTestParam, defineViewportParam, showDocSource, whenRunningInTest, withStoryCard };
package/esm/index.js CHANGED
@@ -37,6 +37,40 @@ function ShowHtml({ selector = "[data-testid=\"subject\"]", config, ...props })
37
37
  });
38
38
  }
39
39
 
40
+ //#endregion
41
+ //#region src/components/story_card.tsx
42
+ function storyCardTheme(state, className) {
43
+ const defaultClassName = storyCardVariants(state);
44
+ if (!className) return defaultClassName;
45
+ return typeof className === "function" ? className({
46
+ ...state,
47
+ defaultClassName
48
+ }) : twMerge(defaultClassName, className);
49
+ }
50
+ const storyCardVariants = cva("flex flex-col gap-1 py-3 px-4 rounded text-black dark:text-gray-100", {
51
+ variants: { status: {
52
+ error: "bg-red-100 dark:bg-red-900",
53
+ warn: "bg-yellow-100 dark:bg-yellow-900",
54
+ info: "bg-sky-100 dark:bg-sky-900"
55
+ } },
56
+ defaultVariants: { status: "info" }
57
+ });
58
+ /**
59
+ * A card component that displays information with optional title and status styling.
60
+ *
61
+ * @param props - StoryCard component props
62
+ * @returns A section element containing the card content
63
+ */
64
+ function StoryCard({ status, className, children, title }) {
65
+ return /* @__PURE__ */ jsxs("section", {
66
+ className: storyCardTheme({ status }, className),
67
+ children: [title && /* @__PURE__ */ jsx("h2", {
68
+ className: "text-lg font-bold",
69
+ children: title
70
+ }), children]
71
+ });
72
+ }
73
+
40
74
  //#endregion
41
75
  //#region src/decorators/show_doc_source.tsx
42
76
  const channel = addons.getChannel();
@@ -44,7 +78,7 @@ const channel = addons.getChannel();
44
78
  * A decorator that shows the source code of a story above the rendered story.
45
79
  * The source code is taken from the story's `parameters.docs.source.code`.
46
80
  */
47
- function showDocSource() {
81
+ function showDocSource(options) {
48
82
  return (Story, { parameters: { docs, darkMode } }) => {
49
83
  const storedItem = window.localStorage.getItem("sb-addon-themes-3");
50
84
  const current = typeof storedItem === "string" ? JSON.parse(storedItem).current : darkMode?.current;
@@ -53,6 +87,13 @@ function showDocSource() {
53
87
  channel.on("DARK_MODE", setIsDark);
54
88
  return () => channel.off("DARK_MODE", setIsDark);
55
89
  }, []);
90
+ const code = options?.showOriginalSource ? docs?.source?.originalSource : docs?.source?.code ?? docs?.source?.originalSource;
91
+ const language = code === docs?.source?.originalSource ? void 0 : docs?.source?.language;
92
+ const isOriginalSource = code === docs?.source?.originalSource;
93
+ const content = /* @__PURE__ */ jsx(SyntaxHighlighter, {
94
+ language,
95
+ children: code
96
+ });
56
97
  return /* @__PURE__ */ jsx(ThemeProvider, {
57
98
  theme: convert(docs?.theme ?? (isDark ? themes.dark : themes.light)),
58
99
  children: /* @__PURE__ */ jsxs("section", {
@@ -61,10 +102,10 @@ function showDocSource() {
61
102
  flexDirection: "column",
62
103
  gap: "1rem"
63
104
  },
64
- children: [/* @__PURE__ */ jsx(SyntaxHighlighter, {
65
- language: docs?.source?.language,
66
- children: docs?.source?.code
67
- }), /* @__PURE__ */ jsx(Story, {})]
105
+ children: [/* @__PURE__ */ jsx(Story, {}), isOriginalSource ? /* @__PURE__ */ jsx(StoryCard, {
106
+ className: "bg-gray-100 dark:bg-gray-900",
107
+ children: content
108
+ }) : content]
68
109
  })
69
110
  });
70
111
  };
@@ -178,43 +219,27 @@ function StoryCardContainer({ children }) {
178
219
  const contextValue = useMemo(() => ({
179
220
  addCard(card) {
180
221
  const key = generateKey("story-card");
181
- setCards((cards$1) => [...cards$1, {
222
+ setCards((cards) => [...cards, {
182
223
  ...card,
183
224
  key
184
225
  }]);
185
226
  return key;
186
227
  },
187
228
  removeCard(key) {
188
- setCards((cards$1) => cards$1.filter((card) => card.key !== key));
229
+ setCards((cards) => cards.filter((card) => card.key !== key));
189
230
  }
190
231
  }), []);
191
232
  return /* @__PURE__ */ jsx(StoryCardContext.Provider, {
192
233
  value: contextValue,
193
234
  children: /* @__PURE__ */ jsxs("div", {
194
235
  className: "flex flex-col gap-2",
195
- children: [cards.map(({ key, status, className, content, title }) => /* @__PURE__ */ jsxs("section", {
196
- className: storyCardTheme({ status }, className),
197
- children: [title && /* @__PURE__ */ jsx("h2", {
198
- className: "text-lg font-bold",
199
- children: title
200
- }), content]
236
+ children: [cards.map(({ content, key, ...rest }) => /* @__PURE__ */ jsx(StoryCard, {
237
+ ...rest,
238
+ children: content
201
239
  }, key)), children]
202
240
  })
203
241
  });
204
242
  }
205
- function storyCardTheme(state, className) {
206
- const defaultClassName = storyCardVariants(state);
207
- if (!className) return defaultClassName;
208
- return typeof className === "function" ? className({
209
- ...state,
210
- defaultClassName
211
- }) : twMerge(defaultClassName, className);
212
- }
213
- const storyCardVariants = cva("flex flex-col gap-1 py-3 px-4 rounded text-black dark:text-gray-100", { variants: { status: {
214
- error: "bg-red-100 dark:bg-red-900",
215
- warn: "bg-yellow-100 dark:bg-yellow-900",
216
- info: "bg-sky-100 dark:bg-sky-900"
217
- } } });
218
243
  function StoryCardCollector({ Story, title, status, className, content }) {
219
244
  const context = useContext(StoryCardContext);
220
245
  const cardIdRef = useRef(null);
@@ -343,7 +368,7 @@ const defineLayoutParam = (layout) => ({ layout });
343
368
  * ```
344
369
  */
345
370
  function defineParameters(param, ...rest) {
346
- return rest.reduce((acc, param$1) => Object.assign(acc, param$1), param);
371
+ return rest.reduce((acc, param) => Object.assign(acc, param), param);
347
372
  }
348
373
 
349
374
  //#endregion
@@ -1,13 +1,14 @@
1
1
 
2
2
  import { TagBadgeParameters } from "storybook-addon-tag-badges/manager-helpers";
3
3
  import { Args, Meta as Meta$1, StoryObj as StoryObj$1 } from "@storybook/react-vite";
4
+ import { IsStringLiteral } from "type-plus";
4
5
 
5
6
  //#region src/storybook-addon-tag-badges/tag_badges.d.ts
6
7
  type TagBadgeParameter = TagBadgeParameters[0];
7
8
  /**
8
9
  * Type representing the names of predefined tags used in Storybook stories.
9
10
  */
10
- type TagNames = 'editor' | 'new' | 'beta' | 'props' | 'deprecated' | 'outdated' | 'danger' | 'todo' | 'code-only' | 'snapshot' | 'unit' | 'integration' | 'keyboard' | 'internal' | 'usecase';
11
+ type TagNames = 'editor' | 'new' | 'beta' | 'props' | 'deprecated' | 'outdated' | 'danger' | 'todo' | 'code-only' | 'snapshot' | 'unit' | 'integration' | 'keyboard' | 'internal' | 'usecase' | 'version:next';
11
12
  /**
12
13
  * Configuration for story tag badges that appear in the Storybook sidebar.
13
14
  * Each badge is associated with a specific tag and displays an emoji with a tooltip.
@@ -44,32 +45,14 @@ declare const internalBadge: TagBadgeParameter;
44
45
  declare const tagBadges: TagBadgeParameters;
45
46
  //#endregion
46
47
  //#region src/types.d.ts
47
- /**
48
- * Extends the Storybook Meta type with custom tag types
49
- * @template TCmpOrArgs - The component or args type
50
- * @template M - The base Meta type
51
- * @template E - The extension type containing tagType
52
- *
53
- * @example
54
- * ```ts
55
- * // Create a generic Meta type for a project
56
- * type Meta<TCmpOrArgs = Args> = ExtendMeta<TCmpOrArgs, Meta<TCmpOrArgs>, { tagType: 'tag1' | 'tag2' }>
57
- *
58
- * // Create a specific Meta type for a component
59
- * type Meta = ExtendMeta<typeof Component, Meta<typeof Component>, { tagType: 'tag1' | 'tag2' }>
60
- * ```
61
- */
62
- type ExtendMeta<TCmpOrArgs, M extends Meta$1<TCmpOrArgs>, E extends {
63
- tag: string;
64
- }> = Omit<M, 'tags'> & {
65
- tags?: Array<E['tag'] | (string & {})> | undefined;
66
- };
67
48
  /**
68
49
  * Extends the Storybook StoryObj type with custom tag types
69
50
  * @template TMetaOrCmpOrArgs - The meta, component or args type
70
51
  * @template S - The base StoryObj type
71
52
  * @template E - The extension type containing tagType
72
53
  *
54
+ * @deprecated use `import { ExtendsStoryObj } from '@repobuddy/storybook'` instead.
55
+ *
73
56
  * @example
74
57
  * ```ts
75
58
  * // Create a generic StoryObj type for a project
@@ -85,8 +68,47 @@ type ExtendStoryObj<TMetaOrCmpOrArgs, S extends StoryObj$1<TMetaOrCmpOrArgs>, E
85
68
  tags?: Array<E['tag'] | (string & {})> | undefined;
86
69
  };
87
70
  //#endregion
71
+ //#region src/types/_extract_string_literals.d.ts
72
+ type ExtractStringLiterals<T> = T extends any ? (string extends T ? never : T) : never;
73
+ //#endregion
74
+ //#region src/types/extends_meta.d.ts
75
+ /**
76
+ * Extends the Storybook Meta type with custom tag types.
77
+ *
78
+ * This utility type allows you to extend the `tags` property of a Storybook Meta type
79
+ * with custom string literal types while preserving existing tag types from the base Meta.
80
+ *
81
+ * @template M - The base Meta type to extend
82
+ * @template E - The extension type containing a `tag` property with the custom tag types
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * import type { ExtendsMeta } from '@repobuddy/storybook'
87
+ * import type { Args, Meta as M } from '@storybook/your-framework'
88
+ *
89
+ * // Create a generic Meta type for your project
90
+ * type Meta<TCmpOrArgs = Args> = ExtendsMeta<
91
+ * M<TCmpOrArgs>,
92
+ * { tag: 'new' | 'beta' | 'deprecated' }
93
+ * >
94
+ *
95
+ * // Use in component stories
96
+ * const meta: Meta<typeof Component> = {
97
+ * tags: ['new'], // <--- gets auto-completion for 'new' | 'beta' | 'deprecated'
98
+ * // ...
99
+ * }
100
+ * ```
101
+ */
102
+ type ExtendsMeta<M extends {
103
+ tags?: string[] | undefined;
104
+ }, E extends {
105
+ tag: string;
106
+ }> = Omit<M, 'tags'> & {
107
+ tags?: ExtractStringLiterals<NonNullable<M['tags']>[number]> extends infer MT ? IsStringLiteral<MT> extends true ? Array<(string & {}) | MT | E['tag']> | undefined : Array<(string & {}) | E['tag']> | undefined : never;
108
+ };
109
+ //#endregion
88
110
  //#region src/storybook-addon-tag-badges/types.d.ts
89
- type Meta<TCmpOrArgs = Args> = ExtendMeta<TCmpOrArgs, Meta$1<TCmpOrArgs>, {
111
+ type Meta<TCmpOrArgs = Args> = ExtendsMeta<Meta$1<TCmpOrArgs>, {
90
112
  tag: TagNames;
91
113
  }>;
92
114
  type StoryObj<TMetaOrCmpOrArgs = Args> = ExtendStoryObj<TMetaOrCmpOrArgs, StoryObj$1<TMetaOrCmpOrArgs>, {
@@ -7,7 +7,6 @@ import { CSSProperties } from "@just-web/css";
7
7
  import { DecoratorFunction } from "storybook/internal/types";
8
8
 
9
9
  //#region src/storybook-dark-mode/dark_mode_docs_container.d.ts
10
-
11
10
  /**
12
11
  * Creates a `DocsContainer` for `storybook` that works with `@storybook-community/storybook-dark-mode`.
13
12
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@repobuddy/storybook",
3
- "version": "2.2.2",
3
+ "version": "2.4.0",
4
4
  "description": "Storybook repo buddy",
5
5
  "keywords": [
6
6
  "storybook",
@@ -50,7 +50,7 @@
50
50
  "!**/*.mdx"
51
51
  ],
52
52
  "dependencies": {
53
- "@just-web/css": "^0.7.0",
53
+ "@just-web/css": "^0.8.0",
54
54
  "@repobuddy/test": "^1.0.0",
55
55
  "class-variance-authority": "^0.7.1",
56
56
  "htmlfy": "^1.0.0",
@@ -76,7 +76,7 @@
76
76
  "storybook": "^10.1.10",
77
77
  "storybook-addon-tag-badges": "^3.0.2",
78
78
  "tailwindcss": "^4.1.17",
79
- "tsdown": "^0.18.0",
79
+ "tsdown": "^0.20.0",
80
80
  "vite": "^7.3.0",
81
81
  "vitest": "^4.0.16"
82
82
  },
@@ -0,0 +1,67 @@
1
+ import { cva } from 'class-variance-authority'
2
+ import type { ReactNode } from 'react'
3
+ import { twMerge } from 'tailwind-merge'
4
+
5
+ export type StoryCardProps = {
6
+ /**
7
+ * Optional title displayed as a heading in the card.
8
+ * Can be any React node (string, JSX, etc.).
9
+ */
10
+ title?: ReactNode | undefined
11
+ /**
12
+ * Visual status of the card, affecting its background color.
13
+ * - `'error'`: Red background (bg-red-100 dark:bg-red-900)
14
+ * - `'warn'`: Yellow background (bg-yellow-100 dark:bg-yellow-900)
15
+ * - `'info'`: Blue background (bg-sky-100 dark:bg-sky-900) - default
16
+ */
17
+ status?: 'error' | 'warn' | 'info' | undefined
18
+ /**
19
+ * Additional CSS classes or a function to compute classes.
20
+ *
21
+ * If a string is provided, it will be merged with the default classes.
22
+ * If a function is provided, it receives the card state and default className,
23
+ * and should return the final className string.
24
+ */
25
+ className?: ((state: Pick<StoryCardProps, 'status'> & { defaultClassName: string }) => string) | string | undefined
26
+ /**
27
+ * Content to display in the card body.
28
+ * Can be any React node (string, JSX, etc.).
29
+ */
30
+ children?: ReactNode | undefined
31
+ }
32
+
33
+ function storyCardTheme(state: Pick<StoryCardProps, 'status'>, className: StoryCardProps['className']) {
34
+ const defaultClassName = storyCardVariants(state)
35
+ if (!className) return defaultClassName
36
+ return typeof className === 'function'
37
+ ? className({ ...state, defaultClassName })
38
+ : twMerge(defaultClassName, className)
39
+ }
40
+
41
+ const storyCardVariants = cva('flex flex-col gap-1 py-3 px-4 rounded text-black dark:text-gray-100', {
42
+ variants: {
43
+ status: {
44
+ error: 'bg-red-100 dark:bg-red-900',
45
+ warn: 'bg-yellow-100 dark:bg-yellow-900',
46
+ info: 'bg-sky-100 dark:bg-sky-900'
47
+ }
48
+ },
49
+ defaultVariants: {
50
+ status: 'info'
51
+ }
52
+ })
53
+
54
+ /**
55
+ * A card component that displays information with optional title and status styling.
56
+ *
57
+ * @param props - StoryCard component props
58
+ * @returns A section element containing the card content
59
+ */
60
+ export function StoryCard({ status, className, children, title }: StoryCardProps) {
61
+ return (
62
+ <section className={storyCardTheme({ status }, className)}>
63
+ {title && <h2 className="text-lg font-bold">{title}</h2>}
64
+ {children}
65
+ </section>
66
+ )
67
+ }
@@ -3,6 +3,7 @@ import { SyntaxHighlighter } from 'storybook/internal/components'
3
3
  import type { Args, DecoratorFunction, Renderer } from 'storybook/internal/csf'
4
4
  import { addons } from 'storybook/preview-api'
5
5
  import { convert, ThemeProvider, themes } from 'storybook/theming'
6
+ import { StoryCard } from '../components/story_card'
6
7
 
7
8
  const channel = addons.getChannel()
8
9
 
@@ -10,10 +11,9 @@ const channel = addons.getChannel()
10
11
  * A decorator that shows the source code of a story above the rendered story.
11
12
  * The source code is taken from the story's `parameters.docs.source.code`.
12
13
  */
13
- export function showDocSource<TRenderer extends Renderer = Renderer, TArgs = Args>(): DecoratorFunction<
14
- TRenderer,
15
- TArgs
16
- > {
14
+ export function showDocSource<TRenderer extends Renderer = Renderer, TArgs = Args>(options?: {
15
+ showOriginalSource?: boolean | undefined
16
+ }): DecoratorFunction<TRenderer, TArgs> {
17
17
  return (Story, { parameters: { docs, darkMode } }) => {
18
18
  // This is a workaround to get the current dark mode from `@storybook-community/storybook-dark-mode` without referencing it.
19
19
  const storedItem = window.localStorage.getItem('sb-addon-themes-3')
@@ -26,6 +26,16 @@ export function showDocSource<TRenderer extends Renderer = Renderer, TArgs = Arg
26
26
  return () => channel.off('DARK_MODE', setIsDark)
27
27
  }, [])
28
28
 
29
+ const code = options?.showOriginalSource
30
+ ? docs?.source?.originalSource
31
+ : (docs?.source?.code ?? docs?.source?.originalSource)
32
+
33
+ const language = code === docs?.source?.originalSource ? undefined : docs?.source?.language
34
+
35
+ const isOriginalSource = code === docs?.source?.originalSource
36
+
37
+ const content = <SyntaxHighlighter language={language}>{code}</SyntaxHighlighter>
38
+
29
39
  return (
30
40
  <ThemeProvider theme={convert(docs?.theme ?? (isDark ? themes.dark : themes.light))}>
31
41
  <section
@@ -35,8 +45,8 @@ export function showDocSource<TRenderer extends Renderer = Renderer, TArgs = Arg
35
45
  gap: '1rem'
36
46
  }}
37
47
  >
38
- <SyntaxHighlighter language={docs?.source?.language}>{docs?.source?.code}</SyntaxHighlighter>
39
48
  <Story />
49
+ {isOriginalSource ? <StoryCard className="bg-gray-100 dark:bg-gray-900">{content}</StoryCard> : content}
40
50
  </section>
41
51
  </ThemeProvider>
42
52
  )
@@ -1,4 +1,3 @@
1
- import { cva } from 'class-variance-authority'
2
1
  import {
3
2
  createContext,
4
3
  useContext,
@@ -10,23 +9,11 @@ import {
10
9
  type ReactNode
11
10
  } from 'react'
12
11
  import type { DecoratorFunction, Renderer } from 'storybook/internal/csf'
13
- import { twMerge } from 'tailwind-merge'
14
12
  import type { RequiredPick } from 'type-plus'
13
+ import { StoryCard, type StoryCardProps } from '../components/story_card.js'
15
14
  import { generateKey } from '../utils/generate_key.js'
16
15
 
17
- export type StoryCardProps = {
18
- /**
19
- * Optional title displayed as a heading in the card.
20
- * Can be any React node (string, JSX, etc.).
21
- */
22
- title?: ReactNode | undefined
23
- /**
24
- * Visual status of the card, affecting its background color.
25
- * - `'error'`: Red background (bg-red-100 dark:bg-red-900)
26
- * - `'warn'`: Yellow background (bg-yellow-100 dark:bg-yellow-900)
27
- * - `'info'`: Blue background (bg-sky-100 dark:bg-sky-900) - default
28
- */
29
- status?: 'error' | 'warn' | 'info' | undefined
16
+ export type WithStoryCardProps = Omit<StoryCardProps, 'children' | 'className'> & {
30
17
  /**
31
18
  * Additional CSS classes or a function to compute classes.
32
19
  *
@@ -34,10 +21,7 @@ export type StoryCardProps = {
34
21
  * If a function is provided, it receives the card state and default className,
35
22
  * and should return the final className string.
36
23
  */
37
- className?:
38
- | ((state: Required<Pick<StoryCardProps, 'status'>> & { defaultClassName: string }) => string)
39
- | string
40
- | undefined
24
+ className?: ((state: Pick<StoryCardProps, 'status'> & { defaultClassName: string }) => string) | string | undefined
41
25
  /**
42
26
  * Content to display in the card body.
43
27
  * Can be any React node (string, JSX, etc.).
@@ -120,7 +104,7 @@ export function withStoryCard<TRenderer extends Renderer = Renderer>({
120
104
  status = 'info',
121
105
  content: contentProp,
122
106
  ...rest
123
- }: StoryCardProps = {}): DecoratorFunction<TRenderer> {
107
+ }: WithStoryCardProps = {}): DecoratorFunction<TRenderer> {
124
108
  return (Story, { parameters, viewMode }) => {
125
109
  if (viewMode === 'docs') return <Story />
126
110
 
@@ -131,7 +115,7 @@ export function withStoryCard<TRenderer extends Renderer = Renderer>({
131
115
  }
132
116
  }
133
117
 
134
- interface StoryCardContainerWrapperProps extends RequiredPick<StoryCardProps, 'status'> {
118
+ interface StoryCardContainerWrapperProps extends RequiredPick<WithStoryCardProps, 'status'> {
135
119
  Story: ComponentType
136
120
  }
137
121
 
@@ -166,11 +150,10 @@ function StoryCardContainer({ children }: { children: ReactNode }) {
166
150
  return (
167
151
  <StoryCardContext.Provider value={contextValue}>
168
152
  <div className="flex flex-col gap-2">
169
- {cards.map(({ key, status, className, content, title }) => (
170
- <section key={key} className={storyCardTheme({ status }, className)}>
171
- {title && <h2 className="text-lg font-bold">{title}</h2>}
153
+ {cards.map(({ content, key, ...rest }) => (
154
+ <StoryCard key={key} {...rest}>
172
155
  {content}
173
- </section>
156
+ </StoryCard>
174
157
  ))}
175
158
  {children}
176
159
  </div>
@@ -178,27 +161,9 @@ function StoryCardContainer({ children }: { children: ReactNode }) {
178
161
  )
179
162
  }
180
163
 
181
- type StoryCardWithKey = RequiredPick<StoryCardProps, 'status'> & { key: string }
182
-
183
- function storyCardTheme(state: Required<Pick<StoryCardProps, 'status'>>, className: StoryCardProps['className']) {
184
- const defaultClassName = storyCardVariants(state)
185
- if (!className) return defaultClassName
186
- return typeof className === 'function'
187
- ? className({ ...state, defaultClassName })
188
- : twMerge(defaultClassName, className)
189
- }
190
-
191
- const storyCardVariants = cva('flex flex-col gap-1 py-3 px-4 rounded text-black dark:text-gray-100', {
192
- variants: {
193
- status: {
194
- error: 'bg-red-100 dark:bg-red-900',
195
- warn: 'bg-yellow-100 dark:bg-yellow-900',
196
- info: 'bg-sky-100 dark:bg-sky-900'
197
- }
198
- }
199
- })
164
+ type StoryCardWithKey = RequiredPick<WithStoryCardProps, 'status'> & { key: string }
200
165
 
201
- interface StoryCardCollectorProps extends RequiredPick<StoryCardProps, 'status'> {
166
+ interface StoryCardCollectorProps extends RequiredPick<WithStoryCardProps, 'status'> {
202
167
  Story: ComponentType
203
168
  }
204
169
 
@@ -226,7 +191,7 @@ function StoryCardCollector({ Story, title, status, className, content }: StoryC
226
191
  }
227
192
 
228
193
  interface StoryCardContextValue {
229
- addCard: (card: RequiredPick<StoryCardProps, 'status'>) => string
194
+ addCard: (card: RequiredPick<WithStoryCardProps, 'status'>) => string
230
195
  removeCard: (id: string) => void
231
196
  }
232
197
 
package/src/index.ts CHANGED
@@ -12,3 +12,5 @@ export * from './parameters/define_viewport_param.ts'
12
12
  export * from './parameters/story_sort.ts'
13
13
  export * from './testing/decorators/when_running_in_test.tsx'
14
14
  export type * from './types.ts'
15
+ export * from './types/extends_meta.ts'
16
+ export * from './types/extends_story_obj.ts'
@@ -23,6 +23,7 @@ export type TagNames =
23
23
  | 'keyboard'
24
24
  | 'internal'
25
25
  | 'usecase'
26
+ | 'version:next'
26
27
 
27
28
  /**
28
29
  * Configuration for story tag badges that appear in the Storybook sidebar.
@@ -1,8 +1,9 @@
1
1
  import type { Args, Meta as M, StoryObj as SBO } from '@storybook/react-vite'
2
- import type { ExtendMeta, ExtendStoryObj } from '../types.js'
2
+ import type { ExtendStoryObj } from '../types.js'
3
+ import type { ExtendsMeta } from '../types/extends_meta.js'
3
4
  import type { TagNames } from './tag_badges.js'
4
5
 
5
- export type Meta<TCmpOrArgs = Args> = ExtendMeta<TCmpOrArgs, M<TCmpOrArgs>, { tag: TagNames }>
6
+ export type Meta<TCmpOrArgs = Args> = ExtendsMeta<M<TCmpOrArgs>, { tag: TagNames }>
6
7
 
7
8
  export type StoryObj<TMetaOrCmpOrArgs = Args> = ExtendStoryObj<
8
9
  TMetaOrCmpOrArgs,
@@ -0,0 +1 @@
1
+ export type ExtractStringLiterals<T> = T extends any ? (string extends T ? never : T) : never
@@ -0,0 +1,37 @@
1
+ import type { IsStringLiteral } from 'type-plus'
2
+ import type { ExtractStringLiterals } from './_extract_string_literals.js'
3
+
4
+ /**
5
+ * Extends the Storybook Meta type with custom tag types.
6
+ *
7
+ * This utility type allows you to extend the `tags` property of a Storybook Meta type
8
+ * with custom string literal types while preserving existing tag types from the base Meta.
9
+ *
10
+ * @template M - The base Meta type to extend
11
+ * @template E - The extension type containing a `tag` property with the custom tag types
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * import type { ExtendsMeta } from '@repobuddy/storybook'
16
+ * import type { Args, Meta as M } from '@storybook/your-framework'
17
+ *
18
+ * // Create a generic Meta type for your project
19
+ * type Meta<TCmpOrArgs = Args> = ExtendsMeta<
20
+ * M<TCmpOrArgs>,
21
+ * { tag: 'new' | 'beta' | 'deprecated' }
22
+ * >
23
+ *
24
+ * // Use in component stories
25
+ * const meta: Meta<typeof Component> = {
26
+ * tags: ['new'], // <--- gets auto-completion for 'new' | 'beta' | 'deprecated'
27
+ * // ...
28
+ * }
29
+ * ```
30
+ */
31
+ export type ExtendsMeta<M extends { tags?: string[] | undefined }, E extends { tag: string }> = Omit<M, 'tags'> & {
32
+ tags?: ExtractStringLiterals<NonNullable<M['tags']>[number]> extends infer MT
33
+ ? IsStringLiteral<MT> extends true
34
+ ? Array<(string & {}) | MT | E['tag']> | undefined
35
+ : Array<(string & {}) | E['tag']> | undefined
36
+ : never
37
+ }
@@ -0,0 +1,42 @@
1
+ import type { IsStringLiteral } from 'type-plus'
2
+ import type { ExtractStringLiterals } from './_extract_string_literals.js'
3
+
4
+ /**
5
+ * Extends the Storybook StoryObj type with custom tag types.
6
+ *
7
+ * This utility type allows you to extend the `tags` property of a Storybook StoryObj type
8
+ * with custom string literal types while preserving existing tag types from the base StoryObj.
9
+ *
10
+ * @template S - The base StoryObj type to extend (must have an optional `tags` property)
11
+ * @template E - The extension type containing a `tag` property with the custom tag types
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * import type { ExtendsStoryObj } from '@repobuddy/storybook'
16
+ * import type { Args, StoryObj as S } from '@storybook/your-framework'
17
+ *
18
+ * // Create a generic StoryObj type for your project
19
+ * type StoryObj<TCmpOrArgs = Args> = ExtendsStoryObj<
20
+ * S<TCmpOrArgs>,
21
+ * { tag: 'new' | 'beta' | 'deprecated' }
22
+ * >
23
+ *
24
+ * // Use in component stories
25
+ * const story: StoryObj<typeof Component> = {
26
+ * tags: ['new'], // <--- gets auto-completion for 'new' | 'beta' | 'deprecated'
27
+ * // ...
28
+ * }
29
+ * ```
30
+ */
31
+ export type ExtendsStoryObj<
32
+ S extends { tags?: string[] | undefined },
33
+ E extends {
34
+ tag: string
35
+ }
36
+ > = Omit<S, 'tags'> & {
37
+ tags?: ExtractStringLiterals<NonNullable<S['tags']>[number]> extends infer MT
38
+ ? IsStringLiteral<MT> extends true
39
+ ? Array<(string & {}) | MT | E['tag']> | undefined
40
+ : Array<(string & {}) | E['tag']> | undefined
41
+ : never
42
+ }
package/src/types.ts CHANGED
@@ -6,6 +6,8 @@ import type { Meta, StoryObj } from '@storybook/react-vite'
6
6
  * @template M - The base Meta type
7
7
  * @template E - The extension type containing tagType
8
8
  *
9
+ * @deprecated use `import { ExtendsMeta } from '@repobuddy/storybook'` instead.
10
+ *
9
11
  * @example
10
12
  * ```ts
11
13
  * // Create a generic Meta type for a project
@@ -31,6 +33,8 @@ export type ExtendMeta<
31
33
  * @template S - The base StoryObj type
32
34
  * @template E - The extension type containing tagType
33
35
  *
36
+ * @deprecated use `import { ExtendsStoryObj } from '@repobuddy/storybook'` instead.
37
+ *
34
38
  * @example
35
39
  * ```ts
36
40
  * // Create a generic StoryObj type for a project
package/styles.css CHANGED
@@ -20,6 +20,7 @@
20
20
  --color-rose-900: oklch(41% 0.159 10.272);
21
21
  --color-gray-100: oklch(96.7% 0.003 264.542);
22
22
  --color-gray-500: oklch(55.1% 0.027 264.364);
23
+ --color-gray-900: oklch(21% 0.034 264.665);
23
24
  --color-black: #000;
24
25
  --color-white: #fff;
25
26
  --spacing: 0.25rem;
@@ -179,6 +180,11 @@
179
180
  background-color: var(--color-gray-500);
180
181
  }
181
182
  }
183
+ .dark\:bg-gray-900 {
184
+ &:where(.dark, .dark *) {
185
+ background-color: var(--color-gray-900);
186
+ }
187
+ }
182
188
  .dark\:bg-green-800 {
183
189
  &:where(.dark, .dark *) {
184
190
  background-color: var(--color-green-800);