Table: Move cell-specific styles out to their own methods (#108941)
* TableNG: Markdown cell, plus custom row height * tab indentation in cue file * fix i18n * trying an auto height with the updated RDG * get auto cellHeight working * i18n updates * hoor disable_sanitize_html flag in MarkdownCell * update react-data-grid version to attempt to support page up and down * removing custom height * use the latest experimental RDG with paging up and down * TableNG: Wrap text for DataLinks and Pills; groundwork for max wrap length * disable editing max wrapped lines for now * disable wrap text line limit e2e * new i18n extract after commenting out input * wip * kill max wrapped lines for now * more cleanup * remove targeting classes added for max wrapped lines * fix Pill test * couple more style cleanups * Table: Move cell-specific styles out to their own methods * move styles into their own file * combine renderer and style declarations to make auto cells work better, complete cleanup of internal cell elements * fix e2es given these updates * add a couple tests * wip: tests * add tests * bump up capital letters in lorem ipsum * fix copy-pasta mistake * whoops, mis-merged the selector * use a local count instead of getCellLinks * use react-data-grid on react-18 branch * fix linting on test * gdev dashboard and smoketest for Markdown table * remove cellHeightCustom * restore bugfix from adversarial AI-generated JSON * reorganize in light of recent and upcoming changes * cleanup * override the whitespace for markdown * what are these auto imports about... * fix cell height selector from merge * also remove cellHeightCustom * i18n * avoid the important override in markdown cell styles * revert some betterer config autoformatting * slight code cleanup * s/cat/grot, add color link panel to kitchen sink, fix color link/image link style issues * update panelid for empty table panel test * link styles outside of cell style setup * flesh out kitchen sink examples, update ImageCell and applyToRow * clean up some inconsistent states * fix lint issue * gdev update * format JSON to satisfy linter * shortening the text in the long text field --------- Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
This commit is contained in:
co-authored by
Leon Sorokin
parent
4b78e54304
commit
cc64b17483
@@ -157,4 +157,23 @@ module.exports = [
|
||||
'no-barrel-files/no-barrel-files': 'error',
|
||||
},
|
||||
},
|
||||
{
|
||||
// custom rule for Table to avoid performance regressions
|
||||
files: ['packages/grafana-ui/src/components/Table/TableNG/Cells/**/*.{ts,tsx}'],
|
||||
rules: {
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
patterns: [
|
||||
{
|
||||
group: ['**/themes/ThemeContext'],
|
||||
importNames: ['useStyles2', 'useTheme2'],
|
||||
message:
|
||||
'Do not use "useStyles2" or "useTheme2" in a cell directly. Instead, provide styles to cells via `getDefaultCellStyles` or `getCellSpecificStyles`.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -93,7 +93,7 @@
|
||||
"pluginVersion": "12.1.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"csvContent": "id,markdown\n1,\"<dl>\n <dt>Definition list</dt>\n <dd>Is something people use sometimes.</dd>\n <dt>Markdown in HTML</dt>\n <dd>Does *not* work **very** well. Use HTML <em onclick=\"\"alert('hi');\"\">tags</em>.</dd>\n</dl>\"\n2,\"Three or more...\n\n---\n\nHyphens\n\n***\n\nAsterisks\n\n___\n\nUnderscores\"\n3,\"Here's a line for us to start with.\n\nThis line is separated from the one above by two newlines, so it will be a *separate paragraph*.\n\nThis line is also a separate paragraph, but...\nThis line is only separated by a single newline, so it's a separate line in the *same paragraph*.\"\n4,\"<span style='color:red'>red</span>, <span style='color:green'>green</span>, <span style='color:blue'>blue</span>\"\n5,\"<img src='https://placecats.com/bella/300/180'>\"\n6,\"[Link](https://grafana.com), or <a href=\"\"https://grafana.com\"\" target=\"\"_blank\"\" ref=\"\"noopener noreferrer\"\">HTML link</a>\"\n7,\"1. foo\n1. bar\n - baz\n * bim\n3. bip\"\n8,\"# heading 1\n## heading 2\n### heading 3\n#### heading 4\n##### heading 5\n###### heading 6\"\n9,\"Emphasis, aka italics, with *asterisks* or _underscores_.\n\nStrong emphasis, aka bold, with **asterisks** or __underscores__.\n\nCombined emphasis with **asterisks and _underscores_**.\n\nStrikethrough uses two tildes. ~~Scratch this.~~\n\n<u>underline does require an HTML element tho.</u>\"\n10,\"```javascript\nvar s = 'JavaScript syntax highlighting';\nalert(s);\n```\"\n11,\"<style>thead { border-bottom: 1px solid lightgray; } td, th { padding: 6px; }</style>\n| Month | Savings |\n| -------- | ------- |\n| I heard | $250 |\n| you like | $80 |\n| tables | $365 |\"\n",
|
||||
"csvContent": "id,markdown\n1,\"<dl>\n <dt>Definition list</dt>\n <dd>Is something people use sometimes.</dd>\n <dt>Markdown in HTML</dt>\n <dd>Does *not* work **very** well. Use HTML <em onclick=\"\"alert('hi');\"\">tags</em>.</dd>\n</dl>\"\n2,\"Three or more...\n\n---\n\nHyphens\n\n***\n\nAsterisks\n\n___\n\nUnderscores\"\n3,\"Here's a line for us to start with.\n\nThis line is separated from the one above by two newlines, so it will be a *separate paragraph*.\n\nThis line is also a separate paragraph, but...\nThis line is only separated by a single newline, so it's a separate line in the *same paragraph*.\"\n4,\"<span style='color:red'>red</span>, <span style='color:green'>green</span>, <span style='color:blue'>blue</span>\"\n5,\"<img src='https://grafana.com/media/golden-grot/graphic-golden-grot-awards-hero-image.png'>\"\n6,\"[Link](https://grafana.com), or <a href=\"\"https://grafana.com\"\" target=\"\"_blank\"\" ref=\"\"noopener noreferrer\"\">HTML link</a>\"\n7,\"1. foo\n1. bar\n - baz\n * bim\n3. bip\"\n8,\"# heading 1\n## heading 2\n### heading 3\n#### heading 4\n##### heading 5\n###### heading 6\"\n9,\"Emphasis, aka italics, with *asterisks* or _underscores_.\n\nStrong emphasis, aka bold, with **asterisks** or __underscores__.\n\nCombined emphasis with **asterisks and _underscores_**.\n\nStrikethrough uses two tildes. ~~Scratch this.~~\n\n<u>underline does require an HTML element tho.</u>\"\n10,\"```javascript\nvar s = 'JavaScript syntax highlighting';\nalert(s);\n```\"\n11,\"<style>thead { border-bottom: 1px solid lightgray; } td, th { padding: 6px; }</style>\n| Month | Savings |\n| -------- | ------- |\n| I heard | $250 |\n| you like | $80 |\n| tables | $365 |\"\n",
|
||||
"datasource": {
|
||||
"type": "grafana-testdata-datasource",
|
||||
"uid": "gdev-testdata"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Page, Locator } from '@playwright/test';
|
||||
|
||||
import { test, expect } from '@grafana/plugin-e2e';
|
||||
import { test, expect, E2ESelectorGroups } from '@grafana/plugin-e2e';
|
||||
|
||||
const DASHBOARD_UID = 'dcb9f5e9-8066-4397-889e-864b99555dbb';
|
||||
|
||||
@@ -41,6 +41,21 @@ const getColumnIdx = async (loc: Page | Locator, columnName: string) => {
|
||||
return result;
|
||||
};
|
||||
|
||||
const disableAllTextWrap = async (loc: Page | Locator, selectors: E2ESelectorGroups) => {
|
||||
// disable text wrapping for all of the columns, since long text with links in them can push the links off the screen.
|
||||
const wrapTextToggle = loc.locator(
|
||||
`[aria-label="${selectors.components.PanelEditor.OptionsPane.fieldLabel('Wrap text')}"]`
|
||||
);
|
||||
const count = await wrapTextToggle.count();
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const toggle = wrapTextToggle.nth(i);
|
||||
if ((await toggle.locator('//preceding-sibling::input').getAttribute('checked')) !== null) {
|
||||
await toggle.click();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table'] }, () => {
|
||||
test('Tests word wrap, hover overflow, and cell inspect', async ({ gotoDashboardPage, selectors, page }) => {
|
||||
const dashboardPage = await gotoDashboardPage({
|
||||
@@ -214,11 +229,7 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table']
|
||||
// because of text wrapping, we're guaranteed to only be showing a single row when we enable pagination.
|
||||
await expect(page.getByText(/([\d]+) - ([\d]+) of ([\d]+) rows/)).toBeVisible();
|
||||
|
||||
// FIXME horrible selector for the "Wrap text" toggle for the "Long text" column.
|
||||
await page
|
||||
.locator('[id="Override 13"]')
|
||||
.locator(`[aria-label="${selectors.components.PanelEditor.OptionsPane.fieldLabel('Wrap text')}"]`)
|
||||
.click();
|
||||
await disableAllTextWrap(page, selectors);
|
||||
|
||||
// any number of rows that is not "1" is allowed here, we don't want to police the exact number of rows that
|
||||
// are rendered since there are tons of factors which could effect this. we do want to grab this number for comparison
|
||||
@@ -281,13 +292,7 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table']
|
||||
dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Table - Kitchen Sink'))
|
||||
).toBeVisible();
|
||||
|
||||
// disable text wrapping for this test to make it easier to click the links, the long lorem ipsum
|
||||
// can push the links off the screen.
|
||||
// FIXME very bad selector to get the correct "wrap text" toggle here.
|
||||
await page
|
||||
.locator('[id="Override 13"]')
|
||||
.locator(`[aria-label="${selectors.components.PanelEditor.OptionsPane.fieldLabel('Wrap text')}"]`)
|
||||
.click();
|
||||
await disableAllTextWrap(page, selectors);
|
||||
|
||||
const infoColumnIdx = await getColumnIdx(page, 'Info');
|
||||
const pillColIdx = await getColumnIdx(page, 'Pills');
|
||||
@@ -351,7 +356,7 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table']
|
||||
test('Empty Table panel', async ({ gotoDashboardPage, selectors }) => {
|
||||
const dashboardPage = await gotoDashboardPage({
|
||||
uid: DASHBOARD_UID,
|
||||
queryParams: new URLSearchParams({ editPanel: '2' }),
|
||||
queryParams: new URLSearchParams({ editPanel: '3' }),
|
||||
});
|
||||
|
||||
await expect(
|
||||
|
||||
@@ -704,6 +704,7 @@ export enum TableCellDisplayMode {
|
||||
Custom = 'custom',
|
||||
DataLinks = 'data-links',
|
||||
Gauge = 'gauge',
|
||||
Geo = 'geo',
|
||||
GradientGauge = 'gradient-gauge',
|
||||
Image = 'image',
|
||||
JSONView = 'json-view',
|
||||
@@ -837,27 +838,6 @@ export interface TablePillCellOptions extends TableWrapTextOptions {
|
||||
type: TableCellDisplayMode.Pill;
|
||||
}
|
||||
|
||||
export interface TableMarkdownCellOptions {
|
||||
dynamicHeight?: boolean;
|
||||
type: TableCellDisplayMode.Markdown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Height of a table cell
|
||||
*/
|
||||
export enum TableCellHeight {
|
||||
Auto = 'auto',
|
||||
Lg = 'lg',
|
||||
Md = 'md',
|
||||
Sm = 'sm',
|
||||
}
|
||||
|
||||
/**
|
||||
* Table cell options. Each cell has a display mode
|
||||
* and other potential options for that display.
|
||||
*/
|
||||
export type TableCellOptions = (TableAutoCellOptions | TableSparklineCellOptions | TableBarGaugeCellOptions | TableColoredBackgroundCellOptions | TableColorTextCellOptions | TableImageCellOptions | TablePillCellOptions | TableDataLinksCellOptions | TableActionsCellOptions | TableJsonViewCellOptions | TableMarkdownCellOptions);
|
||||
|
||||
/**
|
||||
* Use UTC/GMT timezone
|
||||
*/
|
||||
@@ -973,6 +953,29 @@ export enum ComparisonOperation {
|
||||
NEQ = 'neq',
|
||||
}
|
||||
|
||||
export interface TableMarkdownCellOptions {
|
||||
dynamicHeight?: boolean;
|
||||
type: TableCellDisplayMode.Markdown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Height of a table cell
|
||||
*/
|
||||
export enum TableCellHeight {
|
||||
Auto = 'auto',
|
||||
Lg = 'lg',
|
||||
Md = 'md',
|
||||
Sm = 'sm',
|
||||
}
|
||||
|
||||
/**
|
||||
* Table cell options. Each cell has a display mode
|
||||
* and other potential options for that display.
|
||||
*/
|
||||
export type TableCellOptions = (TableAutoCellOptions | TableSparklineCellOptions | TableBarGaugeCellOptions | TableColoredBackgroundCellOptions | TableColorTextCellOptions | TableImageCellOptions | TablePillCellOptions | TableDataLinksCellOptions | TableActionsCellOptions | TableJsonViewCellOptions | TableMarkdownCellOptions | {
|
||||
type: TableCellDisplayMode.Geo
|
||||
});
|
||||
|
||||
/**
|
||||
* Field options for each field within a table (e.g 10, "The String", 64.20, etc.)
|
||||
* Generally defines alignment, filtering capabilties, display options, etc.
|
||||
|
||||
@@ -4,7 +4,7 @@ package common
|
||||
// in the table such as colored text, JSON, gauge, etc.
|
||||
// The color-background-solid, gradient-gauge, and lcd-gauge
|
||||
// modes are deprecated in favor of new cell subOptions
|
||||
TableCellDisplayMode: "auto" | "color-text" | "color-background" | "color-background-solid" | "gradient-gauge" | "lcd-gauge" | "json-view" | "basic" | "image" | "gauge" | "sparkline" | "data-links" | "custom" | "actions" | "pill" | "markdown" @cuetsy(kind="enum",memberNames="Auto|ColorText|ColorBackground|ColorBackgroundSolid|GradientGauge|LcdGauge|JSONView|BasicGauge|Image|Gauge|Sparkline|DataLinks|Custom|Actions|Pill|Markdown")
|
||||
TableCellDisplayMode: "auto" | "color-text" | "color-background" | "color-background-solid" | "gradient-gauge" | "lcd-gauge" | "json-view" | "basic" | "image" | "gauge" | "sparkline" | "data-links" | "custom" | "actions" | "pill" | "markdown" | "geo" @cuetsy(kind="enum",memberNames="Auto|ColorText|ColorBackground|ColorBackgroundSolid|GradientGauge|LcdGauge|JSONView|BasicGauge|Image|Gauge|Sparkline|DataLinks|Custom|Actions|Pill|Markdown|Geo")
|
||||
|
||||
// Display mode to the "Colored Background" display
|
||||
// mode for table cells. Either displays a solid color (basic mode)
|
||||
@@ -96,12 +96,20 @@ TableMarkdownCellOptions: {
|
||||
dynamicHeight?: bool
|
||||
} @cuetsy(kind="interface")
|
||||
|
||||
TableMarkdownCellOptions: {
|
||||
type: TableCellDisplayMode & "markdown"
|
||||
} @cuetsy(kind="interface")
|
||||
|
||||
TableGeoCellOptions: {
|
||||
type: TableCellDisplayMode & "geo"
|
||||
}
|
||||
|
||||
// Height of a table cell
|
||||
TableCellHeight: "sm" | "md" | "lg" | "auto" @cuetsy(kind="enum")
|
||||
|
||||
// Table cell options. Each cell has a display mode
|
||||
// and other potential options for that display.
|
||||
TableCellOptions: TableAutoCellOptions | TableSparklineCellOptions | TableBarGaugeCellOptions | TableColoredBackgroundCellOptions | TableColorTextCellOptions | TableImageCellOptions | TablePillCellOptions | TableDataLinksCellOptions | TableActionsCellOptions | TableJsonViewCellOptions | TableMarkdownCellOptions @cuetsy(kind="type")
|
||||
TableCellOptions: TableAutoCellOptions | TableSparklineCellOptions | TableBarGaugeCellOptions | TableColoredBackgroundCellOptions | TableColorTextCellOptions | TableImageCellOptions | TablePillCellOptions | TableDataLinksCellOptions | TableActionsCellOptions | TableJsonViewCellOptions | TableMarkdownCellOptions | TableGeoCellOptions @cuetsy(kind="type")
|
||||
|
||||
// Field options for each field within a table (e.g 10, "The String", 64.20, etc.)
|
||||
// Generally defines alignment, filtering capabilties, display options, etc.
|
||||
|
||||
@@ -1,29 +1,16 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
|
||||
import { useStyles2 } from '../../../../themes/ThemeContext';
|
||||
import { ActionButton } from '../../../Actions/ActionButton';
|
||||
import { ActionCellProps } from '../types';
|
||||
import { ActionCellProps, TableCellStyles } from '../types';
|
||||
|
||||
export const ActionsCell = ({ field, rowIdx, getActions }: ActionCellProps) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
const actions = getActions(field, rowIdx);
|
||||
|
||||
const actions = useMemo(() => getActions(field, rowIdx), [getActions, field, rowIdx]);
|
||||
if (actions.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.buttonsGap}>
|
||||
{actions.map((action, i) => (
|
||||
<ActionButton key={i} action={action} variant="secondary" />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
return actions.map((action, i) => <ActionButton key={i} action={action} variant="secondary" />);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
buttonsGap: css({
|
||||
display: 'flex',
|
||||
gap: 6,
|
||||
}),
|
||||
});
|
||||
export const getStyles: TableCellStyles = (theme) => css({ gap: theme.spacing(0.75) });
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
import { formattedValueToString } from '@grafana/data';
|
||||
|
||||
import { MaybeWrapWithLink } from '../MaybeWrapWithLink';
|
||||
import { AutoCellProps } from '../types';
|
||||
import { AutoCellProps, TableCellStyles } from '../types';
|
||||
|
||||
export default function AutoCell({ value, field, rowIdx }: AutoCellProps) {
|
||||
export function AutoCell({ value, field, rowIdx }: AutoCellProps) {
|
||||
const displayValue = field.display!(value);
|
||||
const formattedValue = formattedValueToString(displayValue);
|
||||
return (
|
||||
@@ -12,3 +14,24 @@ export default function AutoCell({ value, field, rowIdx }: AutoCellProps) {
|
||||
</MaybeWrapWithLink>
|
||||
);
|
||||
}
|
||||
|
||||
export const getStyles: TableCellStyles = (_theme, { textWrap, shouldOverflow }) =>
|
||||
css({
|
||||
...(textWrap && { whiteSpace: 'pre-line' }),
|
||||
...(shouldOverflow && {
|
||||
'&:hover, &[aria-selected=true]': {
|
||||
whiteSpace: 'pre-line',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
export const getJsonCellStyles: TableCellStyles = (_theme, { textWrap, shouldOverflow }) =>
|
||||
css({
|
||||
fontFamily: 'monospace',
|
||||
...(textWrap && { whiteSpace: 'pre' }),
|
||||
...(shouldOverflow && {
|
||||
'&:hover, &[aria-selected=true]': {
|
||||
whiteSpace: 'pre',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -3,8 +3,9 @@ import { BarGaugeDisplayMode, BarGaugeValueMode, TableCellDisplayMode } from '@g
|
||||
|
||||
import { BarGauge } from '../../../BarGauge/BarGauge';
|
||||
import { MaybeWrapWithLink } from '../MaybeWrapWithLink';
|
||||
import { TABLE } from '../constants';
|
||||
import { BarGaugeCellProps } from '../types';
|
||||
import { extractPixelValue, getCellOptions, getAlignmentFactor } from '../utils';
|
||||
import { getCellOptions, getAlignmentFactor } from '../utils';
|
||||
|
||||
const defaultScale: ThresholdsConfig = {
|
||||
mode: ThresholdsMode.Absolute,
|
||||
@@ -23,7 +24,7 @@ const defaultScale: ThresholdsConfig = {
|
||||
export const BarGaugeCell = ({ value, field, theme, height, width, rowIdx }: BarGaugeCellProps) => {
|
||||
const displayValue = field.display!(value);
|
||||
const cellOptions = getCellOptions(field);
|
||||
const heightOffset = extractPixelValue(theme.spacing(1));
|
||||
const heightOffset = TABLE.CELL_PADDING * 2;
|
||||
|
||||
let config = getFieldConfigWithMinMax(field, false);
|
||||
if (!config.thresholds) {
|
||||
@@ -45,12 +46,14 @@ export const BarGaugeCell = ({ value, field, theme, height, width, rowIdx }: Bar
|
||||
}
|
||||
|
||||
const alignmentFactors = getAlignmentFactor(field, displayValue, rowIdx!);
|
||||
// clamp the height of the gauge so it isn't stretched for large rows
|
||||
const renderedHeight = Math.min(height - heightOffset, TABLE.MAX_CELL_HEIGHT);
|
||||
|
||||
return (
|
||||
<MaybeWrapWithLink field={field} rowIdx={rowIdx}>
|
||||
<BarGauge
|
||||
width={width}
|
||||
height={height - heightOffset}
|
||||
height={renderedHeight}
|
||||
field={config}
|
||||
display={field.display}
|
||||
text={{ valueSize: 14 }}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { DataLinksCellProps } from '../types';
|
||||
import { getCellLinks } from '../utils';
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
import { DataLinksCellProps, TableCellStyles } from '../types';
|
||||
import { getCellLinks, getJustifyContent } from '../utils';
|
||||
|
||||
export const DataLinksCell = ({ field, rowIdx }: DataLinksCellProps) => {
|
||||
const links = getCellLinks(field, rowIdx);
|
||||
@@ -14,3 +16,26 @@ export const DataLinksCell = ({ field, rowIdx }: DataLinksCellProps) => {
|
||||
</a>
|
||||
));
|
||||
};
|
||||
|
||||
export const getStyles: TableCellStyles = (theme, { textWrap, textAlign }) =>
|
||||
css({
|
||||
...(textWrap && {
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: getJustifyContent(textAlign),
|
||||
}),
|
||||
'> a': {
|
||||
flexWrap: 'nowrap',
|
||||
...(!textWrap && {
|
||||
paddingInline: theme.spacing(0.5),
|
||||
borderRight: `2px solid ${theme.colors.border.medium}`,
|
||||
'&:first-child': {
|
||||
paddingInlineStart: 0,
|
||||
},
|
||||
'&:last-child': {
|
||||
paddingInlineEnd: 0,
|
||||
borderRight: 'none',
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2,13 +2,10 @@ import { css } from '@emotion/css';
|
||||
import WKT from 'ol/format/WKT';
|
||||
import { Geometry } from 'ol/geom';
|
||||
|
||||
import { useStyles2 } from '../../../../themes/ThemeContext';
|
||||
import { GeoCellProps } from '../types';
|
||||
import { GeoCellProps, TableCellStyles } from '../types';
|
||||
|
||||
export function GeoCell({ value, justifyContent, height }: GeoCellProps) {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
let disp = '';
|
||||
export function GeoCell({ value }: GeoCellProps) {
|
||||
let disp = null;
|
||||
|
||||
if (value instanceof Geometry) {
|
||||
disp = new WKT().writeGeometry(value, {
|
||||
@@ -19,25 +16,13 @@ export function GeoCell({ value, justifyContent, height }: GeoCellProps) {
|
||||
disp = `${value}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.cell} style={{ justifyContent, height }}>
|
||||
<div className={styles.cellText} style={{ fontFamily: 'monospace' }}>
|
||||
{disp}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return disp;
|
||||
}
|
||||
|
||||
const getStyles = () => ({
|
||||
cell: css({
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '0 8px',
|
||||
}),
|
||||
cellText: css({
|
||||
export const getStyles: TableCellStyles = () =>
|
||||
css({
|
||||
fontFamily: 'monospace',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,36 +1,28 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { Property } from 'csstype';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
|
||||
import { useStyles2 } from '../../../../themes/ThemeContext';
|
||||
import { TableCellDisplayMode } from '../../types';
|
||||
import { MaybeWrapWithLink } from '../MaybeWrapWithLink';
|
||||
import { ImageCellProps } from '../types';
|
||||
|
||||
export const ImageCell = ({ cellOptions, field, height, justifyContent, value, rowIdx }: ImageCellProps) => {
|
||||
const styles = useStyles2(getStyles, height, justifyContent);
|
||||
import { ImageCellProps, TableCellStyles } from '../types';
|
||||
|
||||
export const ImageCell = ({ cellOptions, field, value, rowIdx }: ImageCellProps) => {
|
||||
const { text } = field.display!(value);
|
||||
const { alt, title } =
|
||||
cellOptions.type === TableCellDisplayMode.Image ? cellOptions : { alt: undefined, title: undefined };
|
||||
|
||||
return (
|
||||
<div className={styles.imageContainer}>
|
||||
<MaybeWrapWithLink field={field} rowIdx={rowIdx}>
|
||||
<img alt={alt} src={text} className={styles.image} title={title} />
|
||||
</MaybeWrapWithLink>
|
||||
</div>
|
||||
<MaybeWrapWithLink field={field} rowIdx={rowIdx}>
|
||||
<img alt={alt} src={text} title={title} />
|
||||
</MaybeWrapWithLink>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (_theme: GrafanaTheme2, height: number, justifyContent: Property.JustifyContent) => ({
|
||||
image: css({
|
||||
height,
|
||||
width: 'auto',
|
||||
}),
|
||||
imageContainer: css({
|
||||
display: 'flex',
|
||||
justifyContent,
|
||||
}),
|
||||
});
|
||||
export const getStyles: TableCellStyles = () =>
|
||||
css({
|
||||
'a, img': {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
},
|
||||
img: {
|
||||
objectFit: 'contain',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
import { renderMarkdown } from '@grafana/data';
|
||||
|
||||
import { MaybeWrapWithLink } from '../MaybeWrapWithLink';
|
||||
import { MarkdownCellProps } from '../types';
|
||||
import { MarkdownCellProps, TableCellStyles } from '../types';
|
||||
|
||||
export function MarkdownCell({ field, rowIdx, disableSanitizeHtml }: MarkdownCellProps) {
|
||||
return (
|
||||
@@ -15,3 +17,29 @@ export function MarkdownCell({ field, rowIdx, disableSanitizeHtml }: MarkdownCel
|
||||
</MaybeWrapWithLink>
|
||||
);
|
||||
}
|
||||
|
||||
export const getStyles: TableCellStyles = (theme) =>
|
||||
css({
|
||||
'&, &:hover, &[aria-selected=true]': {
|
||||
whiteSpace: 'normal',
|
||||
},
|
||||
|
||||
'.markdown-container': {
|
||||
width: '100%',
|
||||
},
|
||||
|
||||
'& ol, & ul': {
|
||||
paddingLeft: theme.spacing(1.5),
|
||||
},
|
||||
'& p': {
|
||||
whiteSpace: 'pre-line',
|
||||
},
|
||||
'& a': {
|
||||
color: theme.colors.primary.text,
|
||||
},
|
||||
// for elements like `p`, `h*`, etc. which have an inherent margin,
|
||||
// we want to remove the bottom margin for the last one in the container.
|
||||
'& > .markdown-container > *:last-child': {
|
||||
marginBottom: 0,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import {
|
||||
@@ -11,7 +12,7 @@ import {
|
||||
} from '@grafana/data';
|
||||
import { FieldColorModeId } from '@grafana/schema';
|
||||
|
||||
import { PillCellProps, TableCellValue } from '../types';
|
||||
import { PillCellProps, TableCellStyles, TableCellValue } from '../types';
|
||||
|
||||
export function PillCell({ rowIdx, field, theme }: PillCellProps) {
|
||||
const value = field.values[rowIdx];
|
||||
@@ -102,3 +103,25 @@ function getPillColor(value: string, field: Field, theme: GrafanaTheme2): string
|
||||
|
||||
return getColorByStringHash(colors, value);
|
||||
}
|
||||
|
||||
export const getStyles: TableCellStyles = (theme, { textWrap, shouldOverflow }) =>
|
||||
css({
|
||||
display: 'inline-flex',
|
||||
gap: theme.spacing(0.5),
|
||||
flexWrap: textWrap ? 'wrap' : 'nowrap',
|
||||
|
||||
...(shouldOverflow && {
|
||||
'&:hover, &[aria-selected=true]': {
|
||||
flexWrap: 'wrap',
|
||||
},
|
||||
}),
|
||||
|
||||
'> span': {
|
||||
display: 'flex',
|
||||
padding: theme.spacing(0.25, 0.75),
|
||||
borderRadius: theme.shape.radius.default,
|
||||
fontSize: theme.typography.bodySmall.fontSize,
|
||||
lineHeight: theme.typography.bodySmall.lineHeight,
|
||||
whiteSpace: 'nowrap',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { Property } from 'csstype';
|
||||
import * as React from 'react';
|
||||
|
||||
import {
|
||||
@@ -10,7 +9,6 @@ import {
|
||||
isDataFrame,
|
||||
Field,
|
||||
isDataFrameWithValue,
|
||||
GrafanaTheme2,
|
||||
} from '@grafana/data';
|
||||
import { t } from '@grafana/i18n';
|
||||
import {
|
||||
@@ -24,11 +22,10 @@ import {
|
||||
VisibilityMode,
|
||||
} from '@grafana/schema';
|
||||
|
||||
import { useStyles2 } from '../../../../themes/ThemeContext';
|
||||
import { measureText } from '../../../../utils/measureText';
|
||||
import { FormattedValueDisplay } from '../../../FormattedValueDisplay/FormattedValueDisplay';
|
||||
import { Sparkline } from '../../../Sparkline/Sparkline';
|
||||
import { SparklineCellProps } from '../types';
|
||||
import { SparklineCellProps, TableCellStyles } from '../types';
|
||||
import { getAlignmentFactor, getCellOptions } from '../utils';
|
||||
|
||||
export const defaultSparklineCellConfig: TableSparklineCellOptions = {
|
||||
@@ -45,8 +42,7 @@ export const defaultSparklineCellConfig: TableSparklineCellOptions = {
|
||||
};
|
||||
|
||||
export const SparklineCell = (props: SparklineCellProps) => {
|
||||
const { field, value, theme, timeRange, rowIdx, justifyContent, width } = props;
|
||||
const styles = useStyles2(getStyles, justifyContent);
|
||||
const { field, value, theme, timeRange, rowIdx, width } = props;
|
||||
const sparkline = getSparkline(value, field);
|
||||
|
||||
if (!sparkline) {
|
||||
@@ -95,26 +91,14 @@ export const SparklineCell = (props: SparklineCellProps) => {
|
||||
measureText(`${alignmentFactor.prefix ?? ''}${alignmentFactor.text}${alignmentFactor.suffix ?? ''}`, 16).width +
|
||||
theme.spacing.gridSize;
|
||||
|
||||
valueElement = (
|
||||
<FormattedValueDisplay
|
||||
style={{
|
||||
width: `${valueWidth - theme.spacing.gridSize}px`,
|
||||
textAlign: 'right',
|
||||
marginRight: theme.spacing(1),
|
||||
marginLeft: theme.spacing(1),
|
||||
}}
|
||||
className={styles.valueContainer}
|
||||
value={displayValue}
|
||||
/>
|
||||
);
|
||||
valueElement = <FormattedValueDisplay style={{ width: valueWidth }} value={displayValue} />;
|
||||
}
|
||||
|
||||
// @TODO update width, height
|
||||
return (
|
||||
<div className={styles.cellContainer}>
|
||||
<>
|
||||
{valueElement}
|
||||
<Sparkline width={width - valueWidth} height={25} sparkline={sparkline} config={config} theme={theme} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -153,14 +137,10 @@ function getTableSparklineCellOptions(field: Field): TableSparklineCellOptions {
|
||||
throw new Error(`Expected options type ${TableCellDisplayMode.Sparkline} but got ${options.type}`);
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2, justifyContent: Property.JustifyContent | undefined) => ({
|
||||
cellContainer: css({
|
||||
display: 'flex',
|
||||
export const getStyles: TableCellStyles = (theme, { textAlign }) =>
|
||||
css({
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
justifyContent,
|
||||
}),
|
||||
valueContainer: css({
|
||||
div: { width: 'inherit' },
|
||||
}),
|
||||
});
|
||||
gap: theme.spacing(1),
|
||||
justifyContent: 'space-between',
|
||||
...(textAlign === 'right' && { flexDirection: 'row-reverse' }),
|
||||
});
|
||||
|
||||
@@ -89,7 +89,9 @@ describe('TableNG Cells renderers', () => {
|
||||
cellOptions,
|
||||
cellInspect: false,
|
||||
showFilters: false,
|
||||
justifyContent: 'flex-start',
|
||||
getActions: jest.fn(() => [
|
||||
{ title: 'Action', onClick: jest.fn(() => {}), confirmation: jest.fn(), style: {} },
|
||||
]),
|
||||
})
|
||||
);
|
||||
|
||||
@@ -110,7 +112,6 @@ describe('TableNG Cells renderers', () => {
|
||||
cellOptions,
|
||||
cellInspect: false,
|
||||
showFilters: false,
|
||||
justifyContent: 'flex-start',
|
||||
})
|
||||
);
|
||||
}, iterations);
|
||||
@@ -123,8 +124,8 @@ describe('TableNG Cells renderers', () => {
|
||||
{ type: TableCellDisplayMode.JSONView, fieldType: FieldType.string },
|
||||
{ type: TableCellDisplayMode.Image, fieldType: FieldType.string },
|
||||
{ type: TableCellDisplayMode.DataLinks, fieldType: FieldType.string },
|
||||
{ type: TableCellDisplayMode.Actions, fieldType: FieldType.string },
|
||||
{ type: TableCellDisplayMode.ColorText, fieldType: FieldType.string },
|
||||
{ type: TableCellDisplayMode.Actions, fieldType: FieldType.string },
|
||||
{ type: TableCellDisplayMode.ColorBackground, fieldType: FieldType.string },
|
||||
{ type: TableCellDisplayMode.Auto, fieldType: FieldType.string },
|
||||
] as const)('should render $type cell into the document', ({ type, fieldType }) => {
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import { clsx } from 'clsx';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { Field, FieldType, isDataFrame, isTimeSeriesFrame } from '@grafana/data';
|
||||
import { Field, FieldType, GrafanaTheme2, isDataFrame, isTimeSeriesFrame } from '@grafana/data';
|
||||
|
||||
import { TableCellDisplayMode, TableCellOptions, TableCustomCellOptions } from '../../types';
|
||||
import { TableCellRendererProps } from '../types';
|
||||
import { TableCellRendererProps, TableCellStyleOptions, TableCellStyles } from '../types';
|
||||
|
||||
import { ActionsCell } from './ActionsCell';
|
||||
import AutoCell from './AutoCell';
|
||||
import { ActionsCell, getStyles as getActionsCellStyles } from './ActionsCell';
|
||||
import { AutoCell, getStyles as getAutoCellStyles, getJsonCellStyles } from './AutoCell';
|
||||
import { BarGaugeCell } from './BarGaugeCell';
|
||||
import { DataLinksCell } from './DataLinksCell';
|
||||
import { GeoCell } from './GeoCell';
|
||||
import { ImageCell } from './ImageCell';
|
||||
import { MarkdownCell } from './MarkdownCell';
|
||||
import { PillCell } from './PillCell';
|
||||
import { SparklineCell } from './SparklineCell';
|
||||
import { DataLinksCell, getStyles as getDataLinksStyles } from './DataLinksCell';
|
||||
import { GeoCell, getStyles as getGeoCellStyles } from './GeoCell';
|
||||
import { ImageCell, getStyles as getImageStyles } from './ImageCell';
|
||||
import { MarkdownCell, getStyles as getMarkdownCellStyles } from './MarkdownCell';
|
||||
import { PillCell, getStyles as getPillStyles } from './PillCell';
|
||||
import { SparklineCell, getStyles as getSparklineCellStyles } from './SparklineCell';
|
||||
|
||||
export type TableCellRenderer = (props: TableCellRendererProps) => ReactNode;
|
||||
|
||||
@@ -36,7 +37,6 @@ const SPARKLINE_RENDERER: TableCellRenderer = (props) => (
|
||||
<SparklineCell
|
||||
value={props.value}
|
||||
field={props.field}
|
||||
justifyContent={props.justifyContent}
|
||||
timeRange={props.timeRange}
|
||||
rowIdx={props.rowIdx}
|
||||
theme={props.theme}
|
||||
@@ -44,19 +44,10 @@ const SPARKLINE_RENDERER: TableCellRenderer = (props) => (
|
||||
/>
|
||||
);
|
||||
|
||||
const GEO_RENDERER: TableCellRenderer = (props) => (
|
||||
<GeoCell value={props.value} justifyContent={props.justifyContent} height={props.height} />
|
||||
);
|
||||
const GEO_RENDERER: TableCellRenderer = (props) => <GeoCell value={props.value} height={props.height} />;
|
||||
|
||||
const IMAGE_RENDERER: TableCellRenderer = (props) => (
|
||||
<ImageCell
|
||||
cellOptions={props.cellOptions}
|
||||
field={props.field}
|
||||
height={props.height}
|
||||
justifyContent={props.justifyContent}
|
||||
value={props.value}
|
||||
rowIdx={props.rowIdx}
|
||||
/>
|
||||
<ImageCell cellOptions={props.cellOptions} field={props.field} value={props.value} rowIdx={props.rowIdx} />
|
||||
);
|
||||
|
||||
const DATA_LINKS_RENDERER: TableCellRenderer = (props) => <DataLinksCell field={props.field} rowIdx={props.rowIdx} />;
|
||||
@@ -65,12 +56,23 @@ const ACTIONS_RENDERER: TableCellRenderer = ({ field, rowIdx, getActions = () =>
|
||||
<ActionsCell field={field} rowIdx={rowIdx} getActions={getActions} />
|
||||
);
|
||||
|
||||
const MARKDOWN_RENDERER: TableCellRenderer = (props) => (
|
||||
<MarkdownCell field={props.field} rowIdx={props.rowIdx} disableSanitizeHtml={props.disableSanitizeHtml} />
|
||||
);
|
||||
|
||||
const PILL_RENDERER: TableCellRenderer = (props) => <PillCell {...props} />;
|
||||
|
||||
function isCustomCellOptions(options: TableCellOptions): options is TableCustomCellOptions {
|
||||
return options.type === TableCellDisplayMode.Custom;
|
||||
}
|
||||
|
||||
function mixinAutoCellStyles(fn: TableCellStyles): TableCellStyles {
|
||||
return (theme, options) => {
|
||||
const styles = fn(theme, options);
|
||||
return clsx(styles, getAutoCellStyles(theme, options));
|
||||
};
|
||||
}
|
||||
|
||||
const CUSTOM_RENDERER: TableCellRenderer = (props) => {
|
||||
if (!isCustomCellOptions(props.cellOptions) || !props.cellOptions.cellComponent) {
|
||||
return null; // nonsensical case, but better to typeguard it than throw.
|
||||
@@ -79,23 +81,57 @@ const CUSTOM_RENDERER: TableCellRenderer = (props) => {
|
||||
return <CustomCellComponent field={props.field} rowIndex={props.rowIdx} frame={props.frame} value={props.value} />;
|
||||
};
|
||||
|
||||
const MARKDOWN_RENDERER: TableCellRenderer = (props) => (
|
||||
<MarkdownCell field={props.field} rowIdx={props.rowIdx} disableSanitizeHtml={props.disableSanitizeHtml} />
|
||||
);
|
||||
|
||||
const CELL_RENDERERS: Record<TableCellOptions['type'], TableCellRenderer> = {
|
||||
[TableCellDisplayMode.Sparkline]: SPARKLINE_RENDERER,
|
||||
[TableCellDisplayMode.Gauge]: GAUGE_RENDERER,
|
||||
[TableCellDisplayMode.JSONView]: AUTO_RENDERER,
|
||||
[TableCellDisplayMode.Image]: IMAGE_RENDERER,
|
||||
[TableCellDisplayMode.DataLinks]: DATA_LINKS_RENDERER,
|
||||
[TableCellDisplayMode.Actions]: ACTIONS_RENDERER,
|
||||
[TableCellDisplayMode.Custom]: CUSTOM_RENDERER,
|
||||
[TableCellDisplayMode.ColorText]: AUTO_RENDERER,
|
||||
[TableCellDisplayMode.ColorBackground]: AUTO_RENDERER,
|
||||
[TableCellDisplayMode.Auto]: AUTO_RENDERER,
|
||||
[TableCellDisplayMode.Markdown]: MARKDOWN_RENDERER,
|
||||
[TableCellDisplayMode.Pill]: PILL_RENDERER,
|
||||
const CELL_RENDERERS: Record<TableCellOptions['type'], { renderer: TableCellRenderer; getStyles?: TableCellStyles }> = {
|
||||
[TableCellDisplayMode.Actions]: {
|
||||
renderer: ACTIONS_RENDERER,
|
||||
getStyles: getActionsCellStyles,
|
||||
},
|
||||
[TableCellDisplayMode.Auto]: {
|
||||
renderer: AUTO_RENDERER,
|
||||
getStyles: getAutoCellStyles,
|
||||
},
|
||||
[TableCellDisplayMode.ColorBackground]: {
|
||||
renderer: AUTO_RENDERER,
|
||||
getStyles: getAutoCellStyles,
|
||||
},
|
||||
[TableCellDisplayMode.ColorText]: {
|
||||
renderer: AUTO_RENDERER,
|
||||
getStyles: getAutoCellStyles,
|
||||
},
|
||||
[TableCellDisplayMode.Custom]: {
|
||||
renderer: CUSTOM_RENDERER,
|
||||
},
|
||||
[TableCellDisplayMode.DataLinks]: {
|
||||
renderer: DATA_LINKS_RENDERER,
|
||||
getStyles: getDataLinksStyles,
|
||||
},
|
||||
[TableCellDisplayMode.Gauge]: {
|
||||
renderer: GAUGE_RENDERER,
|
||||
},
|
||||
[TableCellDisplayMode.Geo]: {
|
||||
renderer: GEO_RENDERER,
|
||||
getStyles: getGeoCellStyles,
|
||||
},
|
||||
[TableCellDisplayMode.Image]: {
|
||||
renderer: IMAGE_RENDERER,
|
||||
getStyles: getImageStyles,
|
||||
},
|
||||
[TableCellDisplayMode.JSONView]: {
|
||||
renderer: AUTO_RENDERER,
|
||||
getStyles: mixinAutoCellStyles(getJsonCellStyles),
|
||||
},
|
||||
[TableCellDisplayMode.Pill]: {
|
||||
renderer: PILL_RENDERER,
|
||||
getStyles: getPillStyles,
|
||||
},
|
||||
[TableCellDisplayMode.Sparkline]: {
|
||||
renderer: SPARKLINE_RENDERER,
|
||||
getStyles: getSparklineCellStyles,
|
||||
},
|
||||
[TableCellDisplayMode.Markdown]: {
|
||||
renderer: MARKDOWN_RENDERER,
|
||||
getStyles: getMarkdownCellStyles,
|
||||
},
|
||||
};
|
||||
|
||||
// TODO: come up with a more elegant way to handle this.
|
||||
@@ -108,26 +144,53 @@ const STRING_ONLY_RENDERERS = new Set<TableCellOptions['type']>([
|
||||
export function getCellRenderer(field: Field, cellOptions: TableCellOptions): TableCellRenderer {
|
||||
const cellType = cellOptions?.type ?? TableCellDisplayMode.Auto;
|
||||
if (cellType === TableCellDisplayMode.Auto) {
|
||||
return getAutoRendererResult(field);
|
||||
return CELL_RENDERERS[getAutoRendererDisplayMode(field)].renderer;
|
||||
}
|
||||
|
||||
if (STRING_ONLY_RENDERERS.has(cellType) && field.type !== FieldType.string) {
|
||||
return AUTO_RENDERER;
|
||||
}
|
||||
|
||||
return CELL_RENDERERS[cellType] ?? AUTO_RENDERER;
|
||||
// cautious fallback to Auto renderer in case some garbage cell type has been provided.
|
||||
return CELL_RENDERERS[cellType]?.renderer ?? AUTO_RENDERER;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function getAutoRendererResult(field: Field): TableCellRenderer {
|
||||
export function getCellSpecificStyles(
|
||||
cellType: TableCellOptions['type'],
|
||||
field: Field,
|
||||
theme: GrafanaTheme2,
|
||||
options: TableCellStyleOptions
|
||||
): string | undefined {
|
||||
if (cellType === TableCellDisplayMode.Auto) {
|
||||
return getAutoRendererStyles(theme, options, field);
|
||||
}
|
||||
return CELL_RENDERERS[cellType]?.getStyles?.(theme, options);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function getAutoRendererStyles(
|
||||
theme: GrafanaTheme2,
|
||||
options: TableCellStyleOptions,
|
||||
field: Field
|
||||
): string | undefined {
|
||||
const impliedDisplayMode = getAutoRendererDisplayMode(field);
|
||||
if (impliedDisplayMode !== TableCellDisplayMode.Auto) {
|
||||
return CELL_RENDERERS[impliedDisplayMode]?.getStyles?.(theme, options);
|
||||
}
|
||||
return getAutoCellStyles(theme, options);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function getAutoRendererDisplayMode(field: Field): TableCellOptions['type'] {
|
||||
if (field.type === FieldType.geo) {
|
||||
return GEO_RENDERER;
|
||||
return TableCellDisplayMode.Geo;
|
||||
}
|
||||
if (field.type === FieldType.frame) {
|
||||
const firstValue = field.values[0];
|
||||
if (isDataFrame(firstValue) && isTimeSeriesFrame(firstValue)) {
|
||||
return SPARKLINE_RENDERER;
|
||||
return TableCellDisplayMode.Sparkline;
|
||||
}
|
||||
}
|
||||
return AUTO_RENDERER;
|
||||
return TableCellDisplayMode.Auto;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'react-data-grid/lib/styles.css';
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
import { clsx } from 'clsx';
|
||||
import { Property } from 'csstype';
|
||||
import { CSSProperties, Key, ReactNode, useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
Cell,
|
||||
@@ -22,7 +21,6 @@ import {
|
||||
Field,
|
||||
FieldType,
|
||||
getDisplayProcessor,
|
||||
GrafanaTheme2,
|
||||
ReducerID,
|
||||
} from '@grafana/data';
|
||||
import { t, Trans } from '@grafana/i18n';
|
||||
@@ -38,10 +36,10 @@ import { TableCellInspector, TableCellInspectorMode } from '../TableCellInspecto
|
||||
import { TableCellDisplayMode } from '../types';
|
||||
import { DataLinksActionsTooltipState } from '../utils';
|
||||
|
||||
import { HeaderCell } from './Cells/HeaderCell';
|
||||
import { RowExpander } from './Cells/RowExpander';
|
||||
import { TableCellActions } from './Cells/TableCellActions';
|
||||
import { getCellRenderer } from './Cells/renderers';
|
||||
import { getCellRenderer, getCellSpecificStyles } from './Cells/renderers';
|
||||
import { HeaderCell } from './components/HeaderCell';
|
||||
import { RowExpander } from './components/RowExpander';
|
||||
import { TableCellActions } from './components/TableCellActions';
|
||||
import { COLUMN, TABLE } from './constants';
|
||||
import {
|
||||
useColumnResize,
|
||||
@@ -53,7 +51,8 @@ import {
|
||||
useScrollbarWidth,
|
||||
useSortedRows,
|
||||
} from './hooks';
|
||||
import { TableNGProps, TableRow, TableSummaryRow, TableColumn, ContextMenuProps } from './types';
|
||||
import { getDefaultCellStyles, getFooterStyles, getGridStyles, getHeaderCellStyles, getLinkStyles } from './styles';
|
||||
import { TableNGProps, TableRow, TableSummaryRow, TableColumn, ContextMenuProps, TableCellStyleOptions } from './types';
|
||||
import {
|
||||
applySort,
|
||||
computeColWidths,
|
||||
@@ -74,7 +73,6 @@ import {
|
||||
isCellInspectEnabled,
|
||||
shouldTextOverflow,
|
||||
shouldTextWrap,
|
||||
TextAlign,
|
||||
withDataLinksActionsTooltip,
|
||||
} from './utils';
|
||||
|
||||
@@ -364,37 +362,19 @@ export function TableNG(props: TableNGProps) {
|
||||
: undefined;
|
||||
|
||||
const shouldOverflow = rowHeight !== 'auto' && shouldTextOverflow(field);
|
||||
const shouldWrap = rowHeight === 'auto' || shouldTextWrap(field);
|
||||
const textWrap = rowHeight === 'auto' || shouldTextWrap(field);
|
||||
const withTooltip = withDataLinksActionsTooltip(field, cellType);
|
||||
const canBeColorized =
|
||||
cellType === TableCellDisplayMode.ColorBackground || cellType === TableCellDisplayMode.ColorText;
|
||||
const isMonospace = cellType === TableCellDisplayMode.JSONView;
|
||||
cellType === TableCellDisplayMode.ColorBackground ||
|
||||
cellType === TableCellDisplayMode.ColorText ||
|
||||
Boolean(applyToRowBgFn);
|
||||
const cellStyleOptions: TableCellStyleOptions = { textAlign, textWrap, shouldOverflow };
|
||||
|
||||
result.colsWithTooltip[displayName] = withTooltip;
|
||||
|
||||
// get static cell class based on col props
|
||||
|
||||
let cellClass = '';
|
||||
|
||||
switch (cellType) {
|
||||
case TableCellDisplayMode.Auto:
|
||||
case TableCellDisplayMode.ColorBackground:
|
||||
case TableCellDisplayMode.ColorText:
|
||||
case TableCellDisplayMode.DataLinks:
|
||||
case TableCellDisplayMode.JSONView:
|
||||
case TableCellDisplayMode.Pill:
|
||||
case TableCellDisplayMode.Markdown:
|
||||
cellClass = getCellStyles(
|
||||
theme,
|
||||
cellType,
|
||||
textAlign,
|
||||
shouldWrap,
|
||||
shouldOverflow,
|
||||
canBeColorized,
|
||||
isMonospace
|
||||
);
|
||||
break;
|
||||
}
|
||||
const defaultCellStyles = getDefaultCellStyles(theme, cellStyleOptions);
|
||||
const cellSpecificStyles = getCellSpecificStyles(cellType, field, theme, cellStyleOptions);
|
||||
const linkStyles = getLinkStyles(theme, canBeColorized);
|
||||
|
||||
// TODO: in future extend this to ensure a non-classic color scheme is set with AutoCell
|
||||
|
||||
@@ -433,7 +413,14 @@ export function TableNG(props: TableNGProps) {
|
||||
};
|
||||
}
|
||||
|
||||
return <Cell key={key} {...props} className={clsx(props.className, cellClass)} style={style} />; // TODO: remove expensive concat
|
||||
return (
|
||||
<Cell
|
||||
key={key}
|
||||
{...props}
|
||||
className={clsx(props.className, defaultCellStyles, cellSpecificStyles, linkStyles)}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
result.cellRootRenderers[displayName] = renderCellRoot;
|
||||
@@ -456,7 +443,6 @@ export function TableNG(props: TableNGProps) {
|
||||
frame,
|
||||
field,
|
||||
height,
|
||||
justifyContent,
|
||||
rowIdx,
|
||||
theme,
|
||||
value,
|
||||
@@ -812,264 +798,3 @@ const renderRowFactory =
|
||||
|
||||
return <Row key={key} {...props} {...handlers} />;
|
||||
};
|
||||
|
||||
const getGridStyles = (
|
||||
theme: GrafanaTheme2,
|
||||
{ enablePagination, transparent }: { enablePagination?: boolean; transparent?: boolean }
|
||||
) => ({
|
||||
grid: css({
|
||||
'--rdg-background-color': transparent ? theme.colors.background.canvas : theme.colors.background.primary,
|
||||
'--rdg-header-background-color': transparent ? theme.colors.background.canvas : theme.colors.background.primary,
|
||||
'--rdg-border-color': theme.colors.border.weak,
|
||||
'--rdg-color': theme.colors.text.primary,
|
||||
'--rdg-summary-border-color': theme.colors.border.weak,
|
||||
'--rdg-summary-border-width': '1px',
|
||||
|
||||
// note: this cannot have any transparency since default cells that
|
||||
// overlay/overflow on hover inherit this background and need to occlude cells below
|
||||
'--rdg-row-background-color': transparent ? theme.colors.background.canvas : theme.colors.background.primary,
|
||||
'--rdg-row-hover-background-color': transparent
|
||||
? theme.colors.background.primary
|
||||
: theme.colors.background.secondary,
|
||||
|
||||
// TODO: magic 32px number is unfortunate. it would be better to have the content
|
||||
// flow using flexbox rather than hard-coding this size via a calc
|
||||
blockSize: enablePagination ? 'calc(100% - 32px)' : '100%',
|
||||
scrollbarWidth: 'thin',
|
||||
scrollbarColor: theme.isDark ? '#fff5 #fff1' : '#0005 #0001',
|
||||
|
||||
border: 'none',
|
||||
|
||||
'.rdg-cell': {
|
||||
padding: TABLE.CELL_PADDING,
|
||||
'&:last-child': {
|
||||
borderInlineEnd: 'none',
|
||||
},
|
||||
},
|
||||
|
||||
// add a box shadow on hover and selection for all body cells
|
||||
'& > :not(.rdg-summary-row, .rdg-header-row) > .rdg-cell': {
|
||||
'&:hover, &[aria-selected=true]': {
|
||||
boxShadow: theme.shadows.z2,
|
||||
},
|
||||
// selected cells should appear below hovered cells.
|
||||
'&:hover': {
|
||||
zIndex: theme.zIndex.tooltip - 2,
|
||||
},
|
||||
'&[aria-selected=true]': {
|
||||
zIndex: theme.zIndex.tooltip - 3,
|
||||
},
|
||||
},
|
||||
|
||||
'.rdg-header-row, .rdg-summary-row': {
|
||||
'.rdg-cell': {
|
||||
zIndex: theme.zIndex.tooltip - 1,
|
||||
},
|
||||
},
|
||||
}),
|
||||
gridNested: css({
|
||||
height: '100%',
|
||||
width: `calc(100% - ${COLUMN.EXPANDER_WIDTH - TABLE.CELL_PADDING * 2 - 1}px)`,
|
||||
overflow: 'visible',
|
||||
marginLeft: COLUMN.EXPANDER_WIDTH - TABLE.CELL_PADDING - 1,
|
||||
marginBlock: TABLE.CELL_PADDING,
|
||||
}),
|
||||
cellNested: css({
|
||||
'&[aria-selected=true]': {
|
||||
outline: 'none',
|
||||
},
|
||||
}),
|
||||
noDataNested: css({
|
||||
height: TABLE.NESTED_NO_DATA_HEIGHT,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: theme.colors.text.secondary,
|
||||
fontSize: theme.typography.h4.fontSize,
|
||||
}),
|
||||
cellActions: css({
|
||||
display: 'none',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
margin: 'auto',
|
||||
height: '100%',
|
||||
color: theme.colors.text.primary,
|
||||
background: theme.isDark ? 'rgba(0, 0, 0, 0.7)' : 'rgba(255, 255, 255, 0.7)',
|
||||
padding: theme.spacing.x0_5,
|
||||
paddingInlineStart: theme.spacing.x1,
|
||||
}),
|
||||
cellActionsEnd: css({
|
||||
left: 0,
|
||||
}),
|
||||
cellActionsStart: css({
|
||||
right: 0,
|
||||
}),
|
||||
headerRow: css({
|
||||
paddingBlockStart: 0,
|
||||
fontWeight: 'normal',
|
||||
'& .rdg-cell': {
|
||||
height: '100%',
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
}),
|
||||
displayNone: css({
|
||||
display: 'none',
|
||||
}),
|
||||
paginationContainer: css({
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
marginTop: '8px',
|
||||
width: '100%',
|
||||
}),
|
||||
paginationSummary: css({
|
||||
color: theme.colors.text.secondary,
|
||||
fontSize: theme.typography.bodySmall.fontSize,
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
padding: theme.spacing(0, 1, 0, 2),
|
||||
}),
|
||||
menuItem: css({
|
||||
maxWidth: '200px',
|
||||
}),
|
||||
});
|
||||
|
||||
const getFooterStyles = (justifyContent: Property.JustifyContent) => ({
|
||||
footerCellCountRows: css({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
}),
|
||||
footerCell: css({
|
||||
display: 'flex',
|
||||
justifyContent: justifyContent || 'space-between',
|
||||
}),
|
||||
});
|
||||
|
||||
const getHeaderCellStyles = (theme: GrafanaTheme2, justifyContent: Property.JustifyContent) =>
|
||||
css({
|
||||
display: 'flex',
|
||||
gap: theme.spacing(0.5),
|
||||
zIndex: theme.zIndex.tooltip - 1,
|
||||
paddingInline: TABLE.CELL_PADDING,
|
||||
paddingBlockEnd: TABLE.CELL_PADDING,
|
||||
justifyContent,
|
||||
'&:last-child': {
|
||||
borderInlineEnd: 'none',
|
||||
},
|
||||
});
|
||||
|
||||
const getCellStyles = (
|
||||
theme: GrafanaTheme2,
|
||||
cellType: TableCellDisplayMode,
|
||||
textAlign: TextAlign,
|
||||
shouldWrap: boolean,
|
||||
shouldOverflow: boolean,
|
||||
isColorized: boolean,
|
||||
isMonospace: boolean
|
||||
) => {
|
||||
const whiteSpace: CSSProperties['whiteSpace'] = (() => {
|
||||
if (isMonospace) {
|
||||
return 'pre';
|
||||
}
|
||||
if (cellType === TableCellDisplayMode.Markdown) {
|
||||
return 'normal';
|
||||
}
|
||||
return 'pre-line';
|
||||
})();
|
||||
|
||||
return css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
textAlign,
|
||||
justifyContent: getJustifyContent(textAlign),
|
||||
|
||||
...(isColorized && { backgroundClip: 'padding-box !important' }),
|
||||
...(shouldOverflow && { minHeight: '100%' }),
|
||||
...(shouldWrap && { whiteSpace }),
|
||||
...(isMonospace && { fontFamily: 'monospace' }),
|
||||
|
||||
'&:hover, &[aria-selected=true]': {
|
||||
'.table-cell-actions': {
|
||||
display: 'flex',
|
||||
},
|
||||
...(shouldOverflow && {
|
||||
zIndex: theme.zIndex.tooltip - 2,
|
||||
whiteSpace,
|
||||
height: 'fit-content',
|
||||
minWidth: 'fit-content',
|
||||
...(cellType === TableCellDisplayMode.Pill && {
|
||||
flexWrap: 'wrap',
|
||||
}),
|
||||
}),
|
||||
},
|
||||
|
||||
a: {
|
||||
cursor: 'pointer',
|
||||
...(isColorized
|
||||
? {
|
||||
color: 'inherit',
|
||||
textDecoration: 'underline',
|
||||
}
|
||||
: {
|
||||
color: theme.colors.text.link,
|
||||
textDecoration: 'none',
|
||||
'&:hover': {
|
||||
textDecoration: 'underline',
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
...(cellType === TableCellDisplayMode.DataLinks && {
|
||||
...(shouldWrap && {
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: getJustifyContent(textAlign),
|
||||
}),
|
||||
'> a': {
|
||||
flexWrap: 'nowrap',
|
||||
...(!shouldWrap && {
|
||||
paddingInline: theme.spacing(0.5),
|
||||
borderRight: `2px solid ${theme.colors.border.medium}`,
|
||||
'&:first-child': {
|
||||
paddingInlineStart: 0,
|
||||
},
|
||||
'&:last-child': {
|
||||
paddingInlineEnd: 0,
|
||||
borderRight: 'none',
|
||||
},
|
||||
}),
|
||||
},
|
||||
}),
|
||||
|
||||
...(cellType === TableCellDisplayMode.Pill && {
|
||||
display: 'inline-flex',
|
||||
gap: theme.spacing(0.5),
|
||||
flexWrap: shouldWrap ? 'wrap' : 'nowrap',
|
||||
'> span': {
|
||||
display: 'flex',
|
||||
padding: theme.spacing(0.25, 0.75),
|
||||
borderRadius: theme.shape.radius.default,
|
||||
fontSize: theme.typography.bodySmall.fontSize,
|
||||
lineHeight: theme.typography.bodySmall.lineHeight,
|
||||
whiteSpace: 'nowrap',
|
||||
},
|
||||
}),
|
||||
|
||||
...(cellType === TableCellDisplayMode.Markdown && {
|
||||
'& ol, & ul': {
|
||||
paddingLeft: theme.spacing(1.5),
|
||||
},
|
||||
'& p': {
|
||||
whiteSpace: 'pre-line',
|
||||
},
|
||||
'& a': {
|
||||
color: theme.colors.primary.text,
|
||||
},
|
||||
// for elements like `p`, `h*`, etc. which have an inherent margin,
|
||||
// we want to remove the bottom margin for the last one in the container.
|
||||
'& > .markdown-container > *:last-child': {
|
||||
marginBottom: 0,
|
||||
},
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
+4
-1
@@ -89,7 +89,10 @@ const getStyles = (theme: GrafanaTheme2, headerTextWrap?: boolean) => ({
|
||||
whiteSpace: headerTextWrap ? 'pre-line' : 'nowrap',
|
||||
'&:hover': {
|
||||
textDecoration: 'underline',
|
||||
color: theme.colors.text.link,
|
||||
},
|
||||
'&::selection': {
|
||||
backgroundColor: 'var(--rdg-background-color)',
|
||||
color: theme.colors.text.secondary,
|
||||
},
|
||||
}),
|
||||
headerCellIcon: css({
|
||||
@@ -13,7 +13,6 @@ export const TABLE = {
|
||||
PAGINATION_LIMIT: 750,
|
||||
SCROLL_BAR_WIDTH: 8,
|
||||
SCROLL_BAR_MARGIN: 2,
|
||||
FONT_SIZE: 14,
|
||||
LINE_HEIGHT: 22,
|
||||
HEADER_HEIGHT: 28,
|
||||
NESTED_NO_DATA_HEIGHT: 60,
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { Property } from 'csstype';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
|
||||
import { COLUMN, TABLE } from './constants';
|
||||
import { TableCellStyles } from './types';
|
||||
import { getJustifyContent } from './utils';
|
||||
|
||||
export const getGridStyles = (
|
||||
theme: GrafanaTheme2,
|
||||
{ enablePagination, transparent }: { enablePagination?: boolean; transparent?: boolean }
|
||||
) => ({
|
||||
grid: css({
|
||||
'--rdg-background-color': transparent ? theme.colors.background.canvas : theme.colors.background.primary,
|
||||
'--rdg-header-background-color': transparent ? theme.colors.background.canvas : theme.colors.background.primary,
|
||||
'--rdg-border-color': theme.colors.border.weak,
|
||||
'--rdg-color': theme.colors.text.primary,
|
||||
'--rdg-summary-border-color': theme.colors.border.weak,
|
||||
'--rdg-summary-border-width': '1px',
|
||||
|
||||
// note: this cannot have any transparency since default cells that
|
||||
// overlay/overflow on hover inherit this background and need to occlude cells below
|
||||
'--rdg-row-background-color': transparent ? theme.colors.background.canvas : theme.colors.background.primary,
|
||||
'--rdg-row-hover-background-color': transparent
|
||||
? theme.colors.background.primary
|
||||
: theme.colors.background.secondary,
|
||||
|
||||
// TODO: magic 32px number is unfortunate. it would be better to have the content
|
||||
// flow using flexbox rather than hard-coding this size via a calc
|
||||
blockSize: enablePagination ? 'calc(100% - 32px)' : '100%',
|
||||
scrollbarWidth: 'thin',
|
||||
scrollbarColor: theme.isDark ? '#fff5 #fff1' : '#0005 #0001',
|
||||
|
||||
border: 'none',
|
||||
|
||||
'.rdg-cell': {
|
||||
padding: TABLE.CELL_PADDING,
|
||||
|
||||
'&:last-child': {
|
||||
borderInlineEnd: 'none',
|
||||
},
|
||||
},
|
||||
|
||||
// add a box shadow on hover and selection for all body cells
|
||||
'& > :not(.rdg-summary-row, .rdg-header-row) > .rdg-cell': {
|
||||
'&:hover, &[aria-selected=true]': { boxShadow: theme.shadows.z2 },
|
||||
// selected cells should appear below hovered cells.
|
||||
'&:hover': { zIndex: theme.zIndex.tooltip - 2 },
|
||||
'&[aria-selected=true]': { zIndex: theme.zIndex.tooltip - 3 },
|
||||
},
|
||||
|
||||
'.rdg-header-row, .rdg-summary-row': {
|
||||
'.rdg-cell': { zIndex: theme.zIndex.tooltip - 1 },
|
||||
},
|
||||
}),
|
||||
gridNested: css({
|
||||
height: '100%',
|
||||
width: `calc(100% - ${COLUMN.EXPANDER_WIDTH - TABLE.CELL_PADDING * 2 - 1}px)`,
|
||||
overflow: 'visible',
|
||||
marginLeft: COLUMN.EXPANDER_WIDTH - TABLE.CELL_PADDING - 1,
|
||||
marginBlock: TABLE.CELL_PADDING,
|
||||
}),
|
||||
cellNested: css({ '&[aria-selected=true]': { outline: 'none' } }),
|
||||
noDataNested: css({
|
||||
height: TABLE.NESTED_NO_DATA_HEIGHT,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: theme.colors.text.secondary,
|
||||
fontSize: theme.typography.h4.fontSize,
|
||||
}),
|
||||
cellActions: css({
|
||||
display: 'none',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
margin: 'auto',
|
||||
height: '100%',
|
||||
color: theme.colors.text.primary,
|
||||
background: theme.isDark ? 'rgba(0, 0, 0, 0.7)' : 'rgba(255, 255, 255, 0.7)',
|
||||
padding: theme.spacing.x0_5,
|
||||
paddingInlineStart: theme.spacing.x1,
|
||||
}),
|
||||
cellActionsEnd: css({ left: 0 }),
|
||||
cellActionsStart: css({ right: 0 }),
|
||||
headerRow: css({
|
||||
paddingBlockStart: 0,
|
||||
fontWeight: 'normal',
|
||||
'& .rdg-cell': { height: '100%', alignItems: 'flex-end' },
|
||||
}),
|
||||
displayNone: css({ display: 'none' }),
|
||||
paginationContainer: css({
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
marginTop: '8px',
|
||||
width: '100%',
|
||||
}),
|
||||
paginationSummary: css({
|
||||
color: theme.colors.text.secondary,
|
||||
fontSize: theme.typography.bodySmall.fontSize,
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
padding: theme.spacing(0, 1, 0, 2),
|
||||
}),
|
||||
menuItem: css({ maxWidth: '200px' }),
|
||||
});
|
||||
|
||||
export const getFooterStyles = (justifyContent: Property.JustifyContent) => ({
|
||||
footerCellCountRows: css({ display: 'flex', justifyContent: 'space-between' }),
|
||||
footerCell: css({ display: 'flex', justifyContent: justifyContent || 'space-between' }),
|
||||
});
|
||||
|
||||
export const getHeaderCellStyles = (theme: GrafanaTheme2, justifyContent: Property.JustifyContent) =>
|
||||
css({
|
||||
display: 'flex',
|
||||
gap: theme.spacing(0.5),
|
||||
zIndex: theme.zIndex.tooltip - 1,
|
||||
paddingInline: TABLE.CELL_PADDING,
|
||||
paddingBlockEnd: TABLE.CELL_PADDING,
|
||||
justifyContent,
|
||||
'&:last-child': { borderInlineEnd: 'none' },
|
||||
});
|
||||
|
||||
export const getDefaultCellStyles: TableCellStyles = (theme, { textAlign, shouldOverflow }) =>
|
||||
css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
textAlign,
|
||||
backgroundClip: 'padding-box !important', // helps when cells have a bg color
|
||||
justifyContent: getJustifyContent(textAlign),
|
||||
...(shouldOverflow && { minHeight: '100%' }),
|
||||
'&:hover, &[aria-selected=true]': {
|
||||
'.table-cell-actions': { display: 'flex' },
|
||||
...(shouldOverflow && {
|
||||
zIndex: theme.zIndex.tooltip - 2,
|
||||
height: 'fit-content',
|
||||
minWidth: 'fit-content',
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
export const getLinkStyles = (theme: GrafanaTheme2, canBeColorized: boolean) =>
|
||||
css({
|
||||
a: {
|
||||
cursor: 'pointer',
|
||||
...(canBeColorized
|
||||
? {
|
||||
color: 'inherit',
|
||||
textDecoration: 'underline',
|
||||
}
|
||||
: {
|
||||
color: theme.colors.text.link,
|
||||
textDecoration: 'none',
|
||||
'&:hover': { textDecoration: 'underline' },
|
||||
}),
|
||||
},
|
||||
});
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Property } from 'csstype';
|
||||
import { SyntheticEvent } from 'react';
|
||||
import { Column } from 'react-data-grid';
|
||||
|
||||
@@ -19,6 +18,8 @@ import { TableCellHeight, TableFieldOptions } from '@grafana/schema';
|
||||
import { TableCellInspectorMode } from '../TableCellInspector';
|
||||
import { TableCellOptions } from '../types';
|
||||
|
||||
import { TextAlign } from './utils';
|
||||
|
||||
export const FILTER_FOR_OPERATOR = '=';
|
||||
export const FILTER_OUT_OPERATOR = '!=';
|
||||
|
||||
@@ -161,7 +162,6 @@ export interface TableCellRendererProps {
|
||||
theme: GrafanaTheme2;
|
||||
cellInspect: boolean;
|
||||
showFilters: boolean;
|
||||
justifyContent: Property.JustifyContent;
|
||||
getActions?: GetActionsFunctionLocal;
|
||||
disableSanitizeHtml?: boolean;
|
||||
}
|
||||
@@ -195,7 +195,6 @@ export interface RowExpanderNGProps {
|
||||
|
||||
export interface SparklineCellProps {
|
||||
field: Field;
|
||||
justifyContent: Property.JustifyContent;
|
||||
rowIdx: number;
|
||||
theme: GrafanaTheme2;
|
||||
timeRange?: TimeRange;
|
||||
@@ -215,8 +214,6 @@ export interface BarGaugeCellProps {
|
||||
export interface ImageCellProps {
|
||||
cellOptions: TableCellOptions;
|
||||
field: Field;
|
||||
height: number;
|
||||
justifyContent: Property.JustifyContent;
|
||||
value: TableCellValue;
|
||||
rowIdx: number;
|
||||
}
|
||||
@@ -228,7 +225,6 @@ export interface DataLinksCellProps {
|
||||
|
||||
export interface GeoCellProps {
|
||||
value: TableCellValue;
|
||||
justifyContent: Property.JustifyContent;
|
||||
height: number;
|
||||
}
|
||||
|
||||
@@ -262,6 +258,14 @@ export interface PillCellProps {
|
||||
rowIdx: number;
|
||||
}
|
||||
|
||||
export interface TableCellStyleOptions {
|
||||
textWrap: boolean;
|
||||
textAlign: TextAlign;
|
||||
shouldOverflow: boolean;
|
||||
}
|
||||
|
||||
export type TableCellStyles = (theme: GrafanaTheme2, options: TableCellStyleOptions) => string;
|
||||
|
||||
// Comparator for sorting table values
|
||||
export type Comparator = (a: TableCellValue, b: TableCellValue) => number;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user