diff --git a/packages/grafana-ui/src/components/Table/Table.story.tsx b/packages/grafana-ui/src/components/Table/Table.story.tsx index a494f01c56d..8cd97dcefa9 100644 --- a/packages/grafana-ui/src/components/Table/Table.story.tsx +++ b/packages/grafana-ui/src/components/Table/Table.story.tsx @@ -1,40 +1,10 @@ -import React, { FunctionComponent } from 'react'; +// import React from 'react'; import { storiesOf } from '@storybook/react'; import { Table } from './Table'; import { migratedTestTable, migratedTestStyles, simpleTable } from './examples'; -import { ScopedVars } from '../../types/index'; - -import { renderComponentWithTheme } from '../../utils/storybook/withTheme'; -import { AutoSizer } from 'react-virtualized'; - -const CenteredStory: FunctionComponent<{}> = ({ children }) => { - return ( -
- - {({ width, height }) => ( -
-
- Need to pass {width}/{height} to the table? -
- {children} -
- )} -
-
- ); -}; +import { ScopedVars, TableData } from '../../types/index'; +import { withFullSizeStory } from '../../utils/storybook/withFullSizeStory'; const replaceVariables = (value: any, scopedVars: ScopedVars | undefined) => { // if (scopedVars) { @@ -47,24 +17,49 @@ const replaceVariables = (value: any, scopedVars: ScopedVars | undefined) => { }; storiesOf('UI - Alpha/Table', module) - .addDecorator(story => {story()}) .add('basic', () => { - return renderComponentWithTheme(Table, { + return withFullSizeStory(Table, { styles: [], data: simpleTable, replaceVariables, showHeader: true, - width: 500, - height: 300, }); }) .add('Test Configuration', () => { - return renderComponentWithTheme(Table, { + return withFullSizeStory(Table, { styles: migratedTestStyles, data: migratedTestTable, replaceVariables, showHeader: true, - width: 500, - height: 300, + }); + }) + .add('Lots of cells', () => { + const data = { + columns: [], + rows: [], + type: 'table', + columnMap: {}, + } as TableData; + for (let i = 0; i < 20; i++) { + data.columns.push({ + text: 'Column ' + i, + }); + } + for (let r = 0; r < 500; r++) { + const row = []; + for (let i = 0; i < 20; i++) { + row.push(r + i); + } + data.rows.push(row); + } + console.log('DATA:', data); + + return withFullSizeStory(Table, { + styles: simpleTable, + data, + replaceVariables, + showHeader: true, + fixedColumnCount: 1, + fixedRowCount: 1, }); }); diff --git a/packages/grafana-ui/src/components/Table/Table.test.ts b/packages/grafana-ui/src/components/Table/Table.test.ts index 93cf53b2adb..e53df39750e 100644 --- a/packages/grafana-ui/src/components/Table/Table.test.ts +++ b/packages/grafana-ui/src/components/Table/Table.test.ts @@ -3,9 +3,9 @@ import _ from 'lodash'; import { getColorDefinitionByName } from '@grafana/ui'; import { ScopedVars } from '@grafana/ui/src/types'; import { getTheme } from '../../themes'; -import Table from './Table'; import { migratedTestTable, migratedTestStyles } from './examples'; +import TableXXXX from './TableXXXX'; // TODO: this is commented out with *x* describe! // Essentially all the elements need to replace the with
@@ -28,7 +28,7 @@ xdescribe('when rendering table', () => { }; const table = migratedTestTable; - const renderer = new Table({ + const renderer = new TableXXXX({ styles: migratedTestStyles, data: migratedTestTable, replaceVariables, diff --git a/packages/grafana-ui/src/components/Table/Table.tsx b/packages/grafana-ui/src/components/Table/Table.tsx index f208b7651e8..cbc0b50ebc0 100644 --- a/packages/grafana-ui/src/components/Table/Table.tsx +++ b/packages/grafana-ui/src/components/Table/Table.tsx @@ -1,22 +1,19 @@ // Libraries import _ from 'lodash'; -import React, { Component, CSSProperties, ReactNode } from 'react'; +import React, { Component, ReactNode } from 'react'; import { - Table as RVTable, SortDirectionType, SortIndicator, - Column as RVColumn, - TableHeaderProps, - TableCellProps, + MultiGrid, + CellMeasurerCache, + CellMeasurer, + GridCellProps, } from 'react-virtualized'; import { Themeable } from '../../types/theme'; import { sortTableData } from '../../utils/processTimeSeries'; -import moment from 'moment'; - -import { getValueFormat, TableData, getColorFromHexRgbOrName, InterpolateFunction, Column } from '@grafana/ui'; -import { Index } from 'react-virtualized'; +import { TableData, InterpolateFunction } from '@grafana/ui'; import { ColumnStyle } from './Table'; // APP Imports!!! @@ -27,7 +24,7 @@ export interface ColumnStyle { pattern?: string; alias?: string; - colorMode?: string; + colorMode?: 'cell' | 'value'; colors?: any[]; decimals?: number; thresholds?: any[]; @@ -61,6 +58,8 @@ interface ColumnInfo { interface Props extends Themeable { data?: TableData; showHeader: boolean; + fixedColumnCount: number; + fixedRowCount: number; styles: ColumnStyle[]; replaceVariables: InterpolateFunction; width: number; @@ -78,8 +77,12 @@ export class Table extends Component { columns: ColumnInfo[] = []; colorState: any; + _cache: CellMeasurerCache; + static defaultProps = { showHeader: true, + fixedRowCount: 1, + fixedColumnCount: 0, }; constructor(props: Props) { @@ -89,6 +92,11 @@ export class Table extends Component { data: props.data, }; + this._cache = new CellMeasurerCache({ + defaultHeight: 30, + defaultWidth: 150, + }); + this.initRenderer(); } @@ -109,314 +117,17 @@ export class Table extends Component { } } - initRenderer() { - const { styles } = this.props; - const { data } = this.state; - this.colorState = {}; - if (!data || !data.columns) { - this.columns = []; - return; - } - this.columns = data.columns.map((col, index) => { - let title = col.text; - let style; // ColumnStyle - - // Find the style based on the text - for (let i = 0; i < styles.length; i++) { - const s = styles[i]; - const regex = 'XXX'; //kbn.stringToJsRegex(s.pattern); - if (title.match(regex)) { - style = s; - if (s.alias) { - title = title.replace(regex, s.alias); - } - break; - } - } - - return { - header: title, - accessor: col.text, // unique? - style: style, - formatter: this.createColumnFormatter(col, style), - }; - }); - } - - //---------------------------------------------------------------------- - // renderer.ts copy (taken from angular version!!!) - //---------------------------------------------------------------------- - - getColorForValue(value: any, style: ColumnStyle) { - if (!style.thresholds || !style.colors) { - return null; - } - const { theme } = this.props; - - for (let i = style.thresholds.length; i > 0; i--) { - if (value >= style.thresholds[i - 1]) { - return getColorFromHexRgbOrName(style.colors[i], theme.type); - } - } - return getColorFromHexRgbOrName(_.first(style.colors), theme.type); - } - - defaultCellFormatter(v: any, style?: ColumnStyle): string { - if (v === null || v === void 0 || v === undefined) { - return ''; - } - - if (_.isArray(v)) { - v = v.join(', '); - } - - return v; // react will sanitize - } - - createColumnFormatter(schema: Column, style?: ColumnStyle): CellFormatter { - if (!style) { - return this.defaultCellFormatter; - } - - if (style.type === 'hidden') { - return v => { - return undefined; - }; - } - - if (style.type === 'date') { - return v => { - if (v === undefined || v === null) { - return '-'; - } - - if (_.isArray(v)) { - v = v[0]; - } - let date = moment(v); - if (this.props.isUTC) { - date = date.utc(); - } - return date.format(style.dateFormat); - }; - } - - if (style.type === 'string') { - return v => { - if (_.isArray(v)) { - v = v.join(', '); - } - - const mappingType = style.mappingType || 0; - - if (mappingType === 1 && style.valueMaps) { - for (let i = 0; i < style.valueMaps.length; i++) { - const map = style.valueMaps[i]; - - if (v === null) { - if (map.value === 'null') { - return map.text; - } - continue; - } - - // Allow both numeric and string values to be mapped - if ((!_.isString(v) && Number(map.value) === Number(v)) || map.value === v) { - this.setColorState(v, style); - return this.defaultCellFormatter(map.text, style); - } - } - } - - if (mappingType === 2 && style.rangeMaps) { - for (let i = 0; i < style.rangeMaps.length; i++) { - const map = style.rangeMaps[i]; - - if (v === null) { - if (map.from === 'null' && map.to === 'null') { - return map.text; - } - continue; - } - - if (Number(map.from) <= Number(v) && Number(map.to) >= Number(v)) { - this.setColorState(v, style); - return this.defaultCellFormatter(map.text, style); - } - } - } - - if (v === null || v === void 0) { - return '-'; - } - - this.setColorState(v, style); - return this.defaultCellFormatter(v, style); - }; - } - - if (style.type === 'number') { - const valueFormatter = getValueFormat(style.unit || schema.unit || 'none'); - - return v => { - if (v === null || v === void 0) { - return '-'; - } - - if (_.isString(v) || _.isArray(v)) { - return this.defaultCellFormatter(v, style); - } - - this.setColorState(v, style); - return valueFormatter(v, style.decimals, null); - }; - } - - return value => { - return this.defaultCellFormatter(value, style); - }; - } - - setColorState(value: any, style: ColumnStyle) { - if (!style.colorMode) { - return; - } - - if (value === null || value === void 0 || _.isArray(value)) { - return; - } - - if (_.isNaN(value)) { - return; - } - const numericValue = Number(value); - this.colorState[style.colorMode] = this.getColorForValue(numericValue, style); - } - - renderRowVariables(rowIndex: number) { - const scopedVars: any = {}; - const row = this.rowGetter({ index: rowIndex }); - for (let i = 0; i < row.length; i++) { - scopedVars[`__cell_${i}`] = { value: row[i] }; - } - return scopedVars; - } - - renderCell(columnIndex: number, rowIndex: number, value: any): ReactNode { - const column = this.columns[columnIndex]; - if (column.formatter) { - value = column.formatter(value, column.style); - } - - const style: CSSProperties = {}; - const cellClasses = []; - let cellClass = ''; - - if (this.colorState.cell) { - style.backgroundColor = this.colorState.cell; - style.color = 'white'; - this.colorState.cell = null; - } else if (this.colorState.value) { - style.color = this.colorState.value; - this.colorState.value = null; - } - - if (value === undefined) { - style.display = 'none'; - column.hidden = true; - } else { - column.hidden = false; - } - - if (column.style && column.style.preserveFormat) { - cellClasses.push('table-panel-cell-pre'); - } - - let columnHtml: JSX.Element; - if (column.style && column.style.link) { - // Render cell as link - const { replaceVariables } = this.props; - const scopedVars = this.renderRowVariables(rowIndex); - scopedVars['__cell'] = { value: value }; - - const cellLink = replaceVariables(column.style.linkUrl, scopedVars, encodeURIComponent); - const cellLinkTooltip = replaceVariables(column.style.linkTooltip, scopedVars); - const cellTarget = column.style.linkTargetBlank ? '_blank' : ''; - - cellClasses.push('table-panel-cell-link'); - columnHtml = ( - - {value} - - ); - } else { - columnHtml = {value}; - } - - let filterLink: JSX.Element | null = null; - if (column.filterable) { - cellClasses.push('table-panel-cell-filterable'); - filterLink = ( - - - - - - - - - ); - } - - if (cellClasses.length) { - cellClass = cellClasses.join(' '); - } - - style.width = '100%'; - style.height = '100%'; - columnHtml = ( -
- {columnHtml} - {filterLink} -
- ); - return columnHtml; - } + initRenderer() {} //---------------------------------------------------------------------- //---------------------------------------------------------------------- - rowGetter = ({ index }: Index) => { - return this.state.data!.rows[index]; - }; - - doSort = (info: any) => { - let dir = info.sortDirection; - let sort = info.sortBy; - if (sort !== this.state.sortBy) { + doSort = (columnIndex: number) => { + let sort: any = this.state.sortBy; + let dir = this.state.sortDirection; + if (sort !== columnIndex) { dir = 'DESC'; + sort = columnIndex; } else if (dir === 'DESC') { dir = 'ASC'; } else { @@ -425,58 +136,107 @@ export class Table extends Component { this.setState({ sortBy: sort, sortDirection: dir }); }; - headerRenderer = (header: TableHeaderProps): ReactNode => { - const dataKey = header.dataKey as any; // types say string, but it is number! + handelClick = (rowIndex: number, columnIndex: number) => { + const { showHeader } = this.props; + const { data } = this.state; + const realRowIndex = rowIndex - (showHeader ? 1 : 0); + if (realRowIndex < 0) { + this.doSort(columnIndex); + } else { + const row = data!.rows[realRowIndex]; + const value = row[columnIndex]; + console.log('CLICK', rowIndex, columnIndex, value); + } + }; + + headerRenderer = (columnIndex: number): ReactNode => { const { data, sortBy, sortDirection } = this.state; - const col = data!.columns[dataKey]; + const col = data!.columns[columnIndex]; + const sorting = sortBy === columnIndex; return (
- {col.text} {sortBy === dataKey && } + {col.text}{' '} + {sorting && ( + + {sortDirection} + + + )}
); }; - cellRenderer = (cell: TableCellProps) => { - const { columnIndex, rowIndex } = cell; - const row = this.state.data!.rows[rowIndex]; - const val = row[columnIndex]; - return this.renderCell(columnIndex, rowIndex, val); + cellRenderer = (props: GridCellProps): React.ReactNode => { + const { rowIndex, columnIndex, key, parent, style } = props; + const { showHeader } = this.props; + const { data } = this.state; + if (!data) { + return
?
; + } + + const realRowIndex = rowIndex - (showHeader ? 1 : 0); + + let classNames = 'gf-table-cell'; + let content = null; + + if (realRowIndex < 0) { + content = this.headerRenderer(columnIndex); + classNames = 'gf-table-header'; + } else { + const row = data.rows[realRowIndex]; + const value = row[columnIndex]; + content = ( +
+ {rowIndex}/{columnIndex}: {value} +
+ ); + } + + return ( + +
this.handelClick(rowIndex, columnIndex)} + className={classNames} + style={{ + ...style, + whiteSpace: 'nowrap', + }} + > + {content} +
+
+ ); }; render() { - const { width, height, showHeader } = this.props; - const { data } = this.props; + const { data, showHeader, width, height, fixedColumnCount, fixedRowCount } = this.props; if (!data) { return
NO Data
; } return ( - - {data.columns.map((col, index) => { - return ( - - ); - })} - + height={height} + fixedColumnCount={fixedColumnCount} + fixedRowCount={fixedRowCount} + classNameTopLeftGrid="gf-table-fixed-row-and-column" + classNameTopRightGrid="gf-table-fixed-row" + classNameBottomLeftGrid="gf-table-fixed-column" + classNameBottomRightGrid="gf-table-normal-cell" + /> ); } } diff --git a/packages/grafana-ui/src/components/Table/TableXXXX.tsx b/packages/grafana-ui/src/components/Table/TableXXXX.tsx new file mode 100644 index 00000000000..4c78c3b336b --- /dev/null +++ b/packages/grafana-ui/src/components/Table/TableXXXX.tsx @@ -0,0 +1,456 @@ +// Libraries +import _ from 'lodash'; +import React, { Component, CSSProperties, ReactNode } from 'react'; +import { + Table as RVTable, + SortDirectionType, + SortIndicator, + Column as RVColumn, + TableHeaderProps, + TableCellProps, +} from 'react-virtualized'; +import { Themeable } from '../../types/theme'; + +import { sortTableData } from '../../utils/processTimeSeries'; + +import moment from 'moment'; + +import { getValueFormat, TableData, getColorFromHexRgbOrName, InterpolateFunction, Column } from '@grafana/ui'; +import { Index } from 'react-virtualized'; +import { ColumnStyle } from './Table'; + +type CellFormatter = (v: any, style?: ColumnStyle) => ReactNode; + +interface ColumnInfo { + header: string; + accessor: string; // the field name + style?: ColumnStyle; + hidden?: boolean; + formatter: CellFormatter; + filterable?: boolean; +} + +interface Props extends Themeable { + data?: TableData; + showHeader: boolean; + styles: ColumnStyle[]; + replaceVariables: InterpolateFunction; + width: number; + height: number; + isUTC?: boolean; +} + +interface State { + sortBy?: number; + sortDirection?: SortDirectionType; + data?: TableData; +} + +export class TableXXXX extends Component { + columns: ColumnInfo[] = []; + colorState: any; + + static defaultProps = { + showHeader: true, + }; + + constructor(props: Props) { + super(props); + + this.state = { + data: props.data, + }; + + this.initRenderer(); + } + + componentDidUpdate(prevProps: Props, prevState: State) { + const { data, styles } = this.props; + const { sortBy, sortDirection } = this.state; + const dataChanged = data !== prevProps.data; + + // Update the renderer if options change + if (dataChanged || styles !== prevProps.styles) { + this.initRenderer(); + } + + // Update the data when data or sort changes + if (dataChanged || sortBy !== prevState.sortBy || sortDirection !== prevState.sortDirection) { + const sorted = data ? sortTableData(data, sortBy, sortDirection === 'DESC') : data; + this.setState({ data: sorted }); + } + } + + initRenderer() { + const { styles } = this.props; + const { data } = this.state; + this.colorState = {}; + if (!data || !data.columns) { + this.columns = []; + return; + } + this.columns = data.columns.map((col, index) => { + let title = col.text; + let style; // ColumnStyle + + // Find the style based on the text + for (let i = 0; i < styles.length; i++) { + const s = styles[i]; + const regex = 'XXX'; //kbn.stringToJsRegex(s.pattern); + if (title.match(regex)) { + style = s; + if (s.alias) { + title = title.replace(regex, s.alias); + } + break; + } + } + + return { + header: title, + accessor: col.text, // unique? + style: style, + formatter: this.createColumnFormatter(col, style), + }; + }); + } + + //---------------------------------------------------------------------- + // renderer.ts copy (taken from angular version!!!) + //---------------------------------------------------------------------- + + getColorForValue(value: any, style: ColumnStyle) { + if (!style.thresholds || !style.colors) { + return null; + } + const { theme } = this.props; + + for (let i = style.thresholds.length; i > 0; i--) { + if (value >= style.thresholds[i - 1]) { + return getColorFromHexRgbOrName(style.colors[i], theme.type); + } + } + return getColorFromHexRgbOrName(_.first(style.colors), theme.type); + } + + defaultCellFormatter(v: any, style?: ColumnStyle): string { + if (v === null || v === void 0 || v === undefined) { + return ''; + } + + if (_.isArray(v)) { + v = v.join(', '); + } + + return v; // react will sanitize + } + + createColumnFormatter(schema: Column, style?: ColumnStyle): CellFormatter { + if (!style) { + return this.defaultCellFormatter; + } + + if (style.type === 'hidden') { + return v => { + return undefined; + }; + } + + if (style.type === 'date') { + return v => { + if (v === undefined || v === null) { + return '-'; + } + + if (_.isArray(v)) { + v = v[0]; + } + let date = moment(v); + if (this.props.isUTC) { + date = date.utc(); + } + return date.format(style.dateFormat); + }; + } + + if (style.type === 'string') { + return v => { + if (_.isArray(v)) { + v = v.join(', '); + } + + const mappingType = style.mappingType || 0; + + if (mappingType === 1 && style.valueMaps) { + for (let i = 0; i < style.valueMaps.length; i++) { + const map = style.valueMaps[i]; + + if (v === null) { + if (map.value === 'null') { + return map.text; + } + continue; + } + + // Allow both numeric and string values to be mapped + if ((!_.isString(v) && Number(map.value) === Number(v)) || map.value === v) { + this.setColorState(v, style); + return this.defaultCellFormatter(map.text, style); + } + } + } + + if (mappingType === 2 && style.rangeMaps) { + for (let i = 0; i < style.rangeMaps.length; i++) { + const map = style.rangeMaps[i]; + + if (v === null) { + if (map.from === 'null' && map.to === 'null') { + return map.text; + } + continue; + } + + if (Number(map.from) <= Number(v) && Number(map.to) >= Number(v)) { + this.setColorState(v, style); + return this.defaultCellFormatter(map.text, style); + } + } + } + + if (v === null || v === void 0) { + return '-'; + } + + this.setColorState(v, style); + return this.defaultCellFormatter(v, style); + }; + } + + if (style.type === 'number') { + const valueFormatter = getValueFormat(style.unit || schema.unit || 'none'); + + return v => { + if (v === null || v === void 0) { + return '-'; + } + + if (_.isString(v) || _.isArray(v)) { + return this.defaultCellFormatter(v, style); + } + + this.setColorState(v, style); + return valueFormatter(v, style.decimals, null); + }; + } + + return value => { + return this.defaultCellFormatter(value, style); + }; + } + + setColorState(value: any, style: ColumnStyle) { + if (!style.colorMode) { + return; + } + + if (value === null || value === void 0 || _.isArray(value)) { + return; + } + + if (_.isNaN(value)) { + return; + } + const numericValue = Number(value); + this.colorState[style.colorMode] = this.getColorForValue(numericValue, style); + } + + renderRowVariables(rowIndex: number) { + const scopedVars: any = {}; + const row = this.rowGetter({ index: rowIndex }); + for (let i = 0; i < row.length; i++) { + scopedVars[`__cell_${i}`] = { value: row[i] }; + } + return scopedVars; + } + + renderCell(columnIndex: number, rowIndex: number, value: any): ReactNode { + const column = this.columns[columnIndex]; + if (column.formatter) { + value = column.formatter(value, column.style); + } + + const style: CSSProperties = {}; + const cellClasses = []; + let cellClass = ''; + + if (this.colorState.cell) { + style.backgroundColor = this.colorState.cell; + style.color = 'white'; + this.colorState.cell = null; + } else if (this.colorState.value) { + style.color = this.colorState.value; + this.colorState.value = null; + } + + if (value === undefined) { + style.display = 'none'; + column.hidden = true; + } else { + column.hidden = false; + } + + if (column.style && column.style.preserveFormat) { + cellClasses.push('table-panel-cell-pre'); + } + + let columnHtml: JSX.Element; + if (column.style && column.style.link) { + // Render cell as link + const { replaceVariables } = this.props; + const scopedVars = this.renderRowVariables(rowIndex); + scopedVars['__cell'] = { value: value }; + + const cellLink = replaceVariables(column.style.linkUrl, scopedVars, encodeURIComponent); + const cellLinkTooltip = replaceVariables(column.style.linkTooltip, scopedVars); + const cellTarget = column.style.linkTargetBlank ? '_blank' : ''; + + cellClasses.push('table-panel-cell-link'); + columnHtml = ( + + {value} + + ); + } else { + columnHtml = {value}; + } + + let filterLink: JSX.Element | null = null; + if (column.filterable) { + cellClasses.push('table-panel-cell-filterable'); + filterLink = ( + + + + + + + + + ); + } + + if (cellClasses.length) { + cellClass = cellClasses.join(' '); + } + + style.width = '100%'; + style.height = '100%'; + columnHtml = ( +
+ {columnHtml} + {filterLink} +
+ ); + return columnHtml; + } + + //---------------------------------------------------------------------- + //---------------------------------------------------------------------- + + rowGetter = ({ index }: Index) => { + return this.state.data!.rows[index]; + }; + + doSort = (info: any) => { + let dir = info.sortDirection; + let sort = info.sortBy; + if (sort !== this.state.sortBy) { + dir = 'DESC'; + } else if (dir === 'DESC') { + dir = 'ASC'; + } else { + sort = null; + } + this.setState({ sortBy: sort, sortDirection: dir }); + }; + + headerRenderer = (header: TableHeaderProps): ReactNode => { + const dataKey = header.dataKey as any; // types say string, but it is number! + const { data, sortBy, sortDirection } = this.state; + const col = data!.columns[dataKey]; + + return ( +
+ {col.text} {sortBy === dataKey && } +
+ ); + }; + + cellRenderer = (cell: TableCellProps) => { + const { columnIndex, rowIndex } = cell; + const row = this.state.data!.rows[rowIndex]; + const val = row[columnIndex]; + return this.renderCell(columnIndex, rowIndex, val); + }; + + render() { + const { width, height, showHeader } = this.props; + const { data } = this.props; + if (!data) { + return
NO Data
; + } + + return ( + + {data.columns.map((col, index) => { + return ( + + ); + })} + + ); + } +} + +export default TableXXXX; diff --git a/packages/grafana-ui/src/components/Table/_Table.scss b/packages/grafana-ui/src/components/Table/_Table.scss index b0aa2d6b742..f9cb0271561 100644 --- a/packages/grafana-ui/src/components/Table/_Table.scss +++ b/packages/grafana-ui/src/components/Table/_Table.scss @@ -9,12 +9,6 @@ display: flex; flex-direction: row; align-items: left; - - background: $list-item-bg; - border-top: 2px solid $body-bg; - border-bottom: 2px solid $body-bg; - - color: $blue; } .ReactVirtualized__Table__row { display: flex; @@ -37,12 +31,6 @@ margin-right: 10px; min-width: 0px; } -.ReactVirtualized__Table__rowColumn { - text-overflow: ellipsis; - white-space: nowrap; - - border-right: 2px solid $body-bg; -} .ReactVirtualized__Table__headerColumn:first-of-type, .ReactVirtualized__Table__rowColumn:first-of-type { @@ -62,3 +50,25 @@ width: 1em; fill: currentColor; } + +.gf-table-header { + padding: 3px 10px; + + background: $list-item-bg; + border-top: 2px solid $body-bg; + border-bottom: 2px solid $body-bg; + + cursor: pointer; + + color: $blue; +} + +.gf-table-cell { + padding: 3px 10px; + + text-overflow: ellipsis; + white-space: nowrap; + + border-right: 2px solid $body-bg; + border-bottom: 2px solid $body-bg; +} diff --git a/packages/grafana-ui/src/utils/storybook/withFullSizeStory.tsx b/packages/grafana-ui/src/utils/storybook/withFullSizeStory.tsx new file mode 100644 index 00000000000..c6efbee462f --- /dev/null +++ b/packages/grafana-ui/src/utils/storybook/withFullSizeStory.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { AutoSizer } from 'react-virtualized'; + +export const withFullSizeStory = (component: React.ComponentType, props: any) => ( +
+ + {({ width, height }) => ( + <> + {React.createElement(component, { + ...props, + width, + height, + })} + + )} + +
+);