Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

What's the recommended way to write dynamic stories and yield proper Story source code? #13657

Open
Vadorequest opened this issue Jan 16, 2021 · 3 comments

Comments

@Vadorequest
Copy link

There are many examples around about to write Stories, it's hard for newcomers to understand what is the recommended way, especially when we want to use dynamic stories with args.

Here is what I'm doing now, I think it's not too bad:

import {
  Meta,
  Story,
} from '@storybook/react/types-6-0';
import React from 'react';
import Btn from '../../../components/utils/Btn';
import SpoilerLink, { Props } from '../../../components/utils/SpoilerLink';

export default {
  title: 'Next Right Now/Form/SpoilerLink',
  component: SpoilerLink,
  argTypes: {},
} as Meta;

const Template: Story<Props> = (props) => {
  return (
    <SpoilerLink
      {...props}
    />
  );
};

export const DynamicExample: Story<Props> = Template.bind({});
DynamicExample.args = {
  previewElement: (
    <span>Spoil me!</span>
  ),
  spoilerElement: (
    <span>Arya Stark's phone number is 000000000</span>
  ),
  className: '',
  href: 'tel:000000000',
};

export const DynamicExampleWithBtn: Story<Props> = Template.bind({});
DynamicExampleWithBtn.args = {
  previewElement: (
    <Btn>Spoil me!</Btn>
  ),
  spoilerElement: (
    <Btn mode={'primary-reverse'}>Arya Stark's phone number is 000000000</Btn>
  ),
  className: '',
  href: 'tel:000000000',
};

This way is flexible enough for me to add more stories without too much code duplicate.

The problem is the Story panel that shows meaningless source code (compared to other ways of writing stories):

image

Demo: https://nrn-v2-mst-aptd-at-lcz-sty-storybook.vercel.app/?path=/story/next-right-now-form-spoilerlink--dynamic-example-with-btn

All stories will show the same source code, and it makes the whole "Story" panel unusable.


Is this a known issue? Is there a workaround?
I tried to think about it, and the goal of the "Story" panel is to show code that can be copied for quick use. Displaying either the above source code, or the whole story file (*.stories.tsx) would be unusable. It'd need to show a code example that infers the args, such as:

(props) => {
  return (
    <SpoilerLink
      previewElement={(
	      <span>Spoil me!</span>
	    )}
      spoilerElement={(
	      <span>Arya Stark's phone number is 000000000</span>
	    )}
      className={''}
      href={'tel:000000000'}
    />
  );
}

Something like this would give a much better developer experience.

@shilman
Copy link
Member

shilman commented Feb 22, 2021

We currently support this in addon-docs Source block: #11332

Once that stabilizes, we will try to reconcile the difference between the doc blocks that show up in the docs tab, and addons like Storysource that show up in the addons panel. cc @phated

@Vadorequest
Copy link
Author

Yesterday I had refactored a useUndo story by using a template, but the generated code wasn't as good as actually duplicating the whole code, so I reverted it back today.

See commit: UnlyEd/reaflow@72d1ddc


Before commit: (using Template)
image

Code:

import {
  Meta,
  Story
} from '@storybook/react/types-6-0';
import React, { useState } from 'react';
import {
  EdgeData,
  NodeData
} from '../src';
import { Canvas } from '../src/Canvas';
import {
  UndoRedoEvent,
  useUndo
} from '../src/helpers';
import {
  Add,
  Arrow,
  Edge,
  Icon,
  Label,
  MarkerArrow,
  Node,
  Port,
  Remove
} from '../src/symbols';

export default {
  title: 'Demos/Undo Redo',
  component: Canvas,
  argTypes: {
    initialHistory: {
      control: {
        disable: true // TODO make it readonly when it'll be possible - See https://github.com/storybookjs/storybook/issues/14048
      }
    }
  },
  subcomponents: {
    Node,
    Edge,
    MarkerArrow,
    Arrow,
    Icon,
    Label,
    Port,
    Remove,
    Add
  }
} as Meta;

type PropsWithChildrenMock = {
  initialHistory?: { nodes: NodeData[]; edges: EdgeData[] }[];
};

