123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- 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 `<EmptyStateWarning>`
- */
- emptyMessage?: React.ReactNode;
- /**
- * Displays an `<EmptyStateWarning>` 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 (
- <Wrapper
- columns={headers.length}
- disablePadding={disablePadding}
- className={className}
- hasRows={shouldShowContent}
- {...props}
- >
- {headers.map((header, i) => (
- <PanelTableHeader key={i} sticky={stickyHeaders}>
- {header}
- </PanelTableHeader>
- ))}
- {shouldShowLoading && (
- <LoadingWrapper>{loader || <LoadingIndicator />}</LoadingWrapper>
- )}
- {shouldShowEmptyMessage && (
- <TableEmptyStateWarning>
- <p>{emptyMessage}</p>
- {emptyAction}
- </TableEmptyStateWarning>
- )}
- {shouldShowContent && getContent(children)}
- </Wrapper>
- );
- };
- 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',
- })<WrapperProps>`
- 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;
|