@repobuddy/storybook 2.21.0 → 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
@@ -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-types.d.ts
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.
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: [cards.map(({ content, key, ...rest }) => /* @__PURE__ */ jsx(StoryCard, {
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,7 +180,7 @@ 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
- }, (prev, next) => prev.Story === next.Story);
183
+ }, entryPropsEqual);
173
184
 
174
185
  //#endregion
175
186
  //#region src/decorators/show_doc_source.tsx
@@ -335,6 +346,7 @@ function showDocSource(options) {
335
346
  function withStoryCard({ title, status, appearance, content: contentProp, className, ...rest } = {}) {
336
347
  if (isRunningInTest()) return (Story) => /* @__PURE__ */ jsx(Story, {});
337
348
  return (Story, { parameters, viewMode }) => {
349
+ if (viewMode === "docs") return /* @__PURE__ */ jsx(Story, {});
338
350
  const storyCardParam = parameters.storyCard;
339
351
  const finalTitle = title ?? storyCardParam?.title;
340
352
  const finalAppearance = appearance ?? storyCardParam?.appearance ?? status ?? storyCardParam?.status ?? "info";
@@ -342,7 +354,8 @@ function withStoryCard({ title, status, appearance, content: contentProp, classN
342
354
  const finalContent = contentProp ?? storyCardParam?.content;
343
355
  const finalClassName = className ?? storyCardParam?.className;
344
356
  const content = finalContent ?? parameters.docs?.description?.story ?? parameters.docs?.description?.component;
345
- const scopeProps = useMemo(() => ({
357
+ if (!content && !finalTitle) return /* @__PURE__ */ jsx(Story, {});
358
+ return /* @__PURE__ */ jsx(StoryCardScope, {
346
359
  Story,
347
360
  content,
348
361
  title: finalTitle,
@@ -350,18 +363,7 @@ function withStoryCard({ title, status, appearance, content: contentProp, classN
350
363
  appearance: finalAppearance,
351
364
  className: finalClassName,
352
365
  ...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 });
366
+ });
365
367
  };
366
368
  }
367
369
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@repobuddy/storybook",
3
- "version": "2.21.0",
3
+ "version": "2.21.1",
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
- "coverage": "vitest run --coverage",
123
+ "cov": "vitest run --coverage",
124
124
  "sb": "storybook dev -p 6006",
125
125
  "sb:build": "storybook build",
126
126
  "test": "vitest run",
@@ -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.map(({ content, key, ...rest }) => (
51
- <StoryCard key={key} {...rest}>
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
- const StoryCardCollector = memo(
66
- function StoryCardCollector({ Story, title, status, appearance, className, content }: StoryCardCollectorProps) {
67
- const context = useContext(StoryCardRegistryContext)!
68
- const cardIdRef = useRef<string | null>(null)
69
-
70
- const entry = useMemo(
71
- () => ({ title, status, appearance, className, content }),
72
- [title, status, appearance, className, content]
73
- )
74
-
75
- // Register on mount, unregister on unmount only
76
- useLayoutEffect(() => {
77
- cardIdRef.current = context.add(entry)
78
- return () => {
79
- if (cardIdRef.current !== null) {
80
- context.remove(cardIdRef.current)
81
- cardIdRef.current = null
82
- }
83
- }
84
- }, [context])
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
- // Update registry when entry changes (avoids remove+add churn)
87
- useLayoutEffect(() => {
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.update(cardIdRef.current, entry)
111
+ context.remove(cardIdRef.current)
112
+ cardIdRef.current = null
90
113
  }
91
- }, [context, entry])
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
- return <Story />
94
- },
95
- (prev, next) => prev.Story === next.Story
96
- )
124
+ return <Story />
125
+ }, entryPropsEqual)
@@ -1,5 +1,5 @@
1
1
  import { isRunningInTest } from '@repobuddy/test'
2
- import { type ReactNode, useMemo } from 'react'
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 <StoryCardScope {...scopeProps} />
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,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 {