@repobuddy/storybook 2.2.0 → 2.2.2

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
@@ -4,7 +4,6 @@ import { ReactNode } from "react";
4
4
  import * as react_jsx_runtime0 from "react/jsx-runtime";
5
5
  import { ClassNameProps, StyleProps } from "@just-web/css";
6
6
  import { Args, DecoratorFunction, Renderer } from "storybook/internal/csf";
7
- import { ClassValue } from "class-variance-authority/types";
8
7
  import { Decorator, Meta, StoryContext, StoryObj, StrictArgs } from "@storybook/react-vite";
9
8
  export * from "@repobuddy/test";
10
9
 
@@ -56,9 +55,9 @@ type StoryCardProps = {
56
55
  * If a function is provided, it receives the card state and default className,
57
56
  * and should return the final className string.
58
57
  */
59
- className?: ((state: Pick<StoryCardProps, 'status'> & {
58
+ className?: ((state: Required<Pick<StoryCardProps, 'status'>> & {
60
59
  defaultClassName: string;
61
- }) => string) | ClassValue | undefined;
60
+ }) => string) | string | undefined;
62
61
  /**
63
62
  * Content to display in the card body.
64
63
  * Can be any React node (string, JSX, etc.).
@@ -137,6 +136,7 @@ type StoryCardProps = {
137
136
  */
138
137
  declare function withStoryCard<TRenderer extends Renderer = Renderer>({
139
138
  title,
139
+ status,
140
140
  content: contentProp,
141
141
  ...rest
142
142
  }?: StoryCardProps): DecoratorFunction<TRenderer>;
package/esm/index.js CHANGED
@@ -70,6 +70,19 @@ function showDocSource() {
70
70
  };
71
71
  }
72
72
 
73
+ //#endregion
74
+ //#region src/utils/generate_key.ts
75
+ /**
76
+ * Generates a key for React collections, falling back to a simple counter-based ID if crypto.randomUUID is unavailable.
77
+ * crypto.randomUUID() requires a secure context (HTTPS, localhost, or 127.0.0.1).
78
+ *
79
+ * This can be moved to `@just-web` in the future.
80
+ */
81
+ function generateKey(prefix) {
82
+ const randomId = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
83
+ return prefix ? `${prefix}-${randomId}` : randomId;
84
+ }
85
+
73
86
  //#endregion
74
87
  //#region src/decorators/with_story_card.tsx
75
88
  /**
@@ -137,7 +150,7 @@ function showDocSource() {
137
150
  * or fall back to the component description.
138
151
  * - Cards are collected and displayed in the order they are defined in the decorators array.
139
152
  */
