import React, { FC, ReactNode } from 'react'; import { css, cx } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data'; import { IconButton, useStyles2, useTheme2 } from '@grafana/ui'; import { useMedia } from 'react-use'; export interface DynamicTableColumnProps { id: string | number; label: string; renderCell?: (item: DynamicTableItemProps, index: number) => ReactNode; size?: number | string; } export interface DynamicTableItemProps { id: string | number; data: T; renderExpandedContent?: () => ReactNode; isExpanded?: boolean; } export interface DynamicTableProps { cols: Array>; items: Array>; isExpandable?: boolean; onCollapse?: (id: DynamicTableItemProps) => void; onExpand?: (id: DynamicTableItemProps) => void; renderExpandedContent?: (item: DynamicTableItemProps, index: number) => ReactNode; testIdGenerator?: (item: DynamicTableItemProps) => string; } export const DynamicTable: FC = ({ cols, items, isExpandable = false, onCollapse, onExpand, renderExpandedContent, testIdGenerator, }) => { const styles = useStyles2(getStyles(cols, isExpandable)); const theme = useTheme2(); const isMobile = useMedia(`(${theme.breakpoints.down('sm')})`); return (
{isExpandable &&
} {cols.map((col) => (
{col.label}
))}
{items.map((item, index) => (
{isExpandable && (
(item.isExpanded ? onCollapse?.(item) : onExpand?.(item))} type="button" />
)} {cols.map((col) => (
{col.renderCell?.(item, index)}
))} {item.isExpanded && (
{item.renderExpandedContent ? item.renderExpandedContent() : renderExpandedContent?.(item, index)}
)}
))}
); }; const getStyles = (cols: DynamicTableColumnProps[], isExpandable: boolean) => { const sizes = cols.map((col) => { if (!col.size) { return 'auto'; } if (typeof col.size === 'number') { return `${col.size}fr`; } return col.size; }); if (isExpandable) { sizes.unshift('calc(1em + 16px)'); } return (theme: GrafanaTheme2) => ({ container: css` border: 1px solid ${theme.colors.border.strong}; border-radius: 2px; color: ${theme.colors.text.secondary}; `, row: css` display: grid; grid-template-columns: ${sizes.join(' ')}; grid-template-rows: 1fr auto; &:nth-child(2n + 1) { background-color: ${theme.colors.background.secondary}; } &:nth-child(2n) { background-color: ${theme.colors.background.primary}; } ${theme.breakpoints.down('sm')} { grid-template-columns: auto 1fr; grid-template-areas: 'left right'; padding: 0 ${theme.spacing(0.5)}; &:first-child { display: none; } } `, cell: css` align-items: center; display: grid; padding: ${theme.spacing(1)}; ${theme.breakpoints.down('sm')} { padding: ${theme.spacing(1)} 0; grid-template-columns: 1fr; } `, bodyCell: css` ${theme.breakpoints.down('sm')} { grid-column-end: right; grid-column-start: right; &::before { content: attr(data-column); } } `, expandCell: css` justify-content: center; ${theme.breakpoints.down('sm')} { align-items: start; grid-area: left; } `, expandedContentRow: css` grid-column-end: ${sizes.length + 1}; grid-column-start: 2; grid-row: 2; padding: 0 ${theme.spacing(3)} 0 ${theme.spacing(1)}; ${theme.breakpoints.down('sm')} { border-top: 1px solid ${theme.colors.border.strong}; grid-row: auto; padding: ${theme.spacing(1)} 0 0 0; } `, expandButton: css` margin-right: 0; `, }); };