const Template: Story<PropsWithChildrenMock> = (props) => {
  const { initialHistory } = props;
  const [nodes, setNodes] = useState<any[]>([
    {
      id: '1',
      text: 'Node 1'
    },
    {
      id: '2',
      text: 'Node 2'
    },
    {
      id: '3',
      text: 'Node 3'
    }
  ]);

  const [edges, setEdges] = useState<any[]>([
    {
      id: '1-2',
      from: '1',
      to: '2'
    },
    {
      id: '1-3',
      from: '1',
      to: '3'
    }
  ]);

  const { undo, redo, canUndo, canRedo } = useUndo({
    nodes,
    edges,
    initialHistory,
    onUndoRedo: (state: UndoRedoEvent) => {
      console.log('Undo / Redo', state);
      setEdges(state.edges);
      setNodes(state.nodes);
    }
  });

  const addNode = () => {
    setNodes([
      ...nodes,
      {
        id: `a${Math.random()}`,
        text: `Node ${Math.random()}`
      }
    ]);
  };

  return (
    <div style={{ position: 'absolute', top: 0, bottom: 0, left: 0, right: 0 }}>
      <button
        style={{ position: 'absolute', top: 10, left: 10, zIndex: 999 }}
        onClick={addNode}
      >
        Add Nodes
      </button>
      <button
        style={{ position: 'absolute', top: 10, left: 100, zIndex: 999 }}
        onClick={undo}
        disabled={!canUndo}
      >
        Undo
      </button>
      <button
        style={{ position: 'absolute', top: 10, left: 160, zIndex: 999 }}
        onClick={redo}
        disabled={!canRedo}
      >
        Redo
      </button>
      <Canvas
        nodes={nodes}
        edges={edges}
        onLayoutChange={layout => console.log('Layout', layout)}
      />
    </div>
  );
};

export const Simple: Story<PropsWithChildrenMock> = Template.bind({});
Simple.args = {};

export const WithInitialHistory: Story<PropsWithChildrenMock> = Template.bind({});
WithInitialHistory.args = {
  initialHistory: [
    {
      nodes: [],
      edges: []
    },
    {
      nodes: [
        {
          id: '1',
          text: 'Node 1'
        }
      ],
      edges: []
    },
    {
      nodes: [
        {
          id: '1',
          text: 'Node 1'
        },
        {
          id: '2',
          text: 'Node 2'
        }
      ],
      edges: [
        {
          id: '1-2',
          from: '1',
          to: '2'
        }
      ]
    },
    {
      nodes: [
        {
          id: '1',
          text: 'Node 1'
        },
        {
          id: '2',
          text: 'Node 2'
        },
        {
          id: '3',
          text: 'Node 3'
        }
      ],
      edges: [
        {
          id: '1-2',
          from: '1',
          to: '2'
        },
        {
          id: '1-3',
          from: '1',
          to: '3'
        }
      ]
    }
  ]
};

After commit: (using code duplication, no template)
image

Code:

import {
  Meta,
  Story
} from '@storybook/react/types-6-0';
import React, { useState } from 'react';
import {
  EdgeData,
  NodeData
} from '../src';
import { Canvas } from '../src/Canvas';
import {
  UndoRedoEvent,
  useUndo
} from '../src/helpers';
import {
  Add,
  Arrow,
  Edge,
  Icon,
  Label,
  MarkerArrow,
  Node,
  Port,
  Remove
} from '../src/symbols';

export default {
  title: 'Demos/Undo Redo',
  component: Canvas,
  argTypes: {
    initialHistory: {
      control: {
        disable: true // TODO make it readonly when it'll be possible - See https://github.com/storybookjs/storybook/issues/14048
      }
    }
  },
  subcomponents: {
    Node,
    Edge,
    MarkerArrow,
    Arrow,
    Icon,
    Label,
    Port,
    Remove,
    Add
  }
} as Meta;

type Props = {}