140
- function withStoryCard({ title, content: contentProp, ...rest } = {}) {
153
+ function withStoryCard({ title, status = "info", content: contentProp, ...rest } = {}) {
141
154
  return (Story, { parameters, viewMode }) => {
142
155
  if (viewMode === "docs") return /* @__PURE__ */ jsx(Story, {});
143
156
  const content = contentProp ?? parameters.docs?.description?.story ?? parameters.docs?.description?.component;
@@ -146,6 +159,7 @@ function withStoryCard({ title, content: contentProp, ...rest } = {}) {
146
159
  Story,
147
160
  content,
148
161
  title,
162
+ status,
149
163
  ...rest
150
164
  });
151
165
  };
@@ -159,36 +173,48 @@ function StoryCardContainerWrapper({ Story, ...props }) {
159
173
  if (context === null) return /* @__PURE__ */ jsx(StoryCardContainer, { children: collector });
160
174
  return collector;
161
175
  }
162
- const StoryCardContext = createContext(null);
163
176
  function StoryCardContainer({ children }) {
164
177
  const [cards, setCards] = useState([]);
165
178
  const contextValue = useMemo(() => ({
166
179
  addCard(card) {
167
- const id = `story-card-${crypto.randomUUID()}`;
180
+ const key = generateKey("story-card");
168
181
  setCards((cards$1) => [...cards$1, {
169
182
  ...card,
170
- id
183
+ key
171
184
  }]);
172
- return id;
185
+ return key;
173
186
  },
174
- removeCard(id) {
175
- setCards((cards$1) => cards$1.filter((card) => card.id !== id));
187
+ removeCard(key) {
188
+ setCards((cards$1) => cards$1.filter((card) => card.key !== key));
176
189
  }
177
190
  }), []);
178
191
  return /* @__PURE__ */ jsx(StoryCardContext.Provider, {
179
192
  value: contextValue,
180
193
  children: /* @__PURE__ */ jsxs("div", {
181
194
  className: "flex flex-col gap-2",
182
- children: [cards.map(({ id, status, className, content, title }) => /* @__PURE__ */ jsxs("section", {
195
+ children: [cards.map(({ key, status, className, content, title }) => /* @__PURE__ */ jsxs("section", {
183
196
  className: storyCardTheme({ status }, className),
184
197
  children: [title && /* @__PURE__ */ jsx("h2", {
185
198
  className: "text-lg font-bold",
186
199
  children: title
187
200
  }), content]
188
- }, id)), children]
201
+ }, key)), children]
189
202
  })
190
203
  });
191
204
  }
205
+ function storyCardTheme(state, className) {
206
+ const defaultClassName = storyCardVariants(state);
207
+ if (!className) return defaultClassName;
208
+ return typeof className === "function" ? className({
209
+ ...state,
210
+ defaultClassName
211
+ }) : twMerge(defaultClassName, className);
212
+ }
213
+ const storyCardVariants = cva("flex flex-col gap-1 py-3 px-4 rounded text-black dark:text-gray-100", { variants: { status: {
214
+ error: "bg-red-100 dark:bg-red-900",
215
+ warn: "bg-yellow-100 dark:bg-yellow-900",
216
+ info: "bg-sky-100 dark:bg-sky-900"
217
+ } } });
192
218
  function StoryCardCollector({ Story, title, status, className, content }) {
193
219
  const context = useContext(StoryCardContext);
194
220
  const cardIdRef = useRef(null);
@@ -208,22 +234,7 @@ function StoryCardCollector({ Story, title, status, className, content }) {
208
234
  }, []);
209
235
  return /* @__PURE__ */ jsx(Story, {});
210
236
  }
211
- const storyCardTheme = (state, className) => {
212
- const defaultClassName = storyCardVariants(state);
213
- if (!className) return defaultClassName;
214
- return twMerge(defaultClassName, typeof className === "function" ? className({
215
- ...state,
216
- defaultClassName
217
- }) : className);
218
- };
219
- const storyCardVariants = cva("flex flex-col gap-1 py-3 px-4 rounded text-black dark:text-gray-100", {
220
- variants: { status: {
221
- error: "bg-red-100 dark:bg-red-900",
222
- warn: "bg-yellow-100 dark:bg-yellow-900",
223
- info: "bg-sky-100 dark:bg-sky-900"
224
- } },
225
- defaultVariants: { status: "info" }
226
- });
237
+ const StoryCardContext = createContext(null);
227
238
 
228
239
  //#endregion
229
240
  //#region src/parameters/define_actions_param.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@repobuddy/storybook",
3
- "version": "2.2.0",
3
+ "version": "2.2.2",
4
4
  "description": "Storybook repo buddy",
5
5
  "keywords": [
6
6
  "storybook",
@@ -54,14 +54,15 @@
54
54
  "@repobuddy/test": "^1.0.0",
55
55
  "class-variance-authority": "^0.7.1",
56
56
  "htmlfy": "^1.0.0",
57
- "tailwind-merge": "^3.4.0"
57
+ "tailwind-merge": "^3.4.0",
58
+ "type-plus": "8.0.0-beta.7"
58
59
  },
59
60
  "devDependencies": {
60
61
  "@repobuddy/vitest": "^2.0.0",
61
62
  "@storybook-community/storybook-dark-mode": "^7.0.2",
62
- "@storybook/addon-docs": "^10.0.7",
63
- "@storybook/addon-vitest": "^10.0.7",
64
- "@storybook/react-vite": "^10.0.7",
63
+ "@storybook/addon-docs": "^10.1.10",
64
+ "@storybook/addon-vitest": "^10.1.10",
65
+ "@storybook/react-vite": "^10.1.10",
65
66
  "@tailwindcss/cli": "^4.1.17",
66
67
  "@tailwindcss/vite": "^4.1.17",
67
68
  "@vitest/browser": "^4.0.16",
@@ -72,7 +73,7 @@
72
73
  "react": "^19.2.0",
73
74
  "react-dom": "^19.2.0",
74
75
  "rimraf": "^6.1.0",
75
- "storybook": "^10.0.8",
76
+ "storybook": "^10.1.10",
76
77
  "storybook-addon-tag-badges": "^3.0.2",
77
78
  "tailwindcss": "^4.1.17",
78
79
  "tsdown": "^0.18.0",
@@ -81,7 +82,7 @@
81
82
  },
82
83
  "peerDependencies": {
83
84
  "@storybook-community/storybook-dark-mode": "^7.0.0",
84
- "@storybook/addon-docs": "^10.0.0",
85
+ "@storybook/addon-docs": "^10.1.10",
85
86
  "storybook-addon-tag-badges": "^3.0.2"
86
87
  },
87
88
  "peerDependenciesMeta": {
@@ -1,5 +1,4 @@
1
1
  import { cva } from 'class-variance-authority'
2
- import type { ClassValue } from 'class-variance-authority/types'
3
2
  import {
4
3
  createContext,
5
4
  useContext,
@@ -12,6 +11,8 @@ import {
12
11
  } from 'react'
13
12
  import type { DecoratorFunction, Renderer } from 'storybook/internal/csf'
14
13
  import { twMerge } from 'tailwind-merge'
14
+ import type { RequiredPick } from 'type-plus'
15
+ import { generateKey } from '../utils/generate_key.js'
15
16
 
16
17
  export type StoryCardProps = {
17
18
  /**
@@ -34,8 +35,8 @@ export type StoryCardProps = {
34
35
  * and should return the final className string.
35
36
  */
36
37
  className?:
37
- | ((state: Pick<StoryCardProps, 'status'> & { defaultClassName: string }) => string)
38
- | ClassValue
38
+ | ((state: Required<Pick<StoryCardProps, 'status'>> & { defaultClassName: string }) => string)
39
+ | string
39
40
  | undefined
40
41
  /**
41
42
  * Content to display in the card body.
@@ -116,6 +117,7 @@ export type StoryCardProps = {
116
117
  */
117
118
  export function withStoryCard<TRenderer extends Renderer = Renderer>({
118
119
  title,
120
+ status = 'info',
119
121
  content: contentProp,
120
122
  ...rest
121
123
  }: StoryCardProps = {}): DecoratorFunction<TRenderer> {
@@ -125,11 +127,11 @@ export function withStoryCard<TRenderer extends Renderer = Renderer>({
125
127
  const content = contentProp ?? parameters.docs?.description?.story ?? parameters.docs?.description?.component
126
128
  if (!content && !title) return <Story />
127
129
 
128
- return <StoryCardContainerWrapper Story={Story} content={content} title={title} {...rest} />
130
+ return <StoryCardContainerWrapper Story={Story} content={content} title={title} status={status} {...rest} />
129
131
  }
130
132
  }
131
133
 
132
- interface StoryCardContainerWrapperProps extends StoryCardProps {
134
+ interface StoryCardContainerWrapperProps extends RequiredPick<StoryCardProps, 'status'> {
133
135
  Story: ComponentType
134
136
  }
135
137
 
@@ -144,27 +146,18 @@ function StoryCardContainerWrapper({ Story, ...props }: StoryCardContainerWrappe
144
146
  return collector
145
147
  }
146
148
 
147
- interface StoryCardContextValue {
148
- addCard: (card: StoryCardProps) => string
149
- removeCard: (id: string) => void
150
- }
151
-
152
- const StoryCardContext = createContext<StoryCardContextValue | null>(null)
153
-
154
- type StoryCardWithId = StoryCardProps & { id: string }
155
-
156
149
  function StoryCardContainer({ children }: { children: ReactNode }) {
157
- const [cards, setCards] = useState<StoryCardWithId[]>([])
150
+ const [cards, setCards] = useState<StoryCardWithKey[]>([])
158
151
 
159
152
  const contextValue: StoryCardContextValue = useMemo(
160
153
  () => ({
161
154
  addCard(card) {
162
- const id = `story-card-${crypto.randomUUID()}`
163
- setCards((cards) => [...cards, { ...card, id }])
164
- return id
155
+ const key = generateKey('story-card')
156
+ setCards((cards) => [...cards, { ...card, key }])
157
+ return key
165
158
  },
166
- removeCard(id) {
167
- setCards((cards) => cards.filter((card) => card.id !== id))
159
+ removeCard(key) {
160
+ setCards((cards) => cards.filter((card) => card.key !== key))
168
161
  }
169
162
  }),
170
163
  []
@@ -173,8 +166,8 @@ function StoryCardContainer({ children }: { children: ReactNode }) {
173
166
  return (
174
167
  <StoryCardContext.Provider value={contextValue}>
175
168
  <div className="flex flex-col gap-2">
176
- {cards.map(({ id, status, className, content, title }) => (
177
- <section key={id} className={storyCardTheme({ status }, className)}>
169
+ {cards.map(({ key, status, className, content, title }) => (
170
+ <section key={key} className={storyCardTheme({ status }, className)}>
178
171
  {title && <h2 className="text-lg font-bold">{title}</h2>}
179
172
  {content}
180
173
  </section>
@@ -185,7 +178,27 @@ function StoryCardContainer({ children }: { children: ReactNode }) {
185
178
  )
186
179
  }
187
180
 
188
- interface StoryCardCollectorProps extends StoryCardProps {
181
+ type StoryCardWithKey = RequiredPick<StoryCardProps, 'status'> & { key: string }
182
+
183
+ function storyCardTheme(state: Required<Pick<StoryCardProps, 'status'>>, className: StoryCardProps['className']) {
184
+ const defaultClassName = storyCardVariants(state)
185
+ if (!className) return defaultClassName
186
+ return typeof className === 'function'
187
+ ? className({ ...state, defaultClassName })
188
+ : twMerge(defaultClassName, className)
189
+ }
190
+
191
+ const storyCardVariants = cva('flex flex-col gap-1 py-3 px-4 rounded text-black dark:text-gray-100', {
192
+ variants: {
193
+ status: {
194
+ error: 'bg-red-100 dark:bg-red-900',
195
+ warn: 'bg-yellow-100 dark:bg-yellow-900',
196
+ info: 'bg-sky-100 dark:bg-sky-900'
197
+ }
198
+ }
199
+ })
200
+
201
+ interface StoryCardCollectorProps extends RequiredPick<StoryCardProps, 'status'> {
189
202
  Story: ComponentType
190
203
  }
191
204
 
@@ -212,24 +225,9 @@ function StoryCardCollector({ Story, title, status, className, content }: StoryC
212
225
  return <Story />
213
226
  }
214
227
 
215
- const storyCardTheme = (state: Pick<StoryCardProps, 'status'>, className: StoryCardProps['className']) => {
216
- const defaultClassName = storyCardVariants(state)
217
- if (!className) return defaultClassName
218
- return twMerge(
219
- defaultClassName,
220
- typeof className === 'function' ? className({ ...state, defaultClassName }) : className
221
- )
228
+ interface StoryCardContextValue {
229
+ addCard: (card: RequiredPick<StoryCardProps, 'status'>) => string
230
+ removeCard: (id: string) => void
222
231
  }
223
232
 
224
- const storyCardVariants = cva('flex flex-col gap-1 py-3 px-4 rounded text-black dark:text-gray-100', {
225
- variants: {
226
- status: {
227
- error: 'bg-red-100 dark:bg-red-900',
228
- warn: 'bg-yellow-100 dark:bg-yellow-900',
229
- info: 'bg-sky-100 dark:bg-sky-900'
230
- }
231
- },
232
- defaultVariants: {
233
- status: 'info'
234
- }
235
- })
233
+ const StoryCardContext = createContext<StoryCardContextValue | null>(null)
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Generates a key for React collections, falling back to a simple counter-based ID if crypto.randomUUID is unavailable.
3
+ * crypto.randomUUID() requires a secure context (HTTPS, localhost, or 127.0.0.1).
4
+ *
5
+ * This can be moved to `@just-web` in the future.
6
+ */
7
+ export function generateKey(prefix?: string | undefined): string {
8
+ const randomId =
9
+ typeof crypto !== 'undefined' && crypto.randomUUID
10
+ ? crypto.randomUUID()
11
+ : `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`
12
+
13
+ return prefix ? `${prefix}-${randomId}` : randomId
14
+ }