storybookjs / storybook

Storybook is the industry standard workshop for building, documenting, and testing UI components in isolation
https://storybook.js.org
MIT License
84.49k stars 9.29k forks source link

Controls: string literal union types render as text fields when args supplied #12028

Open petermikitsh opened 4 years ago

petermikitsh commented 4 years ago

Describe the bug

For string literal union types, text fields should render as select dropdowns when args are supplied.

To Reproduce Steps to reproduce the behavior:

  1. Create a TypeScript React component with a string literal union type (e.g., 'foo' | 'bar' | 'baz').
  2. Create a story for the component and supply a value in <Story>.args
  3. Observe a text field containing the value

Expected behavior

In 3, I expect to see a dropdown instead of a text field (which is the behavior when args is not supplied)

Screenshots

Here I have a story with a component that has a string literal union type repeated for props icon, startIcon, and endIcon. Only icon is supplied via args (see code snippet below).

Screen Shot 2020-08-14 at 8 44 29 AM

Code snippets

interface ButtonProps {
  startIcon?: "fullscreen" | "style" | ...;
  endIcon?: "fullscreen" | "style" | ...;
  icon?: "fullscreen" | "style" | ...;
}

const Template = (args) => <Button {...args} />;

export const IconButton = Template.bind({});
IconButton.args = {
  icon: 'star',
  tooltip: 'Star Icon',
  dsOnClick: asyncAction('dsOnClick'),
};
shilman commented 4 years ago

Closing this as a duplicate to #12007

rssfrncs commented 3 years ago

Was this a duplicate? String unions are different to string enums and it appears that string unions are still rendered as text fields in storybook 6.1.17.

avallete commented 3 years ago

Was this a duplicate? String unions are different to string enums and it appears that string unions are still rendered as text fields in storybook 6.1.17.

I confim having the bug with storybook 6.2.9 && react-docgen-typecript-loader 3.7.2

shilman commented 3 years ago

Try upgrading to the latest prerelease:

npx sb@next upgrade --prerelease

Does that fix it?

AndranikA commented 3 years ago

I confirm having the bug after running npx sb@next upgrade --prerelease version 6.3.0-rc.2

tobiaskraus commented 3 years ago

I don't get it. I have one project with sb 6.2.9 where it doesn't work (union type props are shown in "controls" as strings) and one project with sb 6.2.9 where it works (shown as radio buttons).

I don't see any difference. Only the working project is built on create-react-app and uses '@storybook/preset-create-react-app', - but I don't think that's missing puzzle peace...

shilman commented 3 years ago

@tobiaskraus this is a bug in React-docgen-typescript which is an indirect dependency of storybook. Check the version of that in each project and that's probably your answer

tobiaskraus commented 3 years ago

thank you @shilman for the quick answer

but both times I could see in yarn.lock (and in node_modules/) that the resolved versions where exactly the same:

react-docgen-typescript-plugin@0.6.3
react-docgen-typescript@1.22.0
shilman commented 3 years ago

@tobiaskraus are the two use cases exactly the same? can you give an example from each project?

tobiaskraus commented 3 years ago

Project A

Modal.tsx

type Size = "lg" | "md" | "sm";

export interface ModalProps {
    size: Size;
   // ...
}

const ModalOpened: FunctionComponent<ModalProps> = (props: ModalProps) => {
    // ...
}

Modal.stories.tsx:

export default {
    component: Modal,
    title: "Components/modals/Modal",
} as Meta;

export const SmallEmpty: Story<ModalProps> = (args) => <Modal {...args} />;
SmallEmpty.args = {
    isOpen: false,
    size: "sm",
};

Project B

index.tsx

type Application = "draw" | "docu" | "other";

export interface LockBookHeaderProps {
    application: Application;
    // ...
}

export const LockBookHeader: React.ComponentType<LockBookHeaderProps> = (props: LockBookHeaderProps) => (
    // ...
)

Header.stories.tsx

export default {
    title: "LockBookHeader/LockBookHeader",
    component: LockBookHeader,
} as Meta;

const Template: Story<LockBookHeaderProps> = (args) => <LockBookHeader {...args} />;

export const Default: Story<LockBookHeaderProps> = Template.bind({});
Default.args = {
    application: "other",
};
BlackFenix2 commented 3 years ago

I saw this issue when using storybook. when i was at work the string literal union types rendered a radio as expected. When I got home i refactored my component and was able to reproduce the issue. it seems to be determined where you put the ':Props' identifier on the react component

React.FC doesnt generate the radio args

import React from 'react';

export type Props = {
  label: string;
  type?: 'button' | 'submit' | 'reset';
  className?: string;
};

const Button: React.FC<Props> = ({ type = 'button', className, label }) => (
  <button type={type} className={className}>
    {label}
  </button>
);

export default Button;

{ type = 'button', className, label }: Props generates the correct radio args

