Search code examples
reactjstypescriptstorybook

Type-safe extra control on a Story


I have a Chip component that accepts an extra element from its leftSection prop, a ReactNode. One of the possible uses is to render a ColorSwatch.

Then, I have the following story metadata:

const meta = {
  component: Chip,
  argTypes: {
    label: { control: 'text' },
    leftSection: {
      control: 'select',
      options: ['none', 'swatch'],
    },
    swatchColor: {
      control: 'color',
      if: { arg: 'leftSection', eq: 'swatch' },
    },
  },
} as Meta<Component>;

The Chip component doesn't have the swatchColor prop. It's defined only on the storybook for editing its value on the storybook itself.

But when writing the story object, it raises a type error:

export const Default: StoryObj<Component> = {
  render: (args) => {
    const { leftSection, swatchColor, ...rest } = args;
    //                   ^ type error here: Property 'swatchColor' does not exist

    if (leftSection === 'swatch') {
      return (
        <Chip
          leftSection={<ColorSwatch color={swatchColor} />}
          {...rest}
        />
      );
    }

    return <Chip {...rest} />;
  },

  args: {
    label: 'foo',
    leftSection: 'none',
  },
};

Despite it works, I would like to fix the type error at the same time that I have type safety.

Is there a way to have extra controls on the story and still make the TS/Storybook happy?


Solution

  • We can append on the component's Prop our custom story props, and use it instead of Component.

    import Chip, { Props } from './Chip';
    
    type CustomStoryProps = { swatchColor: string };
    type Component = Props & CustomStoryProps;
    
    const meta: Meta<Component> = {
      //        ^ type safer than using `as` ✅
    }
    
    export const Default: StoryObj<Component> = {
      render: (args) => {
        const { leftSection, swatchColor, ...rest } = args;
        //                   ^ no more errors ✅