diff --git a/code/renderers/react/src/__test-dts__/CSF3.test-d.tsx b/code/renderers/react/src/__test-dts__/CSF3.test-d.tsx
index 7f1d97c40bcf..a5df412b1acc 100644
--- a/code/renderers/react/src/__test-dts__/CSF3.test-d.tsx
+++ b/code/renderers/react/src/__test-dts__/CSF3.test-d.tsx
@@ -85,9 +85,8 @@ describe('Story args can be inferred', () => {
const meta = satisfies>()({
component: Button,
- args: { label: 'good', disabled: false },
+ args: { disabled: false },
render: (args, { component }) => {
- // TODO: Might be nice if we can infer that.
// component is not null as it is provided in meta
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const Component = component!;
@@ -99,28 +98,53 @@ describe('Story args can be inferred', () => {
},
});
- const Basic: StoryObj = { args: { theme: 'light' } };
+ const Basic: StoryObj = { args: { theme: 'light', label: 'good' } };
});
+ const withDecorator: DecoratorFn<{ decoratorArg: number }> = (Story, { args }) => (
+ <>
+ Decorator: {args.decoratorArg}
+ This Story allows optional TArgs, but the decorator only knows about the decoratorArg. It
+ should really allow optionally a Partial of TArgs.
+
+ >
+ );
+
test('Correct args are inferred when type is widened for decorators', () => {
type Props = ButtonProps & { decoratorArg: number };
- const withDecorator: DecoratorFn<{ decoratorArg: number }> = (Story, { args }) => (
- <>
- Decorator: {args.decoratorArg}
- This Story allows optional TArgs, but the decorator only knows about the decoratorArg. It
- should really allow optionally a Partial of TArgs.
-
- >
- );
-
const meta = satisfies>()({
component: Button,
- args: { label: 'good', disabled: false },
+ args: { disabled: false },
decorators: [withDecorator],
});
// Yes, decorator arg is required
- const Basic: StoryObj = { args: { decoratorArg: 0 } };
+ const Basic: StoryObj = { args: { decoratorArg: 0, label: 'good' } };
+ });
+
+ test('Correct args are inferred when type is widened for decorators and render functions', () => {
+ type Props = ButtonProps & { decoratorArg: number } & { theme: ThemeData };
+
+ const meta = satisfies>()({
+ component: Button,
+ args: { disabled: false },
+ decorators: [withDecorator],
+ loaders: [async ({ args }: { args: { label: string } }) => ({ data: args.label })],
+ render: (args, { component }) => {
+ // component is not null as it is provided in meta
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ const Component = component!;
+ return (
+
+
+
+ );
+ },
+ });
+
+ const Basic: StoryObj = {
+ args: { decoratorArg: 0, label: 'good', theme: 'light' },
+ };
});
});
diff --git a/code/renderers/react/src/public-types.ts b/code/renderers/react/src/public-types.ts
index f8406ed20223..6d924f184163 100644
--- a/code/renderers/react/src/public-types.ts
+++ b/code/renderers/react/src/public-types.ts
@@ -1,7 +1,14 @@
-import { AnnotatedStoryFn, Args, ComponentAnnotations, StoryAnnotations } from '@storybook/csf';
+import type {
+ AnnotatedStoryFn,
+ Args,
+ ComponentAnnotations,
+ StoryAnnotations,
+ ArgsStoryFn,
+ DecoratorFunction,
+ LoaderFunction,
+} from '@storybook/csf';
import { ComponentType, JSXElementConstructor } from 'react';
import { ReactFramework } from './types';
-import { ArgsStoryFn, DecoratorFunction } from '../../../../../csf/src';
type JSXElement = keyof JSX.IntrinsicElements | JSXElementConstructor;
@@ -10,12 +17,9 @@ type JSXElement = keyof JSX.IntrinsicElements | JSXElementConstructor;
*
* @see [Default export](https://storybook.js.org/docs/formats/component-story-format/#default-export)
*/
-export type Meta<
- CmpOrArgs = Args,
- StoryArgs = CmpOrArgs extends ComponentType ? CmpArgs : CmpOrArgs
-> = CmpOrArgs extends ComponentType
- ? ComponentAnnotations, StoryArgs>
- : ComponentAnnotations, StoryArgs>;
+export type Meta = CmpOrArgs extends ComponentType
+ ? ComponentAnnotations
+ : ComponentAnnotations;
/**
* Story function that represents a CSFv2 component example.
@@ -29,23 +33,29 @@ export type StoryFn = AnnotatedStoryFn;
*
* @see [Named Story exports](https://storybook.js.org/docs/formats/component-story-format/#named-story-exports)
*/
+
export type StoryObj = MetaOrArgs extends {
- render?: ArgsStoryFn;
- decorators?: DecoratorFunction[];
+ render?: ArgsStoryFn;
+ decorators?: DecoratorFunction[];
component?: ComponentType;
- args?: infer D;
+ loaders?: LoaderFunction[];
+ args?: infer DefaultArgs;
}
- ? (unknown extends StoryArgs ? CmpArgs : StoryArgs) extends infer Args
- ? StoryAnnotations, Args> & StrictStoryArgs
+ ? CmpArgs & RArgs & DArgs & LArgs extends infer Args
+ ? StoryAnnotations<
+ ReactFramework,
+ Args,
+ SetOptional)>
+ >
: never
: StoryAnnotations;
-type StrictStoryArgs = {} extends MakeOptional>
- ? { args?: Partial }
- : { args: MakeOptional> };
-
type ActionArgs = {
[P in keyof Args as ((...args: any[]) => void) extends Args[P] ? P : never]: Args[P];
};
-type MakeOptional = Omit & Partial>>;
+type SetOptional = {
+ [P in keyof T as P extends K ? P : never]?: T[P];
+} & {
+ [P in keyof T as P extends K ? never : P]: T[P];
+};
diff --git a/code/renderers/react/src/types.ts b/code/renderers/react/src/types.ts
index 3d4dc6f7657e..ba8eb40ec0b2 100644
--- a/code/renderers/react/src/types.ts
+++ b/code/renderers/react/src/types.ts
@@ -1,12 +1,13 @@
import type { ComponentType, ReactElement } from 'react';
+import type { AnyFramework } from '@storybook/csf';
export type { RenderContext } from '@storybook/store';
export type { StoryContext } from '@storybook/csf';
-export type ReactFramework = {
- component: ComponentType;
- storyResult: StoryFnReactReturnType;
-};
+// export interface ReactFramework extends AnyFramework {
+// component: ComponentType;
+// storyResult: StoryFnReactReturnType;
+// }
export interface ShowErrorArgs {
title: string;
@@ -24,3 +25,23 @@ export interface IStorybookSection {
kind: string;
stories: IStorybookStory[];
}
+
+export type Framework = {
+ component: unknown;
+ T: unknown; // higher kinded type
+ storyResult: unknown;
+};
+
+export interface ReactFramework extends Framework {
+ component: ComponentType; // reference the higher kinded type
+ storyResult: StoryFnReactReturnType;
+}
+
+// used like this:
+interface ComponentAnnotations {
+ // specify the generic type with an intersection
+ component?: (TFramework & { T: TArgs })['component'];
+
+ // falls back to unknown, if you don't intersect T
+ subcomponents?: Record;
+}