storyBook.tsx 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. import type {ReactNode} from 'react';
  2. import {Children, Fragment} from 'react';
  3. import styled from '@emotion/styled';
  4. import SideBySide from 'sentry/components/stories/sideBySide';
  5. import {space} from 'sentry/styles/space';
  6. import {StoryTypes} from 'sentry/views/stories/storyTypes';
  7. type StoryRenderFunction = () => ReactNode | ReactNode[];
  8. type StoryContext = (storyName: string, story: StoryRenderFunction) => void;
  9. type SetupFunction = (
  10. story: StoryContext,
  11. apiReference: (documentation: TypeLoader.ComponentDocWithFilename | undefined) => void
  12. ) => void;
  13. export default function storyBook(
  14. bookContext: string | React.ComponentType<any>,
  15. setup: SetupFunction
  16. ): StoryRenderFunction {
  17. const stories: Array<{
  18. name: string;
  19. render: StoryRenderFunction;
  20. }> = [];
  21. const APIDocumentation: TypeLoader.ComponentDocWithFilename[] = [];
  22. const storyFn: StoryContext = (name: string, render: StoryRenderFunction) => {
  23. stories.push({name, render});
  24. };
  25. const apiReferenceFn: (
  26. documentation: TypeLoader.ComponentDocWithFilename | undefined
  27. ) => void = (documentation: TypeLoader.ComponentDocWithFilename | undefined) => {
  28. if (documentation) {
  29. APIDocumentation.push(documentation);
  30. }
  31. };
  32. setup(storyFn, apiReferenceFn);
  33. return function RenderStory() {
  34. return (
  35. <Fragment>
  36. <BookTitle bookContext={bookContext} />
  37. {stories.map(({name, render}, i) => (
  38. <Story key={i} name={name} render={render} />
  39. ))}
  40. {APIDocumentation.map((documentation, i) => (
  41. <StoryTypes key={i} types={documentation} />
  42. ))}
  43. </Fragment>
  44. );
  45. };
  46. }
  47. function Story(props: {name: string; render: StoryRenderFunction}) {
  48. const children = props.render();
  49. const isOneChild = Children.count(children) === 1;
  50. return (
  51. <StorySection>
  52. <StoryTitle>{props.name}</StoryTitle>
  53. {isOneChild ? children : <SideBySide>{children}</SideBySide>}
  54. </StorySection>
  55. );
  56. }
  57. function BookTitle(props: {bookContext: string | React.ComponentType<any>}) {
  58. const {bookContext} = props;
  59. if (typeof bookContext === 'string') {
  60. return <StoryTitle>{bookContext}</StoryTitle>;
  61. }
  62. return (
  63. <StoryTitle>
  64. <code>{`<${bookContext.displayName ?? bookContext.name ?? bookContext.constructor.name}/>`}</code>
  65. </StoryTitle>
  66. );
  67. }
  68. const StorySection = styled('section')`
  69. margin-top: ${space(4)};
  70. & > p {
  71. margin: ${space(3)} 0;
  72. }
  73. `;
  74. export const StoryTitle = styled('h3')`
  75. border-bottom: 1px solid ${p => p.theme.border};
  76. scroll-margin-top: ${space(2)};
  77. `;