From 56a7de562ea381938087c84d62e57a7da26ce63f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 20 Apr 2020 09:27:40 +0200 Subject: [PATCH] Table: Improvements to column resizing, style and alignment (#23663) * Table: Fixed to column alignment * testing table state reducer * Styles starting to work * Persisting column resize now works * Trying to fix Table storybook stories * Minor updates * fixed ts issue * Table: Support duplicate field names, and use data frame directly instead of copying data and other improvements (#23681) * Poc at use data frame directly * working ok * Table improvements --- .../grafana-data/src/types/fieldOverrides.ts | 5 +- .../CustomScrollbar/_CustomScrollbar.scss | 4 +- .../src/components/Table/BarGaugeCell.tsx | 1 + .../src/components/Table/Table.story.tsx | 108 ++++----- .../grafana-ui/src/components/Table/Table.tsx | 224 +++++++++++------- .../grafana-ui/src/components/Table/styles.ts | 27 ++- .../grafana-ui/src/components/Table/types.ts | 8 +- .../grafana-ui/src/components/Table/utils.ts | 28 +-- packages/grafana-ui/src/themes/mixins.ts | 10 +- public/app/plugins/panel/table/TablePanel.tsx | 93 +++++--- public/app/plugins/panel/table/module.tsx | 23 +- public/app/plugins/panel/table/types.ts | 1 - public/sass/components/_panel_header.scss | 1 + 13 files changed, 308 insertions(+), 225 deletions(-) diff --git a/packages/grafana-data/src/types/fieldOverrides.ts b/packages/grafana-data/src/types/fieldOverrides.ts index 725280161f4..3264443cc98 100644 --- a/packages/grafana-data/src/types/fieldOverrides.ts +++ b/packages/grafana-data/src/types/fieldOverrides.ts @@ -9,9 +9,8 @@ import { GrafanaTheme, TimeZone, } from '../types'; -import { Registry } from '../utils/Registry'; import { InterpolateFunction } from './panel'; -import { StandardEditorProps } from '../field'; +import { StandardEditorProps, FieldConfigOptionsRegistry } from '../field'; import { OptionsEditorItem } from './OptionsUIRegistryBuilder'; export interface DynamicConfigValue { @@ -122,7 +121,7 @@ export interface ApplyFieldOverrideOptions { theme: GrafanaTheme; timeZone?: TimeZone; autoMinMax?: boolean; - fieldConfigRegistry?: Registry; + fieldConfigRegistry?: FieldConfigOptionsRegistry; } export enum FieldConfigProperty { diff --git a/packages/grafana-ui/src/components/CustomScrollbar/_CustomScrollbar.scss b/packages/grafana-ui/src/components/CustomScrollbar/_CustomScrollbar.scss index 8e20df15f2c..fd846025534 100644 --- a/packages/grafana-ui/src/components/CustomScrollbar/_CustomScrollbar.scss +++ b/packages/grafana-ui/src/components/CustomScrollbar/_CustomScrollbar.scss @@ -13,7 +13,7 @@ .track-vertical { border-radius: 3px; - width: 6px !important; + width: 8px !important; right: 2px; bottom: 2px; top: 2px; @@ -21,7 +21,7 @@ .track-horizontal { border-radius: 3px; - height: 6px !important; + height: 8px !important; right: 2px; bottom: 2px; diff --git a/packages/grafana-ui/src/components/Table/BarGaugeCell.tsx b/packages/grafana-ui/src/components/Table/BarGaugeCell.tsx index 0a5c0bfa259..c2c614538da 100644 --- a/packages/grafana-ui/src/components/Table/BarGaugeCell.tsx +++ b/packages/grafana-ui/src/components/Table/BarGaugeCell.tsx @@ -52,6 +52,7 @@ export const BarGaugeCell: FC = props => { width={width} height={tableStyles.cellHeightInner} field={config} + display={field.display} value={displayValue} orientation={VizOrientation.Horizontal} theme={tableStyles.theme} diff --git a/packages/grafana-ui/src/components/Table/Table.story.tsx b/packages/grafana-ui/src/components/Table/Table.story.tsx index 99b382a207e..b6aae20c91d 100644 --- a/packages/grafana-ui/src/components/Table/Table.story.tsx +++ b/packages/grafana-ui/src/components/Table/Table.story.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { merge } from 'lodash'; import { Table } from './Table'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { number } from '@storybook/addon-knobs'; @@ -6,14 +7,13 @@ import { useTheme } from '../../themes'; import mdx from './Table.mdx'; import { applyFieldOverrides, - ConfigOverrideRule, DataFrame, - FieldMatcherID, FieldType, GrafanaTheme, MutableDataFrame, ThresholdsConfig, ThresholdsMode, + FieldConfig, } from '@grafana/data'; export default { @@ -27,7 +27,7 @@ export default { }, }; -function buildData(theme: GrafanaTheme, overrides: ConfigOverrideRule[]): DataFrame { +function buildData(theme: GrafanaTheme, config: Record): DataFrame { const data = new MutableDataFrame({ fields: [ { name: 'Time', type: FieldType.time, values: [] }, // The time field @@ -39,6 +39,7 @@ function buildData(theme: GrafanaTheme, overrides: ConfigOverrideRule[]): DataFr decimals: 0, custom: { align: 'center', + width: 80, }, }, }, @@ -57,14 +58,20 @@ function buildData(theme: GrafanaTheme, overrides: ConfigOverrideRule[]): DataFr values: [], config: { unit: 'percent', + min: 0, + max: 100, custom: { - width: 100, + width: 150, }, }, }, ], }); + for (const field of data.fields) { + field.config = merge(field.config, config[field.name]); + } + for (let i = 0; i < 1000; i++) { data.appendRow([ new Date().getTime(), @@ -78,7 +85,7 @@ function buildData(theme: GrafanaTheme, overrides: ConfigOverrideRule[]): DataFr return applyFieldOverrides({ data: [data], fieldConfig: { - overrides, + overrides: [], defaults: {}, }, theme, @@ -86,40 +93,6 @@ function buildData(theme: GrafanaTheme, overrides: ConfigOverrideRule[]): DataFr })[0]; } -export const Simple = () => { - const theme = useTheme(); - const width = number('width', 700, {}, 'Props'); - const data = buildData(theme, []); - - return ( -
- - - ); -}; - -export const BarGaugeCell = () => { - const theme = useTheme(); - const width = number('width', 700, {}, 'Props'); - const data = buildData(theme, [ - { - matcher: { id: FieldMatcherID.byName, options: 'Progress' }, - properties: [ - { id: 'width', value: '200' }, - { id: 'displayMode', value: 'gradient-gauge' }, - { id: 'min', value: '0' }, - { id: 'max', value: '100' }, - ], - }, - ]); - - return ( -
-
- - ); -}; - const defaultThresholds: ThresholdsConfig = { steps: [ { @@ -134,21 +107,50 @@ const defaultThresholds: ThresholdsConfig = { mode: ThresholdsMode.Absolute, }; -export const ColoredCells = () => { +export const Simple = () => { const theme = useTheme(); - const width = number('width', 750, {}, 'Props'); - const data = buildData(theme, [ - { - matcher: { id: FieldMatcherID.byName, options: 'Progress' }, - properties: [ - { id: 'width', value: '80' }, - { id: 'displayMode', value: 'color-background' }, - { id: 'min', value: '0' }, - { id: 'max', value: '100' }, - { id: 'thresholds', value: defaultThresholds }, - ], - }, - ]); + const width = number('width', 700, {}, 'Props'); + const data = buildData(theme, {}); + + return ( +
+
+ + ); +}; + +export const BarGaugeCell = () => { + const theme = useTheme(); + const width = number('width', 700, {}, 'Props'); + const data = buildData(theme, { + Progress: { + custom: { + width: 200, + displayMode: 'gradient-gauge', + }, + thresholds: defaultThresholds, + }, + }); + + return ( +
+
+ + ); +}; + +export const ColoredCells = () => { + const theme = useTheme(); + const width = number('width', 750, {}, 'Props'); + const data = buildData(theme, { + Progress: { + custom: { + width: 80, + displayMode: 'color-background', + }, + thresholds: defaultThresholds, + }, + }); return (
diff --git a/packages/grafana-ui/src/components/Table/Table.tsx b/packages/grafana-ui/src/components/Table/Table.tsx index 9913aa83bf3..ae80aa28e41 100644 --- a/packages/grafana-ui/src/components/Table/Table.tsx +++ b/packages/grafana-ui/src/components/Table/Table.tsx @@ -1,11 +1,20 @@ -import React, { FC, memo, useMemo } from 'react'; +import React, { FC, memo, useMemo, useCallback } from 'react'; import { DataFrame, Field } from '@grafana/data'; -import { Cell, Column, HeaderGroup, useBlockLayout, useResizeColumns, useSortBy, useTable } from 'react-table'; +import { + Cell, + Column, + HeaderGroup, + useAbsoluteLayout, + useResizeColumns, + useSortBy, + useTable, + UseResizeColumnsState, + UseSortByState, +} from 'react-table'; import { FixedSizeList } from 'react-window'; -import useMeasure from 'react-use/lib/useMeasure'; -import { getColumns, getTableRows, getTextAlign } from './utils'; +import { getColumns, getTextAlign } from './utils'; import { useTheme } from '../../themes'; -import { ColumnResizeActionCallback, TableFilterActionCallback } from './types'; +import { TableColumnResizeActionCallback, TableFilterActionCallback, TableSortByActionCallback } from './types'; import { getTableStyles, TableStyles } from './styles'; import { TableCell } from './TableCell'; import { Icon } from '../Icon/Icon'; @@ -22,109 +31,142 @@ export interface Props { noHeader?: boolean; resizable?: boolean; onCellClick?: TableFilterActionCallback; - onColumnResize?: ColumnResizeActionCallback; + onColumnResize?: TableColumnResizeActionCallback; + onSortBy?: TableSortByActionCallback; } -export const Table: FC = memo( - ({ data, height, onCellClick, width, columnMinWidth = COLUMN_MIN_WIDTH, noHeader, resizable = false }) => { - const theme = useTheme(); - const [ref, headerRowMeasurements] = useMeasure(); - const tableStyles = getTableStyles(theme); - const memoizedColumns = useMemo(() => getColumns(data, width, columnMinWidth), [data, width, columnMinWidth]); - const memoizedData = useMemo(() => getTableRows(data), [data]); +interface ReactTableInternalState extends UseResizeColumnsState<{}>, UseSortByState<{}> {} - const defaultColumn = React.useMemo( - () => ({ - minWidth: memoizedColumns.reduce((minWidth, column) => { - if (column.width) { - const width = typeof column.width === 'string' ? parseInt(column.width, 10) : column.width; - return Math.min(minWidth, width); +function useTableStateReducer(props: Props) { + return useCallback( + (newState: ReactTableInternalState, action: any) => { + console.log(action, newState); + + switch (action.type) { + case 'columnDoneResizing': + if (props.onColumnResize) { + const info = (newState.columnResizing.headerIdWidths as any)[0]; + const columnIdString = info[0]; + const fieldIndex = parseInt(columnIdString, 10); + const width = Math.round(newState.columnResizing.columnWidths[columnIdString] as number); + props.onColumnResize(fieldIndex, width); } - return minWidth; - }, columnMinWidth), - }), - [columnMinWidth, memoizedColumns] - ); + case 'toggleSortBy': + if (props.onSortBy) { + // todo call callback and persist + } + break; + } - const options: any = useMemo( - () => ({ - columns: memoizedColumns, - data: memoizedData, - disableResizing: !resizable, - defaultColumn, - }), - [memoizedColumns, memoizedData, resizable, defaultColumn] - ); + return newState; + }, + [props.onColumnResize] + ); +} - const { getTableProps, headerGroups, rows, prepareRow, totalColumnsWidth } = useTable( - options, - useBlockLayout, - useResizeColumns, - useSortBy - ); +export const Table: FC = memo((props: Props) => { + const { data, height, onCellClick, width, columnMinWidth = COLUMN_MIN_WIDTH, noHeader, resizable = true } = props; + const theme = useTheme(); + const tableStyles = getTableStyles(theme); - const RenderRow = React.useCallback( - ({ index, style }) => { - const row = rows[index]; - prepareRow(row); - return ( -
- {row.cells.map((cell: Cell, index: number) => ( - - ))} -
- ); - }, - [prepareRow, rows] - ); + // React table data array. This data acts just like a dummy array to let react-table know how many rows exist + // The cells use the field to look up values + const memoizedData = useMemo(() => { + return data.fields.length > 0 ? data.fields[0].values.toArray() : []; + }, [data]); - return ( -
- -
- {!noHeader && ( -
- {headerGroups.map((headerGroup: HeaderGroup) => { - return ( -
- {headerGroup.headers.map((column: Column, index: number) => - renderHeaderCell(column, tableStyles, data.fields[index]) - )} -
- ); - })} -
- )} - - {RenderRow} - -
-
-
- ); - } -); + // React-table column definitions + const memoizedColumns = useMemo(() => getColumns(data, width, columnMinWidth), [data, width, columnMinWidth]); + + // Internal react table state reducer + const stateReducer = useTableStateReducer(props); + + const options: any = useMemo( + () => ({ + columns: memoizedColumns, + data: memoizedData, + disableResizing: !resizable, + stateReducer: stateReducer, + // this is how you set initial sort by state + // initialState: { + // sortBy: [{ id: '2', desc: true }], + // }, + }), + [memoizedColumns, memoizedData, stateReducer, resizable] + ); + + const { getTableProps, headerGroups, rows, prepareRow, totalColumnsWidth } = useTable( + options, + useSortBy, + useAbsoluteLayout, + useResizeColumns + ); + + const RenderRow = React.useCallback( + ({ index, style }) => { + const row = rows[index]; + prepareRow(row); + return ( +
+ {row.cells.map((cell: Cell, index: number) => ( + + ))} +
+ ); + }, + [prepareRow, rows] + ); + + const headerHeight = noHeader ? 0 : tableStyles.cellHeight; + + return ( +
+ +
+ {!noHeader && ( +
+ {headerGroups.map((headerGroup: HeaderGroup) => { + return ( +
+ {headerGroup.headers.map((column: Column, index: number) => + renderHeaderCell(column, tableStyles, data.fields[index]) + )} +
+ ); + })} +
+ )} + + {RenderRow} + +
+
+
+ ); +}); Table.displayName = 'Table'; function renderHeaderCell(column: any, tableStyles: TableStyles, field?: Field) { const headerProps = column.getHeaderProps(); + if (column.canResize) { headerProps.style.userSelect = column.isResizing ? 'none' : 'auto'; // disables selecting text while resizing } + headerProps.style.position = 'absolute'; headerProps.style.textAlign = getTextAlign(field); return ( diff --git a/packages/grafana-ui/src/components/Table/styles.ts b/packages/grafana-ui/src/components/Table/styles.ts index edc6f312316..f4d0723ef2b 100644 --- a/packages/grafana-ui/src/components/Table/styles.ts +++ b/packages/grafana-ui/src/components/Table/styles.ts @@ -1,6 +1,6 @@ import { css } from 'emotion'; import { GrafanaTheme } from '@grafana/data'; -import { stylesFactory } from '../../themes'; +import { stylesFactory, styleMixins } from '../../themes'; export interface TableStyles { cellHeight: number; @@ -20,13 +20,14 @@ export interface TableStyles { export const getTableStyles = stylesFactory( (theme: GrafanaTheme): TableStyles => { const { palette, colors } = theme; - const headerBg = theme.colors.panelBorder; - const headerBorderColor = theme.isLight ? palette.gray70 : palette.gray05; - const resizerColor = theme.isLight ? palette.blue77 : palette.blue95; + 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); return { theme, @@ -42,6 +43,7 @@ export const getTableStyles = stylesFactory( `, thead: css` label: thead; + height: ${cellHeight}px; overflow-y: auto; overflow-x: hidden; background: ${headerBg}; @@ -52,7 +54,7 @@ export const getTableStyles = stylesFactory( cursor: pointer; white-space: nowrap; color: ${colors.textBlue}; - border-right: 1px solid ${headerBorderColor}; + border-right: 1px solid ${theme.colors.panelBg}; &:last-child { border-right: none; @@ -60,10 +62,14 @@ export const getTableStyles = stylesFactory( `, row: css` label: row; - border-bottom: 1px solid ${headerBg}; + border-bottom: 1px solid ${borderColor}; + + &:hover { + background-color: ${rowHoverBg}; + } `, tableCellWrapper: css` - border-right: 1px solid ${headerBg}; + border-right: 1px solid ${borderColor}; &:last-child { border-right: none; @@ -79,13 +85,14 @@ export const getTableStyles = stylesFactory( label: resizeHandle; cursor: col-resize !important; display: inline-block; - border-right: 2px solid ${resizerColor}; + background: ${resizerColor}; opacity: 0; transition: opacity 0.2s ease-in-out; - width: 10px; + width: 8px; height: 100%; position: absolute; - right: 0; + right: -4px; + border-radius: 3px; top: 0; z-index: ${theme.zIndex.dropdown}; touch-action: none; diff --git a/packages/grafana-ui/src/components/Table/types.ts b/packages/grafana-ui/src/components/Table/types.ts index 145fd967823..2f07a066b0a 100644 --- a/packages/grafana-ui/src/components/Table/types.ts +++ b/packages/grafana-ui/src/components/Table/types.ts @@ -24,7 +24,13 @@ export interface TableRow { } export type TableFilterActionCallback = (key: string, value: string) => void; -export type ColumnResizeActionCallback = (field: Field, width: number) => void; +export type TableColumnResizeActionCallback = (fieldIndex: number, width: number) => void; +export type TableSortByActionCallback = (state: TableSortByFieldState[]) => void; + +export interface TableSortByFieldState { + fieldIndex: number; + desc?: boolean; +} export interface TableCellProps extends CellProps { tableStyles: TableStyles; diff --git a/packages/grafana-ui/src/components/Table/utils.ts b/packages/grafana-ui/src/components/Table/utils.ts index 259650112f8..296f18e5b33 100644 --- a/packages/grafana-ui/src/components/Table/utils.ts +++ b/packages/grafana-ui/src/components/Table/utils.ts @@ -3,26 +3,11 @@ import { DataFrame, Field, FieldType } from '@grafana/data'; import { Column } from 'react-table'; import { DefaultCell } from './DefaultCell'; import { BarGaugeCell } from './BarGaugeCell'; -import { TableCellDisplayMode, TableCellProps, TableFieldOptions, TableRow } from './types'; +import { TableCellDisplayMode, TableCellProps, TableFieldOptions } from './types'; import { css, cx } from 'emotion'; import { withTableStyles } from './withTableStyles'; import tinycolor from 'tinycolor2'; -export function getTableRows(data: DataFrame): TableRow[] { - const tableData = []; - - for (let i = 0; i < data.length; i++) { - const row: { [key: string]: string | number } = {}; - for (let j = 0; j < data.fields.length; j++) { - const prop = data.fields[j].name; - row[prop] = data.fields[j].values.get(i); - } - tableData.push(row); - } - - return tableData; -} - export function getTextAlign(field?: Field): TextAlignProperty { if (!field) { return 'left'; @@ -52,8 +37,10 @@ export function getColumns(data: DataFrame, availableWidth: number, columnMinWid const columns: Column[] = []; let fieldCountWithoutWidth = data.fields.length; - for (const field of data.fields) { + for (let fieldIndex = 0; fieldIndex < data.fields.length; fieldIndex++) { + const field = data.fields[fieldIndex]; const fieldTableOptions = (field.config.custom || {}) as TableFieldOptions; + if (fieldTableOptions.width) { availableWidth -= fieldTableOptions.width; fieldCountWithoutWidth -= 1; @@ -63,10 +50,13 @@ export function getColumns(data: DataFrame, availableWidth: number, columnMinWid columns.push({ Cell, - id: field.name, + id: fieldIndex.toString(), Header: field.config.title ?? field.name, - accessor: field.name, + accessor: (row: any, i: number) => { + return field.values.get(i); + }, width: fieldTableOptions.width, + minWidth: 50, }); } diff --git a/packages/grafana-ui/src/themes/mixins.ts b/packages/grafana-ui/src/themes/mixins.ts index 469d1605f06..3fefaf4842d 100644 --- a/packages/grafana-ui/src/themes/mixins.ts +++ b/packages/grafana-ui/src/themes/mixins.ts @@ -12,8 +12,14 @@ export function cardChrome(theme: GrafanaTheme): string { `; } -export function hoverColor(color: string, theme: GrafanaTheme) { - return theme.isDark ? tinycolor(color).brighten(2) : tinycolor(color).darken(2); +export function hoverColor(color: string, theme: GrafanaTheme): string { + return theme.isDark + ? tinycolor(color) + .brighten(2) + .toString() + : tinycolor(color) + .darken(2) + .toString(); } export function listItem(theme: GrafanaTheme): string { diff --git a/public/app/plugins/panel/table/TablePanel.tsx b/public/app/plugins/panel/table/TablePanel.tsx index 49ae910ac95..ee7bd479e3a 100644 --- a/public/app/plugins/panel/table/TablePanel.tsx +++ b/public/app/plugins/panel/table/TablePanel.tsx @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import { Table, Select } from '@grafana/ui'; -import { Field, FieldMatcherID, PanelProps, DataFrame, SelectableValue } from '@grafana/data'; +import { FieldMatcherID, PanelProps, DataFrame, SelectableValue } from '@grafana/data'; import { Options } from './types'; import { css } from 'emotion'; import { config } from 'app/core/config'; @@ -13,21 +13,44 @@ export class TablePanel extends Component { super(props); } - onColumnResize = (field: Field, width: number) => { - const current = this.props.fieldConfig; - const matcherId = FieldMatcherID.byName; - const prop = 'width'; - const overrides = current.overrides.filter( - o => o.matcher.id !== matcherId || o.matcher.options !== field.name || o.properties[0].id !== prop - ); + onColumnResize = (fieldIndex: number, width: number) => { + const { fieldConfig, data } = this.props; + const { overrides } = fieldConfig; + const frame = data.series[this.getCurrentFrameIndex()]; - overrides.push({ - matcher: { id: matcherId, options: field.name }, - properties: [{ id: prop, value: width }], - }); + if (!frame) { + return; + } + + const field = frame.fields[fieldIndex]; + if (!field) { + return; + } + + const fieldName = field.name; + const matcherId = FieldMatcherID.byName; + const propId = 'custom.width'; + + // look for existing override + const override = overrides.find(o => o.matcher.id === matcherId && o.matcher.options === fieldName); + + if (override) { + // look for existing property + const property = override.properties.find(prop => prop.id === propId); + if (property) { + property.value = width; + } else { + override.properties.push({ id: propId, value: width }); + } + } else { + overrides.push({ + matcher: { id: matcherId, options: fieldName }, + properties: [{ id: propId, value: width }], + }); + } this.props.onFieldConfigChange({ - ...current, + ...fieldConfig, overrides, }); }; @@ -43,19 +66,28 @@ export class TablePanel extends Component { }; renderTable(frame: DataFrame, width: number, height: number) { - const { - options: { showHeader, resizable }, - } = this.props; - return
; + const { options } = this.props; + + return ( +
+ ); + } + + getCurrentFrameIndex() { + const { data, options } = this.props; + const count = data.series?.length; + return options.frameIndex > 0 && options.frameIndex < count ? options.frameIndex : 0; } render() { - const { - data, - height, - width, - options: { frameIndex }, - } = this.props; + const { data, height, width } = this.props; const count = data.series?.length; @@ -65,8 +97,8 @@ export class TablePanel extends Component { if (count > 1) { const inputHeight = config.theme.spacing.formInputHeight; - const padding = 8; - const index = frameIndex > 0 && frameIndex < count ? frameIndex : 0; + const padding = 8 * 2; + const currentIndex = this.getCurrentFrameIndex(); const names = data.series.map((frame, index) => { return { label: `${frame.name ?? 'Series'}`, @@ -76,13 +108,15 @@ export class TablePanel extends Component { return (
- {this.renderTable(data.series[index], width, height - inputHeight - padding)} - +
); } - return this.renderTable(data.series[0], width, height); + return this.renderTable(data.series[0], width, height - 12); } } @@ -93,4 +127,7 @@ const tableStyles = { justify-content: space-between; height: 100%; `, + selectWrapper: css` + padding: 8px; + `, }; diff --git a/public/app/plugins/panel/table/module.tsx b/public/app/plugins/panel/table/module.tsx index 3ecadbc1c8a..55ae50964c2 100644 --- a/public/app/plugins/panel/table/module.tsx +++ b/public/app/plugins/panel/table/module.tsx @@ -6,23 +6,23 @@ import { tablePanelChangedHandler, tableMigrationHandler } from './migrations'; export const plugin = new PanelPlugin(TablePanel) .setPanelChangeHandler(tablePanelChangedHandler) .setMigrationHandler(tableMigrationHandler) + .setNoPadding() .useFieldConfig({ useCustomConfig: builder => { builder .addNumberInput({ path: 'width', name: 'Column width', - description: 'column width (for table)', settings: { placeholder: 'auto', min: 20, max: 300, }, + shouldApply: () => true, }) .addRadio({ path: 'align', name: 'Column alignment', - description: 'column alignment (for table)', settings: { options: [ { label: 'auto', value: null }, @@ -50,17 +50,10 @@ export const plugin = new PanelPlugin(TablePanel) }, }) .setPanelOptions(builder => { - builder - .addBooleanSwitch({ - path: 'showHeader', - name: 'Show header', - description: "To display table's header or not to display", - defaultValue: true, - }) - .addBooleanSwitch({ - path: 'resizable', - name: 'Resizable', - description: 'Toggles if table columns are resizable or not', - defaultValue: false, - }); + builder.addBooleanSwitch({ + path: 'showHeader', + name: 'Show header', + description: "To display table's header or not to display", + defaultValue: true, + }); }); diff --git a/public/app/plugins/panel/table/types.ts b/public/app/plugins/panel/table/types.ts index 9b6647431f5..b8b46f653b9 100644 --- a/public/app/plugins/panel/table/types.ts +++ b/public/app/plugins/panel/table/types.ts @@ -1,7 +1,6 @@ export interface Options { frameIndex: number; showHeader: boolean; - resizable: boolean; } export interface CustomFieldConfig { diff --git a/public/sass/components/_panel_header.scss b/public/sass/components/_panel_header.scss index 70ec9eb2b88..16023abc9ee 100644 --- a/public/sass/components/_panel_header.scss +++ b/public/sass/components/_panel_header.scss @@ -33,6 +33,7 @@ $panel-header-no-title-zindex: 1; flex-wrap: nowrap; justify-content: center; height: $panel-header-height; + line-height: $panel-header-height; align-items: center; }