@repobuddy/storybook 2.2.1 → 2.3.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
@@ -4,8 +4,8 @@ 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";
8
+ import { IsStringLiteral } from "type-plus";
9
9
  export * from "@repobuddy/test";
10
10
 
11
11
  //#region src/components/show_html.d.ts
@@ -56,9 +56,9 @@ type StoryCardProps = {
56
56
  * If a function is provided, it receives the card state and default className,
57
57
  * and should return the final className string.
58
58
  */
59
- className?: ((state: Pick<StoryCardProps, 'status'> & {
59
+ className?: ((state: Required<Pick<StoryCardProps, 'status'>> & {
60
60
  defaultClassName: string;
61
- }) => string) | ClassValue | undefined;
61
+ }) => string) | string | undefined;
62
62
  /**
63
63
  * Content to display in the card body.
64
64
  * Can be any React node (string, JSX, etc.).
@@ -137,6 +137,7 @@ type StoryCardProps = {
137
137
  */
138
138
  declare function withStoryCard<TRenderer extends Renderer = Renderer>({
139
139
  title,
140
+ status,
140
141
  content: contentProp,
141
142
  ...rest
142
143
  }?: StoryCardProps): DecoratorFunction<TRenderer>;
@@ -645,6 +646,8 @@ declare function whenRunningInTest<TArgs = StrictArgs>(decoratorOrHandler: ((...
645
646
  * @template M - The base Meta type
646
647
  * @template E - The extension type containing tagType
647
648
  *
649
+ * @deprecated use `import { ExtendsMeta } from '@repobuddy/storybook'` instead.
650
+ *
648
651
  * @example
649
652
  * ```ts
650
653
  * // Create a generic Meta type for a project
@@ -665,6 +668,8 @@ type ExtendMeta<TCmpOrArgs, M extends Meta<TCmpOrArgs>, E extends {
665
668
  * @template S - The base StoryObj type
666
669
  * @template E - The extension type containing tagType
667
670
  *
671
+ * @deprecated use `import { ExtendsStoryObj } from '@repobuddy/storybook'` instead.
672
+ *
668
673
  * @example
669
674
  * ```ts
670
675
  * // Create a generic StoryObj type for a project
@@ -680,4 +685,79 @@ type ExtendStoryObj<TMetaOrCmpOrArgs, S extends StoryObj<TMetaOrCmpOrArgs>, E ex
680
685
  tags?: Array<E['tag'] | (string & {})> | undefined;
681
686
  };
682
687
  //#endregion
683
- export { ActionsParam, BackgroundsParam, DocsParam, ExtendMeta, ExtendStoryObj, GlobalApiBackgroundsParam, GlobalApiViewportParam, LayoutParam, ShowHtml, ShowHtmlProps, SourceProps, StoryCardProps, StorySortParam, StorybookBuiltInParams, TestParam, Viewport, ViewportParam, defineActionsParam, defineBackgroundsParam, defineDocsParam, defineLayoutParam, defineParameters, defineTestParam, defineViewportParam, showDocSource, whenRunningInTest, withStoryCard };
688
+ //#region src/types/_extract_string_literals.d.ts
689
+ type ExtractStringLiterals<T> = T extends any ? (string extends T ? never : T) : never;
690
+ //#endregion
691
+ //#region src/types/extends_meta.d.ts
692
+ /**
693
+ * Extends the Storybook Meta type with custom tag types.
694
+ *
695
+ * This utility type allows you to extend the `tags` property of a Storybook Meta type
696
+ * with custom string literal types while preserving existing tag types from the base Meta.
697
+ *
698
+ * @template M - The base Meta type to extend
699
+ * @template E - The extension type containing a `tag` property with the custom tag types
700
+ *
701
+ * @example
702
+ * ```ts
703
+ * import type { ExtendsMeta } from '@repobuddy/storybook'
704
+ * import type { Args, Meta as M } from '@storybook/your-framework'
705
+ *
706
+ * // Create a generic Meta type for your project
707
+ * type Meta<TCmpOrArgs = Args> = ExtendsMeta<
708
+ * M<TCmpOrArgs>,
709
+ * { tag: 'new' | 'beta' | 'deprecated' }
710
+ * >
711
+ *
712
+ * // Use in component stories
713
+ * const meta: Meta<typeof Component> = {
714
+ * tags: ['new'], // <--- gets auto-completion for 'new' | 'beta' | 'deprecated'
715
+ * // ...
716
+ * }
717
+ * ```
718
+ */
719
+ type ExtendsMeta<M extends {
720
+ tags?: string[] | undefined;
721
+ }, E extends {
722
+ tag: string;
723
+ }> = Omit<M, 'tags'> & {
724
+ tags?: ExtractStringLiterals<NonNullable<M['tags']>[number]> extends infer MT ? IsStringLiteral<MT> extends true ? Array<(string & {}) | MT | E['tag']> | undefined : Array<(string & {}) | E['tag']> | undefined : never;
725
+ };
726
+ //#endregion
727
+ //#region src/types/extends_story_obj.d.ts
728
+ /**
729
+ * Extends the Storybook StoryObj type with custom tag types.
730
+ *
731
+ * This utility type allows you to extend the `tags` property of a Storybook StoryObj type
732
+ * with custom string literal types while preserving existing tag types from the base StoryObj.
733
+ *
734
+ * @template S - The base StoryObj type to extend (must have an optional `tags` property)
735
+ * @template E - The extension type containing a `tag` property with the custom tag types
736
+ *
737
+ * @example
738
+ * ```ts
739
+ * import type { ExtendsStoryObj } from '@repobuddy/storybook'
740
+ * import type { Args, StoryObj as S } from '@storybook/your-framework'
741
+ *
742
+ * // Create a generic StoryObj type for your project
743
+ * type StoryObj<TCmpOrArgs = Args> = ExtendsStoryObj<
744
+ * S<TCmpOrArgs>,
745
+ * { tag: 'new' | 'beta' | 'deprecated' }
746
+ * >
747
+ *
748
+ * // Use in component stories
749
+ * const story: StoryObj<typeof Component> = {
750
+ * tags: ['new'], // <--- gets auto-completion for 'new' | 'beta' | 'deprecated'
751
+ * // ...
752
+ * }
753
+ * ```
754
+ */
755
+ type ExtendsStoryObj<S extends {
756
+ tags?: string[] | undefined;
757
+ }, E extends {
758
+ tag: string;
759
+ }> = Omit<S, 'tags'> & {
760
+ tags?: ExtractStringLiterals<NonNullable<S['tags']>[number]> extends infer MT ? IsStringLiteral<MT> extends true ? Array<(string & {}) | MT | E['tag']> | undefined : Array<(string & {}) | E['tag']> | undefined : never;
761
+ };
762
+ //#endregion
763
+ export { ActionsParam, BackgroundsParam, DocsParam, ExtendMeta, ExtendStoryObj, ExtendsMeta, ExtendsStoryObj, GlobalApiBackgroundsParam, GlobalApiViewportParam, LayoutParam, ShowHtml, ShowHtmlProps, SourceProps, StoryCardProps, StorySortParam, StorybookBuiltInParams, TestParam, Viewport, ViewportParam, defineActionsParam, defineBackgroundsParam, defineDocsParam, defineLayoutParam, defineParameters, defineTestParam, defineViewportParam, showDocSource, whenRunningInTest, withStoryCard };
package/esm/index.js CHANGED
@@ -150,7 +150,7 @@ function generateKey(prefix) {
150
150
  * or fall back to the component description.
151
151
  * - Cards are collected and displayed in the order they are defined in the decorators array.
152
152
  */
153
- function withStoryCard({ title, content: contentProp, ...rest } = {}) {
153
+ function withStoryCard({ title, status = "info", content: contentProp, ...rest } = {}) {
154
154
  return (Story, { parameters, viewMode }) => {
155
155
  if (viewMode === "docs") return /* @__PURE__ */ jsx(Story, {});
156
156
  const content = contentProp ?? parameters.docs?.description?.story ?? parameters.docs?.description?.component;
@@ -159,6 +159,7 @@ function withStoryCard({ title, content: contentProp, ...rest } = {}) {
159
159
  Story,
160
160
  content,
161
161
  title,
162
+ status,
162
163
  ...rest
163
164
  });
164
165
  };
@@ -176,47 +177,44 @@ function StoryCardContainer({ children }) {
176
177
  const [cards, setCards] = useState([]);
177
178
  const contextValue = useMemo(() => ({
178
179
  addCard(card) {
179
- const id = generateKey("story-card");
180
+ const key = generateKey("story-card");
180
181
  setCards((cards$1) => [...cards$1, {
181
182
  ...card,
182
- id
183
+ key
183
184
  }]);
184
- return id;
185
+ return key;
185
186
  },
186
- removeCard(id) {
187
- setCards((cards$1) => cards$1.filter((card) => card.id !== id));
187
+ removeCard(key) {
188
+ setCards((cards$1) => cards$1.filter((card) => card.key !== key));
188
189
  }
189
190
  }), []);
190
191
  return /* @__PURE__ */ jsx(StoryCardContext.Provider, {
191
192
  value: contextValue,
192
193
  children: /* @__PURE__ */ jsxs("div", {
193
194
  className: "flex flex-col gap-2",
194
- children: [cards.map(({ id, status, className, content, title }) => /* @__PURE__ */ jsxs("section", {
195
+ children: [cards.map(({ key, status, className, content, title }) => /* @__PURE__ */ jsxs("section", {
195
196
  className: storyCardTheme({ status }, className),
196
197
  children: [title && /* @__PURE__ */ jsx("h2", {
197
198
  className: "text-lg font-bold",
198
199
  children: title
199
200
  }), content]
200
- }, id)), children]
201
+ }, key)), children]
201
202
  })
202
203
  });
203
204
  }
204
- const storyCardTheme = (state, className) => {
205
+ function storyCardTheme(state, className) {
205
206
  const defaultClassName = storyCardVariants(state);
206
207
  if (!className) return defaultClassName;
207
- return twMerge(defaultClassName, typeof className === "function" ? className({
208
+ return typeof className === "function" ? className({
208
209
  ...state,
209
210
  defaultClassName
210
- }) : className);
211
- };
212
- const storyCardVariants = cva("flex flex-col gap-1 py-3 px-4 rounded text-black dark:text-gray-100", {
213
- 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
- } },
218
- defaultVariants: { status: "info" }
219
- });
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
+ } } });
220
218
  function StoryCardCollector({ Story, title, status, className, content }) {
221
219
  const context = useContext(StoryCardContext);
222
220
  const cardIdRef = useRef(null);
@@ -1,6 +1,7 @@
1
1
 
2
2
  import { TagBadgeParameters } from "storybook-addon-tag-badges/manager-helpers";
3
3
  import { Args, Meta as Meta$1, StoryObj as StoryObj$1 } from "@storybook/react-vite";
4
+ import { IsStringLiteral } from "type-plus";
4
5
 
5
6
  //#region src/storybook-addon-tag-badges/tag_badges.d.ts
6
7
  type TagBadgeParameter = TagBadgeParameters[0];
@@ -44,32 +45,14 @@ declare const internalBadge: TagBadgeParameter;
44
45
  declare const tagBadges: TagBadgeParameters;
45
46
  //#endregion
46
47
  //#region src/types.d.ts
47
- /**
48
- * Extends the Storybook Meta type with custom tag types
49
- * @template TCmpOrArgs - The component or args type
50
- * @template M - The base Meta type
51
- * @template E - The extension type containing tagType
52
- *
53
- * @example
54
- * ```ts
55
- * // Create a generic Meta type for a project
56
- * type Meta<TCmpOrArgs = Args> = ExtendMeta<TCmpOrArgs, Meta<TCmpOrArgs>, { tagType: 'tag1' | 'tag2' }>
57
- *
58
- * // Create a specific Meta type for a component
59
- * type Meta = ExtendMeta<typeof Component, Meta<typeof Component>, { tagType: 'tag1' | 'tag2' }>
60
- * ```
61
- */
62
- type ExtendMeta<TCmpOrArgs, M extends Meta$1<TCmpOrArgs>, E extends {
63
- tag: string;
64
- }> = Omit<M, 'tags'> & {
65
- tags?: Array<E['tag'] | (string & {})> | undefined;
66
- };
67
48
  /**
68
49
  * Extends the Storybook StoryObj type with custom tag types
69
50
  * @template TMetaOrCmpOrArgs - The meta, component or args type
70
51
  * @template S - The base StoryObj type
71
52
  * @template E - The extension type containing tagType
72
53
  *
54
+ * @deprecated use `import { ExtendsStoryObj } from '@repobuddy/storybook'` instead.
55
+ *
73
56
  * @example
74
57
  * ```ts
75
58
  * // Create a generic StoryObj type for a project
@@ -85,8 +68,47 @@ type ExtendStoryObj<TMetaOrCmpOrArgs, S extends StoryObj$1<TMetaOrCmpOrArgs>, E
85
68
  tags?: Array<E['tag'] | (string & {})> | undefined;
86
69
  };
87
70
  //#endregion
71
+ //#region src/types/_extract_string_literals.d.ts
72
+ type ExtractStringLiterals<T> = T extends any ? (string extends T ? never : T) : never;
73
+ //#endregion
74
+ //#region src/types/extends_meta.d.ts
75
+ /**
76
+ * Extends the Storybook Meta type with custom tag types.
77
+ *
78
+ * This utility type allows you to extend the `tags` property of a Storybook Meta type
79
+ * with custom string literal types while preserving existing tag types from the base Meta.
80
+ *
81
+ * @template M - The base Meta type to extend
82
+ * @template E - The extension type containing a `tag` property with the custom tag types
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * import type { ExtendsMeta } from '@repobuddy/storybook'
87
+ * import type { Args, Meta as M } from '@storybook/your-framework'
88
+ *
89
+ * // Create a generic Meta type for your project
90
+ * type Meta<TCmpOrArgs = Args> = ExtendsMeta<
91
+ * M<TCmpOrArgs>,
92
+ * { tag: 'new' | 'beta' | 'deprecated' }
93
+ * >
94
+ *
95
+ * // Use in component stories
96
+ * const meta: Meta<typeof Component> = {
97
+ * tags: ['new'], // <--- gets auto-completion for 'new' | 'beta' | 'deprecated'
98
+ * // ...
99
+ * }
100
+ * ```
101
+ */
102
+ type ExtendsMeta<M extends {
103
+ tags?: string[] | undefined;
104
+ }, E extends {
105
+ tag: string;
106
+ }> = Omit<M, 'tags'> & {
107
+ tags?: ExtractStringLiterals<NonNullable<M['tags']>[number]> extends infer MT ? IsStringLiteral<MT> extends true ? Array<(string & {}) | MT | E['tag']> | undefined : Array<(string & {}) | E['tag']> | undefined : never;
108
+ };
109
+ //#endregion
88
110
  //#region src/storybook-addon-tag-badges/types.d.ts
89
- type Meta<TCmpOrArgs = Args> = ExtendMeta<TCmpOrArgs, Meta$1<TCmpOrArgs>, {
111
+ type Meta<TCmpOrArgs = Args> = ExtendsMeta<Meta$1<TCmpOrArgs>, {
90
112
  tag: TagNames;
91
113
  }>;
92
114
  type StoryObj<TMetaOrCmpOrArgs = Args> = ExtendStoryObj<TMetaOrCmpOrArgs, StoryObj$1<TMetaOrCmpOrArgs>, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@repobuddy/storybook",
3
- "version": "2.2.1",
3
+ "version": "2.3.0",
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,7 +11,8 @@ import {
12
11
  } from 'react'
13
12
  import type { DecoratorFunction, Renderer } from 'storybook/internal/csf'
14
13
  import { twMerge } from 'tailwind-merge'
15
- import { generateKey } from '../utils/generate_key'
14
+ import type { RequiredPick } from 'type-plus'
15
+ import { generateKey } from '../utils/generate_key.js'
16
16
 
17
17
  export type StoryCardProps = {
18
18
  /**
@@ -35,8 +35,8 @@ export type StoryCardProps = {
35
35
  * and should return the final className string.
36
36
  */
37
37
  className?:
38
- | ((state: Pick<StoryCardProps, 'status'> & { defaultClassName: string }) => string)
39
- | ClassValue
38
+ | ((state: Required<Pick<StoryCardProps, 'status'>> & { defaultClassName: string }) => string)
39
+ | string
40
40
  | undefined
41
41
  /**
42
42
  * Content to display in the card body.
@@ -117,6 +117,7 @@ export type StoryCardProps = {
117
117
  */
118
118
  export function withStoryCard<TRenderer extends Renderer = Renderer>({
119
119
  title,
120
+ status = 'info',
120
121
  content: contentProp,
121
122
  ...rest
122
123
  }: StoryCardProps = {}): DecoratorFunction<TRenderer> {
@@ -126,11 +127,11 @@ export function withStoryCard<TRenderer extends Renderer = Renderer>({
126
127
  const content = contentProp ?? parameters.docs?.description?.story ?? parameters.docs?.description?.component
127
128
  if (!content && !title) return <Story />
128
129
 
129
- return <StoryCardContainerWrapper Story={Story} content={content} title={title} {...rest} />
130
+ return <StoryCardContainerWrapper Story={Story} content={content} title={title} status={status} {...rest} />
130
131
  }
131
132
  }
132
133
 
133
- interface StoryCardContainerWrapperProps extends StoryCardProps {
134
+ interface StoryCardContainerWrapperProps extends RequiredPick<StoryCardProps, 'status'> {
134
135
  Story: ComponentType
135
136
  }
136
137
 
@@ -146,17 +147,17 @@ function StoryCardContainerWrapper({ Story, ...props }: StoryCardContainerWrappe
146
147
  }
147
148
 
148
149
  function StoryCardContainer({ children }: { children: ReactNode }) {
149
- const [cards, setCards] = useState<StoryCardWithId[]>([])
150
+ const [cards, setCards] = useState<StoryCardWithKey[]>([])
150
151
 
151
152
  const contextValue: StoryCardContextValue = useMemo(
152
153
  () => ({
153
154
  addCard(card) {
154
- const id = generateKey('story-card')
155
- setCards((cards) => [...cards, { ...card, id }])
156
- return id
155
+ const key = generateKey('story-card')
156
+ setCards((cards) => [...cards, { ...card, key }])
157
+ return key
157
158
  },
158
- removeCard(id) {
159
- setCards((cards) => cards.filter((card) => card.id !== id))
159
+ removeCard(key) {
160
+ setCards((cards) => cards.filter((card) => card.key !== key))
160
161
  }
161
162
  }),
162
163
  []
@@ -165,8 +166,8 @@ function StoryCardContainer({ children }: { children: ReactNode }) {
165
166
  return (
166
167
  <StoryCardContext.Provider value={contextValue}>
167
168
  <div className="flex flex-col gap-2">
168
- {cards.map(({ id, status, className, content, title }) => (
169
- <section key={id} className={storyCardTheme({ status }, className)}>
169
+ {cards.map(({ key, status, className, content, title }) => (
170
+ <section key={key} className={storyCardTheme({ status }, className)}>
170
171
  {title && <h2 className="text-lg font-bold">{title}</h2>}
171
172
  {content}
172
173
  </section>
@@ -177,15 +178,14 @@ function StoryCardContainer({ children }: { children: ReactNode }) {
177
178
  )
178
179
  }
179
180
 
180
- type StoryCardWithId = StoryCardProps & { id: string }
181
+ type StoryCardWithKey = RequiredPick<StoryCardProps, 'status'> & { key: string }
181
182
 
182
- const storyCardTheme = (state: Pick<StoryCardProps, 'status'>, className: StoryCardProps['className']) => {
183
+ function storyCardTheme(state: Required<Pick<StoryCardProps, 'status'>>, className: StoryCardProps['className']) {
183
184
  const defaultClassName = storyCardVariants(state)
184
185
  if (!className) return defaultClassName
185
- return twMerge(
186
- defaultClassName,
187
- typeof className === 'function' ? className({ ...state, defaultClassName }) : className
188
- )
186
+ return typeof className === 'function'
187
+ ? className({ ...state, defaultClassName })
188
+ : twMerge(defaultClassName, className)
189
189
  }
190
190
 
191
191
  const storyCardVariants = cva('flex flex-col gap-1 py-3 px-4 rounded text-black dark:text-gray-100', {
@@ -195,13 +195,10 @@ const storyCardVariants = cva('flex flex-col gap-1 py-3 px-4 rounded text-black
195
195
  warn: 'bg-yellow-100 dark:bg-yellow-900',
196
196
  info: 'bg-sky-100 dark:bg-sky-900'
197
197
  }
198
- },
199
- defaultVariants: {
200
- status: 'info'
201
198
  }
202
199
  })
203
200
 
204
- interface StoryCardCollectorProps extends StoryCardProps {
201
+ interface StoryCardCollectorProps extends RequiredPick<StoryCardProps, 'status'> {
205
202
  Story: ComponentType
206
203
  }
207
204
 
@@ -229,7 +226,7 @@ function StoryCardCollector({ Story, title, status, className, content }: StoryC
229
226
  }
230
227
 
231
228
  interface StoryCardContextValue {
232
- addCard: (card: StoryCardProps) => string
229
+ addCard: (card: RequiredPick<StoryCardProps, 'status'>) => string
233
230
  removeCard: (id: string) => void
234
231
  }
235
232
 
package/src/index.ts CHANGED
@@ -12,3 +12,5 @@ export * from './parameters/define_viewport_param.ts'
12
12
  export * from './parameters/story_sort.ts'
13
13
  export * from './testing/decorators/when_running_in_test.tsx'
14
14
  export type * from './types.ts'
15
+ export * from './types/extends_meta.ts'
16
+ export * from './types/extends_story_obj.ts'
@@ -1,8 +1,9 @@
1
1
  import type { Args, Meta as M, StoryObj as SBO } from '@storybook/react-vite'
2
- import type { ExtendMeta, ExtendStoryObj } from '../types.js'
2
+ import type { ExtendStoryObj } from '../types.js'
3
+ import type { ExtendsMeta } from '../types/extends_meta.js'
3
4
  import type { TagNames } from './tag_badges.js'
4
5
 
5
- export type Meta<TCmpOrArgs = Args> = ExtendMeta<TCmpOrArgs, M<TCmpOrArgs>, { tag: TagNames }>
6
+ export type Meta<TCmpOrArgs = Args> = ExtendsMeta<M<TCmpOrArgs>, { tag: TagNames }>
6
7
 
7
8
  export type StoryObj<TMetaOrCmpOrArgs = Args> = ExtendStoryObj<
8
9
  TMetaOrCmpOrArgs,
@@ -0,0 +1 @@
1
+ export type ExtractStringLiterals<T> = T extends any ? (string extends T ? never : T) : never
@@ -0,0 +1,37 @@
1
+ import type { IsStringLiteral } from 'type-plus'
2
+ import type { ExtractStringLiterals } from './_extract_string_literals.js'
3
+
4
+ /**
5
+ * Extends the Storybook Meta type with custom tag types.
6
+ *
7
+ * This utility type allows you to extend the `tags` property of a Storybook Meta type
8
+ * with custom string literal types while preserving existing tag types from the base Meta.
9
+ *
10
+ * @template M - The base Meta type to extend
11
+ * @template E - The extension type containing a `tag` property with the custom tag types
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * import type { ExtendsMeta } from '@repobuddy/storybook'
16
+ * import type { Args, Meta as M } from '@storybook/your-framework'
17
+ *
18
+ * // Create a generic Meta type for your project
19
+ * type Meta<TCmpOrArgs = Args> = ExtendsMeta<
20
+ * M<TCmpOrArgs>,
21
+ * { tag: 'new' | 'beta' | 'deprecated' }
22
+ * >
23
+ *
24
+ * // Use in component stories
25
+ * const meta: Meta<typeof Component> = {
26
+ * tags: ['new'], // <--- gets auto-completion for 'new' | 'beta' | 'deprecated'
27
+ * // ...
28
+ * }
29
+ * ```
30
+ */
31
+ export type ExtendsMeta<M extends { tags?: string[] | undefined }, E extends { tag: string }> = Omit<M, 'tags'> & {
32
+ tags?: ExtractStringLiterals<NonNullable<M['tags']>[number]> extends infer MT
33
+ ? IsStringLiteral<MT> extends true
34
+ ? Array<(string & {}) | MT | E['tag']> | undefined
35
+ : Array<(string & {}) | E['tag']> | undefined
36
+ : never
37
+ }
@@ -0,0 +1,42 @@
1
+ import type { IsStringLiteral } from 'type-plus'
2
+ import type { ExtractStringLiterals } from './_extract_string_literals.js'
3
+
4
+ /**
5
+ * Extends the Storybook StoryObj type with custom tag types.
6
+ *
7
+ * This utility type allows you to extend the `tags` property of a Storybook StoryObj type
8
+ * with custom string literal types while preserving existing tag types from the base StoryObj.
9
+ *
10
+ * @template S - The base StoryObj type to extend (must have an optional `tags` property)
11
+ * @template E - The extension type containing a `tag` property with the custom tag types
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * import type { ExtendsStoryObj } from '@repobuddy/storybook'
16
+ * import type { Args, StoryObj as S } from '@storybook/your-framework'
17
+ *
18
+ * // Create a generic StoryObj type for your project
19
+ * type StoryObj<TCmpOrArgs = Args> = ExtendsStoryObj<
20
+ * S<TCmpOrArgs>,
21
+ * { tag: 'new' | 'beta' | 'deprecated' }
22
+ * >
23
+ *
24
+ * // Use in component stories
25
+ * const story: StoryObj<typeof Component> = {
26
+ * tags: ['new'], // <--- gets auto-completion for 'new' | 'beta' | 'deprecated'
27
+ * // ...
28
+ * }
29
+ * ```
30
+ */
31
+ export type ExtendsStoryObj<
32
+ S extends { tags?: string[] | undefined },
33
+ E extends {
34
+ tag: string
35
+ }
36
+ > = Omit<S, 'tags'> & {
37
+ tags?: ExtractStringLiterals<NonNullable<S['tags']>[number]> extends infer MT
38
+ ? IsStringLiteral<MT> extends true
39
+ ? Array<(string & {}) | MT | E['tag']> | undefined
40
+ : Array<(string & {}) | E['tag']> | undefined
41
+ : never
42
+ }
package/src/types.ts CHANGED
@@ -6,6 +6,8 @@ import type { Meta, StoryObj } from '@storybook/react-vite'
6
6
  * @template M - The base Meta type
7
7
  * @template E - The extension type containing tagType
8
8
  *
9
+ * @deprecated use `import { ExtendsMeta } from '@repobuddy/storybook'` instead.
10
+ *
9
11
  * @example
10
12
  * ```ts
11
13
  * // Create a generic Meta type for a project
@@ -31,6 +33,8 @@ export type ExtendMeta<
31
33
  * @template S - The base StoryObj type
32
34
  * @template E - The extension type containing tagType
33
35
  *
36
+ * @deprecated use `import { ExtendsStoryObj } from '@repobuddy/storybook'` instead.
37
+ *
34
38
  * @example
35
39
  * ```ts
36
40
  * // Create a generic StoryObj type for a project