mirror of https://github.com/grafana/grafana.git
TablePanel: Improve and align table styles with the rest of Grafana (#60365)
* TablePanel: Improve and align table styles with rest of Grafana * Fixing footer styles
This commit is contained in:
parent
8b5ad5824a
commit
0fb9987d12
|
|
@ -14,11 +14,10 @@ export interface FooterRowProps {
|
||||||
footerGroups: HeaderGroup[];
|
footerGroups: HeaderGroup[];
|
||||||
footerValues: FooterItem[];
|
footerValues: FooterItem[];
|
||||||
isPaginationVisible: boolean;
|
isPaginationVisible: boolean;
|
||||||
height: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FooterRow = (props: FooterRowProps) => {
|
export const FooterRow = (props: FooterRowProps) => {
|
||||||
const { totalColumnsWidth, footerGroups, height, isPaginationVisible } = props;
|
const { totalColumnsWidth, footerGroups, isPaginationVisible } = props;
|
||||||
const e2eSelectorsTable = selectors.components.Panels.Visualization.Table;
|
const e2eSelectorsTable = selectors.components.Panels.Visualization.Table;
|
||||||
const tableStyles = useStyles2(getTableStyles);
|
const tableStyles = useStyles2(getTableStyles);
|
||||||
|
|
||||||
|
|
@ -33,14 +32,8 @@ export const FooterRow = (props: FooterRowProps) => {
|
||||||
{footerGroups.map((footerGroup: HeaderGroup) => {
|
{footerGroups.map((footerGroup: HeaderGroup) => {
|
||||||
const { key, ...footerGroupProps } = footerGroup.getFooterGroupProps();
|
const { key, ...footerGroupProps } = footerGroup.getFooterGroupProps();
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={tableStyles.tfoot} {...footerGroupProps} key={key} data-testid={e2eSelectorsTable.footer}>
|
||||||
className={tableStyles.tfoot}
|
{footerGroup.headers.map((column: ColumnInstance) => renderFooterCell(column, tableStyles))}
|
||||||
{...footerGroupProps}
|
|
||||||
key={key}
|
|
||||||
data-testid={e2eSelectorsTable.footer}
|
|
||||||
style={height ? { height: `${height}px` } : undefined}
|
|
||||||
>
|
|
||||||
{footerGroup.headers.map((column: ColumnInstance) => renderFooterCell(column, tableStyles, height))}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
@ -48,7 +41,7 @@ export const FooterRow = (props: FooterRowProps) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function renderFooterCell(column: ColumnInstance, tableStyles: TableStyles, height?: number) {
|
function renderFooterCell(column: ColumnInstance, tableStyles: TableStyles) {
|
||||||
const footerProps = column.getHeaderProps();
|
const footerProps = column.getHeaderProps();
|
||||||
|
|
||||||
if (!footerProps) {
|
if (!footerProps) {
|
||||||
|
|
@ -58,9 +51,6 @@ function renderFooterCell(column: ColumnInstance, tableStyles: TableStyles, heig
|
||||||
footerProps.style = footerProps.style ?? {};
|
footerProps.style = footerProps.style ?? {};
|
||||||
footerProps.style.position = 'absolute';
|
footerProps.style.position = 'absolute';
|
||||||
footerProps.style.justifyContent = (column as any).justifyContent;
|
footerProps.style.justifyContent = (column as any).justifyContent;
|
||||||
if (height) {
|
|
||||||
footerProps.style.height = height;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={tableStyles.headerCell} {...footerProps}>
|
<div className={tableStyles.headerCell} {...footerProps}>
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ export const HeaderRow = (props: HeaderRowProps) => {
|
||||||
const tableStyles = useStyles2(getTableStyles);
|
const tableStyles = useStyles2(getTableStyles);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div role="rowgroup">
|
<div role="rowgroup" className={tableStyles.headerRow}>
|
||||||
{headerGroups.map((headerGroup: HeaderGroup) => {
|
{headerGroups.map((headerGroup: HeaderGroup) => {
|
||||||
const { key, ...headerGroupProps } = headerGroup.getHeaderGroupProps();
|
const { key, ...headerGroupProps } = headerGroup.getHeaderGroupProps();
|
||||||
return (
|
return (
|
||||||
|
|
@ -62,9 +62,12 @@ function renderHeaderCell(column: any, tableStyles: TableStyles, showTypeIcons?:
|
||||||
<Icon name={getFieldTypeIcon(field)} title={field?.type} size="sm" className={tableStyles.typeIcon} />
|
<Icon name={getFieldTypeIcon(field)} title={field?.type} size="sm" className={tableStyles.typeIcon} />
|
||||||
)}
|
)}
|
||||||
<div>{column.render('Header')}</div>
|
<div>{column.render('Header')}</div>
|
||||||
<div>
|
{column.isSorted &&
|
||||||
{column.isSorted && (column.isSortedDesc ? <Icon name="arrow-down" /> : <Icon name="arrow-up" />)}
|
(column.isSortedDesc ? (
|
||||||
</div>
|
<Icon size="lg" name="arrow-down" className={tableStyles.sortIcon} />
|
||||||
|
) : (
|
||||||
|
<Icon name="arrow-up" size="lg" className={tableStyles.sortIcon} />
|
||||||
|
))}
|
||||||
</button>
|
</button>
|
||||||
{column.canFilter && <Filter column={column} tableStyles={tableStyles} field={field} />}
|
{column.canFilter && <Filter column={column} tableStyles={tableStyles} field={field} />}
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -150,13 +150,13 @@ export const Table = memo((props: Props) => {
|
||||||
const variableSizeListScrollbarRef = useRef<HTMLDivElement>(null);
|
const variableSizeListScrollbarRef = useRef<HTMLDivElement>(null);
|
||||||
const tableStyles = useStyles2(getTableStyles);
|
const tableStyles = useStyles2(getTableStyles);
|
||||||
const theme = useTheme2();
|
const theme = useTheme2();
|
||||||
const headerHeight = noHeader ? 0 : tableStyles.cellHeight;
|
const headerHeight = noHeader ? 0 : tableStyles.rowHeight;
|
||||||
const [footerItems, setFooterItems] = useState<FooterItem[] | undefined>(footerValues);
|
const [footerItems, setFooterItems] = useState<FooterItem[] | undefined>(footerValues);
|
||||||
const [expandedIndexes, setExpandedIndexes] = useState<Set<number>>(new Set());
|
const [expandedIndexes, setExpandedIndexes] = useState<Set<number>>(new Set());
|
||||||
const prevExpandedIndexes = usePrevious(expandedIndexes);
|
const prevExpandedIndexes = usePrevious(expandedIndexes);
|
||||||
|
|
||||||
const footerHeight = useMemo(() => {
|
const footerHeight = useMemo(() => {
|
||||||
const EXTENDED_ROW_HEIGHT = 33;
|
const EXTENDED_ROW_HEIGHT = headerHeight;
|
||||||
let length = 0;
|
let length = 0;
|
||||||
|
|
||||||
if (!footerItems) {
|
if (!footerItems) {
|
||||||
|
|
@ -174,7 +174,7 @@ export const Table = memo((props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return EXTENDED_ROW_HEIGHT;
|
return EXTENDED_ROW_HEIGHT;
|
||||||
}, [footerItems]);
|
}, [footerItems, headerHeight]);
|
||||||
|
|
||||||
// React table data array. This data acts just like a dummy array to let react-table know how many rows exist
|
// React table data array. This data acts just like a dummy array to let react-table know how many rows exist
|
||||||
// The cells use the field to look up values
|
// The cells use the field to look up values
|
||||||
|
|
@ -288,7 +288,9 @@ export const Table = memo((props: Props) => {
|
||||||
if (enablePagination) {
|
if (enablePagination) {
|
||||||
listHeight -= tableStyles.cellHeight;
|
listHeight -= tableStyles.cellHeight;
|
||||||
}
|
}
|
||||||
const pageSize = Math.round(listHeight / tableStyles.cellHeight) - 1;
|
|
||||||
|
const pageSize = Math.round(listHeight / tableStyles.rowHeight) - 1;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Don't update the page size if it is less than 1
|
// Don't update the page size if it is less than 1
|
||||||
if (pageSize <= 0) {
|
if (pageSize <= 0) {
|
||||||
|
|
@ -473,7 +475,6 @@ export const Table = memo((props: Props) => {
|
||||||
)}
|
)}
|
||||||
{footerItems && (
|
{footerItems && (
|
||||||
<FooterRow
|
<FooterRow
|
||||||
height={footerHeight}
|
|
||||||
isPaginationVisible={Boolean(enablePagination)}
|
isPaginationVisible={Boolean(enablePagination)}
|
||||||
footerValues={footerItems}
|
footerValues={footerItems}
|
||||||
footerGroups={footerGroups}
|
footerGroups={footerGroups}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,6 @@ import { css, CSSObject } from '@emotion/css';
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
|
||||||
export const getTableStyles = (theme: GrafanaTheme2) => {
|
export const getTableStyles = (theme: GrafanaTheme2) => {
|
||||||
const { colors } = theme;
|
|
||||||
const headerBg = theme.colors.background.secondary;
|
|
||||||
const borderColor = theme.colors.border.weak;
|
const borderColor = theme.colors.border.weak;
|
||||||
const resizerColor = theme.colors.primary.border;
|
const resizerColor = theme.colors.primary.border;
|
||||||
const cellPadding = 6;
|
const cellPadding = 6;
|
||||||
|
|
@ -107,27 +105,29 @@ export const getTableStyles = (theme: GrafanaTheme2) => {
|
||||||
`,
|
`,
|
||||||
thead: css`
|
thead: css`
|
||||||
label: thead;
|
label: thead;
|
||||||
height: ${cellHeight}px;
|
height: ${rowHeight}px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
background: ${headerBg};
|
|
||||||
position: relative;
|
position: relative;
|
||||||
`,
|
`,
|
||||||
tfoot: css`
|
tfoot: css`
|
||||||
label: tfoot;
|
label: tfoot;
|
||||||
height: ${cellHeight}px;
|
height: ${rowHeight}px;
|
||||||
|
border-top: 1px solid ${borderColor};
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
background: ${headerBg};
|
|
||||||
position: relative;
|
position: relative;
|
||||||
`,
|
`,
|
||||||
|
headerRow: css`
|
||||||
|
label: row;
|
||||||
|
border-bottom: 1px solid ${borderColor};
|
||||||
|
`,
|
||||||
headerCell: css`
|
headerCell: css`
|
||||||
padding: ${cellPadding}px;
|
padding: ${cellPadding}px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
color: ${colors.primary.text};
|
|
||||||
border-right: 1px solid ${theme.colors.border.weak};
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
font-weight: ${theme.typography.fontWeightMedium};
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
border-right: none;
|
border-right: none;
|
||||||
|
|
@ -141,8 +141,15 @@ export const getTableStyles = (theme: GrafanaTheme2) => {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
font-weight: ${theme.typography.fontWeightMedium};
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
margin-right: ${theme.spacing(0.5)};
|
margin-right: ${theme.spacing(0.5)};
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
color: ${theme.colors.text.link};
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
cellContainer: buildCellContainerStyle(undefined, undefined, true),
|
cellContainer: buildCellContainerStyle(undefined, undefined, true),
|
||||||
cellContainerNoOverflow: buildCellContainerStyle(undefined, undefined, false),
|
cellContainerNoOverflow: buildCellContainerStyle(undefined, undefined, false),
|
||||||
|
|
@ -152,6 +159,9 @@ export const getTableStyles = (theme: GrafanaTheme2) => {
|
||||||
user-select: text;
|
user-select: text;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
`,
|
`,
|
||||||
|
sortIcon: css`
|
||||||
|
margin-left: ${theme.spacing(0.5)};
|
||||||
|
`,
|
||||||
cellLink: css`
|
cellLink: css`
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
@ -173,12 +183,10 @@ export const getTableStyles = (theme: GrafanaTheme2) => {
|
||||||
`,
|
`,
|
||||||
paginationWrapper: css`
|
paginationWrapper: css`
|
||||||
display: flex;
|
display: flex;
|
||||||
background: ${headerBg};
|
|
||||||
height: ${cellHeight}px;
|
height: ${cellHeight}px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-top: 1px solid ${theme.colors.border.weak};
|
|
||||||
li {
|
li {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ const footerCategory = 'Table footer';
|
||||||
export const plugin = new PanelPlugin<PanelOptions, TableFieldOptions>(TablePanel)
|
export const plugin = new PanelPlugin<PanelOptions, TableFieldOptions>(TablePanel)
|
||||||
.setPanelChangeHandler(tablePanelChangedHandler)
|
.setPanelChangeHandler(tablePanelChangedHandler)
|
||||||
.setMigrationHandler(tableMigrationHandler)
|
.setMigrationHandler(tableMigrationHandler)
|
||||||
.setNoPadding()
|
|
||||||
.useFieldConfig({
|
.useFieldConfig({
|
||||||
useCustomConfig: (builder) => {
|
useCustomConfig: (builder) => {
|
||||||
builder
|
builder
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue