@repobuddy/storybook 2.20.1 → 2.21.1

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
@@ -1,5 +1,6 @@
1
1
 
2
2
  import { UserConfig } from "htmlfy";
3
+ import * as react from "react";
3
4
  import { ReactNode } from "react";
4
5
  import * as react_jsx_runtime0 from "react/jsx-runtime";
5
6
  import { AnyFunction, CreateTuple, IsStringLiteral, Properties, Tail } from "type-plus";
@@ -8,7 +9,7 @@ import { Args, DecoratorFunction, Renderer } from "storybook/internal/csf";
8
9
  import { Decorator, Meta, StoryContext, StoryObj, StrictArgs } from "@storybook/react-vite";
9
10
  export * from "@repobuddy/test";
10
11
 
11
- //#region src/arg-types/fn-to-arg-types.d.ts
12
+ //#region src/arg-types/fn-to-arg.types.d.ts
12
13
  /**
13
14
  * Converts a function's parameter types to `Args` type for Storybook.
14
15
  * Each name maps to the parameter type at the same index in F.
@@ -94,13 +95,7 @@ type StoryCardThemeState = Pick<StoryCardProps, 'status' | 'appearance'> & {
94
95
  * @param props - StoryCard component props
95
96
  * @returns A section element containing the card content
96
97
  */
97
- declare function StoryCard({
98
- status,
99
- appearance,
100
- className,
101
- children,
102
- title
103
- }: StoryCardProps): react_jsx_runtime0.JSX.Element;
98
+ declare const StoryCard: react.NamedExoticComponent<StoryCardProps>;
104
99
  //#endregion
105
100
  //#region src/decorators/show_doc_source.d.ts
106
101
  /**
package/esm/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
 
2
2
  import { isRunningInTest } from "@repobuddy/test";
3
3
  import { prettify } from "htmlfy";
4
- import { createContext, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
5
- import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { createContext, memo, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
5
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
6
  import { cva } from "class-variance-authority";
7
7
  import { twJoin, twMerge } from "tailwind-merge";
8
8
  import { SyntaxHighlighter } from "storybook/internal/components";
@@ -48,7 +48,7 @@ function storyCardTheme(state, className) {
48
48
  if (!className) return state.defaultClassName;
49
49
  return twMerge(typeof className === "function" ? className(state) : twJoin(state.defaultClassName, className));
50
50
  }
51
- const storyCardVariants = cva("rbsb:flex rbsb:flex-col rbsb:gap-1 rbsb:py-3 rbsb:px-4 rbsb:rounded rbsb:border rbsb:border-solid rbsb:text-black rbsb:dark:text-gray-100", {
51
+ const storyCardVariants = cva("rbsb:py-3 rbsb:px-4 rbsb:rounded rbsb:border rbsb:border-solid rbsb:text-black rbsb:dark:text-gray-100", {
52
52
  variants: { appearance: {
53
53
  error: "rbsb:bg-red-100 rbsb:dark:bg-red-900 rbsb:border-red-300 rbsb:dark:border-red-700",
54
54
  warn: "rbsb:bg-yellow-100 rbsb:dark:bg-yellow-900 rbsb:border-yellow-300 rbsb:dark:border-yellow-700",
@@ -64,7 +64,7 @@ const storyCardVariants = cva("rbsb:flex rbsb:flex-col rbsb:gap-1 rbsb:py-3 rbsb
64
64
  * @param props - StoryCard component props
65
65
  * @returns A section element containing the card content
66
66
  */
67
- function StoryCard({ status, appearance, className, children, title }) {
67
+ const StoryCard = memo(function StoryCard({ status, appearance, className, children, title }) {
68
68
  const resolvedAppearance = resolveAppearance(appearance, status);
69
69
  return /* @__PURE__ */ jsxs("section", {
70
70
  className: storyCardTheme({
@@ -77,7 +77,7 @@ function StoryCard({ status, appearance, className, children, title }) {
77
77
  children: title
78
78
  }), children]
79
79
  });
80
- }
80
+ });
81
81
 
82
82
  //#endregion
83
83
  //#region src/utils/generate_key.ts
