mirror of
https://github.com/grafana/grafana.git
synced 2025-07-29 06:12:16 +08:00
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
This commit is contained in:
@ -42,7 +42,8 @@ export function TableCellActions(props: TableCellActionsProps) {
|
|||||||
dataProjection: 'EPSG:4326',
|
dataProjection: 'EPSG:4326',
|
||||||
});
|
});
|
||||||
mode = TableCellInspectorMode.code;
|
mode = TableCellInspectorMode.code;
|
||||||
} else if ('cellType' in cellOptions && cellOptions.cellType === TableCellDisplayMode.JSONView) {
|
}
|
||||||
|
if (cellOptions.type === TableCellDisplayMode.JSONView) {
|
||||||
mode = TableCellInspectorMode.code;
|
mode = TableCellInspectorMode.code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,13 +81,14 @@ export function TableNG(props: TableNGProps) {
|
|||||||
onSortByChange,
|
onSortByChange,
|
||||||
showTypeIcons,
|
showTypeIcons,
|
||||||
structureRev,
|
structureRev,
|
||||||
|
transparent,
|
||||||
width,
|
width,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const theme = useTheme2();
|
const theme = useTheme2();
|
||||||
const styles = useStyles2(getGridStyles, {
|
const styles = useStyles2(getGridStyles, {
|
||||||
enablePagination,
|
enablePagination,
|
||||||
noHeader,
|
transparent,
|
||||||
});
|
});
|
||||||
const panelContext = usePanelContext();
|
const panelContext = usePanelContext();
|
||||||
|
|
||||||
@ -244,21 +245,9 @@ export function TableNG(props: TableNGProps) {
|
|||||||
},
|
},
|
||||||
sortColumns,
|
sortColumns,
|
||||||
rowHeight,
|
rowHeight,
|
||||||
headerRowClass: styles.headerRow,
|
|
||||||
headerRowHeight: headerHeight,
|
|
||||||
bottomSummaryRows: hasFooter ? [{}] : undefined,
|
bottomSummaryRows: hasFooter ? [{}] : undefined,
|
||||||
}) satisfies Partial<DataGridProps<TableRow, TableSummaryRow>>,
|
}) satisfies Partial<DataGridProps<TableRow, TableSummaryRow>>,
|
||||||
[
|
[enableVirtualization, resizeHandler, sortColumns, rowHeight, hasFooter, setSortColumns, onSortByChange]
|
||||||
enableVirtualization,
|
|
||||||
resizeHandler,
|
|
||||||
sortColumns,
|
|
||||||
headerHeight,
|
|
||||||
styles.headerRow,
|
|
||||||
rowHeight,
|
|
||||||
hasFooter,
|
|
||||||
setSortColumns,
|
|
||||||
onSortByChange,
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
interface Schema {
|
interface Schema {
|
||||||
@ -437,6 +426,7 @@ export function TableNG(props: TableNGProps) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasNestedHeaders = firstNestedData.meta?.custom?.noHeader !== true;
|
||||||
const renderRow = renderRowFactory(firstNestedData.fields, panelContext, expandedRows, enableSharedCrosshair);
|
const renderRow = renderRowFactory(firstNestedData.fields, panelContext, expandedRows, enableSharedCrosshair);
|
||||||
const { columns: nestedColumns, cellRootRenderers: nestedCellRootRenderers } = fromFields(
|
const { columns: nestedColumns, cellRootRenderers: nestedCellRootRenderers } = fromFields(
|
||||||
firstNestedData.fields,
|
firstNestedData.fields,
|
||||||
@ -486,11 +476,20 @@ export function TableNG(props: TableNGProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const expandedRecords = applySort(frameToRecords(nestedData), nestedData.fields, sortColumns);
|
const expandedRecords = applySort(frameToRecords(nestedData), nestedData.fields, sortColumns);
|
||||||
|
if (!expandedRecords.length) {
|
||||||
|
return (
|
||||||
|
<div className={styles.noDataNested}>
|
||||||
|
<Trans i18nKey="grafana-ui.table.nested-table.no-data">No data</Trans>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataGrid<TableRow, TableSummaryRow>
|
<DataGrid<TableRow, TableSummaryRow>
|
||||||
{...commonDataGridProps}
|
{...commonDataGridProps}
|
||||||
className={cx(styles.grid, styles.gridNested)}
|
className={cx(styles.grid, styles.gridNested)}
|
||||||
|
headerRowClass={cx(styles.headerRow, { [styles.displayNone]: !hasNestedHeaders })}
|
||||||
|
headerRowHeight={hasNestedHeaders ? defaultHeaderHeight : 0}
|
||||||
columns={nestedColumns}
|
columns={nestedColumns}
|
||||||
rows={expandedRecords}
|
rows={expandedRecords}
|
||||||
renderers={{ renderRow, renderCell: renderCellRoot }}
|
renderers={{ renderRow, renderCell: renderCellRoot }}
|
||||||
@ -509,6 +508,7 @@ export function TableNG(props: TableNGProps) {
|
|||||||
crossFilterOrder,
|
crossFilterOrder,
|
||||||
crossFilterRows,
|
crossFilterRows,
|
||||||
data,
|
data,
|
||||||
|
defaultHeaderHeight,
|
||||||
defaultRowHeight,
|
defaultRowHeight,
|
||||||
enableSharedCrosshair,
|
enableSharedCrosshair,
|
||||||
expandedRows,
|
expandedRows,
|
||||||
@ -552,6 +552,8 @@ export function TableNG(props: TableNGProps) {
|
|||||||
className={styles.grid}
|
className={styles.grid}
|
||||||
columns={structureRevColumns}
|
columns={structureRevColumns}
|
||||||
rows={paginatedRows}
|
rows={paginatedRows}
|
||||||
|
headerRowClass={cx(styles.headerRow, { [styles.displayNone]: noHeader })}
|
||||||
|
headerRowHeight={headerHeight}
|
||||||
onCellClick={({ column, row }, { clientX, clientY, preventGridDefault }) => {
|
onCellClick={({ column, row }, { clientX, clientY, preventGridDefault }) => {
|
||||||
// Note: could be column.field; JS says yes, but TS says no!
|
// Note: could be column.field; JS says yes, but TS says no!
|
||||||
const field = columns[column.idx].field;
|
const field = columns[column.idx].field;
|
||||||
@ -693,17 +695,20 @@ const renderRowFactory =
|
|||||||
|
|
||||||
const getGridStyles = (
|
const getGridStyles = (
|
||||||
theme: GrafanaTheme2,
|
theme: GrafanaTheme2,
|
||||||
{ enablePagination, noHeader }: { enablePagination?: boolean; noHeader?: boolean }
|
{ enablePagination, transparent }: { enablePagination?: boolean; transparent?: boolean }
|
||||||
) => ({
|
) => ({
|
||||||
grid: css({
|
grid: css({
|
||||||
'--rdg-background-color': theme.colors.background.primary,
|
'--rdg-background-color': transparent ? theme.colors.background.canvas : theme.colors.background.primary,
|
||||||
'--rdg-header-background-color': theme.colors.background.primary,
|
'--rdg-header-background-color': transparent ? theme.colors.background.canvas : theme.colors.background.primary,
|
||||||
'--rdg-border-color': theme.isDark ? '#282b30' : '#ebebec',
|
'--rdg-border-color': theme.colors.border.weak,
|
||||||
'--rdg-color': theme.colors.text.primary,
|
'--rdg-color': theme.colors.text.primary,
|
||||||
|
|
||||||
// note: this cannot have any transparency since default cells that
|
// note: this cannot have any transparency since default cells that
|
||||||
// overlay/overflow on hover inherit this background and need to occlude cells below
|
// 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
|
// 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
|
// flow using flexbox rather than hard-coding this size via a calc
|
||||||
@ -723,15 +728,24 @@ const getGridStyles = (
|
|||||||
}),
|
}),
|
||||||
gridNested: css({
|
gridNested: css({
|
||||||
height: '100%',
|
height: '100%',
|
||||||
width: `calc(100% - ${COLUMN.EXPANDER_WIDTH - 1}px)`,
|
width: `calc(100% - ${COLUMN.EXPANDER_WIDTH - TABLE.CELL_PADDING * 2 - 1}px)`,
|
||||||
overflow: 'visible',
|
overflow: 'visible',
|
||||||
marginLeft: COLUMN.EXPANDER_WIDTH - 1,
|
marginLeft: COLUMN.EXPANDER_WIDTH - TABLE.CELL_PADDING - 1,
|
||||||
|
marginBlock: TABLE.CELL_PADDING,
|
||||||
}),
|
}),
|
||||||
cellNested: css({
|
cellNested: css({
|
||||||
'&[aria-selected=true]': {
|
'&[aria-selected=true]': {
|
||||||
outline: 'none',
|
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({
|
cellActions: css({
|
||||||
display: 'none',
|
display: 'none',
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@ -739,7 +753,7 @@ const getGridStyles = (
|
|||||||
margin: 'auto',
|
margin: 'auto',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
color: theme.colors.text.primary,
|
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,
|
padding: theme.spacing.x0_5,
|
||||||
paddingInlineStart: theme.spacing.x1,
|
paddingInlineStart: theme.spacing.x1,
|
||||||
}),
|
}),
|
||||||
@ -752,12 +766,14 @@ const getGridStyles = (
|
|||||||
headerRow: css({
|
headerRow: css({
|
||||||
paddingBlockStart: 0,
|
paddingBlockStart: 0,
|
||||||
fontWeight: 'normal',
|
fontWeight: 'normal',
|
||||||
...(noHeader ? { display: 'none' } : {}),
|
|
||||||
'& .rdg-cell': {
|
'& .rdg-cell': {
|
||||||
height: '100%',
|
height: '100%',
|
||||||
alignItems: 'flex-end',
|
alignItems: 'flex-end',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
displayNone: css({
|
||||||
|
display: 'none',
|
||||||
|
}),
|
||||||
paginationContainer: css({
|
paginationContainer: css({
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -835,6 +851,7 @@ const getCellStyles = (
|
|||||||
whiteSpace: 'pre-line',
|
whiteSpace: 'pre-line',
|
||||||
height: 'fit-content',
|
height: 'fit-content',
|
||||||
minWidth: 'fit-content',
|
minWidth: 'fit-content',
|
||||||
|
paddingBlock: (rowHeight - TABLE.LINE_HEIGHT) / 2 - 1,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
@ -14,5 +14,6 @@ export const TABLE = {
|
|||||||
SCROLL_BAR_WIDTH: 8,
|
SCROLL_BAR_WIDTH: 8,
|
||||||
SCROLL_BAR_MARGIN: 2,
|
SCROLL_BAR_MARGIN: 2,
|
||||||
LINE_HEIGHT: 22,
|
LINE_HEIGHT: 22,
|
||||||
|
NESTED_NO_DATA_HEIGHT: 60,
|
||||||
BORDER_RIGHT: 0.666667,
|
BORDER_RIGHT: 0.666667,
|
||||||
};
|
};
|
||||||
|
@ -503,9 +503,13 @@ export function useRowHeight({
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure we have a minimum height (defaultHeight) for the nested table even if data is empty
|
|
||||||
const rowCount = row.data?.length ?? 0;
|
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
|
// regular rows
|
||||||
@ -519,7 +523,6 @@ export function useRowHeight({
|
|||||||
fields,
|
fields,
|
||||||
hasNestedFrames,
|
hasNestedFrames,
|
||||||
hasWrappedCols,
|
hasWrappedCols,
|
||||||
headerHeight,
|
|
||||||
maxWrapCellOptions,
|
maxWrapCellOptions,
|
||||||
colWidths,
|
colWidths,
|
||||||
]);
|
]);
|
||||||
|
@ -131,6 +131,7 @@ export interface BaseTableProps {
|
|||||||
enablePagination?: boolean;
|
enablePagination?: boolean;
|
||||||
cellHeight?: TableCellHeight;
|
cellHeight?: TableCellHeight;
|
||||||
structureRev?: number;
|
structureRev?: number;
|
||||||
|
transparent?: boolean;
|
||||||
/** @alpha Used by SparklineCell when provided */
|
/** @alpha Used by SparklineCell when provided */
|
||||||
timeRange?: TimeRange;
|
timeRange?: TimeRange;
|
||||||
enableSharedCrosshair?: boolean;
|
enableSharedCrosshair?: boolean;
|
||||||
|
@ -26,7 +26,7 @@ import { Options } from './panelcfg.gen';
|
|||||||
interface Props extends PanelProps<Options> {}
|
interface Props extends PanelProps<Options> {}
|
||||||
|
|
||||||
export function TablePanel(props: Props) {
|
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(() => {
|
useMemo(() => {
|
||||||
cacheFieldDisplayNames(data.series);
|
cacheFieldDisplayNames(data.series);
|
||||||
@ -82,6 +82,7 @@ export function TablePanel(props: Props) {
|
|||||||
fieldConfig={fieldConfig}
|
fieldConfig={fieldConfig}
|
||||||
getActions={_getActions}
|
getActions={_getActions}
|
||||||
structureRev={data.structureRev}
|
structureRev={data.structureRev}
|
||||||
|
transparent={transparent}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -8011,6 +8011,9 @@
|
|||||||
"filter-popup-match-case": "Match case",
|
"filter-popup-match-case": "Match case",
|
||||||
"inspect-drawer-title": "Inspect value",
|
"inspect-drawer-title": "Inspect value",
|
||||||
"inspect-menu-label": "Inspect value",
|
"inspect-menu-label": "Inspect value",
|
||||||
|
"nested-table": {
|
||||||
|
"no-data": "No data"
|
||||||
|
},
|
||||||
"no-values-label": "No values",
|
"no-values-label": "No values",
|
||||||
"pagination-summary": "{{itemsRangeStart}} - {{displayedEnd}} of {{numRows}} rows"
|
"pagination-summary": "{{itemsRangeStart}} - {{displayedEnd}} of {{numRows}} rows"
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user