From b74e0dfb40c6016dc05e2bf1a9ba8f75d2f8821a Mon Sep 17 00:00:00 2001 From: Alex Fedotyev <61838744+alex-fedotyev@users.noreply.github.com> Date: Wed, 24 Jun 2026 15:33:28 +0000 Subject: [PATCH 1/3] feat(dashboards): per-column color on table tiles Bring the number-tile color features to builder table tiles: a per-column static palette color plus ordered conditional color rules, applied per cell at render via the shared resolver. - Add optional `color` and `colorRules` to `DerivedColumnSchema` (the per-column builder config), mirroring the existing per-column `numberFormat`. Move the chart palette tokens and `ColorConditionSchema` above `DerivedColumnSchema` so it can reference them; pure relocation, no logic change (the repo deliberately avoids `z.lazy` forward refs). The legacy-token migration helpers and number-tile schemas stay in place. - Build per-column `colorByColumn` / `rulesByColumn` lookups in DBTableChart alongside `formatByColumn`, and resolve + apply each cell's color in the table renderer, guarding unknown tokens so legacy configs never crash. - Add a per-column color drawer (SeriesColorDrawer) opened from the series row, gated to table tiles, reusing ColorSwatchInput and ColorRulesEditor. Co-Authored-By: Claude Opus 4.8 --- .changeset/table-tile-column-color.md | 11 + packages/app/src/HDXMultiSeriesTableChart.tsx | 50 ++- .../HDXMultiSeriesTableChart.test.tsx | 103 ++++++ .../ChartEditorControls.tsx | 1 + .../DBEditTimeChartForm/ChartSeriesEditor.tsx | 52 ++- .../__tests__/DBEditTimeChartForm.test.tsx | 60 ++++ packages/app/src/components/DBTableChart.tsx | 53 ++- .../app/src/components/SeriesColorDrawer.tsx | 156 +++++++++ .../__tests__/SeriesColorDrawer.test.tsx | 85 +++++ .../common-utils/src/__tests__/types.test.ts | 104 +++++- packages/common-utils/src/types.ts | 318 +++++++++--------- 11 files changed, 835 insertions(+), 158 deletions(-) create mode 100644 .changeset/table-tile-column-color.md create mode 100644 packages/app/src/components/SeriesColorDrawer.tsx create mode 100644 packages/app/src/components/__tests__/SeriesColorDrawer.test.tsx diff --git a/.changeset/table-tile-column-color.md b/.changeset/table-tile-column-color.md new file mode 100644 index 0000000000..26301448db --- /dev/null +++ b/.changeset/table-tile-column-color.md @@ -0,0 +1,11 @@ +--- +"@hyperdx/common-utils": minor +"@hyperdx/app": minor +--- + +Add per-column color to dashboard table tiles. On builder table tiles you can +now set a static color on a column and layer ordered conditional rules (for +example `> 500` turns the cell red), the table-cell counterpart of the +number-tile color. Rules are authored from the column editor and applied per +cell at render, reusing the existing palette tokens so colors reflow across +light and dark themes. diff --git a/packages/app/src/HDXMultiSeriesTableChart.tsx b/packages/app/src/HDXMultiSeriesTableChart.tsx index 23544d5e17..8a5006e559 100644 --- a/packages/app/src/HDXMultiSeriesTableChart.tsx +++ b/packages/app/src/HDXMultiSeriesTableChart.tsx @@ -1,6 +1,11 @@ import { useCallback, useMemo, useRef, useState } from 'react'; import Link from 'next/link'; import cx from 'classnames'; +import { + ChartPaletteToken, + ColorCondition, + isChartPaletteToken, +} from '@hyperdx/common-utils/dist/types'; import { Tooltip, UnstyledButton } from '@mantine/core'; import { IconArrowUpRight, @@ -29,7 +34,11 @@ import type { RowAction } from './hooks/useOnClickLinkBuilder'; import { useBrandDisplayName } from './theme/ThemeProvider'; import { UNDEFINED_WIDTH } from './tableUtils'; import type { NumberFormat } from './types'; -import { formatNumber } from './utils'; +import { + formatNumber, + getColorFromCSSToken, + resolveConditionalColor, +} from './utils'; import styles from './HDXMultiSeriesTableChart.module.scss'; import focusStyles from '@styles/focus.module.scss'; @@ -61,6 +70,12 @@ export const Table = ({ columnWidthPercent?: number; visible?: boolean; sortingFn?: SortingFnOption; + // Per-column static palette-token color (table tiles). Applied to the + // cell text; falls back through `colorRules` then the default color. + color?: ChartPaletteToken; + // Ordered conditional color rules evaluated against each cell's value + // (last match wins). Resolves to `color` when no rule matches. + colorRules?: ColorCondition[]; }[]; groupColumnName?: string; // Returns the row click destination + a hover-hint description. When @@ -136,6 +151,8 @@ export const Table = ({ numberFormat, columnWidthPercent, sortingFn, + color, + colorRules, }, i, ) => @@ -162,6 +179,28 @@ export const Table = ({ formattedValue = formatNumber(value, numberFormat); } + // Resolve this cell's color from the column config: ordered + // rules first (last match wins), then the column's static + // color, else no override. The raw value drives evaluation + // (numbers for comparisons, strings for equality / string + // match); non-primitive values (stringified above) never match. + const colorValue = + typeof value === 'number' || typeof value === 'string' + ? value + : null; + const resolvedColorToken = resolveConditionalColor( + colorValue, + colorRules, + color, + ); + // Guard the CSS resolver: it throws on an unrecognized token, + // so an unknown / legacy token (e.g. a hand-edited config) + // renders with the default color instead of crashing the cell. + const colorStyle = + resolvedColorToken && isChartPaletteToken(resolvedColorToken) + ? { color: getColorFromCSSToken(resolvedColorToken) } + : undefined; + const className = cx('align-top overflow-hidden py-1 pe-3', { 'text-break': wrapLinesEnabled, 'text-truncate': !wrapLinesEnabled, @@ -191,6 +230,7 @@ export const Table = ({ href={action.url} prefetch={false} className={interactiveClassName} + style={colorStyle} data-testid="dashboard-table-row-action" data-shape="link" > @@ -211,6 +251,7 @@ export const Table = ({