import isPropValid from '@emotion/is-prop-valid'; import styled from '@emotion/styled'; import EmptyStateWarning from 'sentry/components/emptyStateWarning'; import LoadingIndicator from 'sentry/components/loadingIndicator'; import {t} from 'sentry/locale'; import space from 'sentry/styles/space'; import Panel from './panel'; export type PanelTableProps = { /** * Headers of the table. */ headers: React.ReactNode[]; /** * The body of the table. Make sure the number of children elements are * multiples of the length of headers. */ children?: React.ReactNode | (() => React.ReactNode); className?: string; /** * Renders without predefined padding on the header and body cells */ disablePadding?: boolean; /** * Action to display when isEmpty is true */ emptyAction?: React.ReactNode; /** * Message to use for `` */ emptyMessage?: React.ReactNode; /** * Displays an `` if true */ isEmpty?: boolean; /** * If this is true, then display a loading indicator */ isLoading?: boolean; /** * A custom loading indicator. */ loader?: React.ReactNode; /** * If true, scrolling headers out of view will pin to the top of container. */ stickyHeaders?: boolean; }; /** * Bare bones table generates a CSS grid template based on the content. * * The number of children elements should be a multiple of `this.props.columns` to have * it look ok. * * * Potential customizations: * - [ ] Add borders for columns to make them more like cells * - [ ] Add prop to disable borders for rows * - [ ] We may need to wrap `children` with our own component (similar to what we're doing * with `headers`. Then we can get rid of that gross `> *` selector * - [ ] Allow customization of wrappers (Header and body cells if added) */ const PanelTable = ({ headers, children, isLoading, isEmpty, disablePadding, className, emptyMessage = t('There are no items to display'), emptyAction, loader, stickyHeaders = false, ...props }: PanelTableProps) => { const shouldShowLoading = isLoading === true; const shouldShowEmptyMessage = !shouldShowLoading && isEmpty; const shouldShowContent = !shouldShowLoading && !shouldShowEmptyMessage; return ( {headers.map((header, i) => ( {header} ))} {shouldShowLoading && ( {loader || } )} {shouldShowEmptyMessage && (

{emptyMessage}

{emptyAction}
)} {shouldShowContent && getContent(children)}
); }; function getContent(children: PanelTableProps['children']) { if (typeof children === 'function') { return children(); } return children; } type WrapperProps = { /** * The number of columns the table will have, this is derived from the headers list */ columns: number; disablePadding: PanelTableProps['disablePadding']; hasRows: boolean; }; const LoadingWrapper = styled('div')``; const TableEmptyStateWarning = styled(EmptyStateWarning)``; const Wrapper = styled(Panel, { shouldForwardProp: p => typeof p === 'string' && isPropValid(p) && p !== 'columns', })` display: grid; grid-template-columns: repeat(${p => p.columns}, auto); > * { ${p => (p.disablePadding ? '' : `padding: ${space(2)};`)} &:nth-last-child(n + ${p => (p.hasRows ? p.columns + 1 : 0)}) { border-bottom: 1px solid ${p => p.theme.border}; } } > ${TableEmptyStateWarning}, > ${LoadingWrapper} { border: none; grid-column: auto / span ${p => p.columns}; } /* safari needs an overflow value or the contents will spill out */ overflow: auto; `; export const PanelTableHeader = styled('div')<{sticky: boolean}>` color: ${p => p.theme.subText}; font-size: ${p => p.theme.fontSizeSmall}; font-weight: 600; text-transform: uppercase; border-radius: ${p => p.theme.borderRadius} ${p => p.theme.borderRadius} 0 0; background: ${p => p.theme.backgroundSecondary}; line-height: 1; display: flex; flex-direction: column; justify-content: center; min-height: 45px; ${p => p.sticky && ` position: sticky; top: 0; z-index: ${p.theme.zIndex.initial}; `} `; export default PanelTable;