Skip to content

Commit

Permalink
implement react, start on vue
Browse files Browse the repository at this point in the history
  • Loading branch information
kasperpeulen committed Sep 23, 2022
1 parent e10b790 commit 26a8200
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 8 deletions.
109 changes: 109 additions & 0 deletions code/renderers/react/src/__test__/CSF3.typetest.tsx
@@ -0,0 +1,109 @@
import React, { ComponentType, KeyboardEventHandler, ReactElement, ReactNode } from 'react';
import { Meta, Story } from '../public-types';

interface ButtonProps {
label: string;
disabled: boolean;
}

declare const Button: (props: ButtonProps) => JSX.Element;

/**
* Mimicking the satisfies operator.
*/
function satisfies<A>() {
return <T extends A>(x: T) => x;
}

// ✅ valid
export const meta1 = satisfies<Meta<typeof Button>>()({
component: Button,
args: { label: 'good', disabled: false },
});

export const Basic1: Story<typeof meta1> = {};

// // ✅ valid
export const meta2 = satisfies<Meta<typeof Button>>()({
component: Button,
args: { label: 'good' },
});

export const Basic2: Story<typeof meta2> = {
args: { disabled: false },
};

// ❌ invalid
const meta3 = satisfies<Meta<typeof Button>>()({
component: Button,
});

export const Basic3: Story<typeof meta3> = {
// @ts-expect-error disabled not provided
args: { label: 'good' },
};

// ❌ invalid
const meta4 = satisfies<Meta<typeof Button>>()({
component: Button,
args: { label: 'good' },
});

// @ts-expect-error disabled not provided
export const Basic4: Story<typeof meta4> = {};

// ❌ invalid
const meta5 = satisfies<Meta<ButtonProps>>()({
component: Button,
});

export const Basic5: Story<typeof meta5> = {
// @ts-expect-error disabled not provided
args: { label: 'good' },
};

interface ButtonProps2 {
label: string;
disabled: boolean;
onClick(): void;
onKeyDown: KeyboardEventHandler;
onLoading: (s: string) => JSX.Element;
submitAction(): void;
}

declare const Button2: (props: ButtonProps2) => JSX.Element;

// ✅ valid
const meta6 = satisfies<Meta<ButtonProps2>>()({
component: Button2,
args: { label: 'good' },
});

export const Basic6: Story<typeof meta6> = {
// all functions are optional, except onLoading
args: { disabled: false, onLoading: () => <div>Loading...</div> },
};

type ThemeData = 'light' | 'dark';
declare const Theme: (props: { theme: ThemeData; children?: ReactNode }) => JSX.Element;
type Props = ButtonProps & { theme: ThemeData };

export const meta7 = satisfies<Meta<Props>>()({
component: Button,
args: { label: 'good', disabled: false },
render: (args, { component }) => {
// component is not null as it is provided in meta
// TODO: Might be nice if we can infer that.
// eslint-disable-next-line
const Component = component!;
return (
<Theme theme={args.theme}>
<Component {...args} />
</Theme>
);
},
});

export const Basic: Story<typeof meta7, Props> = {
args: { theme: 'light' },
};
30 changes: 25 additions & 5 deletions code/renderers/react/src/public-types.ts
@@ -1,5 +1,5 @@
import { AnnotatedStoryFn, Args, ComponentAnnotations, StoryAnnotations } from '@storybook/csf';
import { ComponentProps, JSXElementConstructor } from 'react';
import { ComponentProps, ComponentType, JSXElementConstructor } from 'react';
import { ReactFramework } from './types';

type JSXElement = keyof JSX.IntrinsicElements | JSXElementConstructor<any>;
Expand All @@ -9,7 +9,12 @@ type JSXElement = keyof JSX.IntrinsicElements | JSXElementConstructor<any>;
*
* @see [Default export](https://storybook.js.org/docs/formats/component-story-format/#default-export)
*/
export type Meta<TArgs = Args> = ComponentAnnotations<ReactFramework, TArgs>;
export type Meta<
CmpOrArgs = Args,
StoryArgs = CmpOrArgs extends ComponentType<infer CmpArgs> ? CmpArgs : CmpOrArgs
> = CmpOrArgs extends ComponentType<infer CmpArgs>
? ComponentAnnotations<ReactFramework<CmpArgs>, StoryArgs>
: ComponentAnnotations<ReactFramework<CmpOrArgs>, StoryArgs>;

/**
* Story function that represents a CSFv2 component example.
Expand Down Expand Up @@ -54,14 +59,23 @@ export type ComponentStoryFn<T extends JSXElement> = StoryFn<ComponentProps<T>>;
*/
export type ComponentStoryObj<T extends JSXElement> = StoryObj<ComponentProps<T>>;

/**
/**
* Story function that represents a CSFv3 component example.
*
* @see [Named Story exports](https://storybook.js.org/docs/formats/component-story-format/#named-story-exports)
*/
export type Story<TArgs = Args> = StoryObj<TArgs>;
export type Story<
MetaOrArgs = Args,
StoryArgs = MetaOrArgs extends { component: ComponentType<infer CmpArgs> } ? CmpArgs : MetaOrArgs
> = MetaOrArgs extends {
component: ComponentType<infer CmpArgs>;
args?: infer D;
}
? StoryAnnotations<ReactFramework<CmpArgs>, StoryArgs> &
({} extends MakeOptional<StoryArgs, D & ActionArgs<StoryArgs>>
? unknown
: { args: MakeOptional<StoryArgs, D & ActionArgs<StoryArgs>> })
: StoryAnnotations<ReactFramework, MetaOrArgs>;

/**
* For the common case where a (CSFv3) story is a simple component that receives args as props:
Expand All @@ -72,3 +86,9 @@ export type Story<TArgs = Args> = StoryObj<TArgs>;
* }
* ```
*/ export type ComponentStory<T extends JSXElement> = ComponentStoryObj<T>;

type ActionArgs<Args> = {
[P in keyof Args as ((...args: any[]) => void) extends Args[P] ? P : never]: Args[P];
};

type MakeOptional<T, O> = Omit<T, keyof O> & Partial<Pick<T, Extract<keyof T, keyof O>>>;
4 changes: 2 additions & 2 deletions code/renderers/react/src/types.ts
Expand Up @@ -3,8 +3,8 @@ import type { ComponentType, ReactElement } from 'react';
export type { RenderContext } from '@storybook/store';
export type { StoryContext } from '@storybook/csf';

export type ReactFramework = {
component: ComponentType<any>;
export type ReactFramework<CmpArgs = any> = {
component: ComponentType<CmpArgs>;
storyResult: StoryFnReactReturnType;
};

Expand Down
17 changes: 16 additions & 1 deletion code/renderers/vue3/src/public-types.ts
@@ -1,13 +1,28 @@
import type { ComponentOptionsBase } from 'vue';
import type {
AnnotatedStoryFn,
Args,
ComponentAnnotations,
StoryAnnotations,
AnnotatedStoryFn,
} from '@storybook/csf';
import { VueFramework } from './types';

export type { Args, ArgTypes, Parameters, StoryContext } from '@storybook/csf';

// TODO
export type PropsOf<T> = T extends ComponentOptionsBase<
infer Props,
any,
any,
any,
any,
any,
any,
any
>
? Props
: never;

/**
* Metadata to configure the stories for a component.
*
Expand Down

0 comments on commit 26a8200

Please sign in to comment.