@repobuddy/storybook 2.21.0 → 2.22.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 +30 -15
- package/esm/index.js +50 -37
- package/esm/storybook-addon-tag-badges/index.d.ts +6 -3
- package/esm/storybook-addon-tag-badges/index.js +20 -2
- package/package.json +2 -2
- package/readme.md +1 -0
- package/src/contexts/_story_card_scope.tsx +63 -34
- package/src/decorators/show_doc_source.tsx +8 -135
- package/src/decorators/show_source.tsx +139 -0
- package/src/decorators/with_story_card.tsx +14 -16
- package/src/index.ts +2 -1
- package/src/storybook-addon-tag-badges/tag_badges.ts +23 -1
- package/styles.css +10 -1
- /package/src/arg-types/{fn-to-arg-types.ts → fn-to-arg.types.ts} +0 -0
package/esm/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { Args, DecoratorFunction, Renderer } from "storybook/internal/csf";
|
|
|
9
9
|
import { Decorator, Meta, StoryContext, StoryObj, StrictArgs } from "@storybook/react-vite";
|
|
10
10
|
export * from "@repobuddy/test";
|
|
11
11
|
|
|
12
|
-
//#region src/arg-types/fn-to-arg
|
|
12
|
+
//#region src/arg-types/fn-to-arg.types.d.ts
|
|
13
13
|
/**
|
|
14
14
|
* Converts a function's parameter types to `Args` type for Storybook.
|
|
15
15
|
* Each name maps to the parameter type at the same index in F.
|
|
@@ -97,28 +97,43 @@ type StoryCardThemeState = Pick<StoryCardProps, 'status' | 'appearance'> & {
|
|
|
97
97
|
*/
|
|
98
98
|
declare const StoryCard: react.NamedExoticComponent<StoryCardProps>;
|
|
99
99
|
//#endregion
|
|
100
|
-
//#region src/decorators/
|
|
100
|
+
//#region src/decorators/show_source.d.ts
|
|
101
|
+
/**
|
|
102
|
+
* Options for the {@link showSource} decorator.
|
|
103
|
+
*
|
|
104
|
+
* @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.
|
|
105
|
+
* @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.
|
|
106
|
+
* @property showOriginalSource - When true, use the story's original (untransformed) source instead of the rendered source.
|
|
107
|
+
* @property placement - Where to show the source code relative to the story. Defaults to `'before'`.
|
|
108
|
+
*/
|
|
109
|
+
type ShowSourceOptions = Pick<StoryCardProps, 'className'> & {
|
|
110
|
+
source?: ((source: string | undefined) => string) | string | undefined;
|
|
111
|
+
showOriginalSource?: boolean | undefined;
|
|
112
|
+
placement?: 'before' | 'after' | undefined;
|
|
113
|
+
};
|
|
101
114
|
/**
|
|
102
|
-
* A decorator that shows the source code of a story
|
|
115
|
+
* A decorator that shows the source code of a story relative to the rendered story.
|
|
103
116
|
* The source code is taken from the story's `parameters.docs.source.code`.
|
|
104
117
|
*
|
|
105
|
-
* @param options - Options for the
|
|
118
|
+
* @param options - Options for the showSource decorator
|
|
106
119
|
* @param options.showOriginalSource - Whether to show the original source code in a card
|
|
107
120
|
* @param options.className - Class name to apply to the card
|
|
108
121
|
* @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
122
|
* @param options.placement - Where to show the source code relative to the story.
|
|
110
123
|
* @returns A decorator function that shows the source code of a story above or below the rendered story
|
|
111
124
|
*/
|
|
112
|
-
declare function
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
125
|
+
declare function showSource<TRenderer extends Renderer = Renderer, TArgs = Args>(options?: ShowSourceOptions): DecoratorFunction<TRenderer, TArgs>;
|
|
126
|
+
//#endregion
|
|
127
|
+
//#region src/decorators/show_doc_source.d.ts
|
|
128
|
+
/**
|
|
129
|
+
* A decorator that shows the source code of a story below the rendered story.
|
|
130
|
+
* Uses {@link showSource} with `placement: 'after'`.
|
|
131
|
+
*
|
|
132
|
+
* @deprecated Use `showSource({ placement: 'after' })` instead.
|
|
133
|
+
* @param options - Same options as showSource; placement is forced to 'after'
|
|
134
|
+
* @returns A decorator function that shows the source code below the story
|
|
135
|
+
*/
|
|
136
|
+
declare function showDocSource<TRenderer extends Renderer = Renderer, TArgs = Args>(options?: ShowSourceOptions): DecoratorFunction<TRenderer, TArgs>;
|
|
122
137
|
//#endregion
|
|
123
138
|
//#region src/decorators/with_story_card.d.ts
|
|
124
139
|
type WithStoryCardProps = Omit<StoryCardProps, 'children' | 'className'> & {
|
|
@@ -935,4 +950,4 @@ type ExtendStoryObj<TMetaOrCmpOrArgs, S extends StoryObj<TMetaOrCmpOrArgs>, E ex
|
|
|
935
950
|
tags?: Array<E['tag'] | (string & {})> | undefined;
|
|
936
951
|
};
|
|
937
952
|
//#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 };
|
|
953
|
+
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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { isRunningInTest } from "@repobuddy/test";
|
|
3
3
|
import { prettify } from "htmlfy";
|
|
4
4
|
import { createContext, memo, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
5
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
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";
|
|
@@ -108,6 +108,17 @@ const StoryCardScope = memo(function StoryCardScope(props) {
|
|
|
108
108
|
if (context === null) return /* @__PURE__ */ jsx(StoryCardContainer, { children: collector });
|
|
109
109
|
return collector;
|
|
110
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
|
+
});
|
|
111
122
|
function StoryCardContainer({ children }) {
|
|
112
123
|
const [cards, setCards] = useState([]);
|
|
113
124
|
const contextValue = useMemo(() => ({
|
|
@@ -133,13 +144,13 @@ function StoryCardContainer({ children }) {
|
|
|
133
144
|
value: contextValue,
|
|
134
145
|
children: /* @__PURE__ */ jsxs("div", {
|
|
135
146
|
className: "rbsb:flex rbsb:flex-col rbsb:gap-2",
|
|
136
|
-
children: [
|
|
137
|
-
...rest,
|
|
138
|
-
children: content
|
|
139
|
-
}, key)), children]
|
|
147
|
+
children: [/* @__PURE__ */ jsx(StoryCardList, { cards }), /* @__PURE__ */ jsx(StableScopeChildren, { children })]
|
|
140
148
|
})
|
|
141
149
|
});
|
|
142
150
|
}
|
|
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
|
+
}
|
|
143
154
|
const StoryCardCollector = memo(function StoryCardCollector({ Story, title, status, appearance, className, content }) {
|
|
144
155
|
const context = useContext(StoryCardRegistryContext);
|
|
145
156
|
const cardIdRef = useRef(null);
|
|
@@ -169,23 +180,23 @@ const StoryCardCollector = memo(function StoryCardCollector({ Story, title, stat
|
|
|
169
180
|
if (cardIdRef.current !== null) context.update(cardIdRef.current, entry);
|
|
170
181
|
}, [context, entry]);
|
|
171
182
|
return /* @__PURE__ */ jsx(Story, {});
|
|
172
|
-
},
|
|
183
|
+
}, entryPropsEqual);
|
|
173
184
|
|
|
174
185
|
//#endregion
|
|
175
|
-
//#region src/decorators/
|
|
186
|
+
//#region src/decorators/show_source.tsx
|
|
176
187
|
const channel = addons.getChannel();
|
|
177
188
|
/**
|
|
178
|
-
* A decorator that shows the source code of a story
|
|
189
|
+
* A decorator that shows the source code of a story relative to the rendered story.
|
|
179
190
|
* The source code is taken from the story's `parameters.docs.source.code`.
|
|
180
191
|
*
|
|
181
|
-
* @param options - Options for the
|
|
192
|
+
* @param options - Options for the showSource decorator
|
|
182
193
|
* @param options.showOriginalSource - Whether to show the original source code in a card
|
|
183
194
|
* @param options.className - Class name to apply to the card
|
|
184
195
|
* @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.
|
|
185
196
|
* @param options.placement - Where to show the source code relative to the story.
|
|
186
197
|
* @returns A decorator function that shows the source code of a story above or below the rendered story
|
|
187
198
|
*/
|
|
188
|
-
function
|
|
199
|
+
function showSource(options) {
|
|
189
200
|
if (isRunningInTest()) return (Story) => /* @__PURE__ */ jsx(Story, {});
|
|
190
201
|
return (Story, { parameters: { docs, darkMode } }) => {
|
|
191
202
|
const storedItem = window.localStorage.getItem("sb-addon-themes-3");
|
|
@@ -204,7 +215,7 @@ function showDocSource(options) {
|
|
|
204
215
|
language,
|
|
205
216
|
children: code
|
|
206
217
|
}), [code, language]);
|
|
207
|
-
const showBefore = options?.placement === "before";
|
|
218
|
+
const showBefore = (options?.placement ?? "before") === "before";
|
|
208
219
|
const sourceCardClassName = useCallback((state) => {
|
|
209
220
|
const modifiedState = {
|
|
210
221
|
...state,
|
|
@@ -217,21 +228,15 @@ function showDocSource(options) {
|
|
|
217
228
|
function DocSourceCard({ children }) {
|
|
218
229
|
return /* @__PURE__ */ jsx("div", { children });
|
|
219
230
|
}
|
|
220
|
-
|
|
221
|
-
|
|
231
|
+
if (isRunningInTest()) return /* @__PURE__ */ jsx(Story, {});
|
|
232
|
+
if (showBefore) return /* @__PURE__ */ jsx(StoryCardScope, {
|
|
222
233
|
Story,
|
|
223
|
-
content:
|
|
234
|
+
content: /* @__PURE__ */ jsx(ThemeProvider, {
|
|
235
|
+
theme,
|
|
236
|
+
children: /* @__PURE__ */ jsx(DocSourceCard, { children: sourceContent })
|
|
237
|
+
}),
|
|
224
238
|
className: sourceCardClassName,
|
|
225
239
|
appearance: "source"
|
|
226
|
-
}), [
|
|
227
|
-
Story,
|
|
228
|
-
scopeContent,
|
|
229
|
-
sourceCardClassName
|
|
230
|
-
]);
|
|
231
|
-
if (isRunningInTest()) return /* @__PURE__ */ jsx(Story, {});
|
|
232
|
-
if (showBefore) return /* @__PURE__ */ jsx(ThemeProvider, {
|
|
233
|
-
theme,
|
|
234
|
-
children: /* @__PURE__ */ jsx(StoryCardScope, { ...scopeProps })
|
|
235
240
|
});
|
|
236
241
|
return /* @__PURE__ */ jsx(ThemeProvider, {
|
|
237
242
|
theme,
|
|
@@ -251,6 +256,23 @@ function showDocSource(options) {
|
|
|
251
256
|
};
|
|
252
257
|
}
|
|
253
258
|
|
|
259
|
+
//#endregion
|
|
260
|
+
//#region src/decorators/show_doc_source.tsx
|
|
261
|
+
/**
|
|
262
|
+
* A decorator that shows the source code of a story below the rendered story.
|
|
263
|
+
* Uses {@link showSource} with `placement: 'after'`.
|
|
264
|
+
*
|
|
265
|
+
* @deprecated Use `showSource({ placement: 'after' })` instead.
|
|
266
|
+
* @param options - Same options as showSource; placement is forced to 'after'
|
|
267
|
+
* @returns A decorator function that shows the source code below the story
|
|
268
|
+
*/
|
|
269
|
+
function showDocSource(options) {
|
|
270
|
+
return showSource({
|
|
271
|
+
placement: "after",
|
|
272
|
+
...options
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
254
276
|
//#endregion
|
|
255
277
|
//#region src/decorators/with_story_card.tsx
|
|
256
278
|
/**
|
|
@@ -335,6 +357,7 @@ function showDocSource(options) {
|
|
|
335
357
|
function withStoryCard({ title, status, appearance, content: contentProp, className, ...rest } = {}) {
|
|
336
358
|
if (isRunningInTest()) return (Story) => /* @__PURE__ */ jsx(Story, {});
|
|
337
359
|
return (Story, { parameters, viewMode }) => {
|
|
360
|
+
if (viewMode === "docs") return /* @__PURE__ */ jsx(Story, {});
|
|
338
361
|
const storyCardParam = parameters.storyCard;
|
|
339
362
|
const finalTitle = title ?? storyCardParam?.title;
|
|
340
363
|
const finalAppearance = appearance ?? storyCardParam?.appearance ?? status ?? storyCardParam?.status ?? "info";
|
|
@@ -342,7 +365,8 @@ function withStoryCard({ title, status, appearance, content: contentProp, classN
|
|
|
342
365
|
const finalContent = contentProp ?? storyCardParam?.content;
|
|
343
366
|
const finalClassName = className ?? storyCardParam?.className;
|
|
344
367
|
const content = finalContent ?? parameters.docs?.description?.story ?? parameters.docs?.description?.component;
|
|
345
|
-
|
|
368
|
+
if (!content && !finalTitle) return /* @__PURE__ */ jsx(Story, {});
|
|
369
|
+
return /* @__PURE__ */ jsx(StoryCardScope, {
|
|
346
370
|
Story,
|
|
347
371
|
content,
|
|
348
372
|
title: finalTitle,
|
|
@@ -350,18 +374,7 @@ function withStoryCard({ title, status, appearance, content: contentProp, classN
|
|
|
350
374
|
appearance: finalAppearance,
|
|
351
375
|
className: finalClassName,
|
|
352
376
|
...rest
|
|
353
|
-
})
|
|
354
|
-
Story,
|
|
355
|
-
content,
|
|
356
|
-
finalTitle,
|
|
357
|
-
finalStatus,
|
|
358
|
-
finalAppearance,
|
|
359
|
-
finalClassName,
|
|
360
|
-
rest
|
|
361
|
-
]);
|
|
362
|
-
if (viewMode === "docs") return /* @__PURE__ */ jsx(Story, {});
|
|
363
|
-
if (!content && !finalTitle) return /* @__PURE__ */ jsx(Story, {});
|
|
364
|
-
return /* @__PURE__ */ jsx(StoryCardScope, { ...scopeProps });
|
|
377
|
+
});
|
|
365
378
|
};
|
|
366
379
|
}
|
|
367
380
|
|
|
@@ -594,4 +607,4 @@ function whenRunningInTest(decoratorOrHandler) {
|
|
|
594
607
|
}
|
|
595
608
|
|
|
596
609
|
//#endregion
|
|
597
|
-
export { ShowHtml, StoryCard, defineActionsParam, defineBackgroundsParam, defineDocsParam, defineLayoutParam, defineParameters, defineStoryCardParam, defineTestParam, defineViewportParam, showDocSource, whenRunningInTest, withStoryCard };
|
|
610
|
+
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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@repobuddy/storybook",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.22.0",
|
|
4
4
|
"description": "Storybook repo buddy",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"storybook",
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
"build:code": "tsdown",
|
|
121
121
|
"build:css": "tailwindcss -i ./tailwind.css -o ./styles.css",
|
|
122
122
|
"clean": "rimraf .turbo coverage cjs esm storybook-static *.tsbuildinfo",
|
|
123
|
-
"
|
|
123
|
+
"cov": "vitest run --coverage",
|
|
124
124
|
"sb": "storybook dev -p 6006",
|
|
125
125
|
"sb:build": "storybook build",
|
|
126
126
|
"test": "vitest run",
|
package/readme.md
CHANGED
|
@@ -88,6 +88,7 @@ we provide a different set of badges that uses emojis (order: first match wins):
|
|
|
88
88
|
- 🚨 `danger` - Dangerous or cautionary patterns
|
|
89
89
|
- 🎯 `use-case` - Specific use case or scenario
|
|
90
90
|
- ✨ `example` - Example or demo stories
|
|
91
|
+
- ⚡ `perf` - Performance (stories that demonstrate or test performance)
|
|
91
92
|
- ⌨️ `keyboard` - Keyboard interaction
|
|
92
93
|
- `</>` `source` - Source-code-focused stories
|
|
93
94
|
- `<T>` `type` - TypeScript types (shown in MDX)
|
|
@@ -24,6 +24,24 @@ export const StoryCardScope = memo(function StoryCardScope(props: StoryCardScope
|
|
|
24
24
|
return collector
|
|
25
25
|
})
|
|
26
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
|
+
})
|
|
44
|
+
|
|
27
45
|
function StoryCardContainer({ children }: { children: ReactNode }) {
|
|
28
46
|
const [cards, setCards] = useState<StoryCardEntryWithKey[]>([])
|
|
29
47
|
|
|
@@ -47,12 +65,8 @@ function StoryCardContainer({ children }: { children: ReactNode }) {
|
|
|
47
65
|
return (
|
|
48
66
|
<StoryCardRegistryContext.Provider value={contextValue}>
|
|
49
67
|
<div className="rbsb:flex rbsb:flex-col rbsb:gap-2">
|
|
50
|
-
{cards
|
|
51
|
-
|
|
52
|
-
{content}
|
|
53
|
-
</StoryCard>
|
|
54
|
-
))}
|
|
55
|
-
{children}
|
|
68
|
+
<StoryCardList cards={cards} />
|
|
69
|
+
<StableScopeChildren>{children}</StableScopeChildren>
|
|
56
70
|
</div>
|
|
57
71
|
</StoryCardRegistryContext.Provider>
|
|
58
72
|
)
|
|
@@ -62,35 +76,50 @@ type StoryCardEntryWithKey = StoryCardEntry & { key: string }
|
|
|
62
76
|
|
|
63
77
|
interface StoryCardCollectorProps extends StoryCardScopeProps {}
|
|
64
78
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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) {
|
|
98
|
+
const context = useContext(StoryCardRegistryContext)!
|
|
99
|
+
const cardIdRef = useRef<string | null>(null)
|
|
85
100
|
|
|
86
|
-
|
|
87
|
-
|
|
101
|
+
const entry = useMemo(
|
|
102
|
+
() => ({ title, status, appearance, className, content }),
|
|
103
|
+
[title, status, appearance, className, content]
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
// Register on mount, unregister on unmount only
|
|
107
|
+
useLayoutEffect(() => {
|
|
108
|
+
cardIdRef.current = context.add(entry)
|
|
109
|
+
return () => {
|
|
88
110
|
if (cardIdRef.current !== null) {
|
|
89
|
-
context.
|
|
111
|
+
context.remove(cardIdRef.current)
|
|
112
|
+
cardIdRef.current = null
|
|
90
113
|
}
|
|
91
|
-
}
|
|
114
|
+
}
|
|
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])
|
|
92
123
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
(prev, next) => prev.Story === next.Story
|
|
96
|
-
)
|
|
124
|
+
return <Story />
|
|
125
|
+
}, entryPropsEqual)
|
|
@@ -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 {
|
|
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
|
|
15
|
-
*
|
|
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
|
-
* @
|
|
18
|
-
* @param options
|
|
19
|
-
* @
|
|
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?:
|
|
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
|
-
|
|
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,139 @@
|
|
|
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'> & {
|
|
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
|
+
options?: ShowSourceOptions
|
|
40
|
+
): DecoratorFunction<TRenderer, TArgs> {
|
|
41
|
+
if (isRunningInTest()) {
|
|
42
|
+
return (Story) => <Story />
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return (Story, { parameters: { docs, darkMode } }) => {
|
|
46
|
+
// This is a workaround to get the current dark mode from `@storybook-community/storybook-dark-mode` without referencing it.
|
|
47
|
+
const storedItem = window.localStorage.getItem('sb-addon-themes-3')
|
|
48
|
+
const current = typeof storedItem === 'string' ? JSON.parse(storedItem).current : darkMode?.current
|
|
49
|
+
const [isDark, setIsDark] = useState((darkMode?.stylePreview && current === 'dark') ?? false)
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
channel.on('DARK_MODE', setIsDark)
|
|
53
|
+
|
|
54
|
+
return () => channel.off('DARK_MODE', setIsDark)
|
|
55
|
+
}, [])
|
|
56
|
+
|
|
57
|
+
const originalSource = options?.showOriginalSource
|
|
58
|
+
? docs?.source?.originalSource
|
|
59
|
+
: (docs?.source?.code ?? docs?.source?.originalSource)
|
|
60
|
+
|
|
61
|
+
const code =
|
|
62
|
+
typeof options?.source === 'function' ? options?.source(originalSource) : (options?.source ?? originalSource)
|
|
63
|
+
|
|
64
|
+
const language = code === docs?.source?.originalSource ? undefined : docs?.source?.language
|
|
65
|
+
|
|
66
|
+
const isOriginalSource = code === docs?.source?.originalSource
|
|
67
|
+
|
|
68
|
+
const sourceContent = useMemo(
|
|
69
|
+
() => (
|
|
70
|
+
<SyntaxHighlighter data-testid="source-content" language={language}>
|
|
71
|
+
{code}
|
|
72
|
+
</SyntaxHighlighter>
|
|
73
|
+
),
|
|
74
|
+
[code, language]
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
const showBefore = (options?.placement ?? 'before') === 'before'
|
|
78
|
+
|
|
79
|
+
const sourceCardClassName = useCallback(
|
|
80
|
+
(state: Pick<StoryCardProps, 'status' | 'appearance'> & { defaultClassName: string }) => {
|
|
81
|
+
const modifiedState = {
|
|
82
|
+
...state,
|
|
83
|
+
defaultClassName: twJoin(
|
|
84
|
+
state.defaultClassName,
|
|
85
|
+
isOriginalSource && 'rbsb:bg-transparent rbsb:dark:bg-transparent'
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const className = options?.className
|
|
90
|
+
return typeof className === 'function'
|
|
91
|
+
? className(modifiedState)
|
|
92
|
+
: twJoin(modifiedState.defaultClassName, className)
|
|
93
|
+
},
|
|
94
|
+
[options?.className, isOriginalSource]
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
const theme = convert(docs?.theme ?? (isDark ? themes.dark : themes.light))
|
|
98
|
+
|
|
99
|
+
function DocSourceCard({ children }: { children: ReactNode }) {
|
|
100
|
+
return <div>{children}</div>
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (isRunningInTest()) {
|
|
104
|
+
return <Story />
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// For 'before', use StoryCardScope so this card participates in parent scope order.
|
|
108
|
+
// Wrap content in ThemeProvider so SyntaxHighlighter has theme when rendered by parent.
|
|
109
|
+
if (showBefore) {
|
|
110
|
+
const scopeContent = (
|
|
111
|
+
<ThemeProvider theme={theme}>
|
|
112
|
+
<DocSourceCard>{sourceContent}</DocSourceCard>
|
|
113
|
+
</ThemeProvider>
|
|
114
|
+
)
|
|
115
|
+
return <StoryCardScope Story={Story} content={scopeContent} className={sourceCardClassName} appearance="source" />
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const storyCard = (
|
|
119
|
+
<StoryCard className={sourceCardClassName} appearance="source">
|
|
120
|
+
<DocSourceCard>{sourceContent}</DocSourceCard>
|
|
121
|
+
</StoryCard>
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<ThemeProvider theme={theme}>
|
|
126
|
+
<section
|
|
127
|
+
style={{
|
|
128
|
+
display: 'flex',
|
|
129
|
+
flexDirection: 'column',
|
|
130
|
+
gap: '1rem'
|
|
131
|
+
}}
|
|
132
|
+
>
|
|
133
|
+
<Story />
|
|
134
|
+
{storyCard}
|
|
135
|
+
</section>
|
|
136
|
+
</ThemeProvider>
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isRunningInTest } from '@repobuddy/test'
|
|
2
|
-
import {
|
|
2
|
+
import type { ReactNode } from 'react'
|
|
3
3
|
import type { DecoratorFunction, Renderer } from 'storybook/internal/csf'
|
|
4
4
|
import type { StoryCardProps, StoryCardStatus } from '../components/story_card.js'
|
|
5
5
|
import { StoryCardScope } from '../contexts/_story_card_scope.js'
|
|
@@ -124,6 +124,8 @@ export function withStoryCard<TRenderer extends Renderer = Renderer>({
|
|
|
124
124
|
return (Story) => <Story />
|
|
125
125
|
}
|
|
126
126
|
return (Story, { parameters, viewMode }) => {
|
|
127
|
+
if (viewMode === 'docs') return <Story />
|
|
128
|
+
|
|
127
129
|
// Get story card config from parameters if available
|
|
128
130
|
const storyCardParam = (parameters as Partial<StoryCardParam>).storyCard
|
|
129
131
|
// Decorator props override parameter values
|
|
@@ -137,22 +139,18 @@ export function withStoryCard<TRenderer extends Renderer = Renderer>({
|
|
|
137
139
|
// Fallback to docs description if no content provided
|
|
138
140
|
const content = finalContent ?? parameters.docs?.description?.story ?? parameters.docs?.description?.component
|
|
139
141
|
|
|
140
|
-
const scopeProps = useMemo(
|
|
141
|
-
() => ({
|
|
142
|
-
Story,
|
|
143
|
-
content,
|
|
144
|
-
title: finalTitle,
|
|
145
|
-
status: finalStatus,
|
|
146
|
-
appearance: finalAppearance,
|
|
147
|
-
className: finalClassName,
|
|
148
|
-
...rest
|
|
149
|
-
}),
|
|
150
|
-
[Story, content, finalTitle, finalStatus, finalAppearance, finalClassName, rest]
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
if (viewMode === 'docs') return <Story />
|
|
154
142
|
if (!content && !finalTitle) return <Story />
|
|
155
143
|
|
|
156
|
-
return
|
|
144
|
+
return (
|
|
145
|
+
<StoryCardScope
|
|
146
|
+
Story={Story}
|
|
147
|
+
content={content}
|
|
148
|
+
title={finalTitle}
|
|
149
|
+
status={finalStatus}
|
|
150
|
+
appearance={finalAppearance}
|
|
151
|
+
className={finalClassName}
|
|
152
|
+
{...rest}
|
|
153
|
+
/>
|
|
154
|
+
)
|
|
157
155
|
}
|
|
158
156
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export * from '@repobuddy/test'
|
|
2
|
-
export type * from './arg-types/fn-to-arg
|
|
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'
|
|
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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! tailwindcss v4.
|
|
1
|
+
/*! tailwindcss v4.2.0 | MIT License | https://tailwindcss.com */
|
|
2
2
|
@layer properties;
|
|
3
3
|
@layer theme {
|
|
4
4
|
:root, :host {
|
|
@@ -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);
|
|
File without changes
|