Commit f06dcfc9 by Torkel Ödegaard Committed by GitHub

Table: New cell hover behavior and major refactoring of table cells & style build (#27669)

* Table: Image cell and new hover behavior

* ImageCell: progress

* Table: refactoring cell style generation, tricky stuff

* About to do something

* Getting close

* Need another big change

* Almost everything working

* Filter actions working

* Updated

* Updated

* removed unused prop from interface

* Fixed unit test

* remove unused type
parent 8018059f
......@@ -18,11 +18,7 @@ const defaultScale: ThresholdsConfig = {
};
export const BarGaugeCell: FC<TableCellProps> = props => {
const { field, column, tableStyles, cell } = props;
if (!field.display) {
return null;
}
const { field, column, tableStyles, cell, cellProps } = props;
let { config } = field;
if (!config.thresholds) {
......@@ -32,7 +28,7 @@ export const BarGaugeCell: FC<TableCellProps> = props => {
};
}
const displayValue = field.display(cell.value);
const displayValue = field.display!(cell.value);
let barGaugeMode = BarGaugeDisplayMode.Gradient;
if (field.config.custom && field.config.custom.displayMode === TableCellDisplayMode.LcdGauge) {
......@@ -49,7 +45,7 @@ export const BarGaugeCell: FC<TableCellProps> = props => {
}
return (
<div className={tableStyles.tableCell}>
<div {...cellProps} className={tableStyles.cellContainer}>
<BarGauge
width={width}
height={tableStyles.cellHeightInner}
......
import React, { FC } from 'react';
import { formattedValueToString, LinkModel } from '@grafana/data';
import React, { FC, MouseEventHandler } from 'react';
import { DisplayValue, Field, formattedValueToString, LinkModel } from '@grafana/data';
import { TableCellProps } from './types';
import { TableCellDisplayMode, TableCellProps } from './types';
import tinycolor from 'tinycolor2';
import { TableStyles } from './styles';
import { FilterActions } from './FilterActions';
export const DefaultCell: FC<TableCellProps> = props => {
const { field, cell, tableStyles, row } = props;
let link: LinkModel<any> | undefined;
const { field, cell, tableStyles, row, cellProps } = props;
const displayValue = field.display!(cell.value);
const value = formattedValueToString(displayValue);
const cellStyle = getCellStyle(tableStyles, field, displayValue);
const showFilters = field.config.filterable;
const displayValue = field.display ? field.display(cell.value) : cell.value;
let link: LinkModel<any> | undefined;
let onClick: MouseEventHandler<HTMLAnchorElement> | undefined;
if (field.getLinks) {
link = field.getLinks({
valueRowIndex: row.index,
})[0];
}
const value = field.display ? formattedValueToString(displayValue) : `${displayValue}`;
if (!link) {
return <div className={tableStyles.tableCell}>{value}</div>;
if (link && link.onClick) {
onClick = event => {
// Allow opening in new tab
if (!(event.ctrlKey || event.metaKey || event.shiftKey) && link!.onClick) {
event.preventDefault();
link!.onClick(event);
}
};
}
return (
<div className={tableStyles.tableCell}>
<a
href={link.href}
onClick={
link.onClick
? event => {
// Allow opening in new tab
if (!(event.ctrlKey || event.metaKey || event.shiftKey) && link!.onClick) {
event.preventDefault();
link!.onClick(event);
}
}
: undefined
}
target={link.target}
title={link.title}
className={tableStyles.tableCellLink}
>
{value}
</a>
<div {...cellProps} className={cellStyle}>
{!link && <div className={tableStyles.cellText}>{value}</div>}
{link && (
<a href={link.href} onClick={onClick} target={link.target} title={link.title} className={tableStyles.cellLink}>
{value}
</a>
)}
{showFilters && cell.value && <FilterActions {...props} />}
</div>
);
};
function getCellStyle(tableStyles: TableStyles, field: Field, displayValue: DisplayValue) {
if (field.config.custom?.displayMode === TableCellDisplayMode.ColorText) {
return tableStyles.buildCellContainerStyle(displayValue.color);
}
if (field.config.custom?.displayMode === TableCellDisplayMode.ColorBackground) {
const themeFactor = tableStyles.theme.isDark ? 1 : -0.7;
const bgColor2 = tinycolor(displayValue.color)
.darken(10 * themeFactor)
.spin(5)
.toRgbString();
return tableStyles.buildCellContainerStyle('white', `linear-gradient(120deg, ${bgColor2}, ${displayValue.color})`);
}
return tableStyles.cellContainer;
}
import React, { FC, useCallback } from 'react';
import { FILTER_FOR_OPERATOR, FILTER_OUT_OPERATOR, TableCellProps } from './types';
import { Icon, Tooltip } from '..';
export const FilterActions: FC<TableCellProps> = ({ cell, field, tableStyles, onCellFilterAdded }) => {
const onFilterFor = useCallback(
(event: React.MouseEvent<HTMLDivElement>) =>
onCellFilterAdded({ key: field.name, operator: FILTER_FOR_OPERATOR, value: cell.value }),
[cell, field, onCellFilterAdded]
);
const onFilterOut = useCallback(
(event: React.MouseEvent<HTMLDivElement>) =>
onCellFilterAdded({ key: field.name, operator: FILTER_OUT_OPERATOR, value: cell.value }),
[cell, field, onCellFilterAdded]
);
return (
<div className={tableStyles.filterWrapper}>
<div className={tableStyles.filterItem}>
<Tooltip content="Filter for value" placement="top">
<Icon name={'search-plus'} onClick={onFilterFor} />
</Tooltip>
</div>
<div className={tableStyles.filterItem}>
<Tooltip content="Filter out value" placement="top">
<Icon name={'search-minus'} onClick={onFilterOut} />
</Tooltip>
</div>
</div>
);
};
import React, { FC, useCallback, useState } from 'react';
import { TableCellProps } from 'react-table';
import { GrafanaTheme } from '@grafana/data';
import { css } from 'emotion';
import { stylesFactory, useTheme } from '../../themes';
import { FILTER_FOR_OPERATOR, FILTER_OUT_OPERATOR, TableFilterActionCallback } from './types';
import { Icon, Tooltip } from '..';
import { Props, renderCell } from './TableCell';
interface FilterableTableCellProps extends Pick<Props, 'cell' | 'field' | 'tableStyles'> {
onCellFilterAdded: TableFilterActionCallback;
cellProps: TableCellProps;
}
export const FilterableTableCell: FC<FilterableTableCellProps> = ({
cell,
field,
tableStyles,
onCellFilterAdded,
cellProps,
}) => {
const [showFilters, setShowFilter] = useState(false);
const onMouseOver = useCallback((event: React.MouseEvent<HTMLDivElement>) => setShowFilter(true), [setShowFilter]);
const onMouseLeave = useCallback((event: React.MouseEvent<HTMLDivElement>) => setShowFilter(false), [setShowFilter]);
const onFilterFor = useCallback(
(event: React.MouseEvent<HTMLDivElement>) =>
onCellFilterAdded({ key: field.name, operator: FILTER_FOR_OPERATOR, value: cell.value }),
[cell, field, onCellFilterAdded]
);
const onFilterOut = useCallback(
(event: React.MouseEvent<HTMLDivElement>) =>
onCellFilterAdded({ key: field.name, operator: FILTER_OUT_OPERATOR, value: cell.value }),
[cell, field, onCellFilterAdded]
);
const theme = useTheme();
const styles = getFilterableTableCellStyles(theme);
return (
<div {...cellProps} className={tableStyles.tableCellWrapper} onMouseOver={onMouseOver} onMouseLeave={onMouseLeave}>
{renderCell(cell, field, tableStyles)}
{showFilters && cell.value && (
<div className={styles.filterWrapper}>
<div className={styles.filterItem}>
<Tooltip content="Filter for value" placement="top">
<Icon name={'search-plus'} onClick={onFilterFor} />
</Tooltip>
</div>
<div className={styles.filterItem}>
<Tooltip content="Filter out value" placement="top">
<Icon name={'search-minus'} onClick={onFilterOut} />
</Tooltip>
</div>
</div>
)}
</div>
);
};
const getFilterableTableCellStyles = stylesFactory((theme: GrafanaTheme) => ({
filterWrapper: css`
label: filterWrapper;
display: inline-flex;
justify-content: space-around;
cursor: pointer;
`,
filterItem: css`
label: filterItem;
color: ${theme.colors.textSemiWeak};
padding: 0 ${theme.spacing.xxs};
`,
}));
import React, { FC } from 'react';
import { TableCellProps } from './types';
export const ImageCell: FC<TableCellProps> = props => {
const { cell, tableStyles, cellProps } = props;
return (
<div {...cellProps} className={tableStyles.cellContainer}>
<img src={cell.value} className={tableStyles.imageCell} />
</div>
);
};
......@@ -8,11 +8,7 @@ import { TableCellProps } from './types';
import { GrafanaTheme } from '@grafana/data';
export const JSONViewCell: FC<TableCellProps> = props => {
const { field, cell, tableStyles } = props;
if (!field.display) {
return null;
}
const { cell, tableStyles, cellProps } = props;
const txt = css`
cursor: pointer;
......@@ -21,6 +17,7 @@ export const JSONViewCell: FC<TableCellProps> = props => {
let value = cell.value;
let displayValue = value;
if (isString(value)) {
try {
value = JSON.parse(value);
......@@ -28,11 +25,13 @@ export const JSONViewCell: FC<TableCellProps> = props => {
} else {
displayValue = JSON.stringify(value);
}
const content = <JSONTooltip value={value} />;
return (
<div className={cx(txt, tableStyles.tableCell)}>
<div {...cellProps} className={tableStyles.cellContainer}>
<Tooltip placement="auto" content={content} theme="info-alt">
<div className={tableStyles.overflow}>{displayValue}</div>
<div className={cx(tableStyles.cellText, txt)}>{displayValue}</div>
</Tooltip>
</div>
);
......
......@@ -14,7 +14,7 @@ import {
useTable,
} from 'react-table';
import { FixedSizeList } from 'react-window';
import { getColumns, getHeaderAlign } from './utils';
import { getColumns } from './utils';
import { useTheme } from '../../themes';
import {
TableColumnResizeActionCallback,
......@@ -23,10 +23,10 @@ import {
TableSortByFieldState,
} from './types';
import { getTableStyles, TableStyles } from './styles';
import { TableCell } from './TableCell';
import { Icon } from '../Icon/Icon';
import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar';
import { Filter } from './Filter';
import { TableCell } from './TableCell';
const COLUMN_MIN_WIDTH = 150;
......@@ -229,7 +229,7 @@ function renderHeaderCell(column: any, tableStyles: TableStyles, field?: Field)
}
headerProps.style.position = 'absolute';
headerProps.style.justifyContent = getHeaderAlign(field);
headerProps.style.justifyContent = (column as any).justifyContent;
return (
<div className={tableStyles.headerCell} {...headerProps}>
......
import React, { FC } from 'react';
import { Cell } from 'react-table';
import { Field } from '@grafana/data';
import { getTextAlign } from './utils';
import { TableFilterActionCallback } from './types';
import { TableStyles } from './styles';
import { FilterableTableCell } from './FilterableTableCell';
export interface Props {
cell: Cell;
......@@ -15,31 +12,25 @@ export interface Props {
}
export const TableCell: FC<Props> = ({ cell, field, tableStyles, onCellFilterAdded }) => {
const filterable = field.config.filterable;
const cellProps = cell.getCellProps();
if (cellProps.style) {
cellProps.style.textAlign = getTextAlign(field);
if (!field.display) {
return null;
}
if (filterable && onCellFilterAdded) {
return (
<FilterableTableCell
cell={cell}
field={field}
tableStyles={tableStyles}
onCellFilterAdded={onCellFilterAdded}
cellProps={cellProps}
/>
);
if (cellProps.style) {
cellProps.style.minWidth = cellProps.style.width;
cellProps.style.justifyContent = (cell.column as any).justifyContent;
}
return (
<div {...cellProps} className={tableStyles.tableCellWrapper}>
{renderCell(cell, field, tableStyles)}
</div>
<>
{cell.render('Cell', {
field,
tableStyles,
onCellFilterAdded,
cellProps,
})}
</>
);
};
export const renderCell = (cell: Cell, field: Field, tableStyles: TableStyles) =>
cell.render('Cell', { field, tableStyles });
import { css } from 'emotion';
import { css, cx } from 'emotion';
import { GrafanaTheme } from '@grafana/data';
import { styleMixins, stylesFactory } from '../../themes';
import { getScrollbarWidth } from '../../utils';
export interface TableStyles {
cellHeight: number;
cellHeightInner: number;
cellPadding: number;
rowHeight: number;
table: string;
thead: string;
headerCell: string;
headerCellLabel: string;
headerFilter: string;
tableCell: string;
tableCellWrapper: string;
tableCellLink: string;
row: string;
theme: GrafanaTheme;
resizeHandle: string;
overflow: string;
}
export const getTableStyles = stylesFactory((theme: GrafanaTheme) => {
const { palette, colors } = theme;
const headerBg = theme.colors.bg2;
const borderColor = theme.colors.border1;
const resizerColor = theme.isLight ? palette.blue95 : palette.blue77;
const cellPadding = 6;
const lineHeight = theme.typography.lineHeight.md;
const bodyFontSize = 14;
const cellHeight = cellPadding * 2 + bodyFontSize * lineHeight;
const rowHoverBg = styleMixins.hoverColor(theme.colors.bg1, theme);
const scollbarWidth = getScrollbarWidth();
export const getTableStyles = stylesFactory(
(theme: GrafanaTheme): TableStyles => {
const { palette, colors } = theme;
const headerBg = theme.colors.bg2;
const borderColor = theme.colors.border1;
const resizerColor = theme.isLight ? palette.blue95 : palette.blue77;
const padding = 6;
const lineHeight = theme.typography.lineHeight.md;
const bodyFontSize = 14;
const cellHeight = padding * 2 + bodyFontSize * lineHeight;
const rowHoverBg = styleMixins.hoverColor(theme.colors.bg1, theme);
const scollbarWidth = getScrollbarWidth();
const buildCellContainerStyle = (color?: string, background?: string) => {
return css`
padding: ${cellPadding}px;
width: 100%;
height: 100%;
display: flex;
align-items: center;
border-right: 1px solid ${borderColor};
return {
theme,
cellHeight,
cellPadding: padding,
cellHeightInner: bodyFontSize * lineHeight,
rowHeight: cellHeight + 2,
table: css`
height: 100%;
width: 100%;
overflow: auto;
display: flex;
`,
thead: css`
label: thead;
height: ${cellHeight}px;
overflow-y: auto;
overflow-x: hidden;
background: ${headerBg};
position: relative;
`,
headerCell: css`
padding: ${padding}px;
overflow: hidden;
white-space: nowrap;
color: ${colors.textBlue};
border-right: 1px solid ${theme.colors.panelBg};
display: flex;
${color ? `color: ${color};` : ''};
${background ? `background: ${background};` : ''};
&:last-child {
border-right: none;
}
`,
headerCellLabel: css`
cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: flex;
margin-right: ${theme.spacing.xs};
`,
headerFilter: css`
label: headerFilter;
cursor: pointer;
`,
row: css`
label: row;
border-bottom: 1px solid ${borderColor};
&:last-child {
border-right: none;
&:hover {
background-color: ${rowHoverBg};
> div {
padding-right: ${scollbarWidth + cellPadding}px;
}
`,
tableCellWrapper: css`
border-right: 1px solid ${borderColor};
display: inline-flex;
align-items: center;
height: 100%;
}
&:last-child {
border-right: none;
&:hover {
overflow: visible;
width: auto !important;
box-shadow: 0 0 2px ${theme.colors.formFocusOutline};
background: ${background ?? rowHoverBg};
z-index: 1;
> div {
padding-right: ${scollbarWidth + padding}px;
}
.cell-filter-actions  {
display: inline-flex;
}
`,
tableCellLink: css`
text-decoration: underline;
`,
tableCell: css`
padding: ${padding}px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
flex: 1;
`,
overflow: css`
overflow: hidden;
text-overflow: ellipsis;
`,
resizeHandle: css`
label: resizeHandle;
cursor: col-resize !important;
display: inline-block;
background: ${resizerColor};
opacity: 0;
transition: opacity 0.2s ease-in-out;
width: 8px;
height: 100%;
position: absolute;
right: -4px;
border-radius: 3px;
top: 0;
z-index: ${theme.zIndex.dropdown};
touch-action: none;
}
`;
};
&:hover {
opacity: 1;
}
return {
theme,
cellHeight,
buildCellContainerStyle,
cellPadding,
cellHeightInner: bodyFontSize * lineHeight,
rowHeight: cellHeight + 2,
table: css`
height: 100%;
width: 100%;
overflow: auto;
display: flex;
`,
thead: css`
label: thead;
height: ${cellHeight}px;
overflow-y: auto;
overflow-x: hidden;
background: ${headerBg};
position: relative;
`,
headerCell: css`
padding: ${cellPadding}px;
overflow: hidden;
white-space: nowrap;
color: ${colors.textBlue};
border-right: 1px solid ${theme.colors.panelBg};
display: flex;
&:last-child {
border-right: none;
}
`,
headerCellLabel: css`
cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: flex;
margin-right: ${theme.spacing.xs};
`,
cellContainer: buildCellContainerStyle(),
cellText: css`
cursor: text;
overflow: hidden;
text-overflow: ellipsis;
user-select: text;
white-space: nowrap;
`,
cellLink: css`
cursor: pointer;
overflow: hidden;
text-overflow: ellipsis;
user-select: text;
white-space: nowrap;
text-decoration: underline;
`,
headerFilter: css`
label: headerFilter;
cursor: pointer;
`,
row: css`
label: row;
border-bottom: 1px solid ${borderColor};
&:hover {
background-color: ${rowHoverBg};
}
`,
imageCell: css`
height: 100%;
`,
resizeHandle: css`
label: resizeHandle;
cursor: col-resize !important;
display: inline-block;
background: ${resizerColor};
opacity: 0;
transition: opacity 0.2s ease-in-out;
width: 8px;
height: 100%;
position: absolute;
right: -4px;
border-radius: 3px;
top: 0;
z-index: ${theme.zIndex.dropdown};
touch-action: none;
&:hover {
opacity: 1;
}
`,
filterWrapper: cx(
css`
label: filterWrapper;
display: none;
justify-content: flex-end;
flex-grow: 1;
opacity: 0.6;
padding-left: ${theme.spacing.xxs};
`,
};
}
);
'cell-filter-actions'
),
filterItem: css`
label: filterItem;
cursor: pointer;
padding: 0 ${theme.spacing.xxs};
`,
};
});
export type TableStyles = ReturnType<typeof getTableStyles>;
import { CellProps } from 'react-table';
import { Field } from '@grafana/data';
import { TableStyles } from './styles';
import { FC } from 'react';
import { CSSProperties, FC } from 'react';
export interface TableFieldOptions {
width: number;
......@@ -18,6 +18,7 @@ export enum TableCellDisplayMode {
LcdGauge = 'lcd-gauge',
JSONView = 'json-view',
BasicGauge = 'basic',
Image = 'image',
}
export type FieldTextAlignment = 'auto' | 'left' | 'right' | 'center';
......@@ -41,7 +42,9 @@ export interface TableSortByFieldState {
export interface TableCellProps extends CellProps<any> {
tableStyles: TableStyles;
cellProps: CSSProperties;
field: Field;
onCellFilterAdded: TableFilterActionCallback;
}
export type CellComponent = FC<TableCellProps>;
......@@ -66,7 +66,7 @@ describe('Table utils', () => {
it('Should set textAlign to right for number values', () => {
const data = getData();
const textAlign = getTextAlign(data.fields[1]);
expect(textAlign).toBe('right');
expect(textAlign).toBe('flex-end');
});
});
......
import { Column, Row } from 'react-table';
import memoizeOne from 'memoize-one';
import { css, cx } from 'emotion';
import tinycolor from 'tinycolor2';
import { ContentPosition, TextAlignProperty } from 'csstype';
import { ContentPosition } from 'csstype';
import {
DataFrame,
Field,
......@@ -14,13 +12,13 @@ import {
import { DefaultCell } from './DefaultCell';
import { BarGaugeCell } from './BarGaugeCell';
import { TableCellDisplayMode, TableCellProps, TableFieldOptions } from './types';
import { withTableStyles } from './withTableStyles';
import { TableCellDisplayMode, TableFieldOptions } from './types';
import { JSONViewCell } from './JSONViewCell';
import { ImageCell } from './ImageCell';
export function getTextAlign(field?: Field): TextAlignProperty {
export function getTextAlign(field?: Field): ContentPosition {
if (!field) {
return 'left';
return 'flex-start';
}
if (field.config.custom) {
......@@ -28,19 +26,19 @@ export function getTextAlign(field?: Field): TextAlignProperty {
switch (custom.align) {
case 'right':
return 'right';
return 'flex-end';
case 'left':
return 'left';
return 'flex-start';
case 'center':
return 'center';
}
}
if (field.type === FieldType.number) {
return 'right';
return 'flex-end';
}
return 'left';
return 'flex-start';
}
export function getColumns(data: DataFrame, availableWidth: number, columnMinWidth: number): Column[] {
......@@ -68,6 +66,7 @@ export function getColumns(data: DataFrame, availableWidth: number, columnMinWid
return 'alphanumeric';
}
};
const Cell = getCellComponent(fieldTableOptions.displayMode, field);
columns.push({
Cell,
......@@ -80,6 +79,7 @@ export function getColumns(data: DataFrame, availableWidth: number, columnMinWid
width: fieldTableOptions.width,
minWidth: 50,
filter: memoizeOne(filterByValue),
justifyContent: getTextAlign(field),
});
}
......@@ -97,9 +97,10 @@ export function getColumns(data: DataFrame, availableWidth: number, columnMinWid
function getCellComponent(displayMode: TableCellDisplayMode, field: Field) {
switch (displayMode) {
case TableCellDisplayMode.ColorText:
return withTableStyles(DefaultCell, getTextColorStyle);
case TableCellDisplayMode.ColorBackground:
return withTableStyles(DefaultCell, getBackgroundColorStyle);
return DefaultCell;
case TableCellDisplayMode.Image:
return ImageCell;
case TableCellDisplayMode.LcdGauge:
case TableCellDisplayMode.BasicGauge:
case TableCellDisplayMode.GradientGauge:
......@@ -115,58 +116,6 @@ function getCellComponent(displayMode: TableCellDisplayMode, field: Field) {
return DefaultCell;
}
function getTextColorStyle(props: TableCellProps) {
const { field, cell, tableStyles } = props;
if (!field.display) {
return tableStyles;
}
const displayValue = field.display(cell.value);
if (!displayValue.color) {
return tableStyles;
}
const extendedStyle = css`
color: ${displayValue.color};
`;
return {
...tableStyles,
tableCell: cx(tableStyles.tableCell, extendedStyle),
};
}
function getBackgroundColorStyle(props: TableCellProps) {
const { field, cell, tableStyles } = props;
if (!field.display) {
return tableStyles;
}
const displayValue = field.display(cell.value);
if (!displayValue.color) {
return tableStyles;
}
const themeFactor = tableStyles.theme.isDark ? 1 : -0.7;
const bgColor2 = tinycolor(displayValue.color)
.darken(10 * themeFactor)
.spin(5)
.toRgbString();
const extendedStyle = css`
background: linear-gradient(120deg, ${bgColor2}, ${displayValue.color});
color: white;
height: ${tableStyles.cellHeight}px;
padding: ${tableStyles.cellPadding}px;
`;
return {
...tableStyles,
tableCell: cx(tableStyles.tableCell, extendedStyle),
};
}
export function filterByValue(rows: Row[], id: string, filterValues?: SelectableValue[]) {
if (rows.length === 0) {
return rows;
......@@ -186,20 +135,6 @@ export function filterByValue(rows: Row[], id: string, filterValues?: Selectable
});
}
export function getHeaderAlign(field?: Field): ContentPosition {
const align = getTextAlign(field);
if (align === 'right') {
return 'flex-end';
}
if (align === 'center') {
return align;
}
return 'flex-start';
}
export function calculateUniqueFieldValues(rows: any[], field?: Field) {
if (!field || rows.length === 0) {
return {};
......
import { CellComponent, TableCellProps } from './types';
import { TableStyles } from './styles';
export const withTableStyles = (
CellComponent: CellComponent,
getExtendedStyles: (props: TableCellProps) => TableStyles
): CellComponent => {
function WithTableStyles(props: TableCellProps) {
return CellComponent({ ...props, tableStyles: getExtendedStyles(props) });
}
WithTableStyles.displayName = CellComponent.displayName || CellComponent.name;
return WithTableStyles;
};
......@@ -47,6 +47,7 @@ export const plugin = new PanelPlugin<Options, CustomFieldConfig>(TablePanel)
{ value: TableCellDisplayMode.LcdGauge, label: 'LCD gauge' },
{ value: TableCellDisplayMode.BasicGauge, label: 'Basic gauge' },
{ value: TableCellDisplayMode.JSONView, label: 'JSON View' },
{ value: TableCellDisplayMode.Image, label: 'Image' },
],
},
})
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment