storyBook.tsx 1.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. import type {JSXElementConstructor, ReactNode} from 'react';
  2. import {Children} from 'react';
  3. import styled from '@emotion/styled';
  4. import {Flex} from 'sentry/components/container/flex';
  5. import SideBySide from 'sentry/components/stories/sideBySide';
  6. import {space} from 'sentry/styles/space';
  7. type RenderFn = () => ReactNode | ReactNode[];
  8. type StoryFn = (storyName: string, storyRender: RenderFn) => void;
  9. type SetupFn = (story: StoryFn) => void;
  10. type Context = {
  11. name: string;
  12. render: RenderFn;
  13. };
  14. export default function storyBook(
  15. bookContext: string | JSXElementConstructor<any>,
  16. setup: SetupFn
  17. ) {
  18. const contexts: Context[] = [];
  19. const storyFn: StoryFn = (name: string, render: RenderFn) => {
  20. contexts.push({name, render});
  21. };
  22. setup(storyFn);
  23. return function RenderStory() {
  24. return (
  25. <Flex column gap={space(4)}>
  26. <BookHeading bookContext={bookContext} />
  27. {contexts.map(({name, render}, i) => {
  28. const children = render();
  29. const isOneChild = Children.count(children) === 1;
  30. const key = `${i}_${name}`;
  31. return (
  32. <Story key={key}>
  33. <StoryTitle id={key}>{name}</StoryTitle>
  34. {isOneChild ? children : <SideBySide>{children}</SideBySide>}
  35. </Story>
  36. );
  37. })}
  38. </Flex>
  39. );
  40. };
  41. }
  42. function BookHeading({bookContext}) {
  43. if (typeof bookContext === 'string') {
  44. return <BookTitle>{bookContext}</BookTitle>;
  45. }
  46. const componentName =
  47. bookContext.displayName ?? bookContext.name ?? bookContext.constructor.name;
  48. if (!componentName) {
  49. return null;
  50. }
  51. return (
  52. <BookTitle>
  53. <code>{`<${componentName}/>`}</code>
  54. </BookTitle>
  55. );
  56. }
  57. const BookTitle = styled('h3')`
  58. margin: 0;
  59. `;
  60. const Story = styled('section')`
  61. & > p {
  62. margin: ${space(3)} 0;
  63. }
  64. `;
  65. const StoryTitle = styled('h4')`
  66. border-bottom: 1px solid ${p => p.theme.border};
  67. `;