@@ -102,15 +102,23 @@ const StoryCardRegistryContext = createContext(null);
102
102
  * Ensures a story-card collection scope: creates the root container when no context exists,
103
103
  * otherwise renders the collector so this card participates in the existing scope.
104
104
  */
105
- function StoryCardScope({ Story, ...props }) {
105
+ const StoryCardScope = memo(function StoryCardScope(props) {
106
106
  const context = useContext(StoryCardRegistryContext);
107
- const collector = /* @__PURE__ */ jsx(StoryCardCollector, {
108
- Story,
109
- ...props
110
- });
107
+ const collector = /* @__PURE__ */ jsx(StoryCardCollector, { ...props });
111
108
  if (context === null) return /* @__PURE__ */ jsx(StoryCardContainer, { children: collector });
112
109
  return collector;
113
- }
110
+ });
111
+ /** Renders cards from registry state without re-rendering when only children change (avoids cascade). */
112
+ const StoryCardList = memo(function StoryCardList({ cards }) {
113
+ return /* @__PURE__ */ jsx(Fragment, { children: cards.map(({ content, key, ...rest }) => /* @__PURE__ */ jsx(StoryCard, {
114
+ ...rest,
115
+ children: content
116
+ }, key)) });
117
+ });
118
+ /** Keeps container children from re-rendering when container state (cards) updates. */
119
+ const StableScopeChildren = memo(function StableScopeChildren({ children }) {
120
+ return /* @__PURE__ */ jsx(Fragment, { children });
121
+ });
114
122
  function StoryCardContainer({ children }) {
115
123
  const [cards, setCards] = useState([]);
116
124
  const contextValue = useMemo(() => ({
@@ -124,39 +132,55 @@ function StoryCardContainer({ children }) {
124
132
  },
125
133
  remove(key) {
126
134
  setCards((cards) => cards.filter((card) => card.key !== key));
135
+ },
136
+ update(key, card) {
137
+ setCards((cards) => cards.map((c) => c.key === key ? {
138
+ ...card,
139
+ key
140
+ } : c));
127
141
  }
128
142
  }), []);
129
143
  return /* @__PURE__ */ jsx(StoryCardRegistryContext.Provider, {
130
144
  value: contextValue,
131
145
  children: /* @__PURE__ */ jsxs("div", {
132
146
  className: "rbsb:flex rbsb:flex-col rbsb:gap-2",
133
- children: [cards.map(({ content, key, ...rest }) => /* @__PURE__ */ jsx(StoryCard, {
134
- ...rest,
135
- children: content
136
- }, key)), children]
147
+ children: [/* @__PURE__ */ jsx(StoryCardList, { cards }), /* @__PURE__ */ jsx(StableScopeChildren, { children })]
137
148
  })
138
149
  });
139
150
  }
140
- function StoryCardCollector({ Story, title, status, appearance, className, content }) {
151
+ function entryPropsEqual(a, b) {
152
+ return a.Story === b.Story && a.title === b.title && a.status === b.status && a.appearance === b.appearance && a.className === b.className && a.content === b.content;
153
+ }
154
+ const StoryCardCollector = memo(function StoryCardCollector({ Story, title, status, appearance, className, content }) {
141
155
  const context = useContext(StoryCardRegistryContext);
142
156
  const cardIdRef = useRef(null);
157
+ const entry = useMemo(() => ({
158
+ title,
159
+ status,
160
+ appearance,
161
+ className,
162
+ content
163
+ }), [
164
+ title,
165
+ status,
166
+ appearance,
167
+ className,
168
+ content
169
+ ]);
143
170
  useLayoutEffect(() => {
144
- if (cardIdRef.current === null) cardIdRef.current = context.add({
145
- title,
146
- status,
147
- appearance,
148
- className,
149
- content
150
- });
171
+ cardIdRef.current = context.add(entry);
151
172
  return () => {
152
173
  if (cardIdRef.current !== null) {
153
174
  context.remove(cardIdRef.current);
154
175
  cardIdRef.current = null;
155
176
  }
156
177
  };
157
- }, []);
178
+ }, [context]);
179
+ useLayoutEffect(() => {
180
+ if (cardIdRef.current !== null) context.update(cardIdRef.current, entry);
181
+ }, [context, entry]);
158
182
  return /* @__PURE__ */ jsx(Story, {});
159
- }
183
+ }, entryPropsEqual);
160
184
 
