Table: Styling from field (#110991)
* Table: Styling from field * fix mistake with gdev * e2e for kitchen sink * add counter-example in e2e for completeness * unit tests for utils * update to store style field per-column, replace util * optimize branches column-level variables
This commit is contained in:
@@ -1009,6 +1009,10 @@ export interface TableFieldOptions extends HideableFieldConfig {
|
||||
hideHeader?: boolean;
|
||||
inspect: boolean;
|
||||
minWidth?: number;
|
||||
/**
|
||||
* The name of the field which contains styling overrides for this cell
|
||||
*/
|
||||
styleField?: string;
|
||||
/**
|
||||
* Selecting or hovering this field will show a tooltip containing the content within the target field
|
||||
*/
|
||||
|
||||
@@ -129,6 +129,8 @@ TableFieldOptions: {
|
||||
wrapHeaderText?: bool
|
||||
// Selecting or hovering this field will show a tooltip containing the content within the target field
|
||||
tooltip?: TableCellTooltipOptions
|
||||
// The name of the field which contains styling overrides for this cell
|
||||
styleField?: string
|
||||
// options for the footer for this field
|
||||
footer?: TableFooterOptions
|
||||
} & HideableFieldConfig @cuetsy(kind="interface")
|
||||
|
||||
@@ -96,6 +96,7 @@ import {
|
||||
shouldTextWrap,
|
||||
withDataLinksActionsTooltip,
|
||||
getSummaryCellTextAlign,
|
||||
parseStyleJson,
|
||||
} from './utils';
|
||||
|
||||
const EXPANDED_COLUMN_KEY = 'expanded';
|
||||
@@ -477,6 +478,10 @@ export function TableNG(props: TableNGProps) {
|
||||
const linkStyles = getLinkStyles(theme, canBeColorized);
|
||||
const cellParentStyles = clsx(defaultCellStyles, linkStyles);
|
||||
const maxHeightClassName = maxRowHeight ? getMaxHeightCellStyles(theme, cellStyleOptions) : undefined;
|
||||
const styleFieldValue = field.config.custom?.styleField;
|
||||
const styleField = styleFieldValue ? data.fields.find(predicateByName(styleFieldValue)) : undefined;
|
||||
const styleFieldName = styleField ? getDisplayName(styleField) : undefined;
|
||||
const hasValidStyleField = Boolean(styleFieldName);
|
||||
|
||||
// TODO: in future extend this to ensure a non-classic color scheme is set with AutoCell
|
||||
|
||||
@@ -504,6 +509,9 @@ export function TableNG(props: TableNGProps) {
|
||||
const cellColorStyles = getCellColorInlineStyles(cellOptions, displayValue, applyToRowBgFn != null);
|
||||
Object.assign(style, cellColorStyles);
|
||||
}
|
||||
if (hasValidStyleField) {
|
||||
style = { ...style, ...parseStyleJson(props.row[styleFieldName!]) };
|
||||
}
|
||||
|
||||
return (
|
||||
<Cell
|
||||
|
||||
@@ -46,6 +46,7 @@ import {
|
||||
getDefaultRowHeight,
|
||||
getDisplayName,
|
||||
predicateByName,
|
||||
parseStyleJson,
|
||||
calculateFooterHeight,
|
||||
} from './utils';
|
||||
|
||||
@@ -1459,4 +1460,49 @@ describe('TableNG utils', () => {
|
||||
expect(predicate(field)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseStyleJson', () => {
|
||||
it('parses the contents of the styleField for this row and returns a style object', () => {
|
||||
expect(parseStyleJson('{"color":"red"}')).toEqual({ color: 'red' });
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ type: 'number', value: 12345 },
|
||||
{ type: 'boolean', value: true },
|
||||
{ type: 'null', value: null },
|
||||
{ type: 'undefined', value: undefined },
|
||||
{ type: 'object', value: { color: 'red' } },
|
||||
{ type: 'array', value: ['not', 'a', 'string'] },
|
||||
])('returns void if input is a $type', ({ value }) => {
|
||||
expect(parseStyleJson(value)).toBeUndefined();
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ type: 'array', value: '["not","an","object"]' },
|
||||
{ type: 'string', value: '"just a string"' },
|
||||
{ type: 'number', value: '12345' },
|
||||
{ type: 'boolean', value: 'true' },
|
||||
{ type: 'null', value: 'null' },
|
||||
])('returns void and does not throw if the parsed JSON is a $type', ({ value }) => {
|
||||
expect(parseStyleJson(value)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns void and does not throw if this is invalid JSON (but it does console.error)', () => {
|
||||
jest.spyOn(console, 'error').mockImplementation();
|
||||
expect(parseStyleJson('{"mal": "formed}')).toBeUndefined();
|
||||
expect(console.error).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('only calls console.error once for a given malformed style', () => {
|
||||
jest.spyOn(console, 'error').mockImplementation();
|
||||
for (let i = 0; i < 100; i++) {
|
||||
parseStyleJson('{"mal": "formed-in-a-new-way}');
|
||||
}
|
||||
expect(console.error).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('returns an object with invalid style properties, because we do not validate the style properties', () => {
|
||||
expect(parseStyleJson('{"notARealStyle": "someValue"}')).toEqual({ notARealStyle: 'someValue' });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1009,3 +1009,23 @@ export function getSummaryCellTextAlign(textAlign: TextAlign, cellType: TableCel
|
||||
|
||||
return textAlign;
|
||||
}
|
||||
|
||||
// we keep this set to avoid spamming the heck out of the console, since it's quite likely that if we fail to parse
|
||||
// a value once, it'll happen again and again for many rows in a table, and spamming the console is slow.
|
||||
let warnedAboutStyleJsonSet = new Set<string>();
|
||||
export function parseStyleJson(rawValue: unknown): CSSProperties | void {
|
||||
// confirms existence of value and serves as a type guard
|
||||
if (typeof rawValue === 'string') {
|
||||
try {
|
||||
const parsedJsonValue = JSON.parse(rawValue);
|
||||
if (parsedJsonValue != null && typeof parsedJsonValue === 'object' && !Array.isArray(parsedJsonValue)) {
|
||||
return parsedJsonValue;
|
||||
}
|
||||
} catch (e) {
|
||||
if (!warnedAboutStyleJsonSet.has(rawValue)) {
|
||||
console.error(`encountered invalid cell style JSON: ${rawValue}`, e);
|
||||
warnedAboutStyleJsonSet.add(rawValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user