@repobuddy/storybook 2.21.1 → 2.23.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
@@ -85,6 +85,10 @@ type StoryCardProps = {
85
85
  * Can be any React node (string, JSX, etc.).
86
86
  */
87
87
  children?: ReactNode | undefined;
88
+ /**
89
+ * Test identifier for the card.
90
+ */
91
+ 'data-testid'?: string | undefined;
88
92
  };
89
93
  type StoryCardThemeState = Pick<StoryCardProps, 'status' | 'appearance'> & {
90
94
  defaultClassName: string;
@@ -97,28 +101,49 @@ type StoryCardThemeState = Pick<StoryCardProps, 'status' | 'appearance'> & {
97
101
  */
98
102
  declare const StoryCard: react.NamedExoticComponent<StoryCardProps>;
99
103
  //#endregion
100
- //#region src/decorators/show_doc_source.d.ts
104
+ //#region src/decorators/show_source.d.ts
101
105
  /**
102
- * A decorator that shows the source code of a story above the rendered story.
106
+ * Options for the {@link showSource} decorator.
107
+ *
108
+ * @property className - Class name to apply to the source card. Can be a string or a function that receives the card state and returns a string.
109
+ * @property source - Source code to display. A string, or a function `(originalSource) => string` that receives the story's original source and returns the code to show. Defaults to the story's docs source.
110
+ * @property showOriginalSource - When true, use the story's original (untransformed) source instead of the rendered source.
111
+ * @property placement - Where to show the source code relative to the story. Defaults to `'before'`.
112
+ */
113
+ type ShowSourceOptions = Pick<StoryCardProps, 'className' | 'data-testid'> & {
114
+ source?: ((source: string | undefined) => string) | string | undefined;
115
+ showOriginalSource?: boolean | undefined;
116
+ placement?: 'before' | 'after' | undefined;
117
+ };
118
+ /**
119
+ * A decorator that shows the source code of a story relative to the rendered story.
103
120
  * The source code is taken from the story's `parameters.docs.source.code`.
104
121
  *
105
- * @param options - Options for the showDocSource decorator
122
+ * @param options - Options for the showSource decorator
106
123
  * @param options.showOriginalSource - Whether to show the original source code in a card
107
124
  * @param options.className - Class name to apply to the card
108
125
  * @param options.source - Source code to show. Can be a string, or a function `(originalSource) => string` that receives the story's original source and returns the code to display.
109
126
  * @param options.placement - Where to show the source code relative to the story.
110
127
  * @returns A decorator function that shows the source code of a story above or below the rendered story
111
128
  */
112
- declare function showDocSource<TRenderer extends Renderer = Renderer, TArgs = Args>(options?: Pick<StoryCardProps, 'className'> & {
113
- source?: ((source: string | undefined) => string) | string | undefined;
114
- showOriginalSource?: boolean | undefined;
115
- /**
116
- * Where to show the source code relative to the story.
117
- *
118
- * @default 'after'
119
- */
120
- placement?: 'before' | 'after' | undefined;
121
- }): DecoratorFunction<TRenderer, TArgs>;
129
+ declare function showSource<TRenderer extends Renderer = Renderer, TArgs = Args>({
130
+ className,
131
+ placement,
132
+ showOriginalSource,
133
+ source,
134
+ ...options
135
+ }?: ShowSourceOptions): DecoratorFunction<TRenderer, TArgs>;
136
+ //#endregion
137
+ //#region src/decorators/show_doc_source.d.ts
138
+ /**
139
+ * A decorator that shows the source code of a story below the rendered story.
140
+ * Uses {@link showSource} with `placement: 'after'`.
141
+ *
142
+ * @deprecated Use `showSource({ placement: 'after' })` instead.
143
+ * @param options - Same options as showSource; placement is forced to 'after'
144
+ * @returns A decorator function that shows the source code below the story
145
+ */
146
+ declare function showDocSource<TRenderer extends Renderer = Renderer, TArgs = Args>(options?: ShowSourceOptions): DecoratorFunction<TRenderer, TArgs>;
122
147
  //#endregion
123
148
  //#region src/decorators/with_story_card.d.ts
124
149
  type WithStoryCardProps = Omit<StoryCardProps, 'children' | 'className'> & {
@@ -935,4 +960,4 @@ type ExtendStoryObj<TMetaOrCmpOrArgs, S extends StoryObj<TMetaOrCmpOrArgs>, E ex
935
960
  tags?: Array<E['tag'] | (string & {})> | undefined;
936
961
  };
937
962
  //#endregion
938
- export { ActionsParam, BackgroundsParam, DocsParam, ExtendMeta, ExtendStoryObj, ExtendsMeta, ExtendsStoryObj, FnToArgTypes, GlobalApiBackgroundsParam, GlobalApiViewportParam, LayoutParam, ShowHtml, ShowHtmlProps, SourceProps, StoryCard, StoryCardAppearance, StoryCardParam, StoryCardProps, StoryCardStatus, StoryCardThemeState, StorySortParam, StorybookBuiltInParams, TestParam, Viewport, ViewportParam, WithStoryCardProps, defineActionsParam, defineBackgroundsParam, defineDocsParam, defineLayoutParam, defineParameters, defineStoryCardParam, defineTestParam, defineViewportParam, showDocSource, whenRunningInTest, withStoryCard };
963
+ export { ActionsParam, BackgroundsParam, DocsParam, ExtendMeta, ExtendStoryObj, ExtendsMeta, ExtendsStoryObj, FnToArgTypes, GlobalApiBackgroundsParam, GlobalApiViewportParam, LayoutParam, ShowHtml, ShowHtmlProps, ShowSourceOptions, SourceProps, StoryCard, StoryCardAppearance, StoryCardParam, StoryCardProps, StoryCardStatus, StoryCardThemeState, StorySortParam, StorybookBuiltInParams, TestParam, Viewport, ViewportParam, WithStoryCardProps, defineActionsParam, defineBackgroundsParam, defineDocsParam, defineLayoutParam, defineParameters, defineStoryCardParam, defineTestParam, defineViewportParam, showDocSource, showSource, whenRunningInTest, withStoryCard };
package/esm/index.js CHANGED
@@ -64,7 +64,7 @@ const storyCardVariants = cva("rbsb:py-3 rbsb:px-4 rbsb:rounded rbsb:border rbsb
64
64
  * @param props - StoryCard component props
65
65
  * @returns A section element containing the card content
66
66
  */
67
- const StoryCard = memo(function StoryCard({ status, appearance, className, children, title }) {
67
+ const StoryCard = memo(function StoryCard({ status, appearance, className, children, title, ...rest }) {
68
68
  const resolvedAppearance = resolveAppearance(appearance, status);
69
69
  return /* @__PURE__ */ jsxs("section", {
70
70
  className: storyCardTheme({
@@ -72,6 +72,7 @@ const StoryCard = memo(function StoryCard({ status, appearance, className, child
72
72
  appearance: resolvedAppearance,
73
73
  defaultClassName: storyCardVariants({ appearance: resolvedAppearance })
74
74
  }, className),
75
+ ...rest,
75
76
  children: [title && /* @__PURE__ */ jsx("h2", {
76
77
  className: "rbsb:text-lg rbsb:font-bold",
77
78
  children: title
@@ -183,20 +184,20 @@ const StoryCardCollector = memo(function StoryCardCollector({ Story, title, stat
183
184
  }, entryPropsEqual);
184
185
 
185
186
  //#endregion
186
- //#region src/decorators/show_doc_source.tsx
187
+ //#region src/decorators/show_source.tsx
187
188
  const channel = addons.getChannel();
188
189
  /**
189
- * A decorator that shows the source code of a story above the rendered story.
190
+ * A decorator that shows the source code of a story relative to the rendered story.
190
191
  * The source code is taken from the story's `parameters.docs.source.code`.
191
192
  *
192
- * @param options - Options for the showDocSource decorator
193
+ * @param options - Options for the showSource decorator
193
194
  * @param options.showOriginalSource - Whether to show the original source code in a card
194
195
  * @param options.className - Class name to apply to the card
195
196
  * @param options.source - Source code to show. Can be a string, or a function `(originalSource) => string` that receives the story's original source and returns the code to display.
196
197
  * @param options.placement - Where to show the source code relative to the story.
197
198
  * @returns A decorator function that shows the source code of a story above or below the rendered story
198
199
  */
199
- function showDocSource(options) {
200
+ function showSource({ className, placement, showOriginalSource, source, ...options } = {}) {
200
201
  if (isRunningInTest()) return (Story) => /* @__PURE__ */ jsx(Story, {});
201
202
  return (Story, { parameters: { docs, darkMode } }) => {
202
203
  const storedItem = window.localStorage.getItem("sb-addon-themes-3");
@@ -206,8 +207,8 @@ function showDocSource(options) {
206
207
  channel.on("DARK_MODE", setIsDark);
207
208
  return () => channel.off("DARK_MODE", setIsDark);
208
209
  }, []);
209
- const originalSource = options?.showOriginalSource ? docs?.source?.originalSource : docs?.source?.code ?? docs?.source?.originalSource;
210
- const code = typeof options?.source === "function" ? options?.source(originalSource) : options?.source ?? originalSource;
210
+ const originalSource = showOriginalSource ? docs?.source?.originalSource : docs?.source?.code ?? docs?.source?.originalSource;
211
+ const code = typeof source === "function" ? source(originalSource) : source ?? originalSource;
211
212
  const language = code === docs?.source?.originalSource ? void 0 : docs?.source?.language;
212
213
  const isOriginalSource = code === docs?.source?.originalSource;
213
214
  const sourceContent = useMemo(() => /* @__PURE__ */ jsx(SyntaxHighlighter, {
@@ -215,34 +216,33 @@ function showDocSource(options) {
215
216
  language,
216
217
  children: code
217
218
  }), [code, language]);
218
- const showBefore = options?.placement === "before";
219
+ const showBefore = (placement ?? "before") === "before";
219
220
  const sourceCardClassName = useCallback((state) => {
220
221
  const modifiedState = {
221
222
  ...state,
222
223
  defaultClassName: twJoin(state.defaultClassName, isOriginalSource && "rbsb:bg-transparent rbsb:dark:bg-transparent")
223
224
  };
224
- const className = options?.className;
225
225
  return typeof className === "function" ? className(modifiedState) : twJoin(modifiedState.defaultClassName, className);
226
- }, [options?.className, isOriginalSource]);
226
+ }, [className, isOriginalSource]);
227
227
  const theme = convert(docs?.theme ?? (isDark ? themes.dark : themes.light));
228
228
  function DocSourceCard({ children }) {
229
229
  return /* @__PURE__ */ jsx("div", { children });
230
230
  }
231
- const scopeContent = useMemo(() => /* @__PURE__ */ jsx(DocSourceCard, { children: sourceContent }), [sourceContent]);
232
- const scopeProps = useMemo(() => ({
231
+ if (isRunningInTest()) return /* @__PURE__ */ jsx(Story, {});
232
+ if (showBefore) return /* @__PURE__ */ jsx(StoryCardScope, {
233
233
  Story,
234
- content: scopeContent,
234
+ content: /* @__PURE__ */ jsx(ThemeProvider, {
235
+ theme,
236
+ children: /* @__PURE__ */ jsx(DocSourceCard, { children: sourceContent })
237
+ }),
235
238
  className: sourceCardClassName,
236
239
  appearance: "source"
237
- }), [
238
- Story,
239
- scopeContent,
240
- sourceCardClassName
241
- ]);
242
- if (isRunningInTest()) return /* @__PURE__ */ jsx(Story, {});
243
- if (showBefore) return /* @__PURE__ */ jsx(ThemeProvider, {
244
- theme,
245
- children: /* @__PURE__ */ jsx(StoryCardScope, { ...scopeProps })
240
+ });
241
+ const storyCard = /* @__PURE__ */ jsx(StoryCard, {
242
+ className: sourceCardClassName,
243
+ appearance: "source",
244
+ ...options,
245
+ children: /* @__PURE__ */ jsx(DocSourceCard, { children: sourceContent })
246
246
  });
247
247
  return /* @__PURE__ */ jsx(ThemeProvider, {
248
248
  theme,
@@ -252,16 +252,29 @@ function showDocSource(options) {
252
252
  flexDirection: "column",
253
253
  gap: "1rem"
254
254
  },
255
- children: [/* @__PURE__ */ jsx(Story, {}), /* @__PURE__ */ jsx(StoryCard, {
256
- className: sourceCardClassName,
257
- appearance: "source",
258
- children: /* @__PURE__ */ jsx(DocSourceCard, { children: sourceContent })
259
- })]
255
+ children: [/* @__PURE__ */ jsx(Story, {}), storyCard]
260
256
  })
261
257
  });
262
258
  };
263
259
  }
264
260
 
261
+ //#endregion
262
+ //#region src/decorators/show_doc_source.tsx
263
+ /**
264
+ * A decorator that shows the source code of a story below the rendered story.
265
+ * Uses {@link showSource} with `placement: 'after'`.
266
+ *
267
+ * @deprecated Use `showSource({ placement: 'after' })` instead.
268
+ * @param options - Same options as showSource; placement is forced to 'after'
269
+ * @returns A decorator function that shows the source code below the story
270
+ */
271
+ function showDocSource(options) {
272
+ return showSource({
273
+ placement: "after",
274
+ ...options
275
+ });
276
+ }
277
+
265
278
  //#endregion
266
279
  //#region src/decorators/with_story_card.tsx
267
280
  /**
@@ -596,4 +609,4 @@ function whenRunningInTest(decoratorOrHandler) {
596
609
  }
597
610
 
598
611
  //#endregion
599
- export { ShowHtml, StoryCard, defineActionsParam, defineBackgroundsParam, defineDocsParam, defineLayoutParam, defineParameters, defineStoryCardParam, defineTestParam, defineViewportParam, showDocSource, whenRunningInTest, withStoryCard };
612
+ export { ShowHtml, StoryCard, defineActionsParam, defineBackgroundsParam, defineDocsParam, defineLayoutParam, defineParameters, defineStoryCardParam, defineTestParam, defineViewportParam, showDocSource, showSource, whenRunningInTest, withStoryCard };
@@ -8,7 +8,7 @@ type TagBadgeParameter = TagBadgeParameters[0];
8
8
  /**
9
9
  * Type representing the names of predefined tags used in Storybook stories.
10
10
  */
11
- type TagNames = '!test' | 'editor' | 'source' | 'type' | '!type' | 'func' | '!func' | 'var' | '!var' | 'new' | 'alpha' | 'beta' | 'rc' | 'props' | 'deprecated' | 'outdated' | 'danger' | 'todo' | 'code-only' | 'snapshot' | '!snapshot' | 'unit' | 'integration' | 'keyboard' | 'internal' | 'usecase' | 'use-case' | 'example' | 'version:next' | 'remove' | 'remove:next' | 'autodocs';
11
+ type TagNames = '!test' | 'editor' | 'source' | 'type' | '!type' | 'func' | '!func' | 'var' | '!var' | 'new' | 'alpha' | 'beta' | 'rc' | 'props' | 'deprecated' | 'outdated' | 'danger' | 'todo' | 'code-only' | 'snapshot' | '!snapshot' | 'unit' | 'integration' | 'keyboard' | 'internal' | 'usecase' | 'use-case' | 'example' | 'perf' | 'version:next' | 'remove' | 'remove:next' | 'autodocs';
12
12
  /** Badge (✏️) for stories with a live editor. Shown in sidebar on story and inherited. */
13
13
  declare const editorBadge: TagBadgeParameter;
14
14
  /** Badge (🆕) for recently added stories. */
@@ -55,12 +55,14 @@ declare const internalBadge: TagBadgeParameter;
55
55
  declare const useCaseBadge: TagBadgeParameter;
56
56
  /** Badge (✨) for example or demo stories. */
57
57
  declare const exampleBadge: TagBadgeParameter;
58
+ /** Badge (⚡) for stories that demonstrate or test performance. */
59
+ declare const perfBadge: TagBadgeParameter;
58
60
  /**
59
61
  * Configuration for story tag badges that appear in the Storybook sidebar.
60
62
  * Each badge is associated with a specific tag and displays an emoji or symbol with a tooltip.
61
63
  *
62
64
  * Badge order (first match wins): New → Alpha → Beta → RC → Deprecated → Remove → Outdated → Danger → Use Case →
63
- * Example → Keyboard → Source → Type → Function → Var → Props → Todo → Unit → Integration →
65
+ * Example → Perf → Keyboard → Source → Type → Function → Var → Props → Todo → Unit → Integration →
64
66
  * Editor → Code Only → Version → Internal → Snapshot.
65
67
  *
66
68
  * - 🆕 New - Recently added stories
@@ -73,6 +75,7 @@ declare const exampleBadge: TagBadgeParameter;
73
75
  * - 🚨 Danger - Stories demonstrating dangerous patterns
74
76
  * - 🎯 Use Case - Stories that demonstrate a specific use case or scenario
75
77
  * - ✨ Example - Example or demo stories
78
+ * - ⚡ Perf - Stories that demonstrate or test performance
76
79
  * - ⌨️ Keyboard - Stories that demonstrate or test keyboard interaction
77
80
  * - `</>` Source - Source-code-focused stories
78
81
  * - `<T>` Type - Stories that showcase or document TypeScript types
@@ -162,4 +165,4 @@ type StoryObj<TMetaOrCmpOrArgs = Args> = ExtendStoryObj<TMetaOrCmpOrArgs, StoryO
162
165
  tag: TagNames;
163
166
  }>;
164
167
  //#endregion
165
- export { Meta, StoryObj, TagNames, alphaBadge, betaBadge, codeOnlyBadge, dangerBadge, deprecatedBadge, editorBadge, exampleBadge, functionBadge, integrationBadge, internalBadge, keyboardBadge, newBadge, outdatedBadge, propsBadge, rcBadge, removeBadge, snapshotBadge, sourceBadge, tagBadges, todoBadge, typeBadge, unitBadge, useCaseBadge, varBadge };
168
+ export { Meta, StoryObj, TagNames, alphaBadge, betaBadge, codeOnlyBadge, dangerBadge, deprecatedBadge, editorBadge, exampleBadge, functionBadge, integrationBadge, internalBadge, keyboardBadge, newBadge, outdatedBadge, perfBadge, propsBadge, rcBadge, removeBadge, snapshotBadge, sourceBadge, tagBadges, todoBadge, typeBadge, unitBadge, useCaseBadge, varBadge };
@@ -331,12 +331,28 @@ const exampleBadge = {
331
331
  skipInherited: false
332
332
  } }
333
333
  };
334
+ /** Badge (⚡) for stories that demonstrate or test performance. */
335
+ const perfBadge = {
336
+ tags: "perf",
337
+ badge: {
338
+ text: "⚡",
339
+ style: {
340
+ backgroundColor: "transparent",
341
+ borderColor: "transparent"
342
+ },
343
+ tooltip: "Performance"
344
+ },
345
+ display: { sidebar: {
346
+ type: "story",
347
+ skipInherited: false
348
+ } }
349
+ };
334
350
  /**
335
351
  * Configuration for story tag badges that appear in the Storybook sidebar.
336
352
  * Each badge is associated with a specific tag and displays an emoji or symbol with a tooltip.
337
353
  *
338
354
  * Badge order (first match wins): New → Alpha → Beta → RC → Deprecated → Remove → Outdated → Danger → Use Case →
339
- * Example → Keyboard → Source → Type → Function → Var → Props → Todo → Unit → Integration →
355
+ * Example → Perf → Keyboard → Source → Type → Function → Var → Props → Todo → Unit → Integration →
340
356
  * Editor → Code Only → Version → Internal → Snapshot.
341
357
  *
342
358
  * - 🆕 New - Recently added stories
@@ -349,6 +365,7 @@ const exampleBadge = {
349
365
  * - 🚨 Danger - Stories demonstrating dangerous patterns
350
366
  * - 🎯 Use Case - Stories that demonstrate a specific use case or scenario
351
367
  * - ✨ Example - Example or demo stories
368
+ * - ⚡ Perf - Stories that demonstrate or test performance
352
369
  * - ⌨️ Keyboard - Stories that demonstrate or test keyboard interaction
353
370
  * - `</>` Source - Source-code-focused stories
354
371
  * - `<T>` Type - Stories that showcase or document TypeScript types
@@ -376,6 +393,7 @@ const tagBadges = [
376
393
  dangerBadge,
377
394
  useCaseBadge,
378
395
  exampleBadge,
396
+ perfBadge,
379
397
  keyboardBadge,
380
398
  sourceBadge,
381
399
  typeBadge,
@@ -393,4 +411,4 @@ const tagBadges = [
393
411
  ];
394
412
 
395
413
  //#endregion
396
- export { alphaBadge, betaBadge, codeOnlyBadge, dangerBadge, deprecatedBadge, editorBadge, exampleBadge, functionBadge, integrationBadge, internalBadge, keyboardBadge, newBadge, outdatedBadge, propsBadge, rcBadge, removeBadge, snapshotBadge, sourceBadge, tagBadges, todoBadge, typeBadge, unitBadge, useCaseBadge, varBadge };
414
+ export { alphaBadge, betaBadge, codeOnlyBadge, dangerBadge, deprecatedBadge, editorBadge, exampleBadge, functionBadge, integrationBadge, internalBadge, keyboardBadge, newBadge, outdatedBadge, perfBadge, propsBadge, rcBadge, removeBadge, snapshotBadge, sourceBadge, tagBadges, todoBadge, typeBadge, unitBadge, useCaseBadge, varBadge };
package/package.json CHANGED
@@ -1,12 +1,21 @@
1
1
  {
2
2
  "name": "@repobuddy/storybook",
3
- "version": "2.21.1",
3
+ "version": "2.23.0",
4
4
  "description": "Storybook repo buddy",
5
5
  "keywords": [
6
6
  "storybook",
7
7
  "testing",
8
8
  "documentation"
9
9
  ],
10
+ "homepage": "https://github.com/repobuddy/storybook",
11
+ "bugs": {
12
+ "url": "https://github.com/repobuddy/storybook/issues"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/repobuddy/storybook.git",
17
+ "directory": "libs/storybook"
18
+ },
10
19
  "license": "MIT",
11
20
  "author": {
12
21
  "name": "Homa Wong",
package/readme.md CHANGED
@@ -78,30 +78,33 @@ addons.setConfig({
78
78
  If you use [`storybook-addon-tag-badges`][`storybook-addon-tag-badges`],
79
79
  we provide a different set of badges that uses emojis (order: first match wins):
80
80
 
81
- - 🆕 `new` - Recently added stories
82
- - 🔴 `alpha` - Features in alpha
83
- - 🟡 `beta` - Features in beta
84
- - 🔵 `rc` - Release candidate
85
- - 🗑️ `deprecated` - Deprecated features
86
- - ☠️ `remove` or `remove:next` (same) or `remove:<version>` - Will be removed in the next or specified version
87
- - ⚠️ `outdated` - Stories that need updating
88
- - 🚨 `danger` - Dangerous or cautionary patterns
89
- - 🎯 `use-case` - Specific use case or scenario
90
- - `example` - Example or demo stories
91
- - ⌨️ `keyboard` - Keyboard interaction
92
- - `</>` `source` - Source-code-focused stories
93
- - `<T>` `type` - TypeScript types (shown in MDX)
94
- - `ƒ(x)` `func` - Functions (shown in MDX)
95
- - `var` `var` - Variables (shown in MDX)
96
- - 🔧 `props` - Props or configuration
97
- - 📋 `todo` - Todo or incomplete stories
98
- - 🧪 `unit` - Unit tests
99
- - 🔗 `integration` - Integration tests (hidden in sidebar)
100
- - ✏️ `editor` - Live editor stories (with [`storybook-addon-code-editor`][`storybook-addon-code-editor`])
101
- - 📝 `code-only` - Stories without visual examples (hidden in MDX)
102
- - Version indicators (unchanged)
103
- - 🔒 `internal` - Internal or private-use-only stories
104
- - 📸 `snapshot` - Snapshot tests (toolbar only, not sidebar)
81
+ | Badge | Tag | Description |
82
+ | :----- | :------------------------------------------------------- | :---------------------------------------------------------------------------------------- |
83
+ | 🆕 | `new` | Recently added stories |
84
+ | 🔴 | `alpha` | Features in alpha |
85
+ | 🟡 | `beta` | Features in beta |
86
+ | 🔵 | `rc` | Release candidate |
87
+ | 🗑️ | `deprecated` | Deprecated features |
88
+ | ☠️ | `remove`<br/>`remove:next` (same)<br/>`remove:<version>` | Will be removed in the next or specified version |
89
+ | ⚠️ | `outdated` | Stories that need updating |
90
+ | 🚨 | `danger` | Dangerous or cautionary patterns |
91
+ | 🎯 | `use-case` | Specific use case or scenario |
92
+ | ✨ | `example` | Example or demo stories |
93
+ | ⚡ | `perf` | Performance (stories that demonstrate or test performance) |
94
+ | ⌨️ | `keyboard` | Keyboard interaction |
95
+ | `</>` | `source` | Source-code-focused stories |
96
+ | `<T>` | `type` | TypeScript types (shown in MDX) |
97
+ | `ƒ(x)` | `func` | Functions (shown in MDX) |
98
+ | `var` | `var` | Variables (shown in MDX) |
99
+ | 🔧 | `props` | Props or configuration |
100
+ | 📋 | `todo` | Todo or incomplete stories |
101
+ | 🧪 | `unit` | Unit tests |
102
+ | 🔗 | `integration` | Integration tests (hidden in sidebar) |
103
+ | ✏️ | `editor` | Live editor stories (with [`storybook-addon-code-editor`][`storybook-addon-code-editor`]) |
104
+ | 📝 | `code-only` | Stories without visual examples (hidden in MDX) |
105
+ | | Version indicators | (unchanged) |
106
+ | 🔒 | `internal` | Internal or private-use-only stories |
107
+ | 📸 | `snapshot` | Snapshot tests (toolbar only, not sidebar) |
105
108
 
106
109
  To use them, add them to your `.storybook/manager.ts`:
107
110
 
@@ -48,6 +48,10 @@ export type StoryCardProps = {
48
48
  * Can be any React node (string, JSX, etc.).
49
49
  */
50
50
  children?: ReactNode | undefined
51
+ /**
52
+ * Test identifier for the card.
53
+ */
54
+ 'data-testid'?: string | undefined
51
55
  }
52
56
 
53
57
  export type StoryCardThemeState = Pick<StoryCardProps, 'status' | 'appearance'> & { defaultClassName: string }
@@ -90,7 +94,14 @@ const storyCardVariants = cva(
90
94
  * @param props - StoryCard component props
91
95
  * @returns A section element containing the card content
92
96
  */
93
- export const StoryCard = memo(function StoryCard({ status, appearance, className, children, title }: StoryCardProps) {
97
+ export const StoryCard = memo(function StoryCard({
98
+ status,
99
+ appearance,
100
+ className,
101
+ children,
102
+ title,
103
+ ...rest
104
+ }: StoryCardProps) {
94
105
  const resolvedAppearance = resolveAppearance(appearance, status)
95
106
  const state: StoryCardThemeState = {
96
107
  status,
@@ -98,7 +109,7 @@ export const StoryCard = memo(function StoryCard({ status, appearance, className
98
109
  defaultClassName: storyCardVariants({ appearance: resolvedAppearance })
99
110
  }
100
111
  return (
101
- <section className={storyCardTheme(state, className)}>
112
+ <section className={storyCardTheme(state, className)} {...rest}>
102
113
  {title && <h2 className="rbsb:text-lg rbsb:font-bold">{title}</h2>}
103
114
  {children}
104
115
  </section>
@@ -1,143 +1,16 @@
1
- import { isRunningInTest } from '@repobuddy/test'
2
- import { type ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
3
- import { SyntaxHighlighter } from 'storybook/internal/components'
4
1
  import type { Args, DecoratorFunction, Renderer } from 'storybook/internal/csf'
5
- import { addons } from 'storybook/preview-api'
6
- import { convert, ThemeProvider, themes } from 'storybook/theming'
7
- import { twJoin } from 'tailwind-merge'
8
- import { StoryCard, type StoryCardProps } from '../components/story_card'
9
- import { StoryCardScope } from '../contexts/_story_card_scope'
10
-
11
- const channel = addons.getChannel()
2
+ import { type ShowSourceOptions, showSource } from './show_source'
12
3
 
13
4
  /**
14
- * A decorator that shows the source code of a story above the rendered story.
15
- * The source code is taken from the story's `parameters.docs.source.code`.
5
+ * A decorator that shows the source code of a story below the rendered story.
6
+ * Uses {@link showSource} with `placement: 'after'`.
16
7
  *
17
- * @param options - Options for the showDocSource decorator
18
- * @param options.showOriginalSource - Whether to show the original source code in a card
19
- * @param options.className - Class name to apply to the card
20
- * @param options.source - Source code to show. Can be a string, or a function `(originalSource) => string` that receives the story's original source and returns the code to display.
21
- * @param options.placement - Where to show the source code relative to the story.
22
- * @returns A decorator function that shows the source code of a story above or below the rendered story
8
+ * @deprecated Use `showSource({ placement: 'after' })` instead.
9
+ * @param options - Same options as showSource; placement is forced to 'after'
10
+ * @returns A decorator function that shows the source code below the story
23
11
  */
24
12
  export function showDocSource<TRenderer extends Renderer = Renderer, TArgs = Args>(
25
- options?: Pick<StoryCardProps, 'className'> & {
26
- source?: ((source: string | undefined) => string) | string | undefined
27
- showOriginalSource?: boolean | undefined
28
- /**
29
- * Where to show the source code relative to the story.
30
- *
31
- * @default 'after'
32
- */
33
- placement?: 'before' | 'after' | undefined
34
- }
13
+ options?: ShowSourceOptions
35
14
  ): DecoratorFunction<TRenderer, TArgs> {
36
- if (isRunningInTest()) {
37
- return (Story) => <Story />
38
- }
39
-
40
- return (Story, { parameters: { docs, darkMode } }) => {
41
- // This is a workaround to get the current dark mode from `@storybook-community/storybook-dark-mode` without referencing it.
42
- const storedItem = window.localStorage.getItem('sb-addon-themes-3')
43
- const current = typeof storedItem === 'string' ? JSON.parse(storedItem).current : darkMode?.current
44
- const [isDark, setIsDark] = useState((darkMode?.stylePreview && current === 'dark') ?? false)
45
-
46
- useEffect(() => {
47
- channel.on('DARK_MODE', setIsDark)
48
-
49
- return () => channel.off('DARK_MODE', setIsDark)
50
- }, [])
51
-
52
- const originalSource = options?.showOriginalSource
53
- ? docs?.source?.originalSource
54
- : (docs?.source?.code ?? docs?.source?.originalSource)
55
-
56
- const code =
57
- typeof options?.source === 'function' ? options?.source(originalSource) : (options?.source ?? originalSource)
58
-
59
- const language = code === docs?.source?.originalSource ? undefined : docs?.source?.language
60
-
61
- const isOriginalSource = code === docs?.source?.originalSource
62
-
63
- const sourceContent = useMemo(
64
- () => (
65
- <SyntaxHighlighter data-testid="source-content" language={language}>
66
- {code}
67
- </SyntaxHighlighter>
68
- ),
69
- [code, language]
70
- )
71
-
72
- const showBefore = options?.placement === 'before'
73
-
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
- )
91
-
92
- const theme = convert(docs?.theme ?? (isDark ? themes.dark : themes.light))
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
-
114
- if (showBefore) {
115
- return (
116
- <ThemeProvider theme={theme}>
117
- <StoryCardScope {...scopeProps} />
118
- </ThemeProvider>
119
- )
120
- }
121
-
122
- const storyCard = (
123
- <StoryCard className={sourceCardClassName} appearance="source">
124
- <DocSourceCard>{sourceContent}</DocSourceCard>
125
- </StoryCard>
126
- )
127
-
128
- return (
129
- <ThemeProvider theme={theme}>
130
- <section
131
- style={{
132
- display: 'flex',
133
- flexDirection: 'column',
134
- gap: '1rem'
135
- }}
136
- >
137
- <Story />
138
- {storyCard}
139
- </section>
140
- </ThemeProvider>
141
- )
142
- }
15
+ return showSource({ placement: 'after', ...options })
143
16
  }
@@ -0,0 +1,141 @@
1
+ import { isRunningInTest } from '@repobuddy/test'
2
+ import { type ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
3
+ import { SyntaxHighlighter } from 'storybook/internal/components'
4
+ import type { Args, DecoratorFunction, Renderer } from 'storybook/internal/csf'
5
+ import { addons } from 'storybook/preview-api'
6
+ import { convert, ThemeProvider, themes } from 'storybook/theming'
7
+ import { twJoin } from 'tailwind-merge'
8
+ import { StoryCard, type StoryCardProps } from '../components/story_card'
9
+ import { StoryCardScope } from '../contexts/_story_card_scope'
10
+
11
+ const channel = addons.getChannel()
12
+
13
+ /**
14
+ * Options for the {@link showSource} decorator.
15
+ *
16
+ * @property className - Class name to apply to the source card. Can be a string or a function that receives the card state and returns a string.
17
+ * @property source - Source code to display. A string, or a function `(originalSource) => string` that receives the story's original source and returns the code to show. Defaults to the story's docs source.
18
+ * @property showOriginalSource - When true, use the story's original (untransformed) source instead of the rendered source.
19
+ * @property placement - Where to show the source code relative to the story. Defaults to `'before'`.
20
+ */
21
+ export type ShowSourceOptions = Pick<StoryCardProps, 'className' | 'data-testid'> & {
22
+ source?: ((source: string | undefined) => string) | string | undefined
23
+ showOriginalSource?: boolean | undefined
24
+ placement?: 'before' | 'after' | undefined
25
+ }
26
+
27
+ /**
28
+ * A decorator that shows the source code of a story relative to the rendered story.
29
+ * The source code is taken from the story's `parameters.docs.source.code`.
30
+ *
31
+ * @param options - Options for the showSource decorator
32
+ * @param options.showOriginalSource - Whether to show the original source code in a card
33
+ * @param options.className - Class name to apply to the card
34
+ * @param options.source - Source code to show. Can be a string, or a function `(originalSource) => string` that receives the story's original source and returns the code to display.
35
+ * @param options.placement - Where to show the source code relative to the story.
36
+ * @returns A decorator function that shows the source code of a story above or below the rendered story
37
+ */
38
+ export function showSource<TRenderer extends Renderer = Renderer, TArgs = Args>({
39
+ className,
40
+ placement,
41
+ showOriginalSource,
42
+ source,
43
+ ...options
44
+ }: ShowSourceOptions = {}): DecoratorFunction<TRenderer, TArgs> {
45
+ if (isRunningInTest()) {
46
+ return (Story) => <Story />
47
+ }
48
+
49
+ return (Story, { parameters: { docs, darkMode } }) => {
50
+ // This is a workaround to get the current dark mode from `@storybook-community/storybook-dark-mode` without referencing it.
51
+ const storedItem = window.localStorage.getItem('sb-addon-themes-3')
52
+ const current = typeof storedItem === 'string' ? JSON.parse(storedItem).current : darkMode?.current
53
+ const [isDark, setIsDark] = useState((darkMode?.stylePreview && current === 'dark') ?? false)
54
+
55
+ useEffect(() => {
56
+ channel.on('DARK_MODE', setIsDark)
57
+
58
+ return () => channel.off('DARK_MODE', setIsDark)
59
+ }, [])
60
+
61
+ const originalSource = showOriginalSource
62
+ ? docs?.source?.originalSource
63
+ : (docs?.source?.code ?? docs?.source?.originalSource)
64
+
65
+ const code = typeof source === 'function' ? source(originalSource) : (source ?? originalSource)
66
+
67
+ const language = code === docs?.source?.originalSource ? undefined : docs?.source?.language
68
+
69
+ const isOriginalSource = code === docs?.source?.originalSource
70
+
71
+ const sourceContent = useMemo(
72
+ () => (
73
+ <SyntaxHighlighter data-testid="source-content" language={language}>
74
+ {code}
75
+ </SyntaxHighlighter>
76
+ ),
77
+ [code, language]
78
+ )
79
+
80
+ const showBefore = (placement ?? 'before') === 'before'
81
+
82
+ const sourceCardClassName = useCallback(
83
+ (state: Pick<StoryCardProps, 'status' | 'appearance'> & { defaultClassName: string }) => {
84
+ const modifiedState = {
85
+ ...state,
86
+ defaultClassName: twJoin(
87
+ state.defaultClassName,
88
+ isOriginalSource && 'rbsb:bg-transparent rbsb:dark:bg-transparent'
89
+ )
90
+ }
91
+
92
+ return typeof className === 'function'
93
+ ? className(modifiedState)
94
+ : twJoin(modifiedState.defaultClassName, className)
95
+ },
96
+ [className, isOriginalSource]
97
+ )
98
+
99
+ const theme = convert(docs?.theme ?? (isDark ? themes.dark : themes.light))
100
+
101
+ function DocSourceCard({ children }: { children: ReactNode }) {
102
+ return <div>{children}</div>
103
+ }
104
+
105
+ if (isRunningInTest()) {
106
+ return <Story />
107
+ }
108
+
109
+ // For 'before', use StoryCardScope so this card participates in parent scope order.
110
+ // Wrap content in ThemeProvider so SyntaxHighlighter has theme when rendered by parent.
111
+ if (showBefore) {
112
+ const scopeContent = (
113
+ <ThemeProvider theme={theme}>
114
+ <DocSourceCard>{sourceContent}</DocSourceCard>
115
+ </ThemeProvider>
116
+ )
117
+ return <StoryCardScope Story={Story} content={scopeContent} className={sourceCardClassName} appearance="source" />
118
+ }
119
+
120
+ const storyCard = (
121
+ <StoryCard className={sourceCardClassName} appearance="source" {...options}>
122
+ <DocSourceCard>{sourceContent}</DocSourceCard>
123
+ </StoryCard>
124
+ )
125
+
126
+ return (
127
+ <ThemeProvider theme={theme}>
128
+ <section
129
+ style={{
130
+ display: 'flex',
131
+ flexDirection: 'column',
132
+ gap: '1rem'
133
+ }}
134
+ >
135
+ <Story />
136
+ {storyCard}
137
+ </section>
138
+ </ThemeProvider>
139
+ )
140
+ }
141
+ }
package/src/index.ts CHANGED
@@ -3,6 +3,7 @@ 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'
6
+ export * from './decorators/show_source.tsx'
6
7
  export * from './decorators/with_story_card.tsx'
7
8
  export * from './parameters/define_actions_param.ts'
8
9
  export * from './parameters/define_backgrounds_param.ts'
@@ -36,6 +36,7 @@ export type TagNames =
36
36
  | 'usecase'
37
37
  | 'use-case'
38
38
  | 'example'
39
+ | 'perf'
39
40
  | 'version:next'
40
41
  | 'remove'
41
42
  | 'remove:next'
@@ -413,12 +414,31 @@ export const exampleBadge: TagBadgeParameter = {
413
414
  }
414
415
  }
415
416
 
417
+ /** Badge (⚡) for stories that demonstrate or test performance. */
418
+ export const perfBadge: TagBadgeParameter = {
419
+ tags: 'perf',
420
+ badge: {
421
+ text: '⚡',
422
+ style: {
423
+ backgroundColor: 'transparent',
424
+ borderColor: 'transparent'
425
+ },
426
+ tooltip: 'Performance'
427
+ },
428
+ display: {
429
+ sidebar: {
430
+ type: 'story',
431
+ skipInherited: false
432
+ }
433
+ }
434
+ }
435
+
416
436
  /**
417
437
  * Configuration for story tag badges that appear in the Storybook sidebar.
418
438
  * Each badge is associated with a specific tag and displays an emoji or symbol with a tooltip.
419
439
  *
420
440
  * Badge order (first match wins): New → Alpha → Beta → RC → Deprecated → Remove → Outdated → Danger → Use Case →
421
- * Example → Keyboard → Source → Type → Function → Var → Props → Todo → Unit → Integration →
441
+ * Example → Perf → Keyboard → Source → Type → Function → Var → Props → Todo → Unit → Integration →
422
442
  * Editor → Code Only → Version → Internal → Snapshot.
423
443
  *
424
444
  * - 🆕 New - Recently added stories
@@ -431,6 +451,7 @@ export const exampleBadge: TagBadgeParameter = {
431
451
  * - 🚨 Danger - Stories demonstrating dangerous patterns
432
452
  * - 🎯 Use Case - Stories that demonstrate a specific use case or scenario
433
453
  * - ✨ Example - Example or demo stories
454
+ * - ⚡ Perf - Stories that demonstrate or test performance
434
455
  * - ⌨️ Keyboard - Stories that demonstrate or test keyboard interaction
435
456
  * - `</>` Source - Source-code-focused stories
436
457
  * - `<T>` Type - Stories that showcase or document TypeScript types
@@ -458,6 +479,7 @@ export const tagBadges: TagBadgeParameters = [
458
479
  dangerBadge,
459
480
  useCaseBadge,
460
481
  exampleBadge,
482
+ perfBadge,
461
483
  keyboardBadge,
462
484
  sourceBadge,
463
485
  typeBadge,
package/styles.css CHANGED
@@ -33,6 +33,7 @@
33
33
  --rbsb-color-blue-500: oklch(62.3% 0.214 259.815);
34
34
  --rbsb-color-blue-800: oklch(42.4% 0.199 265.638);
35
35
  --rbsb-color-blue-900: oklch(37.9% 0.146 265.522);
36
+ --rbsb-color-purple-400: oklch(71.4% 0.203 305.504);
36
37
  --rbsb-color-purple-500: oklch(62.7% 0.265 303.9);
37
38
  --rbsb-color-rose-400: oklch(71.2% 0.194 13.428);
38
39
  --rbsb-color-rose-900: oklch(41% 0.159 10.272);
@@ -80,6 +81,9 @@
80
81
  .rbsb\:rounded {
81
82
  border-radius: 0.25rem;
82
83
  }
84
+ .rbsb\:rounded-full {
85
+ border-radius: calc(infinity * 1px);
86
+ }
83
87
  .rbsb\:rounded-lg {
84
88
  border-radius: var(--rbsb-radius-lg);
85
89
  }
@@ -234,6 +238,11 @@
234
238
  border-color: var(--rbsb-color-green-700);
235
239
  }
236
240
  }
241
+ .rbsb\:dark\:border-purple-400 {
242
+ @media (prefers-color-scheme: dark) {
243
+ border-color: var(--rbsb-color-purple-400);
244
+ }
245
+ }
237
246
  .rbsb\:dark\:border-red-700 {
238
247
  @media (prefers-color-scheme: dark) {
239
248
  border-color: var(--rbsb-color-red-700);