@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 +3 -3
- package/esm/index.js +36 -25
- package/package.json +8 -7
- package/src/decorators/with_story_card.tsx +40 -42
- package/src/utils/generate_key.ts +14 -0
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) |
|
|
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
|
|
180
|
+
const key = generateKey("story-card");
|
|
168
181
|
setCards((cards$1) => [...cards$1, {
|
|
169
182
|
...card,
|
|
170
|
-
|
|
183
|
+
key
|
|
171
184
|
}]);
|
|
172
|
-
return
|
|
185
|
+
return key;
|
|
173
186
|
},
|
|
174
|
-
removeCard(
|
|
175
|
-
setCards((cards$1) => cards$1.filter((card) => card.
|
|
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(({
|
|
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
|
-
},
|
|
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
|
|
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.
|
|
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.
|
|
63
|
-
"@storybook/addon-vitest": "^10.
|
|
64
|
-
"@storybook/react-vite": "^10.
|
|
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.
|
|
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.
|
|
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'
|
|
38
|
-
|
|
|
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<
|
|
150
|
+
const [cards, setCards] = useState<StoryCardWithKey[]>([])
|
|
158
151
|
|
|
159
152
|
const contextValue: StoryCardContextValue = useMemo(
|
|
160
153
|
() => ({
|
|
161
154
|
addCard(card) {
|
|
162
|
-
const
|
|
163
|
-
setCards((cards) => [...cards, { ...card,
|
|
164
|
-
return
|
|
155
|
+
const key = generateKey('story-card')
|
|
156
|
+
setCards((cards) => [...cards, { ...card, key }])
|
|
157
|
+
return key
|
|
165
158
|
},
|
|
166
|
-
removeCard(
|
|
167
|
-
setCards((cards) => cards.filter((card) => card.
|
|
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(({
|
|
177
|
-
<section key={
|
|
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
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
|
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
|
+
}
|