From a63a9357bbc95d87c675f5512832147e77dbb51a Mon Sep 17 00:00:00 2001 From: Paul Marbach Date: Wed, 9 Jul 2025 14:54:48 -0400 Subject: [PATCH] TableNG: Follow-up style fixes (#107274) * open cell inspect in code mode for JSON panels * increase opacity of TableCellActions background for legibility in overlap cases * fix nested grid width calculation * fix 'jumping' on hover overflow * route transparency through (needs a scenes update) * base row hover color on transparency * update the colors for table * remove console.log * reinstate header toggle for nested row transformation * fix #59474 - graceful handling when subtable has no rows * fix i18n * use TABLE.LINE_HEIGHT * change nestedData back to const --- .../Table/TableNG/Cells/TableCellActions.tsx | 3 +- .../src/components/Table/TableNG/TableNG.tsx | 63 ++++++++++++------- .../src/components/Table/TableNG/constants.ts | 1 + .../src/components/Table/TableNG/hooks.ts | 9 ++- .../src/components/Table/TableNG/types.ts | 1 + .../panel/table/table-new/TablePanel.tsx | 3 +- public/locales/en-US/grafana.json | 3 + 7 files changed, 55 insertions(+), 28 deletions(-) diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/TableCellActions.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/TableCellActions.tsx index 558d1c7978d..ba5cc0749f1 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Cells/TableCellActions.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/TableCellActions.tsx @@ -42,7 +42,8 @@ export function TableCellActions(props: TableCellActionsProps) { dataProjection: 'EPSG:4326', }); mode = TableCellInspectorMode.code; - } else if ('cellType' in cellOptions && cellOptions.cellType === TableCellDisplayMode.JSONView) { + } + if (cellOptions.type === TableCellDisplayMode.JSONView) { mode = TableCellInspectorMode.code; } diff --git a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx index aa35b99ae9c..129eb256c97 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx @@ -81,13 +81,14 @@ export function TableNG(props: TableNGProps) { onSortByChange, showTypeIcons, structureRev, + transparent, width, } = props; const theme = useTheme2(); const styles = useStyles2(getGridStyles, { enablePagination, - noHeader, + transparent, }); const panelContext = usePanelContext(); @@ -244,21 +245,9 @@ export function TableNG(props: TableNGProps) { }, sortColumns, rowHeight, - headerRowClass: styles.headerRow, - headerRowHeight: headerHeight, bottomSummaryRows: hasFooter ? [{}] : undefined, }) satisfies Partial>, - [ - enableVirtualization, - resizeHandler, - sortColumns, - headerHeight, - styles.headerRow, - rowHeight, - hasFooter, - setSortColumns, - onSortByChange, - ] + [enableVirtualization, resizeHandler, sortColumns, rowHeight, hasFooter, setSortColumns, onSortByChange] ); interface Schema { @@ -437,6 +426,7 @@ export function TableNG(props: TableNGProps) { return result; } + const hasNestedHeaders = firstNestedData.meta?.custom?.noHeader !== true; const renderRow = renderRowFactory(firstNestedData.fields, panelContext, expandedRows, enableSharedCrosshair); const { columns: nestedColumns, cellRootRenderers: nestedCellRootRenderers } = fromFields( firstNestedData.fields, @@ -486,11 +476,20 @@ export function TableNG(props: TableNGProps) { } const expandedRecords = applySort(frameToRecords(nestedData), nestedData.fields, sortColumns); + if (!expandedRecords.length) { + return ( +
+ No data +
+ ); + } return ( {...commonDataGridProps} className={cx(styles.grid, styles.gridNested)} + headerRowClass={cx(styles.headerRow, { [styles.displayNone]: !hasNestedHeaders })} + headerRowHeight={hasNestedHeaders ? defaultHeaderHeight : 0} columns={nestedColumns} rows={expandedRecords} renderers={{ renderRow, renderCell: renderCellRoot }} @@ -509,6 +508,7 @@ export function TableNG(props: TableNGProps) { crossFilterOrder, crossFilterRows, data, + defaultHeaderHeight, defaultRowHeight, enableSharedCrosshair, expandedRows, @@ -552,6 +552,8 @@ export function TableNG(props: TableNGProps) { className={styles.grid} columns={structureRevColumns} rows={paginatedRows} + headerRowClass={cx(styles.headerRow, { [styles.displayNone]: noHeader })} + headerRowHeight={headerHeight} onCellClick={({ column, row }, { clientX, clientY, preventGridDefault }) => { // Note: could be column.field; JS says yes, but TS says no! const field = columns[column.idx].field; @@ -693,17 +695,20 @@ const renderRowFactory = const getGridStyles = ( theme: GrafanaTheme2, - { enablePagination, noHeader }: { enablePagination?: boolean; noHeader?: boolean } + { enablePagination, transparent }: { enablePagination?: boolean; transparent?: boolean } ) => ({ grid: css({ - '--rdg-background-color': theme.colors.background.primary, - '--rdg-header-background-color': theme.colors.background.primary, - '--rdg-border-color': theme.isDark ? '#282b30' : '#ebebec', + '--rdg-background-color': transparent ? theme.colors.background.canvas : theme.colors.background.primary, + '--rdg-header-background-color': transparent ? theme.colors.background.canvas : theme.colors.background.primary, + '--rdg-border-color': theme.colors.border.weak, '--rdg-color': theme.colors.text.primary, // note: this cannot have any transparency since default cells that // overlay/overflow on hover inherit this background and need to occlude cells below - '--rdg-row-hover-background-color': theme.isDark ? '#212428' : '#f4f5f5', + '--rdg-row-background-color': transparent ? theme.colors.background.canvas : theme.colors.background.primary, + '--rdg-row-hover-background-color': transparent + ? theme.colors.background.primary + : theme.colors.background.secondary, // TODO: magic 32px number is unfortunate. it would be better to have the content // flow using flexbox rather than hard-coding this size via a calc @@ -723,15 +728,24 @@ const getGridStyles = ( }), gridNested: css({ height: '100%', - width: `calc(100% - ${COLUMN.EXPANDER_WIDTH - 1}px)`, + width: `calc(100% - ${COLUMN.EXPANDER_WIDTH - TABLE.CELL_PADDING * 2 - 1}px)`, overflow: 'visible', - marginLeft: COLUMN.EXPANDER_WIDTH - 1, + marginLeft: COLUMN.EXPANDER_WIDTH - TABLE.CELL_PADDING - 1, + marginBlock: TABLE.CELL_PADDING, }), cellNested: css({ '&[aria-selected=true]': { outline: 'none', }, }), + noDataNested: css({ + height: TABLE.NESTED_NO_DATA_HEIGHT, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + color: theme.colors.text.secondary, + fontSize: theme.typography.h4.fontSize, + }), cellActions: css({ display: 'none', position: 'absolute', @@ -739,7 +753,7 @@ const getGridStyles = ( margin: 'auto', height: '100%', color: theme.colors.text.primary, - background: theme.isDark ? 'rgba(0, 0, 0, 0.3)' : 'rgba(255, 255, 255, 0.7)', + background: theme.isDark ? 'rgba(0, 0, 0, 0.7)' : 'rgba(255, 255, 255, 0.7)', padding: theme.spacing.x0_5, paddingInlineStart: theme.spacing.x1, }), @@ -752,12 +766,14 @@ const getGridStyles = ( headerRow: css({ paddingBlockStart: 0, fontWeight: 'normal', - ...(noHeader ? { display: 'none' } : {}), '& .rdg-cell': { height: '100%', alignItems: 'flex-end', }, }), + displayNone: css({ + display: 'none', + }), paginationContainer: css({ alignItems: 'center', display: 'flex', @@ -835,6 +851,7 @@ const getCellStyles = ( whiteSpace: 'pre-line', height: 'fit-content', minWidth: 'fit-content', + paddingBlock: (rowHeight - TABLE.LINE_HEIGHT) / 2 - 1, }), }, }), diff --git a/packages/grafana-ui/src/components/Table/TableNG/constants.ts b/packages/grafana-ui/src/components/Table/TableNG/constants.ts index d7d69c160c0..31a537136d2 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/constants.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/constants.ts @@ -14,5 +14,6 @@ export const TABLE = { SCROLL_BAR_WIDTH: 8, SCROLL_BAR_MARGIN: 2, LINE_HEIGHT: 22, + NESTED_NO_DATA_HEIGHT: 60, BORDER_RIGHT: 0.666667, }; diff --git a/packages/grafana-ui/src/components/Table/TableNG/hooks.ts b/packages/grafana-ui/src/components/Table/TableNG/hooks.ts index 33c89162939..4ef2c8f185e 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/hooks.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/hooks.ts @@ -503,9 +503,13 @@ export function useRowHeight({ return 0; } - // Ensure we have a minimum height (defaultHeight) for the nested table even if data is empty const rowCount = row.data?.length ?? 0; - return Math.max(defaultHeight, defaultHeight * rowCount + headerHeight); + if (rowCount === 0) { + return TABLE.NESTED_NO_DATA_HEIGHT + TABLE.CELL_PADDING * 2; + } + + const nestedHeaderHeight = row.data?.meta?.custom?.noHeader ? 0 : defaultHeight; + return Math.max(defaultHeight, defaultHeight * rowCount + nestedHeaderHeight + TABLE.CELL_PADDING * 2); } // regular rows @@ -519,7 +523,6 @@ export function useRowHeight({ fields, hasNestedFrames, hasWrappedCols, - headerHeight, maxWrapCellOptions, colWidths, ]); diff --git a/packages/grafana-ui/src/components/Table/TableNG/types.ts b/packages/grafana-ui/src/components/Table/TableNG/types.ts index 04caa62c85c..65d40d3c4d1 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/types.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/types.ts @@ -131,6 +131,7 @@ export interface BaseTableProps { enablePagination?: boolean; cellHeight?: TableCellHeight; structureRev?: number; + transparent?: boolean; /** @alpha Used by SparklineCell when provided */ timeRange?: TimeRange; enableSharedCrosshair?: boolean; diff --git a/public/app/plugins/panel/table/table-new/TablePanel.tsx b/public/app/plugins/panel/table/table-new/TablePanel.tsx index d5bf34dd869..78f97127d9c 100644 --- a/public/app/plugins/panel/table/table-new/TablePanel.tsx +++ b/public/app/plugins/panel/table/table-new/TablePanel.tsx @@ -26,7 +26,7 @@ import { Options } from './panelcfg.gen'; interface Props extends PanelProps {} export function TablePanel(props: Props) { - const { data, height, width, options, fieldConfig, id, timeRange, replaceVariables } = props; + const { data, height, width, options, fieldConfig, id, timeRange, replaceVariables, transparent } = props; useMemo(() => { cacheFieldDisplayNames(data.series); @@ -82,6 +82,7 @@ export function TablePanel(props: Props) { fieldConfig={fieldConfig} getActions={_getActions} structureRev={data.structureRev} + transparent={transparent} /> ); diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 8b70a50723d..34de246aaba 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -8011,6 +8011,9 @@ "filter-popup-match-case": "Match case", "inspect-drawer-title": "Inspect value", "inspect-menu-label": "Inspect value", + "nested-table": { + "no-data": "No data" + }, "no-values-label": "No values", "pagination-summary": "{{itemsRangeStart}} - {{displayedEnd}} of {{numRows}} rows" },