161
185
  //#endregion
162
186
  //#region src/decorators/show_doc_source.tsx
@@ -173,6 +197,7 @@ const channel = addons.getChannel();
173
197
  * @returns A decorator function that shows the source code of a story above or below the rendered story
174
198
  */
175
199
  function showDocSource(options) {
200
+ if (isRunningInTest()) return (Story) => /* @__PURE__ */ jsx(Story, {});
176
201
  return (Story, { parameters: { docs, darkMode } }) => {
177
202
  const storedItem = window.localStorage.getItem("sb-addon-themes-3");
178
203
  const current = typeof storedItem === "string" ? JSON.parse(storedItem).current : darkMode?.current;
@@ -185,28 +210,39 @@ function showDocSource(options) {
185
210
  const code = typeof options?.source === "function" ? options?.source(originalSource) : options?.source ?? originalSource;
186
211
  const language = code === docs?.source?.originalSource ? void 0 : docs?.source?.language;
187
212
  const isOriginalSource = code === docs?.source?.originalSource;
188
- const sourceContent = /* @__PURE__ */ jsx(SyntaxHighlighter, {
213
+ const sourceContent = useMemo(() => /* @__PURE__ */ jsx(SyntaxHighlighter, {
214
+ "data-testid": "source-content",
189
215
  language,
190
216
  children: code
191
- });
217
+ }), [code, language]);
192
218
  const showBefore = options?.placement === "before";
193
- const sourceCardClassName = (state) => {
219
+ const sourceCardClassName = useCallback((state) => {
194
220
  const modifiedState = {
195
221
  ...state,
196
222
  defaultClassName: twJoin(state.defaultClassName, isOriginalSource && "rbsb:bg-transparent rbsb:dark:bg-transparent")
197
223
  };
198
224
  const className = options?.className;
199
225
  return typeof className === "function" ? className(modifiedState) : twJoin(modifiedState.defaultClassName, className);
200
- };
226
+ }, [options?.className, isOriginalSource]);
201
227
  const theme = convert(docs?.theme ?? (isDark ? themes.dark : themes.light));
228
+ function DocSourceCard({ children }) {
229
+ return /* @__PURE__ */ jsx("div", { children });
230
+ }
231
+ const scopeContent = useMemo(() => /* @__PURE__ */ jsx(DocSourceCard, { children: sourceContent }), [sourceContent]);
232
+ const scopeProps = useMemo(() => ({
233
+ Story,
234
+ content: scopeContent,
235
+ className: sourceCardClassName,
236
+ appearance: "source"
237
+ }), [
238
+ Story,
239
+ scopeContent,
240
+ sourceCardClassName
241
+ ]);
242
+ if (isRunningInTest()) return /* @__PURE__ */ jsx(Story, {});
202
243
  if (showBefore) return /* @__PURE__ */ jsx(ThemeProvider, {
203
244
  theme,
204
- children: /* @__PURE__ */ jsx(StoryCardScope, {
205
- Story,
206
- content: sourceContent,
207
- className: sourceCardClassName,
208
- appearance: "source"
209
- })
245
+ children: /* @__PURE__ */ jsx(StoryCardScope, { ...scopeProps })
210
246
  });
211
247
  return /* @__PURE__ */ jsx(ThemeProvider, {
212
248
  theme,
@@ -219,7 +255,7 @@ function showDocSource(options) {
219
255
  children: [/* @__PURE__ */ jsx(Story, {}), /* @__PURE__ */ jsx(StoryCard, {
220
256
  className: sourceCardClassName,
221
257
  appearance: "source",
222
- children: sourceContent
258
+ children: /* @__PURE__ */ jsx(DocSourceCard, { children: sourceContent })
223
259
  })]
224
260
  })
225
261
  });
@@ -308,6 +344,7 @@ function showDocSource(options) {
308
344
  * - The `status` option is deprecated; use `appearance` instead for the same behavior and additional variants (`source`, `output`).
309
345
  */
310
346
  function withStoryCard({ title, status, appearance, content: contentProp, className, ...rest } = {}) {
347
+ if (isRunningInTest()) return (Story) => /* @__PURE__ */ jsx(Story, {});
311
348
  return (Story, { parameters, viewMode }) => {
312
349
  if (viewMode === "docs") return /* @__PURE__ */ jsx(Story, {});
313
350
  const storyCardParam = parameters.storyCard;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@repobuddy/storybook",
3
- "version": "2.20.1",
3
+ "version": "2.21.1",
4
4
  "description": "Storybook repo buddy",
5
5
  "keywords": [
6
6
  "storybook",
@@ -92,6 +92,7 @@
92
92
  "storybook": "^10.2.8",
93
93
  "storybook-addon-code-editor": "^6.1.3",
94
94
  "storybook-addon-tag-badges": "^3.0.6",
95
+ "storybook-addon-vis": "^3.1.2",
95
96
  "tailwindcss": "^4.1.17",
96
97
  "tsdown": "^0.20.0",
97
98
  "vite": "^7.3.0",
@@ -119,7 +120,7 @@
119
120
  "build:code": "tsdown",
120
121
  "build:css": "tailwindcss -i ./tailwind.css -o ./styles.css",
121
122
  "clean": "rimraf .turbo coverage cjs esm storybook-static *.tsbuildinfo",
122
- "coverage": "vitest run --coverage",
123
+ "cov": "vitest run --coverage",
123
124
  "sb": "storybook dev -p 6006",
124
125
  "sb:build": "storybook build",
125
126
  "test": "vitest run",
@@ -1,5 +1,5 @@
1
1
  import { cva } from 'class-variance-authority'
2
- import type { ReactNode } from 'react'
2
+ import { memo, type ReactNode } from 'react'
3
3
  import { twJoin, twMerge } from 'tailwind-merge'
4
4
 
5
5
  /**
@@ -67,7 +67,7 @@ function storyCardTheme(state: StoryCardThemeState, className: StoryCardProps['c
67
67
  }
68
68
 
69
69
  const storyCardVariants = cva(
70
- 'rbsb:flex rbsb:flex-col rbsb:gap-1 rbsb:py-3 rbsb:px-4 rbsb:rounded rbsb:border rbsb:border-solid rbsb:text-black rbsb:dark:text-gray-100',
70
+ 'rbsb:py-3 rbsb:px-4 rbsb:rounded rbsb:border rbsb:border-solid rbsb:text-black rbsb:dark:text-gray-100',
71
71
  {
72
72
  variants: {
73
73
  appearance: {
@@ -90,7 +90,7 @@ const storyCardVariants = cva(
90
90
  * @param props - StoryCard component props
91
91
  * @returns A section element containing the card content
92
92
  */
93
- export function StoryCard({ status, appearance, className, children, title }: StoryCardProps) {
93
+ export const StoryCard = memo(function StoryCard({ status, appearance, className, children, title }: StoryCardProps) {
94
94
  const resolvedAppearance = resolveAppearance(appearance, status)
95
95
  const state: StoryCardThemeState = {
96
96
  status,
@@ -103,4 +103,4 @@ export function StoryCard({ status, appearance, className, children, title }: St
103
103
  {children}
104
104
  </section>
105
105
  )
106
- }
106
+ })
@@ -11,6 +11,7 @@ export type StoryCardEntry = Omit<StoryCardProps, 'children'> & { content?: Reac
11
11
  export interface StoryCardRegistryContextValue {
12
12
  add: (card: StoryCardEntry) => string
13
13
  remove: (id: string) => void
14
+ update: (id: string, card: StoryCardEntry) => void
14
15
  }
15
16
 
16
17
  export const StoryCardRegistryContext = createContext<StoryCardRegistryContextValue | null>(null)
@@ -1,4 +1,4 @@
1
- import { type ComponentType, type ReactNode, useContext, useLayoutEffect, useMemo, useRef, useState } from 'react'
1
+ import { type ComponentType, memo, type ReactNode, useContext, useLayoutEffect, useMemo, useRef, useState } from 'react'
2
2
  import { StoryCard } from '../components/story_card.js'
3
3
  import { generateKey } from '../utils/generate_key.js'
4
4
  import {
@@ -13,16 +13,34 @@ export type StoryCardScopeProps = { Story: ComponentType } & StoryCardEntry
13
13
  * Ensures a story-card collection scope: creates the root container when no context exists,
14
14
  * otherwise renders the collector so this card participates in the existing scope.
15
15
  */
16
- export function StoryCardScope({ Story, ...props }: StoryCardScopeProps) {
16
+ export const StoryCardScope = memo(function StoryCardScope(props: StoryCardScopeProps) {
17
17
  const context = useContext(StoryCardRegistryContext)
18
- const collector = <StoryCardCollector Story={Story} {...props} />
18
+ const collector = <StoryCardCollector {...props} />
19
19
 
20
20
  if (context === null) {
21
21
  return <StoryCardContainer>{collector}</StoryCardContainer>
22
22
  }
23
23
 
24
24
  return collector
25
- }
25
+ })
26
+
27
+ /** Renders cards from registry state without re-rendering when only children change (avoids cascade). */
28
+ const StoryCardList = memo(function StoryCardList({ cards }: { cards: StoryCardEntryWithKey[] }) {
29
+ return (
30
+ <>
31
+ {cards.map(({ content, key, ...rest }) => (
32
+ <StoryCard key={key} {...rest}>
33
+ {content}
34
+ </StoryCard>
35
+ ))}
36
+ </>
37
+ )
38
+ })
39
+
40
+ /** Keeps container children from re-rendering when container state (cards) updates. */
41
+ const StableScopeChildren = memo(function StableScopeChildren({ children }: { children: ReactNode }) {
42
+ return <>{children}</>
43
+ })
26
44
 
27
45
  function StoryCardContainer({ children }: { children: ReactNode }) {
28
46
  const [cards, setCards] = useState<StoryCardEntryWithKey[]>([])
@@ -36,6 +54,9 @@ function StoryCardContainer({ children }: { children: ReactNode }) {
36
54
  },
37
55
  remove(key) {
38
56
  setCards((cards) => cards.filter((card) => card.key !== key))
57
+ },
58
+ update(key, card) {
59
+ setCards((cards) => cards.map((c) => (c.key === key ? { ...card, key } : c)))
39
60
  }
40
61
  }),
41
62
  []
@@ -44,12 +65,8 @@ function StoryCardContainer({ children }: { children: ReactNode }) {
44
65
  return (
45
66
  <StoryCardRegistryContext.Provider value={contextValue}>
46
67
  <div className="rbsb:flex rbsb:flex-col rbsb:gap-2">
47
- {cards.map(({ content, key, ...rest }) => (
48
- <StoryCard key={key} {...rest}>
49
- {content}
50
- </StoryCard>
51
- ))}
52
- {children}
68
+ <StoryCardList cards={cards} />
69
+ <StableScopeChildren>{children}</StableScopeChildren>
53
70
  </div>
54
71
  </StoryCardRegistryContext.Provider>
55
72
  )
@@ -59,25 +76,50 @@ type StoryCardEntryWithKey = StoryCardEntry & { key: string }
59
76
 
60
77
  interface StoryCardCollectorProps extends StoryCardScopeProps {}
61
78
 
62
- function StoryCardCollector({ Story, title, status, appearance, className, content }: StoryCardCollectorProps) {
63
- // StoryCardCollector is an internal component. Context is guaranteed to be not null by `StoryCardContainer`.
79
+ function entryPropsEqual(a: StoryCardCollectorProps, b: StoryCardCollectorProps): boolean {
80
+ return (
81
+ a.Story === b.Story &&
82
+ a.title === b.title &&
83
+ a.status === b.status &&
84
+ a.appearance === b.appearance &&
85
+ a.className === b.className &&
86
+ a.content === b.content
87
+ )
88
+ }
89
+
90
+ const StoryCardCollector = memo(function StoryCardCollector({
91
+ Story,
92
+ title,
93
+ status,
94
+ appearance,
95
+ className,
96
+ content
97
+ }: StoryCardCollectorProps) {
64
98
  const context = useContext(StoryCardRegistryContext)!
65
99
  const cardIdRef = useRef<string | null>(null)
66
100
 
67
- // Collect this card once into the collection
68
- useLayoutEffect(() => {
69
- // Only add if not already added (handles Strict Mode double-render)
70
- if (cardIdRef.current === null) {
71
- cardIdRef.current = context.add({ title, status, appearance, className, content })
72
- }
101
+ const entry = useMemo(
102
+ () => ({ title, status, appearance, className, content }),
103
+ [title, status, appearance, className, content]
104
+ )
73
105
 
106
+ // Register on mount, unregister on unmount only
107
+ useLayoutEffect(() => {
108
+ cardIdRef.current = context.add(entry)
74
109
  return () => {
75
110
  if (cardIdRef.current !== null) {
76
111
  context.remove(cardIdRef.current)
77
112
  cardIdRef.current = null
78
113
  }
79
114
  }
80
- }, [])
115
+ }, [context])
116
+
117
+ // Update registry when entry changes (avoids remove+add churn)
118
+ useLayoutEffect(() => {
119
+ if (cardIdRef.current !== null) {
120
+ context.update(cardIdRef.current, entry)
121
+ }
122
+ }, [context, entry])
81
123
 
82
124
  return <Story />
83
- }
125
+ }, entryPropsEqual)
@@ -1,4 +1,5 @@
1
- import { useEffect, useState } from 'react'
1
+ import { isRunningInTest } from '@repobuddy/test'
2
+ import { type ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
2
3
  import { SyntaxHighlighter } from 'storybook/internal/components'
3
4
  import type { Args, DecoratorFunction, Renderer } from 'storybook/internal/csf'
4
5
  import { addons } from 'storybook/preview-api'
@@ -32,6 +33,10 @@ export function showDocSource<TRenderer extends Renderer = Renderer, TArgs = Arg
32
33
  placement?: 'before' | 'after' | undefined
33
34
  }
34
35
  ): DecoratorFunction<TRenderer, TArgs> {
36
+ if (isRunningInTest()) {
37
+ return (Story) => <Story />
38
+ }
39
+
35
40
  return (Story, { parameters: { docs, darkMode } }) => {
36
41
  // This is a workaround to get the current dark mode from `@storybook-community/storybook-dark-mode` without referencing it.
37
42
  const storedItem = window.localStorage.getItem('sb-addon-themes-3')
@@ -55,40 +60,68 @@ export function showDocSource<TRenderer extends Renderer = Renderer, TArgs = Arg
55
60
 
56
61
  const isOriginalSource = code === docs?.source?.originalSource
57
62
 
58
- const sourceContent = <SyntaxHighlighter language={language}>{code}</SyntaxHighlighter>
63
+ const sourceContent = useMemo(
64
+ () => (
65
+ <SyntaxHighlighter data-testid="source-content" language={language}>
66
+ {code}
67
+ </SyntaxHighlighter>
68
+ ),
69
+ [code, language]
70
+ )
59
71
 
60
72
  const showBefore = options?.placement === 'before'
61
73
 
62
- const sourceCardClassName = (
63
- state: Pick<StoryCardProps, 'status' | 'appearance'> & { defaultClassName: string }
64
- ) => {
65
- const modifiedState = {
66
- ...state,
67
- defaultClassName: twJoin(
68
- state.defaultClassName,
69
- isOriginalSource && 'rbsb:bg-transparent rbsb:dark:bg-transparent'
70
- )
71
- }
72
-
73
- const className = options?.className
74
- return typeof className === 'function'
75
- ? className(modifiedState)
76
- : twJoin(modifiedState.defaultClassName, className)
77
- }
74
+ const sourceCardClassName = useCallback(
75
+ (state: Pick<StoryCardProps, 'status' | 'appearance'> & { defaultClassName: string }) => {
76
+ const modifiedState = {
77
+ ...state,
78
+ defaultClassName: twJoin(
79
+ state.defaultClassName,
80
+ isOriginalSource && 'rbsb:bg-transparent rbsb:dark:bg-transparent'
81
+ )
82
+ }
83
+
84
+ const className = options?.className
85
+ return typeof className === 'function'
86
+ ? className(modifiedState)
87
+ : twJoin(modifiedState.defaultClassName, className)
88
+ },
89
+ [options?.className, isOriginalSource]
90
+ )
78
91
 
79
92
  const theme = convert(docs?.theme ?? (isDark ? themes.dark : themes.light))
80
93
 
94
+ function DocSourceCard({ children }: { children: ReactNode }) {
95
+ return <div>{children}</div>
96
+ }
97
+
98
+ const scopeContent = useMemo(() => <DocSourceCard>{sourceContent}</DocSourceCard>, [sourceContent])
99
+
100
+ const scopeProps = useMemo(
101
+ () => ({
102
+ Story,
103
+ content: scopeContent,
104
+ className: sourceCardClassName,
105
+ appearance: 'source' as const
106
+ }),
107
+ [Story, scopeContent, sourceCardClassName]
108
+ )
109
+
110
+ if (isRunningInTest()) {
111
+ return <Story />
112
+ }
113
+
81
114
  if (showBefore) {
82
115
  return (
83
116
  <ThemeProvider theme={theme}>
84
- <StoryCardScope Story={Story} content={sourceContent} className={sourceCardClassName} appearance="source" />
117
+ <StoryCardScope {...scopeProps} />
85
118
  </ThemeProvider>
86
119
  )
87
120
  }
88
121
 
89
122
  const storyCard = (
90
123
  <StoryCard className={sourceCardClassName} appearance="source">
91
- {sourceContent}
124
+ <DocSourceCard>{sourceContent}</DocSourceCard>
92
125
  </StoryCard>
93
126
  )
94
127
 
@@ -1,3 +1,4 @@
1
+ import { isRunningInTest } from '@repobuddy/test'
1
2
  import type { ReactNode } from 'react'
2
3
  import type { DecoratorFunction, Renderer } from 'storybook/internal/csf'
3
4
  import type { StoryCardProps, StoryCardStatus } from '../components/story_card.js'
@@ -119,6 +120,9 @@ export function withStoryCard<TRenderer extends Renderer = Renderer>({
119
120
  className,
120
121
  ...rest
121
122
  }: WithStoryCardProps = {}): DecoratorFunction<TRenderer> {
123
+ if (isRunningInTest()) {
124
+ return (Story) => <Story />
125
+ }
122
126
  return (Story, { parameters, viewMode }) => {
123
127
  if (viewMode === 'docs') return <Story />
124
128
 
@@ -134,6 +138,7 @@ export function withStoryCard<TRenderer extends Renderer = Renderer>({
134
138
 
135
139
  // Fallback to docs description if no content provided
136
140
  const content = finalContent ?? parameters.docs?.description?.story ?? parameters.docs?.description?.component
141
+
137
142
  if (!content && !finalTitle) return <Story />
138
143
 
139
144
  return (
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export * from '@repobuddy/test'
2
- export type * from './arg-types/fn-to-arg-types.ts'
2
+ export type * from './arg-types/fn-to-arg.types.ts'
3
3
  export * from './components/show_html.tsx'
4
4
  export * from './components/story_card.tsx'
5
5
  export * from './decorators/show_doc_source.tsx'
package/styles.css CHANGED
@@ -1,4 +1,4 @@
1
- /*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
1
+ /*! tailwindcss v4.2.0 | MIT License | https://tailwindcss.com */
2
2
  @layer properties;
3
3
  @layer theme {
4
4
  :root, :host {
@@ -68,9 +68,6 @@
68
68
  .rbsb\:flex-col {
69
69
  flex-direction: column;
70
70
  }
71
- .rbsb\:gap-1 {
72
- gap: calc(var(--rbsb-spacing) * 1);
73
- }
74
71
  .rbsb\:gap-2 {
75
72
  gap: calc(var(--rbsb-spacing) * 2);
76
73
  }