storyFile.tsx 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. import type {ComponentProps} from 'react';
  2. import {Fragment} from 'react';
  3. import styled from '@emotion/styled';
  4. import {LinkButton} from 'sentry/components/button';
  5. import {CopyToClipboardButton} from 'sentry/components/copyToClipboardButton';
  6. import TextOverflow from 'sentry/components/textOverflow';
  7. import {IconGithub} from 'sentry/icons';
  8. import {t} from 'sentry/locale';
  9. import {space} from 'sentry/styles/space';
  10. import type {StoryDescriptor} from './useStoriesLoader';
  11. interface Props extends ComponentProps<'div'> {
  12. story: StoryDescriptor;
  13. }
  14. export default function StoryFile({story, ...htmlProps}: Props) {
  15. return (
  16. <StoryFileLayout {...htmlProps}>
  17. <FlexRow style={{gap: space(1), justifyContent: 'space-between'}}>
  18. <FlexRow style={{alignItems: 'center', gap: space(1)}}>
  19. <H2>
  20. <TextOverflow>{story.filename}</TextOverflow>
  21. </H2>
  22. <CopyToClipboardButton size="xs" iconSize="xs" text={story.filename} />
  23. </FlexRow>
  24. <StoryLinksContainer>
  25. <GithubLinks story={story} />
  26. </StoryLinksContainer>
  27. </FlexRow>
  28. <StoryExports story={story} />
  29. </StoryFileLayout>
  30. );
  31. }
  32. export function StoryExports(props: {story: StoryDescriptor}) {
  33. const {default: DefaultExport, ...namedExports} = props.story.exports;
  34. return (
  35. <Fragment>
  36. {DefaultExport ? (
  37. <Story key="default">
  38. <DefaultExport />
  39. </Story>
  40. ) : null}
  41. {Object.entries(namedExports).map(([name, MaybeComponent]) => {
  42. if (typeof MaybeComponent === 'function') {
  43. return (
  44. <Story key={name}>
  45. <MaybeComponent />
  46. </Story>
  47. );
  48. }
  49. throw new Error(
  50. `Story exported an unsupported key ${name} with value: ${typeof MaybeComponent}`
  51. );
  52. })}
  53. </Fragment>
  54. );
  55. }
  56. function GithubLinks(props: {story: StoryDescriptor}) {
  57. return (
  58. <Fragment>
  59. <LinkButton
  60. href={`https://github.com/getsentry/sentry/blob/master/static/${props.story.filename}`}
  61. external
  62. icon={<IconGithub />}
  63. size="xs"
  64. aria-label={t('View on GitHub')}
  65. >
  66. {t('View')}
  67. </LinkButton>
  68. <LinkButton
  69. href={`https://github.com/getsentry/sentry/edit/master/static/${props.story.filename}`}
  70. external
  71. icon={<IconGithub />}
  72. size="xs"
  73. aria-label={t('Edit on GitHub')}
  74. >
  75. {t('Edit')}
  76. </LinkButton>
  77. </Fragment>
  78. );
  79. }
  80. const FlexRow = styled('div')`
  81. display: flex;
  82. flex-direction: row;
  83. flex-wrap: wrap;
  84. gap: var(--stories-grid-space);
  85. align-content: flex-start;
  86. `;
  87. const StoryLinksContainer = styled('div')`
  88. display: flex;
  89. flex-direction: row;
  90. flex-wrap: wrap;
  91. gap: ${space(1)};
  92. align-content: flex-start;
  93. grid-area: header-links;
  94. `;
  95. const StoryFileLayout = styled('section')`
  96. padding-top: ${space(2)};
  97. `;
  98. const Story = styled('section')`
  99. padding-top: ${space(2)};
  100. `;
  101. const H2 = styled('h2')`
  102. font-family: ${p => p.theme.text.familyMono};
  103. font-size: ${p => p.theme.fontSizeMedium};
  104. font-weight: ${p => p.theme.fontWeightNormal};
  105. margin: 0;
  106. `;