components.tsx 2.8 KB

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