diff --git a/devenv/dev-dashboards/panel-table/table_kitchen_sink.json b/devenv/dev-dashboards/panel-table/table_kitchen_sink.json index 6e806785038..8ff35adb06d 100644 --- a/devenv/dev-dashboards/panel-table/table_kitchen_sink.json +++ b/devenv/dev-dashboards/panel-table/table_kitchen_sink.json @@ -434,9 +434,6 @@ "id": 1, "options": { "cellHeight": "sm", - "frozenColumns": { - "left": 1 - }, "footer": { "countRows": false, "enablePagination": false, @@ -445,6 +442,9 @@ "show": true }, "frameIndex": 0, + "frozenColumns": { + "left": 1 + }, "showHeader": true, "sortBy": [ { @@ -797,6 +797,10 @@ "value": { "type": "data-links" } + }, + { + "id": "custom.width", + "value": 140 } ] } @@ -804,7 +808,7 @@ }, "gridPos": { "h": 7, - "w": 24, + "w": 12, "x": 0, "y": 12 }, @@ -834,6 +838,166 @@ "title": "Colors and Links", "type": "table" }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "transparent", + "mode": "fixed" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false, + "width": 100, + "wrapHeaderText": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "highlight" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "applyToRow": true, + "mode": "basic", + "type": "color-background" + } + }, + { + "id": "mappings", + "value": [ + { + "options": { + "1": { + "color": "#fff899", + "index": 0 + } + }, + "type": "value" + } + ] + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "/^color-*/" + }, + "properties": [ + { + "id": "mappings", + "value": [ + { + "options": { + "1": { + "color": "green", + "index": 0 + } + }, + "type": "value" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "color-bg" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "color-background" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "color-text" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + }, + { + "id": "color", + "value": { + "fixedColor": "text", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "use-case" + }, + "properties": [ + { + "id": "custom.width" + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 12 + }, + "id": 10, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": ["sum"], + "show": false + }, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "12.2.0-pre", + "targets": [ + { + "csvContent": "use-case,normal,color-text,color-bg,highlight\n\"color bg and apply to row\",1,0,1,1\n\"color text and apply to row\",1,1,0,1\n\"color text + color bg + apply to row\",1,1,1,1\n\"only apply to row\",1,0,0,1\n\"only color bg\",1,0,1,0\n\"only color text\",1,1,0,0\n\"color text + color bg\",1,1,1,0\n\"no colorization\",1,0,0,0", + "refId": "A", + "scenarioId": "csv_content" + } + ], + "title": "Apply to Row - mixed color cell types", + "type": "table" + }, { "datasource": { "type": "grafana-testdata-datasource" @@ -1697,5 +1861,5 @@ "timezone": "", "title": "Panel Tests - Table - Kitchen Sink", "uid": "dcb9f5e9-8066-4397-889e-864b99555dbb", - "version": 9 + "version": 32 } diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.test.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.test.tsx index 57001a4176e..0b54c3ef717 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.test.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.test.tsx @@ -2,6 +2,8 @@ import { render, RenderResult } from '@testing-library/react'; import { Field, FieldType, MappingType, createTheme } from '@grafana/data'; +import { getTextColorForBackground } from '../../../../utils/colors'; + import { PillCell } from './PillCell'; describe('PillCell', () => { @@ -26,49 +28,94 @@ describe('PillCell', () => { describe('Color by hash (classic palette)', () => { it('single value', () => { expectHTML( - render(), - `value1` + render( + + ), + `value1` ); }); it('empty string', () => { - expectHTML(render(), ''); + expectHTML( + render( + + ), + '' + ); }); it('null', () => { - const { container } = render(); + const { container } = render( + + ); expect(container).toBeEmptyDOMElement(); }); it('CSV values', () => { expectHTML( - render(), + render( + + ), ` - value1 - value2 - value3 + value1 + value2 + value3 ` ); }); it('JSON array values', () => { expectHTML( - render(), + render( + + ), ` - value1 - value2 - value3 + value1 + value2 + value3 ` ); }); it('non-string values', () => { expectHTML( - render(), + render( + + ), ` - 100 - 200 - 300 + 100 + 200 + 300 ` ); }); @@ -107,12 +154,14 @@ describe('PillCell', () => { } satisfies Field; expectHTML( - render(), + render( + + ), ` - success - error - warning - unknown + success + error + warning + unknown ` ); }); diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.tsx index 4ba20f36605..9ab5e732d8d 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.tsx @@ -4,7 +4,6 @@ import { useMemo } from 'react'; import { GrafanaTheme2, classicColors, - colorManipulator, Field, getColorByStringHash, FALLBACK_COLOR, @@ -14,12 +13,23 @@ import { FieldColorModeId } from '@grafana/schema'; import { PillCellProps, TableCellStyles, TableCellValue } from '../types'; -export function PillCell({ rowIdx, field, theme }: PillCellProps) { +export function PillCell({ rowIdx, field, theme, getTextColorForBackground }: PillCellProps) { const value = field.values[rowIdx]; const pills: Pill[] = useMemo(() => { const pillValues = inferPills(value); - return pillValues.length > 0 ? createPills(pillValues, field, theme) : []; - }, [value, field, theme]); + return pillValues.length > 0 + ? pillValues.map((pill, index) => { + const bgColor = getPillColor(pill, field, theme); + const textColor = getTextColorForBackground(bgColor); + return { + value: String(pill), + key: `${pill}-${index}`, + bgColor, + color: textColor, + }; + }) + : []; + }, [value, field, theme, getTextColorForBackground]); if (pills.length === 0) { return null; @@ -49,19 +59,6 @@ interface Pill { const SPLIT_RE = /\s*,\s*/; const TRANSPARENT = 'rgba(0,0,0,0)'; -function createPills(pillValues: unknown[], field: Field, theme: GrafanaTheme2): Pill[] { - return pillValues.map((pill, index) => { - const bgColor = getPillColor(pill, field, theme); - const textColor = colorManipulator.getContrastRatio('#FFFFFF', bgColor) >= 4.5 ? '#FFFFFF' : '#000000'; - return { - value: String(pill), - key: `${pill}-${index}`, - bgColor, - color: textColor, - }; - }); -} - export function inferPills(rawValue: TableCellValue): unknown[] { if (rawValue === '' || rawValue == null) { return []; diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.test.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.test.tsx index 48b444839a6..da01b284216 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.test.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.test.tsx @@ -91,6 +91,7 @@ describe('TableNG Cells renderers', () => { getActions={jest.fn(() => [ { title: 'Action', onClick: jest.fn(() => {}), confirmation: jest.fn(), style: {} }, ])} + getTextColorForBackground={jest.fn(() => '#000000')} /> ); }; @@ -109,6 +110,7 @@ describe('TableNG Cells renderers', () => { height={100} width={100} theme={createTheme()} + getTextColorForBackground={jest.fn(() => '#000000')} cellOptions={cellOptions} cellInspect={false} showFilters={false} diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.tsx index ba8df94ac5c..6079a4f63d0 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.tsx @@ -110,7 +110,12 @@ const CELL_REGISTRY: Record = { [TableCellDisplayMode.Pill]: { // eslint-disable-next-line react/display-name renderer: memo((props: TableCellRendererProps) => ( - + )), getStyles: getPillStyles, testField: (field: Field) => field.type === FieldType.string, diff --git a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx index bb3c3fea8fa..39979cabfb3 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx @@ -1,6 +1,7 @@ import 'react-data-grid/lib/styles.css'; import { clsx } from 'clsx'; +import memoize from 'micro-memoize'; import { CSSProperties, Key, ReactNode, useCallback, useMemo, useRef, useState } from 'react'; import { Cell, @@ -28,6 +29,7 @@ import { Trans } from '@grafana/i18n'; import { FieldColorModeId, TableCellTooltipPlacement } from '@grafana/schema'; import { useStyles2, useTheme2 } from '../../../themes/ThemeContext'; +import { getTextColorForBackground as _getTextColorForBackground } from '../../../utils/colors'; import { Pagination } from '../../Pagination/Pagination'; import { PanelContext, usePanelContext } from '../../PanelChrome'; import { DataLinksActionsTooltip } from '../DataLinksActionsTooltip'; @@ -80,7 +82,7 @@ import { frameToRecords, getAlignment, getApplyToRowBgFn, - getCellColorInlineStyles, + getCellColorInlineStylesFactory, getCellLinks, getCellOptions, getDefaultRowHeight, @@ -143,6 +145,7 @@ export function TableNG(props: TableNGProps) { const rows = useMemo(() => frameToRecords(data), [data]); const hasNestedFrames = useMemo(() => getIsNestedTable(data.fields), [data]); + const getTextColorForBackground = useMemo(() => memoize(_getTextColorForBackground, { maxSize: 1000 }), []); const { rows: filteredRows, @@ -174,6 +177,11 @@ export function TableNG(props: TableNGProps) { () => (hasNestedFrames ? width - COLUMN.EXPANDER_WIDTH : width) - scrollbarWidth, [width, hasNestedFrames, scrollbarWidth] ); + const getCellColorInlineStyles = useMemo(() => getCellColorInlineStylesFactory(theme), [theme]); + const applyToRowBgFn = useMemo( + () => getApplyToRowBgFn(data.fields, getCellColorInlineStyles) ?? undefined, + [data.fields, getCellColorInlineStyles] + ); const typographyCtx = useMemo( () => createTypographyContext( @@ -226,7 +234,6 @@ export function TableNG(props: TableNGProps) { footerOptions, isCountRowsSet, }); - const applyToRowBgFn = useMemo(() => getApplyToRowBgFn(data.fields, theme) ?? undefined, [data.fields, theme]); // normalize the row height into a function which returns a number, so we avoid a bunch of conditionals during rendering. const rowHeightFn = useMemo((): ((row: TableRow) => number) => { @@ -438,14 +445,12 @@ export function TableNG(props: TableNGProps) { } } - let style: CSSProperties | undefined; - - if (rowCellStyle.color != null || rowCellStyle.background != null) { - style = rowCellStyle; - } else if (canBeColorized) { + let style: CSSProperties = { ...rowCellStyle }; + if (canBeColorized) { const value = props.row[props.column.key]; const displayValue = field.display!(value); // this fires here to get colors, then again to get rendered value? - style = getCellColorInlineStyles(theme, cellOptions, displayValue); + const cellColorStyles = getCellColorInlineStyles(cellOptions, displayValue, applyToRowBgFn != null); + Object.assign(style, cellColorStyles); } return ( @@ -486,6 +491,7 @@ export function TableNG(props: TableNGProps) { showFilters={showFilters} getActions={getCellActions} disableSanitizeHtml={disableSanitizeHtml} + getTextColorForBackground={getTextColorForBackground} /> {showActions && ( ): JSX.Element => { // cached so we don't care about multiple calls. const tooltipHeight = rowHeightFn(props.row); - let tooltipStyle: CSSProperties | undefined; + let tooltipStyle: CSSProperties = { ...rowCellStyle }; if (tooltipCanBeColorized) { - const tooltipDisplayValue = tooltipField.display!(props.row[tooltipDisplayName]); // this is yet another call to field.display() for the tooltip field - tooltipStyle = getCellColorInlineStyles(theme, tooltipCellOptions, tooltipDisplayValue); + const tooltipDisplayValue = tooltipField.display!(props.row[tooltipDisplayName]); + const tooltipCellColorStyles = getCellColorInlineStyles( + tooltipCellOptions, + tooltipDisplayValue, + applyToRowBgFn != null + ); + Object.assign(tooltipStyle, tooltipCellColorStyles); } return ( @@ -624,6 +636,8 @@ export function TableNG(props: TableNGProps) { footerCalcs, frozenColumns, getCellActions, + getCellColorInlineStyles, + getTextColorForBackground, isCountRowsSet, numFrozenColsFullyInView, onCellFilterAdded, diff --git a/packages/grafana-ui/src/components/Table/TableNG/components/TableCellTooltip.tsx b/packages/grafana-ui/src/components/Table/TableNG/components/TableCellTooltip.tsx index 156854bcfd9..232f3bb01a5 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/components/TableCellTooltip.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/components/TableCellTooltip.tsx @@ -19,6 +19,7 @@ export interface TableCellTooltipProps { disableSanitizeHtml?: boolean; field: Field; getActions: (field: Field, rowIdx: number) => ActionModel[]; + getTextColorForBackground: (bgColor: string) => string; gridRef: RefObject; height: number; placement?: TableCellTooltipPlacement; @@ -40,6 +41,7 @@ export const TableCellTooltip = memo( disableSanitizeHtml, field, getActions, + getTextColorForBackground, gridRef, height, placement, @@ -100,6 +102,7 @@ export const TableCellTooltip = memo( field, frame: data, getActions, + getTextColorForBackground, height, rowIdx, showFilters: false, @@ -107,7 +110,19 @@ export const TableCellTooltip = memo( value: rawValue, width, }) satisfies TableCellRendererProps, - [cellOptions, data, disableSanitizeHtml, field, getActions, height, rawValue, rowIdx, theme, width] + [ + cellOptions, + data, + disableSanitizeHtml, + field, + getActions, + getTextColorForBackground, + height, + rawValue, + rowIdx, + theme, + width, + ] ); const cellElement = tooltipCaretRef.current?.closest('.rdg-cell'); diff --git a/packages/grafana-ui/src/components/Table/TableNG/types.ts b/packages/grafana-ui/src/components/Table/TableNG/types.ts index efdd9495336..123a8f39e7d 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/types.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/types.ts @@ -167,6 +167,7 @@ export interface TableCellRendererProps { showFilters: boolean; getActions?: GetActionsFunctionLocal; disableSanitizeHtml?: boolean; + getTextColorForBackground: (color: string) => string; } export type InspectCellProps = { @@ -250,6 +251,7 @@ export interface PillCellProps { theme: GrafanaTheme2; field: Field; rowIdx: number; + getTextColorForBackground: (color: string) => string; } export interface TableCellStyleOptions { diff --git a/packages/grafana-ui/src/components/Table/TableNG/utils.test.ts b/packages/grafana-ui/src/components/Table/TableNG/utils.test.ts index 8ca2ba7870f..bec5094ac77 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/utils.test.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/utils.test.ts @@ -23,7 +23,7 @@ import { extractPixelValue, frameToRecords, getAlignmentFactor, - getCellColorInlineStyles, + getCellColorInlineStylesFactory, getCellLinks, getCellOptions, getComparator, @@ -107,28 +107,104 @@ describe('TableNG utils', () => { }, } as unknown as GrafanaTheme2; - it('should handle color background mode', () => { - const field = { type: TableCellDisplayMode.ColorBackground as const, mode: TableCellBackgroundDisplayMode.Basic }; + it('should handle color text cell type', () => { + const cellOptions = { + type: TableCellDisplayMode.ColorText as const, + }; const displayValue = { text: '100', numeric: 100, color: '#ff0000' }; - const colors = getCellColorInlineStyles(theme, field, displayValue); - expect(colors.background).toBe('rgb(255, 0, 0)'); + const getCellColorInlineStyles = getCellColorInlineStylesFactory(theme); + const colors = getCellColorInlineStyles(cellOptions, displayValue, false); + expect(colors.color).toBe('#ff0000'); + expect(colors).not.toHaveProperty('background'); + }); + + it('should pass thru color background cell type in basic mode', () => { + const cellOptions = { + type: TableCellDisplayMode.ColorBackground as const, + mode: TableCellBackgroundDisplayMode.Basic, + }; + + const displayValue = { text: '100', numeric: 100, color: '#ff0000' }; + + const getCellColorInlineStyles = getCellColorInlineStylesFactory(theme); + const colors = getCellColorInlineStyles(cellOptions, displayValue, false); + expect(colors.background).toBe('#ff0000'); expect(colors.color).toBe('rgb(247, 248, 250)'); }); - it('should handle color background gradient mode', () => { - const field = { + it('should handle color background cell type in gradient mode', () => { + const cellOptions = { type: TableCellDisplayMode.ColorBackground as const, mode: TableCellBackgroundDisplayMode.Gradient, }; const displayValue = { text: '100', numeric: 100, color: '#ff0000' }; - const colors = getCellColorInlineStyles(theme, field, displayValue); + const getCellColorInlineStyles = getCellColorInlineStylesFactory(theme); + const colors = getCellColorInlineStyles(cellOptions, displayValue, false); expect(colors.background).toBe('linear-gradient(120deg, rgb(255, 54, 36), #ff0000)'); expect(colors.color).toBe('rgb(247, 248, 250)'); }); + + it('does not set CSSProperties for un-mapped cell types', () => { + const cellOptions = { type: TableCellDisplayMode.JSONView as const }; + + const displayValue = { text: '100', numeric: 100, color: '#ff0000' }; + + const getCellColorInlineStyles = getCellColorInlineStylesFactory(theme); + const colors = getCellColorInlineStyles(cellOptions, displayValue, false); + + expect(colors).toEqual({}); + }); + + describe('applyToRow', () => { + it.each([ + ['hex', '#ffffff00'], + ['rgba', 'rgba(255,255,255,0)'], + ['hsla', 'hsla(0,100%,100%,0)'], + ])( + 'should not apply background color if the display value is transparent (%s) and applyToRow is on', + (_format, colorDisplayValue) => { + const cellOptions = { + type: TableCellDisplayMode.ColorBackground as const, + mode: TableCellBackgroundDisplayMode.Basic, + }; + + const displayValue = { text: '100', numeric: 100, color: colorDisplayValue }; + + const getCellColorInlineStyles = getCellColorInlineStylesFactory(theme); + const colors = getCellColorInlineStyles(cellOptions, displayValue, true); + + expect(colors).toEqual({}); + } + ); + + it.each([ + ['hex', '#ffffff00'], + ['rgba', 'rgba(255,255,255,0)'], + ['hsla', 'hsla(0,100%,100%,0)'], + ])( + 'should apply background color if the display value is transparent (%s) and applyToRow is off', + (_format, colorDisplayValue) => { + const cellOptions = { + type: TableCellDisplayMode.ColorBackground as const, + mode: TableCellBackgroundDisplayMode.Basic, + }; + + const displayValue = { text: '100', numeric: 100, color: colorDisplayValue }; + + const getCellColorInlineStyles = getCellColorInlineStylesFactory(theme); + const colors = getCellColorInlineStyles(cellOptions, displayValue, false); + + expect(colors).toEqual({ + background: colorDisplayValue, + color: 'rgb(32, 34, 38)', + }); + } + ); + }); }); describe('frame to records conversion', () => { diff --git a/packages/grafana-ui/src/components/Table/TableNG/utils.ts b/packages/grafana-ui/src/components/Table/TableNG/utils.ts index 3eb89230a58..e6289ca0cba 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/utils.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/utils.ts @@ -1,4 +1,5 @@ import { Property } from 'csstype'; +import memoize from 'micro-memoize'; import { CSSProperties } from 'react'; import { SortColumn } from 'react-data-grid'; import tinycolor from 'tinycolor2'; @@ -499,36 +500,58 @@ const CELL_GRADIENT_HUE_ROTATION_DEGREES = 5; * @internal * Returns the text and background colors for a table cell based on its options and display value. */ -export function getCellColorInlineStyles( - theme: GrafanaTheme2, - cellOptions: TableCellOptions, - displayValue: DisplayValue -): CSSProperties { - // How much to darken elements depends upon if we're in dark mode - const darkeningFactor = theme.isDark ? 1 : -0.7; - - // Setup color variables - let textColor: string | undefined = undefined; - let bgColor: string | undefined = undefined; - - if (cellOptions.type === TableCellDisplayMode.ColorText) { - textColor = displayValue.color; - } else if (cellOptions.type === TableCellDisplayMode.ColorBackground) { - const mode = cellOptions.mode ?? TableCellBackgroundDisplayMode.Gradient; - - if (mode === TableCellBackgroundDisplayMode.Basic) { - textColor = getTextColorForAlphaBackground(displayValue.color!, theme.isDark); - bgColor = tinycolor(displayValue.color).toRgbString(); - } else if (mode === TableCellBackgroundDisplayMode.Gradient) { - const bgColor2 = tinycolor(displayValue.color) +export function getCellColorInlineStylesFactory(theme: GrafanaTheme2) { + const bgCellTextColor = memoize((color: string) => getTextColorForAlphaBackground(color, theme.isDark), { + maxSize: 1000, + }); + const darkeningFactor = theme.isDark ? 1 : -0.7; // How much to darken elements depends upon if we're in dark mode + const gradientBg = memoize( + (color: string) => + tinycolor(color) .darken(CELL_COLOR_DARKENING_MULTIPLIER * darkeningFactor) - .spin(CELL_GRADIENT_HUE_ROTATION_DEGREES); - textColor = getTextColorForAlphaBackground(displayValue.color!, theme.isDark); - bgColor = `linear-gradient(120deg, ${bgColor2.toRgbString()}, ${displayValue.color})`; - } - } + .spin(CELL_GRADIENT_HUE_ROTATION_DEGREES) + .toRgbString(), + { maxSize: 1000 } + ); + const isTransparent = memoize( + (color: string) => { + // if hex, do the simple thing. + if (color[0] === '#') { + return color.length === 9 && color.endsWith('00'); + } + // if not hex, just use tinycolor to avoid extra logic. + return tinycolor(color).getAlpha() === 0; + }, + { maxSize: 1000 } + ); - return { color: textColor, background: bgColor }; + return (cellOptions: TableCellOptions, displayValue: DisplayValue, hasApplyToRow: boolean): CSSProperties => { + const result: CSSProperties = {}; + const displayValueColor = displayValue.color; + + if (!displayValueColor) { + return result; + } + + if (cellOptions.type === TableCellDisplayMode.ColorText) { + result.color = displayValueColor; + } else if (cellOptions.type === TableCellDisplayMode.ColorBackground) { + // return without setting anything if the bg is transparent. this allows + // the cell to inherit the row bg color if `applyToRow` is set. + if (hasApplyToRow && isTransparent(displayValueColor)) { + return result; + } + + const mode = cellOptions.mode ?? TableCellBackgroundDisplayMode.Gradient; + result.color = bgCellTextColor(displayValueColor); + result.background = + mode === TableCellBackgroundDisplayMode.Gradient + ? `linear-gradient(120deg, ${gradientBg(displayValueColor)}, ${displayValueColor})` + : displayValueColor; + } + + return result; + }; } /** @@ -881,7 +904,10 @@ export function computeColWidths(fields: Field[], availWidth: number) { * @internal * if applyToRow is true in any field, return a function that gets the row background color */ -export function getApplyToRowBgFn(fields: Field[], theme: GrafanaTheme2): ((rowIndex: number) => CSSProperties) | void { +export function getApplyToRowBgFn( + fields: Field[], + getCellColorInlineStyles: ReturnType +): ((rowIndex: number) => CSSProperties) | void { for (const field of fields) { const cellOptions = getCellOptions(field); const fieldDisplay = field.display; @@ -890,7 +916,7 @@ export function getApplyToRowBgFn(fields: Field[], theme: GrafanaTheme2): ((rowI cellOptions.type === TableCellDisplayMode.ColorBackground && cellOptions.applyToRow === true ) { - return (rowIndex: number) => getCellColorInlineStyles(theme, cellOptions, fieldDisplay(field.values[rowIndex])); + return (rowIndex: number) => getCellColorInlineStyles(cellOptions, fieldDisplay(field.values[rowIndex]), true); } } }