TableNG: Simplify Pill cellType and reduce scope (#107934)

Co-authored-by: Paul Marbach <paul.marbach@grafana.com>
pull/108051/head
Leon Sorokin 1 week ago committed by GitHub
parent b0e85a637f
commit 7344c1c555
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      packages/grafana-data/src/field/fieldColor.ts
  2. 2
      packages/grafana-data/src/index.ts
  3. 2
      packages/grafana-schema/src/common/common.gen.ts
  4. 3
      packages/grafana-schema/src/common/table.cue
  5. 196
      packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.test.tsx
  6. 161
      packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.tsx
  7. 6
      packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.tsx
  8. 35
      packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx
  9. 5
      packages/grafana-ui/src/components/Table/TableNG/utils.ts
  10. 4
      public/app/plugins/panel/table/table-new/TableCellOptionEditor.tsx
  11. 66
      public/app/plugins/panel/table/table-new/cells/PillCellOptionsEditor.tsx
  12. 8
      public/locales/en-US/grafana.json

@ -218,7 +218,7 @@ export class FieldColorSchemeMode implements FieldColorMode {
}
} else if (this.useSeriesName) {
return (_: number, _percent: number, _threshold?: Threshold) => {
return colors[Math.abs(stringHash(field.state?.displayName ?? field.name)) % colors.length];
return getColorByStringHash(colors, field.state?.displayName ?? field.name);
};
} else {
return (_: number, _percent: number, _threshold?: Threshold) => {
@ -263,6 +263,10 @@ export function getFieldSeriesColor(field: Field, theme: GrafanaTheme2): ColorSc
return scale(value);
}
export function getColorByStringHash(colors: string[], string: string) {
return colors[Math.abs(stringHash(string)) % colors.length];
}
function getFixedColor(field: Field, theme: GrafanaTheme2) {
return () => {
return theme.visualization.getColorByName(field.config.color?.fixedColor ?? FALLBACK_COLOR);

@ -137,6 +137,8 @@ export {
fieldColorModeRegistry,
type FieldColorMode,
getFieldSeriesColor,
/** @internal */
getColorByStringHash,
} from './field/fieldColor';
export { FieldConfigOptionsRegistry } from './field/FieldConfigOptionsRegistry';
export { sortThresholds, getActiveThreshold } from './field/thresholds';

@ -977,8 +977,6 @@ export enum ComparisonOperation {
}
export interface TablePillCellOptions {
color?: string;
colorMode?: ('auto' | 'fixed' | 'mapped');
type: TableCellDisplayMode.Pill;
}

@ -111,7 +111,4 @@ TableFieldOptions: {
TablePillCellOptions: {
type: TableCellDisplayMode & "pill"
color?: string
colorMode?: "auto" | "fixed" | "mapped"
} @cuetsy(kind="interface")

@ -1,16 +1,18 @@
import { render, screen } from '@testing-library/react';
import { render, RenderResult } from '@testing-library/react';
import { DataFrame, Field, FieldType, GrafanaTheme2, MappingType, createTheme } from '@grafana/data';
import { TableCellDisplayMode, TablePillCellOptions } from '@grafana/schema';
import { mockThemeContext } from '../../../../themes/ThemeContext';
import { PillCell, inferPills } from './PillCell';
import { PillCell, getStyles } from './PillCell';
describe('PillCell', () => {
let pillClass: string;
let restoreThemeContext: () => void;
beforeEach(() => {
pillClass = getStyles(createTheme()).pill;
restoreThemeContext = mockThemeContext(createTheme());
});
@ -20,7 +22,6 @@ describe('PillCell', () => {
const mockCellOptions: TablePillCellOptions = {
type: TableCellDisplayMode.Pill,
colorMode: 'auto',
};
const mockField: Field = {
@ -37,7 +38,6 @@ describe('PillCell', () => {
};
const defaultProps = {
value: 'test-value',
field: mockField,
justifyContent: 'flex-start' as const,
cellOptions: mockCellOptions,
@ -50,68 +50,63 @@ describe('PillCell', () => {
showFilters: false,
};
describe('pill parsing', () => {
it('should render pills for single values', () => {
render(<PillCell {...defaultProps} />);
expect(screen.getByText('test-value')).toBeInTheDocument();
});
const ser = new XMLSerializer();
it('should render pills for CSV values', () => {
render(<PillCell {...defaultProps} value="value1,value2,value3" />);
expect(screen.getByText('value1')).toBeInTheDocument();
expect(screen.getByText('value2')).toBeInTheDocument();
expect(screen.getByText('value3')).toBeInTheDocument();
});
const expectHTML = (result: RenderResult, expected: string) => {
let actual = ser.serializeToString(result.asFragment()).replace(/xmlns=".*?" /g, '');
expect(actual).toEqual(expected.replace(/^\s*|\n/gm, ''));
};
it('should render pills for JSON array values', () => {
render(<PillCell {...defaultProps} value='["item1","item2","item3"]' />);
expect(screen.getByText('item1')).toBeInTheDocument();
expect(screen.getByText('item2')).toBeInTheDocument();
expect(screen.getByText('item3')).toBeInTheDocument();
});
// one class for lightTextPill, darkTextPill
it('should show dash for empty values', () => {
render(<PillCell {...defaultProps} value="" />);
expect(screen.getByText('-')).toBeInTheDocument();
});
describe('Color by hash (classic palette)', () => {
const props = { ...defaultProps };
it('should show dash for null values', () => {
render(<PillCell {...defaultProps} value={null as unknown as string} />);
expect(screen.getByText('-')).toBeInTheDocument();
});
it('single value', () => {
expectHTML(
render(<PillCell {...props} value="value1" />),
`<span class="${pillClass}" style="background-color: rgb(63, 43, 91); color: rgb(255, 255, 255);">value1</span>`
);
});
describe('color mapping', () => {
// These tests primarily ensure the color logic executes without throwing.
// For true color verification, visual regression tests would be needed.
it('should use mapped colors when colorMode is mapped', () => {
const mappedOptions: TablePillCellOptions = {
type: TableCellDisplayMode.Pill,
colorMode: 'mapped',
};
it('empty string', () => {
expectHTML(render(<PillCell {...props} value="" />), '');
});
render(<PillCell {...defaultProps} value="success,error,warning,unknown" cellOptions={mappedOptions} />);
// it('null', () => {
// expectHTML(
// render(<PillCell {...props} value={null} />),
// '<span class="${pillClass}" style="background-color: rgb(63, 43, 91); color: rgb(255, 255, 255);">value1</span>'
// );
// });
const successPill = screen.getByText('success');
const errorPill = screen.getByText('error');
const warningPill = screen.getByText('warning');
const unknownPill = screen.getByText('unknown');
it('CSV values', () => {
expectHTML(
render(<PillCell {...props} value="value1,value2,value3" />),
`
<span class="${pillClass}" style="background-color: rgb(63, 43, 91); color: rgb(255, 255, 255);">value1</span>
<span class="${pillClass}" style="background-color: rgb(252, 226, 222); color: rgb(0, 0, 0);">value2</span>
<span class="${pillClass}" style="background-color: rgb(81, 149, 206); color: rgb(0, 0, 0);">value3</span>
`
);
});
expect(successPill).toBeInTheDocument();
expect(errorPill).toBeInTheDocument();
expect(warningPill).toBeInTheDocument();
expect(unknownPill).toBeInTheDocument();
it('JSON array values', () => {
expectHTML(
render(<PillCell {...props} value='["value1","value2","value3"]' />),
`
<span class="${pillClass}" style="background-color: rgb(63, 43, 91); color: rgb(255, 255, 255);">value1</span>
<span class="${pillClass}" style="background-color: rgb(252, 226, 222); color: rgb(0, 0, 0);">value2</span>
<span class="${pillClass}" style="background-color: rgb(81, 149, 206); color: rgb(0, 0, 0);">value3</span>
`
);
});
it('should use field-level value mappings when available', () => {
const mappedOptions: TablePillCellOptions = {
type: TableCellDisplayMode.Pill,
colorMode: 'mapped',
};
// TODO: handle null values?
});
// Mock field with value mappings
const fieldWithMappings: Field = {
describe('Color by value mappings', () => {
const field: Field = {
...mockField,
config: {
...mockField.config,
@ -129,91 +124,28 @@ describe('PillCell', () => {
display: (value: unknown) => ({
text: String(value),
color:
String(value) === 'success'
? '#00FF00'
: String(value) === 'error'
? '#FF0000'
: String(value) === 'warning'
? '#FFFF00'
: '#FF780A',
value === 'success' ? '#00FF00' : value === 'error' ? '#FF0000' : value === 'warning' ? '#FFFF00' : '#FF780A',
numeric: 0,
}),
};
render(
<PillCell
{...defaultProps}
value="success,error,warning,unknown"
cellOptions={mappedOptions}
field={fieldWithMappings}
/>
);
const successPill = screen.getByText('success');
const errorPill = screen.getByText('error');
const warningPill = screen.getByText('warning');
const unknownPill = screen.getByText('unknown');
expect(successPill).toBeInTheDocument();
expect(errorPill).toBeInTheDocument();
expect(warningPill).toBeInTheDocument();
expect(unknownPill).toBeInTheDocument();
});
it('should use fixed color when colorMode is fixed', () => {
const fixedOptions: TablePillCellOptions = {
type: TableCellDisplayMode.Pill,
colorMode: 'fixed',
color: '#FF00FF',
};
render(<PillCell {...defaultProps} cellOptions={fixedOptions} />);
expect(screen.getByText('test-value')).toBeInTheDocument();
});
it('should use auto color when colorMode is auto', () => {
const autoOptions: TablePillCellOptions = {
type: TableCellDisplayMode.Pill,
colorMode: 'auto',
const props = {
...defaultProps,
field,
};
render(<PillCell {...defaultProps} cellOptions={autoOptions} />);
expect(screen.getByText('test-value')).toBeInTheDocument();
});
});
});
describe('inferPills', () => {
// These tests verify the pill parsing logic handles various input formats correctly.
// They ensure the function can extract pill values from different data structures.
it('should return empty array for null/undefined values', () => {
expect(inferPills(null)).toEqual([]);
expect(inferPills(undefined)).toEqual([]);
expect(inferPills('')).toEqual([]);
});
it('should parse single values', () => {
expect(inferPills('test')).toEqual(['test']);
expect(inferPills('"quoted"')).toEqual(['quoted']);
expect(inferPills("'quoted'")).toEqual(['quoted']);
});
it('should parse CSV strings', () => {
expect(inferPills('value1,value2,value3')).toEqual(['value1', 'value2', 'value3']);
expect(inferPills(' value1 , value2 , value3 ')).toEqual(['value1', 'value2', 'value3']);
expect(inferPills('value1, ,value3')).toEqual(['value1', 'value3']);
});
it('should parse JSON arrays', () => {
expect(inferPills('["item1","item2","item3"]')).toEqual(['item1', 'item2', 'item3']);
expect(inferPills('["item1", "item2", "item3"]')).toEqual(['item1', 'item2', 'item3']);
expect(inferPills('["item1", null, "item3"]')).toEqual(['item1', 'item3']);
it('CSV values', () => {
expectHTML(
render(<PillCell {...props} value="success,error,warning,unknown" />),
`
<span class="${pillClass}" style="background-color: rgb(0, 255, 0); color: rgb(0, 0, 0);">success</span>
<span class="${pillClass}" style="background-color: rgb(255, 0, 0); color: rgb(0, 0, 0);">error</span>
<span class="${pillClass}" style="background-color: rgb(255, 255, 0); color: rgb(0, 0, 0);">warning</span>
<span class="${pillClass}" style="background-color: rgb(255, 120, 10); color: rgb(0, 0, 0);">unknown</span>
`
);
});
it('should handle mixed content', () => {
// When JSON parsing fails, it falls back to CSV parsing
expect(inferPills('["item1", "item2"],extra')).toEqual(['["item1"', '"item2"]', 'extra']);
expect(inferPills('not-json,value')).toEqual(['not-json', 'value']);
// TODO: handle null values?
});
});

@ -1,15 +1,19 @@
import { css } from '@emotion/css';
import { Property } from 'csstype';
import { useMemo } from 'react';
import { GrafanaTheme2, isDataFrame, classicColors, colorManipulator, Field } from '@grafana/data';
import { TablePillCellOptions } from '@grafana/schema';
import { useStyles2 } from '../../../../themes/ThemeContext';
import {
GrafanaTheme2,
classicColors,
colorManipulator,
Field,
getColorByStringHash,
FALLBACK_COLOR,
} from '@grafana/data';
import { FieldColorModeId } from '@grafana/schema';
import { useStyles2, useTheme2 } from '../../../../themes/ThemeContext';
import { TableCellRendererProps } from '../types';
const DEFAULT_PILL_BG_COLOR = '#FF780A';
interface Pill {
value: string;
key: string;
@ -17,9 +21,9 @@ interface Pill {
color: string;
}
function createPills(pillValues: string[], cellOptions: TableCellRendererProps['cellOptions'], field: Field): Pill[] {
function createPills(pillValues: string[], field: Field, theme: GrafanaTheme2): Pill[] {
return pillValues.map((pill, index) => {
const bgColor = getPillColor(pill, cellOptions, field);
const bgColor = getPillColor(pill, field, theme);
const textColor = colorManipulator.getContrastRatio('#FFFFFF', bgColor) >= 4.5 ? '#FFFFFF' : '#000000';
return {
value: pill,
@ -30,156 +34,73 @@ function createPills(pillValues: string[], cellOptions: TableCellRendererProps['
});
}
export function PillCell({ value, field, justifyContent, cellOptions }: TableCellRendererProps) {
const styles = useStyles2(getStyles, justifyContent);
export function PillCell({ value, field }: TableCellRendererProps) {
const styles = useStyles2(getStyles);
const theme = useTheme2();
const pills: Pill[] = useMemo(() => {
const pillValues = inferPills(value);
return createPills(pillValues, cellOptions, field);
}, [value, cellOptions, field]);
if (pills.length === 0) {
return <div className={styles.cell}>-</div>;
}
const pillValues = inferPills(String(value));
return createPills(pillValues, field, theme);
}, [value, field, theme]);
return (
<div className={styles.cell}>
<div className={styles.pillsContainer}>
{pills.map((pill) => (
return pills.map((pill) => (
<span
key={pill.key}
className={styles.pill}
style={{
backgroundColor: pill.bgColor,
color: pill.color,
border: pill.bgColor === TRANSPARENT ? `1px solid ${theme.colors.border.strong}` : undefined,
}}
>
{pill.value}
</span>
))}
</div>
</div>
);
));
}
export function inferPills(value: unknown): string[] {
if (!value) {
return [];
}
const SPLIT_RE = /\s*,\s*/;
const TRANSPARENT = 'rgba(0,0,0,0)';
// Handle DataFrame - not supported for pills
if (isDataFrame(value)) {
export function inferPills(value: string): string[] {
if (value === '') {
return [];
}
// Handle different value types
const stringValue = String(value);
// Try to parse as JSON first
if (value[0] === '[') {
try {
const parsed = JSON.parse(stringValue);
if (Array.isArray(parsed)) {
// JSON array of strings
return parsed
.filter((item) => item != null && item !== '')
.map(String)
.map((text) => text.trim())
.filter((item) => item !== '');
}
return JSON.parse(value);
} catch {
// Not valid JSON, continue with other parsing
}
// Handle CSV string
if (stringValue.includes(',')) {
return stringValue
.split(',')
.map((text) => text.trim())
.filter((item) => item !== '');
}
// Single value - strip quotes
return [stringValue.replace(/["'`]/g, '').trim()];
return value.trim().split(SPLIT_RE);
}
function isPillCellOptions(cellOptions: TableCellRendererProps['cellOptions']): cellOptions is TablePillCellOptions {
return cellOptions?.type === 'pill';
}
function getPillColor(pill: string, cellOptions: TableCellRendererProps['cellOptions'], field: Field): string {
if (!isPillCellOptions(cellOptions)) {
return getDeterministicColor(pill);
}
const colorMode = cellOptions.colorMode || 'auto';
// Fixed color mode (highest priority)
if (colorMode === 'fixed' && cellOptions.color) {
return cellOptions.color;
}
// Mapped color mode - use field's value mappings
if (colorMode === 'mapped') {
// Check if field has value mappings
if (field.config.mappings && field.config.mappings.length > 0) {
// Use the field's display processor to get the mapped value
const displayValue = field.display!(pill);
if (displayValue.color) {
return displayValue.color;
}
}
// Fallback to default color for unmapped values
return cellOptions.color || DEFAULT_PILL_BG_COLOR;
return value.trim().split(SPLIT_RE);
}
// Auto mode - deterministic color assignment based on string hash
if (colorMode === 'auto') {
return getDeterministicColor(pill);
}
function getPillColor(value: string, field: Field, theme: GrafanaTheme2): string {
const cfg = field.config;
// Default color for unknown values or fallback
return DEFAULT_PILL_BG_COLOR;
if (cfg.mappings?.length ?? 0 > 0) {
return field.display!(value).color ?? FALLBACK_COLOR;
}
function getDeterministicColor(text: string): string {
// Create a simple hash of the string to get consistent colors
let hash = 0;
for (let i = 0; i < text.length; i++) {
const char = text.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash = hash & hash; // Convert to 32-bit integer
if (cfg.color?.mode === FieldColorModeId.Fixed) {
return theme.visualization.getColorByName(cfg.color?.fixedColor ?? FALLBACK_COLOR);
}
// Use absolute value and modulo to get a consistent index
const colorValues = Object.values(classicColors);
const index = Math.abs(hash) % colorValues.length;
return colorValues[index];
// TODO: instead of classicColors we need to pull colors from theme, same way as FieldColorModeId.PaletteClassicByName (see fieldColor.ts)
return getColorByStringHash(classicColors, value);
}
const getStyles = (theme: GrafanaTheme2, justifyContent: Property.JustifyContent | undefined) => ({
cell: css({
display: 'flex',
justifyContent: justifyContent || 'flex-start',
alignItems: 'center',
height: '100%',
padding: theme.spacing(0.5),
}),
pillsContainer: css({
display: 'flex',
flexWrap: 'wrap',
gap: theme.spacing(0.5),
maxWidth: '100%',
}),
export const getStyles = (theme: GrafanaTheme2) => ({
pill: css({
display: 'inline-block',
padding: theme.spacing(0.25, 0.75),
marginInlineEnd: theme.spacing(0.5),
marginBlock: theme.spacing(0.5),
borderRadius: theme.shape.radius.default,
fontSize: theme.typography.bodySmall.fontSize,
lineHeight: theme.typography.bodySmall.lineHeight,
fontWeight: theme.typography.fontWeightMedium,
whiteSpace: 'nowrap',
textAlign: 'center',
minWidth: 'fit-content',
}),
});

@ -109,6 +109,12 @@ export function getCellRenderer(field: Field, cellOptions: TableCellOptions): Ta
if (cellType === TableCellDisplayMode.Auto) {
return getAutoRendererResult(field);
}
// TODO: add support boolean, enum, (maybe int). but for now just string fields
if (cellType === TableCellDisplayMode.Pill && field.type !== FieldType.string) {
return AUTO_RENDERER;
}
return CELL_RENDERERS[cellType] ?? AUTO_RENDERER;
}

@ -14,9 +14,18 @@ import {
SortColumn,
} from 'react-data-grid';
import { DataHoverClearEvent, DataHoverEvent, Field, FieldType, GrafanaTheme2, ReducerID } from '@grafana/data';
import {
DataHoverClearEvent,
DataHoverEvent,
FALLBACK_COLOR,
Field,
FieldType,
getDisplayProcessor,
GrafanaTheme2,
ReducerID,
} from '@grafana/data';
import { t, Trans } from '@grafana/i18n';
import { TableCellHeight } from '@grafana/schema';
import { FieldColorModeId, TableCellHeight } from '@grafana/schema';
import { useStyles2, useTheme2 } from '../../../themes/ThemeContext';
import { ContextMenu } from '../../ContextMenu/ContextMenu';
@ -272,11 +281,30 @@ export function TableNG(props: TableNGProps) {
let _rowHeight = 0;
f.forEach((field, i) => {
const cellOptions = getCellOptions(field);
const cellType = cellOptions.type;
// make sure we use mappings exclusively if they exist, ignore default thresholds mode
// we hack this by using the single color mode calculator
if (cellType === TableCellDisplayMode.Pill && (field.config.mappings?.length ?? 0 > 0)) {
field = {
...field,
config: {
...field.config,
color: {
...field.config.color,
mode: FieldColorModeId.Fixed,
fixedColor: field.config.color?.fixedColor ?? FALLBACK_COLOR,
},
},
};
field.display = getDisplayProcessor({ field, theme });
}
const justifyContent = getTextAlign(field);
const footerStyles = getFooterStyles(justifyContent);
const displayName = getDisplayName(field);
const headerCellClass = getHeaderCellStyles(theme, justifyContent).headerCell;
const cellOptions = getCellOptions(field);
const renderFieldCell = getCellRenderer(field, cellOptions);
const cellInspect = isCellInspectEnabled(field);
@ -293,7 +321,6 @@ export function TableNG(props: TableNGProps) {
)
: undefined;
const cellType = cellOptions.type;
const shouldOverflow = shouldTextOverflow(field);
const shouldWrap = shouldTextWrap(field);
const withTooltip = withDataLinksActionsTooltip(field, cellType);

@ -128,11 +128,14 @@ export function getMaxWrapCell(
* Returns true if text overflow handling should be applied to the cell.
*/
export function shouldTextOverflow(field: Field): boolean {
let type = getCellOptions(field).type;
return (
field.type === FieldType.string &&
// Tech debt: Technically image cells are of type string, which is misleading (kinda?)
// so we need to ensure we don't apply overflow hover states for type image
getCellOptions(field).type !== TableCellDisplayMode.Image &&
type !== TableCellDisplayMode.Image &&
type !== TableCellDisplayMode.Pill &&
!shouldTextWrap(field) &&
!isCellInspectEnabled(field)
);

@ -10,7 +10,6 @@ import { AutoCellOptionsEditor } from './cells/AutoCellOptionsEditor';
import { BarGaugeCellOptionsEditor } from './cells/BarGaugeCellOptionsEditor';
import { ColorBackgroundCellOptionsEditor } from './cells/ColorBackgroundCellOptionsEditor';
import { ImageCellOptionsEditor } from './cells/ImageCellOptionsEditor';
import { PillCellOptionsEditor } from './cells/PillCellOptionsEditor';
import { SparklineCellOptionsEditor } from './cells/SparklineCellOptionsEditor';
// The props that any cell type editor are expected
@ -78,9 +77,6 @@ export const TableCellOptionEditor = ({ value, onChange }: Props) => {
{cellType === TableCellDisplayMode.Image && (
<ImageCellOptionsEditor cellOptions={value} onChange={onCellOptionsChange} />
)}
{cellType === TableCellDisplayMode.Pill && (
<PillCellOptionsEditor cellOptions={value} onChange={onCellOptionsChange} />
)}
</div>
);
};

@ -1,66 +0,0 @@
import { t } from '@grafana/i18n';
import { TablePillCellOptions } from '@grafana/schema';
import { Field, ColorPicker, RadioButtonGroup, Stack } from '@grafana/ui';
import { TableCellEditorProps } from '../TableCellOptionEditor';
const colorModeOptions: Array<{ value: 'auto' | 'fixed' | 'mapped'; label: string }> = [
{ value: 'auto', label: 'Auto' },
{ value: 'fixed', label: 'Fixed color' },
{ value: 'mapped', label: 'Value mapping' },
];
export const PillCellOptionsEditor = ({ cellOptions, onChange }: TableCellEditorProps<TablePillCellOptions>) => {
const colorMode = cellOptions.colorMode || 'auto';
const onColorModeChange = (mode: 'auto' | 'fixed' | 'mapped') => {
const updatedOptions = { ...cellOptions, colorMode: mode };
onChange(updatedOptions);
};
const onColorChange = (color: string) => {
const updatedOptions = { ...cellOptions, color };
onChange(updatedOptions);
};
return (
<Stack direction="column" gap={1}>
<Field
label={t('table.pill-cell-options-editor.label-color-mode', 'Color Mode')}
description={t(
'table.pill-cell-options-editor.description-color-mode',
'Choose how colors are assigned to pills'
)}
noMargin
>
<RadioButtonGroup value={colorMode} onChange={onColorModeChange} options={colorModeOptions} />
</Field>
{colorMode === 'fixed' && (
<Field
label={t('table.pill-cell-options-editor.label-fixed-color', 'Fixed Color')}
description={t(
'table.pill-cell-options-editor.description-fixed-color',
'All pills in this column will use this color'
)}
noMargin
>
<ColorPicker color={cellOptions.color || '#FF780A'} onChange={onColorChange} enableNamedColors={false} />
</Field>
)}
{colorMode === 'mapped' && (
<Field
label={t('table.pill-cell-options-editor.label-value-mappings-info', 'Value Mappings')}
description={t(
'table.pill-cell-options-editor.description-value-mappings-info',
'For Value Mappings either use the global table Value Mappings or the Field overrides Value Mappings. The default will fall back to the Color Scheme. '
)}
noMargin
>
<div>&nbsp;</div>
</Field>
)}
</Stack>
);
};

@ -11871,14 +11871,6 @@
"name-show-table-footer": "Show table footer",
"name-show-table-header": "Show table header",
"name-wrap-header-text": "Wrap header text",
"pill-cell-options-editor": {
"description-color-mode": "Choose how colors are assigned to pills",
"description-fixed-color": "All pills in this column will use this color",
"description-value-mappings-info": "For Value Mappings either use the global table Value Mappings or the Field overrides Value Mappings. The default will fall back to the Color Scheme. ",
"label-color-mode": "Color Mode",
"label-fixed-color": "Fixed Color",
"label-value-mappings-info": "Value Mappings"
},
"placeholder-column-width": "auto",
"placeholder-fields": "All Numeric Fields"
},

Loading…
Cancel
Save