/* eslint-disable react/button-has-type */
import React from 'react';

export interface Props {
  label: string;
  type?: 'button' | 'submit' | 'reset';
  className?: string;
}

const Button = ({ type = 'button', className, label }: Props) => (
  <button type={type} className={className}>
    {label}
  </button>
);

export default Button;

methinks the typescript docgen is looking at the function argument types, when the type is applied to the function name storybook does not pickup the prop types.

this was all tested using the default CSF template.

Lazzaro83 commented 2 years ago

Was anyone able to solve this somehow? I am running storybook version 6.4.19 and still have this issue. Actually, I am having this issue if my type looks like this:

import type { IconType } from 'common-types';
export type IconProps = {
    name: IconType; // (storybook control panel shows input field with value that was passed as prop)
        ...other fields
}

But it is working if I define name property directly there as string literal union type:

export type IconProps = {
    name: 'add' | 'address' | 'clock'; // (storybook control panel shows select field with all the options and option passed as component prop is preselected)
        ...other fields
}
jarcoal commented 2 years ago

@Lazzaro83 can confirm that I'm seeing the exact issue you are. 6.4.19 as well.

arthurzakharov-zz commented 2 years ago

On 6.5.8 I still can see it

nextglabs commented 2 years ago

Same on 6.5.9

nowinskp commented 2 years ago

Can confirm the issue on 6.5.9 too.

aabuhijleh commented 1 year ago

On 6.5.10, still an issue

lawrence-ardosa-matter commented 1 year ago

Encountering on 6.5.15 @storybook/builder-vite & @storybook/preact

MateuszCieslak commented 1 year ago

Hey, I also have this issue on storybook 6.5.16. I would like to add when it happen: type/interface must be imported from external dependency (in my case we have some common packages where the type is). I found also when I change something in component code (like add space), SB rebuild and I refresh SB page it works fine...

ivesdebruycker commented 1 year ago

For me, it only works correctly when I use a named export of the component, even if I import it from the default import in the story:

export const Button =...;

export default Button;

Seems somewhat related to @tobiaskraus' case: export const LockBookHeader vs const ModalOpened

bmsuseluda commented 1 year ago

We have the same problem with compound components in storybook 7:

export type Size = "small" | "medium" | "large";
export type ListColor = "yellow" | "orange" | "green";

export interface RootProps {
  size: Size;
  decorationColor: ListColor;
}

const Root: React.FC<RootProps> = (props) => {
  ...
};

export const List = { Root, Body, Description };

When i use List.Root in the stories, the union types are not shown in storybook.

If i add an export to Root on top of the compound component export, it works.

export type Size = "small" | "medium" | "large";
export type ListColor = "yellow" | "orange" | "green";

export interface RootProps {
  size: Size;
  decorationColor: ListColor;
}

export const Root: React.FC<RootProps> = (props) => {
  ...
};

export const List = { Root, Body, Description };
asrytis commented 1 year ago

Same on 7.0.22 except it doesn't work even with named exports.

JackHowa commented 10 months ago

Seeing union in the description for ^7.5.3`

cjorge commented 8 months ago

I was getting this issue and I managed to fix it. The types are inferred by Typescript with react-docgen-typecript so, in case you are building a Wrapper component inside the stories.tsx the type will not be infered.

If you make a component file and then import it to stories.tsx then the infered typing will work.

Pourya-Alipanah commented 7 months ago

Still see union in the description for ^8.0.0and ^8.0.4 and not inferred size type image image image

mark7dev commented 5 months ago

I solved a similar issue by adding the following typescript configuration in the main.ts file inside the .stroybook folder

"Configures which library, if any, Storybook uses to parse React components, react-docgen or react-docgen-typescript. Set to false to disable parsing React components. react-docgen-typescript invokes the TypeScript compiler, which makes it slow but generally accurate. react-docgen performs its own analysis, which is much faster but incomplete."

Storybook Docs

image

image
geekyarjun commented 4 months ago

I solved a similar issue by adding the following typescript configuration in the main.ts file inside the .stroybook folder

"Configures which library, if any, Storybook uses to parse React components, react-docgen or react-docgen-typescript. Set to false to disable parsing React components. react-docgen-typescript invokes the TypeScript compiler, which makes it slow but generally accurate. react-docgen performs its own analysis, which is much faster but incomplete."

Storybook Docs

image

image

Hey, This worked for me.

janverhulst commented 2 months ago

I solved a similar issue by adding the following typescript configuration in the main.ts file inside the .stroybook folder

"Configures which library, if any, Storybook uses to parse React components, react-docgen or react-docgen-typescript. Set to false to disable parsing React components. react-docgen-typescript invokes the TypeScript compiler, which makes it slow but generally accurate. react-docgen performs its own analysis, which is much faster but incomplete."

Storybook Docs

image

image

Worked for me too, thanks!