panelTable.tsx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. import React from 'react';
  2. import isPropValid from '@emotion/is-prop-valid';
  3. import styled from '@emotion/styled';
  4. import EmptyStateWarning from 'app/components/emptyStateWarning';
  5. import LoadingIndicator from 'app/components/loadingIndicator';
  6. import {t} from 'app/locale';
  7. import space from 'app/styles/space';
  8. import Panel from './panel';
  9. type Props = {
  10. /**
  11. * Headers of the table.
  12. */
  13. headers: React.ReactNode[];
  14. /**
  15. * The body of the table. Make sure the number of children elements are
  16. * multiples of the length of headers.
  17. */
  18. children?: React.ReactNode | (() => React.ReactNode);
  19. /**
  20. * If this is true, then display a loading indicator
  21. */
  22. isLoading?: boolean;
  23. /**
  24. * Displays an `<EmptyStateWarning>` if true
  25. */
  26. isEmpty?: boolean;
  27. /**
  28. * Message to use for `<EmptyStateWarning>`
  29. */
  30. emptyMessage?: React.ReactNode;
  31. /**
  32. * Action to display when isEmpty is true
  33. */
  34. emptyAction?: React.ReactNode;
  35. /**
  36. * Renders without predefined padding on the header and body cells
  37. */
  38. disablePadding?: boolean;
  39. className?: string;
  40. /**
  41. * A custom loading indicator.
  42. */
  43. loader?: React.ReactNode;
  44. };
  45. /**
  46. * Bare bones table generates a CSS grid template based on the content.
  47. *
  48. * The number of children elements should be a multiple of `this.props.columns` to have
  49. * it look ok.
  50. *
  51. *
  52. * Potential customizations:
  53. * - [ ] Add borders for columns to make them more like cells
  54. * - [ ] Add prop to disable borders for rows
  55. * - [ ] We may need to wrap `children` with our own component (similar to what we're doing
  56. * with `headers`. Then we can get rid of that gross `> *` selector
  57. * - [ ] Allow customization of wrappers (Header and body cells if added)
  58. */
  59. const PanelTable = ({
  60. headers,
  61. children,
  62. isLoading,
  63. isEmpty,
  64. disablePadding,
  65. className,
  66. emptyMessage = t('There are no items to display'),
  67. emptyAction,
  68. loader,
  69. }: Props) => {
  70. const shouldShowLoading = isLoading === true;
  71. const shouldShowEmptyMessage = !shouldShowLoading && isEmpty;
  72. const shouldShowContent = !shouldShowLoading && !shouldShowEmptyMessage;
  73. return (
  74. <Wrapper
  75. columns={headers.length}
  76. disablePadding={disablePadding}
  77. className={className}
  78. hasRows={shouldShowContent}
  79. >
  80. {headers.map((header, i) => (
  81. <PanelTableHeader key={i}>{header}</PanelTableHeader>
  82. ))}
  83. {shouldShowLoading && (
  84. <LoadingWrapper>{loader || <LoadingIndicator />}</LoadingWrapper>
  85. )}
  86. {shouldShowEmptyMessage && (
  87. <TableEmptyStateWarning>
  88. <p>{emptyMessage}</p>
  89. {emptyAction}
  90. </TableEmptyStateWarning>
  91. )}
  92. {shouldShowContent && getContent(children)}
  93. </Wrapper>
  94. );
  95. };
  96. function getContent(children: Props['children']) {
  97. if (typeof children === 'function') {
  98. return children();
  99. }
  100. return children;
  101. }
  102. type WrapperProps = {
  103. /**
  104. * The number of columns the table will have, this is derived from the headers list
  105. */
  106. columns: number;
  107. hasRows: boolean;
  108. disablePadding: Props['disablePadding'];
  109. };
  110. const LoadingWrapper = styled('div')``;
  111. const TableEmptyStateWarning = styled(EmptyStateWarning)``;
  112. const Wrapper = styled(Panel, {
  113. shouldForwardProp: p => isPropValid(p) && p !== 'columns',
  114. })<WrapperProps>`
  115. display: grid;
  116. grid-template-columns: repeat(${p => p.columns}, auto);
  117. > * {
  118. ${p => (p.disablePadding ? '' : `padding: ${space(2)};`)}
  119. &:nth-last-child(n + ${p => (p.hasRows ? p.columns + 1 : 0)}) {
  120. border-bottom: 1px solid ${p => p.theme.border};
  121. }
  122. }
  123. > ${/* sc-selector */ TableEmptyStateWarning}, > ${/* sc-selector */ LoadingWrapper} {
  124. border: none;
  125. grid-column: auto / span ${p => p.columns};
  126. }
  127. /* safari needs an overflow value or the contents will spill out */
  128. overflow: auto;
  129. `;
  130. export const PanelTableHeader = styled('div')`
  131. color: ${p => p.theme.subText};
  132. font-size: ${p => p.theme.fontSizeSmall};
  133. font-weight: 600;
  134. text-transform: uppercase;
  135. border-radius: ${p => p.theme.borderRadius} ${p => p.theme.borderRadius} 0 0;
  136. background: ${p => p.theme.backgroundSecondary};
  137. line-height: 1;
  138. display: flex;
  139. flex-direction: column;
  140. justify-content: center;
  141. min-height: 45px;
  142. `;
  143. export default PanelTable;