components.tsx 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. import {Fragment, ReactNode, useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import {KeyValueTable, KeyValueTableRow} from 'sentry/components/keyValueTable';
  4. import {IconChevron} from 'sentry/icons';
  5. import {t} from 'sentry/locale';
  6. import {space} from 'sentry/styles/space';
  7. export const Indent = styled('div')`
  8. padding-left: ${space(4)};
  9. `;
  10. const NotFoundText = styled('span')`
  11. color: ${p => p.theme.subText};
  12. font-size: ${p => p.theme.fontSizeSmall};
  13. `;
  14. export type KeyValueTuple = {
  15. key: string;
  16. value: string | ReactNode;
  17. type?: 'warning' | 'error';
  18. };
  19. export function keyValueTableOrNotFound(data: KeyValueTuple[], notFoundText: string) {
  20. return data.length ? (
  21. <StyledKeyValueTable noMargin>
  22. {data.map(({key, value, type}) => (
  23. <KeyValueTableRow
  24. key={key}
  25. keyName={key}
  26. type={type}
  27. value={<ValueContainer>{value}</ValueContainer>}
  28. />
  29. ))}
  30. </StyledKeyValueTable>
  31. ) : (
  32. <Indent>
  33. <NotFoundText>{notFoundText}</NotFoundText>
  34. </Indent>
  35. );
  36. }
  37. const ValueContainer = styled('span')`
  38. overflow: auto;
  39. `;
  40. const SectionTitle = styled('dt')``;
  41. const SectionTitleExtra = styled('span')`
  42. flex-grow: 1;
  43. text-align: right;
  44. font-weight: normal;
  45. `;
  46. const SectionData = styled('dd')`
  47. font-size: ${p => p.theme.fontSizeExtraSmall};
  48. `;
  49. const ToggleButton = styled('button')`
  50. background: ${p => p.theme.background};
  51. border: 0;
  52. color: ${p => p.theme.headingColor};
  53. font-size: ${p => p.theme.fontSizeSmall};
  54. font-weight: 600;
  55. line-height: ${p => p.theme.text.lineHeightBody};
  56. width: 100%;
  57. display: flex;
  58. align-items: center;
  59. justify-content: flex-start;
  60. gap: ${space(1)};
  61. padding: ${space(0.5)} ${space(1)};
  62. :hover {
  63. background: ${p => p.theme.backgroundSecondary};
  64. }
  65. `;
  66. export function SectionItem({
  67. children,
  68. title,
  69. titleExtra,
  70. }: {
  71. children: ReactNode;
  72. title: ReactNode;
  73. titleExtra?: ReactNode;
  74. }) {
  75. const [isOpen, setIsOpen] = useState(true);
  76. return (
  77. <Fragment>
  78. <SectionTitle>
  79. <ToggleButton aria-label={t('toggle section')} onClick={() => setIsOpen(!isOpen)}>
  80. <IconChevron direction={isOpen ? 'down' : 'right'} size="xs" />
  81. {title}
  82. {titleExtra ? <SectionTitleExtra>{titleExtra}</SectionTitleExtra> : null}
  83. </ToggleButton>
  84. </SectionTitle>
  85. <SectionData>{isOpen ? children : null}</SectionData>
  86. </Fragment>
  87. );
  88. }
  89. const StyledKeyValueTable = styled(KeyValueTable)`
  90. & > dt {
  91. font-size: ${p => p.theme.fontSizeSmall};
  92. padding-left: ${space(4)};
  93. }
  94. & > dd {
  95. ${p => p.theme.overflowEllipsis};
  96. font-size: ${p => p.theme.fontSizeSmall};
  97. display: flex;
  98. justify-content: flex-end;
  99. white-space: normal;
  100. text-align: right;
  101. }
  102. `;