export const Simple: Story<Props> = () => {
  const [nodes, setNodes] = useState<any[]>([
    {
      id: '1',
      text: 'Node 1'
    },
    {
      id: '2',
      text: 'Node 2'
    },
    {
      id: '3',
      text: 'Node 3'
    }
  ]);

  const [edges, setEdges] = useState<any[]>([
    {
      id: '1-2',
      from: '1',
      to: '2'
    },
    {
      id: '1-3',
      from: '1',
      to: '3'
    }
  ]);

  const { undo, redo, canUndo, canRedo } = useUndo({
    nodes,
    edges,
    onUndoRedo: (state: UndoRedoEvent) => {
      console.log('Undo / Redo', state);
      setEdges(state.edges);
      setNodes(state.nodes);
    }
  });

  const addNode = () => {
    setNodes([
      ...nodes,
      {
        id: `a${Math.random()}`,
        text: `Node ${Math.random()}`
      }
    ]);
  };

  return (
    <div style={{ position: 'absolute', top: 0, bottom: 0, left: 0, right: 0 }}>
      <button
        style={{ position: 'absolute', top: 10, left: 10, zIndex: 999 }}
        onClick={addNode}
      >
        Add Nodes
      </button>
      <button
        style={{ position: 'absolute', top: 10, left: 100, zIndex: 999 }}
        onClick={undo}
        disabled={!canUndo}
      >
        Undo
      </button>
      <button
        style={{ position: 'absolute', top: 10, left: 160, zIndex: 999 }}
        onClick={redo}
        disabled={!canRedo}
      >
        Redo
      </button>
      <Canvas
        nodes={nodes}
        edges={edges}
        onLayoutChange={layout => console.log('Layout', layout)}
      />
    </div>
  );
};

export const WithInitialHistory: Story<Props> = () => {
  const initialHistory: { nodes: NodeData[]; edges: EdgeData[] }[] = [
    {
      nodes: [],
      edges: []
    },
    {
      nodes: [
        {
          id: '1',
          text: 'Node 1'
        }
      ],
      edges: []
    },
    {
      nodes: [
        {
          id: '1',
          text: 'Node 1'
        },
        {
          id: '2',
          text: 'Node 2'
        }
      ],
      edges: [
        {
          id: '1-2',
          from: '1',
          to: '2'
        }
      ]
    },
    {
      nodes: [
        {
          id: '1',
          text: 'Node 1'
        },
        {
          id: '2',
          text: 'Node 2'
        },
        {
          id: '3',
          text: 'Node 3'
        }
      ],
      edges: [
        {
          id: '1-2',
          from: '1',
          to: '2'
        },
        {
          id: '1-3',
          from: '1',
          to: '3'
        }
      ]
    }
  ];
  const [nodes, setNodes] = useState<any[]>([
    {
      id: '1',
      text: 'Node 1'
    },
    {
      id: '2',
      text: 'Node 2'
    },
    {
      id: '3',
      text: 'Node 3'
    }
  ]);

  const [edges, setEdges] = useState<any[]>([
    {
      id: '1-2',
      from: '1',
      to: '2'
    },
    {
      id: '1-3',
      from: '1',
      to: '3'
    }
  ]);

  const { undo, redo, canUndo, canRedo } = useUndo({
    nodes,
    edges,
    initialHistory,
    onUndoRedo: (state: UndoRedoEvent) => {
      console.log('Undo / Redo', state);
      setEdges(state.edges);
      setNodes(state.nodes);
    }
  });

  const addNode = () => {
    setNodes([
      ...nodes,
      {
        id: `a${Math.random()}`,
        text: `Node ${Math.random()}`
      }
    ]);
  };

  return (
    <div style={{ position: 'absolute', top: 0, bottom: 0, left: 0, right: 0 }}>
      <button
        style={{ position: 'absolute', top: 10, left: 10, zIndex: 999 }}
        onClick={addNode}
      >
        Add Nodes
      </button>
      <button
        style={{ position: 'absolute', top: 10, left: 100, zIndex: 999 }}
        onClick={undo}
        disabled={!canUndo}
      >
        Undo
      </button>
      <button
        style={{ position: 'absolute', top: 10, left: 160, zIndex: 999 }}
        onClick={redo}
        disabled={!canRedo}
      >
        Redo
      </button>
      <Canvas
        nodes={nodes}
        edges={edges}
        onLayoutChange={layout => console.log('Layout', layout)}
      />
    </div>
  );
};

I'm not sure what's the right way to keep the most reusable code possible while providing the best user experience while reading the Story documentation.
I feel like things have changed since last time (has it?), but it's not helpful enough still 😞

@Tim-arts
Copy link

Is there any update for this? Sadly the current state of things makes the storysource addon not as helpful as it could be

@shilman shilman added this to the 8.x source addon milestone Jun 15, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants