将 CellMeasurer 与 MultiGrid 结合使用
Using CellMeasurer with MultiGrid
我可能正在尝试做一些不受支持的事情,但我正在尝试将 react-virtualized 的 CellMeasurer 与 MultiGrid 一起使用。我还使用 ScrollSync
来检测用户何时一直向右滚动,并使用 show/hide 指示器。
一个警告是我有一个选项卡控件可以操作哪些数据(行和列)。当数据发生变化时,我在我的 redux 存储中设置了一个标志,我正在使用它来重新测量我的单元格。
它的工作非常接近我的预期。我第一次转到新选项卡时,所有单元格都被正确测量,但有两个例外。
1) 第一列(1 个固定列)重新测量,但左上角和左下角网格的宽度没有更新。这会在新测量值和默认尺寸之间留下差距。一旦我滚动,它就会自行修复 - 很确定,因为我有 ScrollSync。
滚动前
滚动后
2) 列索引 1 永远不会小于默认宽度。这是第一个 non-fixed 列。
适用于更大的内容:
然后,主要问题是当我 return 到之前已经显示的选项卡时。发生这种情况时,即使我的新数据标志仍在触发重新测量,但前一个选项卡中存在的列的测量值仍然存在。我想我需要做一些清除缓存的事情,但到目前为止我的尝试导致所有列都变为默认宽度。是否有特定的 CellMeasurerCache.clearAll
、MultiGrid.measureAllCells
和 MultiGrid.recomputeGridSize
顺序适合我?
渲染
render() {
const { tableName, ui } = this.props;
const dataSet = this.getFinalData();
console.log('rendering');
return (
<ScrollSync>
{({
// clientHeight,
// scrollHeight,
// scrollTop,
clientWidth, // width of the grid
scrollWidth, // width of the entire page
scrollLeft, // how far the user has scrolled
onScroll,
}) => {
// if we have new daya, default yo scrolled left
const newData = Ui.getTableNewData(ui, tableName);
const scrolledAllRight = !newData &&
(scrollLeft + clientWidth >= scrollWidth);
const scrolledAllLeft = newData || scrollLeft === 0;
return (
<AutoSizer>
{({ width, height }) => {
const boxShadow = scrolledAllLeft ? false :
'1px -3px 3px #a2a2a2';
return (
<div className="grid-container">
<MultiGrid
cellRenderer={this.cellRenderer}
columnWidth={this.getColumnWidth}
columnCount={this.getColumnCount()}
fixedColumnCount={1}
height={height}
rowHeight={this.getRowHeight}
rowCount={dataSet.length}
fixedRowCount={1}
deferredMeasurementCache={this.cellSizeCache}
noRowsRenderer={DataGrid.emptyRenderer}
width={width}
className={classNames('data-grid', {
'scrolled-left': scrolledAllLeft,
})}
onScroll={onScroll}
styleBottomLeftGrid={{ boxShadow }}
ref={(grid) => {
this.mainGrid = grid;
}}
/>
<div
className={classNames('scroll-x-indicator', {
faded: scrolledAllRight,
})}
>
<i className="fa fa-fw fa-angle-double-right" />
</div>
</div>
);
}}
</AutoSizer>
);
}}
</ScrollSync>
);
}
单元格渲染器
cellRenderer({ columnIndex, rowIndex, style, parent }) {
const data = this.getFinalData(rowIndex);
const column = this.getColumn(columnIndex);
return (
<CellMeasurer
cache={this.cellSizeCache}
columnIndex={columnIndex}
key={`${columnIndex},${rowIndex}`}
parent={parent}
rowIndex={rowIndex}
ref={(cellMeasurer) => {
this.cellMeasurer = cellMeasurer;
}}
>
<div
style={{
...style,
maxWidth: 500,
}}
className={classNames({
'grid-header-cell': rowIndex === 0,
'grid-cell': rowIndex > 0,
'grid-row-even': rowIndex % 2 === 0,
'first-col': columnIndex === 0,
'last-col': columnIndex === this.getColumnCount(),
})}
>
<div className="grid-cell-data">
{data[column.key]}
</div>
</div>
</CellMeasurer>
);
}
组件生命周期
constructor() {
super();
this.cellSizeCache = new CellMeasurerCache({
defaultWidth: 300,
});
// used to update the sizing on command
this.cellMeasurer = null;
this.mainGrid = null;
// this binding for event methods
this.sort = this.sort.bind(this);
this.cellRenderer = this.cellRenderer.bind(this);
this.getColumnWidth = this.getColumnWidth.bind(this);
this.getRowHeight = this.getRowHeight.bind(this);
}
componentDidMount() {
this.componentDidUpdate();
setTimeout(() => {
this.mainGrid.recomputeGridSize();
setTimeout(() => {
this.mainGrid.measureAllCells();
}, 1);
}, 1);
}
componentDidUpdate() {
const { tableName, ui } = this.props;
// if we did have new data, it is now complete
if (Ui.getTableNewData(ui, tableName)) {
console.log('clearing');
setTimeout(() => {
this.mainGrid.measureAllCells();
setTimeout(() => {
this.mainGrid.recomputeGridSize();
}, 1);
}, 1);
this.props.setTableNewData(tableName, false);
}
}
编辑 Here is a plunker。这个例子展示了我所解释的大部分内容。它还为行提供了比预期更高的高度(无法说出与我的其他实现有什么不同)
第一个建议:不要使用 ScrollSync
。直接用MultiGrid
的onScroll
属性就可以了。我认为 ScrollSync
对于这种情况有点矫枉过正。
第二个建议:如果可能,避免使用 CellMeasurer
测量宽度 和 高度,因为这将需要贪婪地测量整个 Grid
计算每列+行中的最大单元格。在您的 Plnkr 中记录了一个关于此的开发警告,但它被其他日志记录所掩盖:
CellMeasurerCache should only measure a cell's width or height. You have configured CellMeasurerCache to measure both. This will result in poor performance.
不幸的是,为了解决您问题的实质 - 我相信您已经发现了 CellMeasurer
和 MultiGrid
之间交互的一些缺陷。
编辑 这些缺陷已在 9.2.3 版本中得到解决。请升级。 :)
您可以看到 CellMeasurer
+ MultiGrid
here and the source code can be seen here 的演示。
也许这个例子对你有帮助。
你可以看看下面的代码。我使用 CellMeasurer 来设置单元格大小。列宽和行高都将在运行时测量。
import classnames from 'classnames';
import React, {Component} from 'react';
import {AutoSizer, CellMeasurer, CellMeasurerCache, MultiGrid} from 'react-virtualized';
import './Spreadsheet.css';
const LETTERS = ' ABCDEFGHIJKLMNOPQRSTUVWXYZ';
export default class MySlide extends Component {
constructor(props, context) {
super(props, context);
this.state = {
cellValues: {},
focusedColumnIndex: null,
focusedRowIndex: null
};
this._cache = new CellMeasurerCache({
defaultHeight: 30,
defaultWidth: 150
});
this._cellRenderer = this._cellRenderer.bind(this);
this._setRef = this._setRef.bind(this);
}
getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
componentWillUpdate(nextProps, nextState) {
const {cellValues, focusedColumnIndex, focusedRowIndex} = this.state;
if (
focusedColumnIndex !== nextState.focusedColumnIndex ||
focusedRowIndex !== nextState.focusedRowIndex
) {
this._multiGrid.forceUpdate();
} else if (cellValues !== nextState.cellValues) {
this._multiGrid.forceUpdate();
}
}
render() {
return (
<AutoSizer disableHeight>
{({width}) => (
<MultiGrid
cellRenderer={this._cellRenderer}
columnCount={LETTERS.length}
fixedColumnCount={1}
fixedRowCount={1}
height={600}
columnWidth={this._cache.columnWidth}
rowHeight={this._cache.rowHeight}
deferredMeasurementCache={this._cache}
overscanColumnCount={0}
overscanRowCount={0}
ref={this._setRef}
rowCount={100}
style={{
border: '1px solid #dadada',
whiteSpace: 'pre',
overflowX: 'hidden',
textOverflow: 'ellipsis'
}}
styleBottomLeftGrid={{
backgroundColor: '#ffffff'
}}
styleTopLeftGrid={{
backgroundColor: '#f3f3f3',
borderBottom: '4px solid #bcbcbc',
borderRight: '4px solid #bcbcbc'
}}
styleTopRightGrid={{
backgroundColor: '#f3f3f3'
}}
width={width}
/>
)}
</AutoSizer>
);
}
_cellRenderer({columnIndex, key, parent, rowIndex, style}) {
if (columnIndex === 0 && rowIndex === 0) {
return <div key={key} style={style}/>
} else if (columnIndex === 0) {
return this._cellRendererLeft({columnIndex, key, parent, rowIndex, style})
} else if (rowIndex === 0) {
return this._cellRendererTop({columnIndex, key, parent, rowIndex, style})
} else {
return this._cellRendererMain({columnIndex, key, parent, rowIndex, style})
}
}
_cellRendererLeft = ({columnIndex, key, parent, rowIndex, style}) => {
const {focusedRowIndex} = this.state;
return (
<CellMeasurer
cache={this._cache}
columnIndex={columnIndex}
key={key}
parent={parent}
rowIndex={rowIndex}>
<div
className={classnames('FixedGridCell', {
FixedGridCellFocused: rowIndex === focusedRowIndex
})}
key={key}
style={{
...style,
whiteSpace: 'nowrap',
padding: '16px'
}}
>
{rowIndex}
</div>
</CellMeasurer>
);
}
_cellRendererMain = ({columnIndex, key, parent, rowIndex, style}) => {
const {cellValues, focusedColumnIndex, focusedRowIndex} = this.state;
const value = cellValues[key] || '';
const isFocused = (
columnIndex === focusedColumnIndex &&
rowIndex === focusedRowIndex
);
return (
<CellMeasurer
cache={this._cache}
columnIndex={columnIndex}
key={key}
parent={parent}
rowIndex={rowIndex}>
<div
key={key}
style={{
...style,
whiteSpace: 'nowrap',
padding: '16px'
}}
className={classnames('MainGridCell', {
MainGridCellFocused: isFocused,
})}
/*onFocus={() => this.setState({
focusedColumnIndex: columnIndex,
focusedRowIndex: rowIndex
})}
onChange={(event) => {
this.setState({
cellValues: {
...cellValues,
[key]: event.target.value
}
})
}}*/>{rowIndex + ',' + columnIndex}
{columnIndex % 3 === 0 && ' This is a long sentence'}<br/>
{rowIndex % 4 === 0 && <br/>}
{rowIndex % 4 === 0 && 'This is a another line'}
{rowIndex % 6 === 0 && <br/>}
{rowIndex % 6 === 0 && 'This is a long sentence'}
</div>
</CellMeasurer>
);
}
_cellRendererTop = ({columnIndex, key, parent, rowIndex, style}) => {
const {focusedColumnIndex} = this.state;
return (
<CellMeasurer
cache={this._cache}
columnIndex={columnIndex}
key={key}
parent={parent}
rowIndex={rowIndex}>
<div
className={classnames('FixedGridCell', {
FixedGridCellFocused: columnIndex === focusedColumnIndex
})}
key={key}
style={{
...style,
whiteSpace: 'nowrap',
}}
>
{LETTERS[columnIndex]}
</div>
</CellMeasurer>
);
}
_setRef(ref) {
this._multiGrid = ref;
}
}
Spreadsheet.css
.GridContainer {
height: 300px;
position: relative;
border: 1px solid #dadada;
overflow: hidden;
}
.TopLeftCell {
height: 40px;
width: 50px;
background-color: #f3f3f3;
border-bottom: 4px solid #bcbcbc;
border-right: 4px solid #bcbcbc;
}
.MainGrid {
position: absolute !important;
left: 50px;
top: 40px;
}
.MainGridCell {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
padding: 0.25rem;
outline: 0;
border: none;
border-right: 1px solid #dadada;
border-bottom: 1px solid #dadada;
background-color: #fff;
font-size: 1rem;
}
.MainGridCellFocused {
box-shadow: 0 0 0 2px #4285FA inset;
}
.LeftGrid {
position: absolute !important;
left: 0;
top: 40px;
overflow: hidden !important;
}
.TopGrid {
position: absolute !important;
left: 50px;
top: 0;
height: 40px;
overflow: hidden !important;
}
.FixedGridCell {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
background-color: #f3f3f3;
border-right: 1px solid #ccc;
border-bottom: 1px solid #ccc;
}
.FixedGridCellFocused {
background-color: #dddddd;
}
我可能正在尝试做一些不受支持的事情,但我正在尝试将 react-virtualized 的 CellMeasurer 与 MultiGrid 一起使用。我还使用 ScrollSync
来检测用户何时一直向右滚动,并使用 show/hide 指示器。
一个警告是我有一个选项卡控件可以操作哪些数据(行和列)。当数据发生变化时,我在我的 redux 存储中设置了一个标志,我正在使用它来重新测量我的单元格。
它的工作非常接近我的预期。我第一次转到新选项卡时,所有单元格都被正确测量,但有两个例外。
1) 第一列(1 个固定列)重新测量,但左上角和左下角网格的宽度没有更新。这会在新测量值和默认尺寸之间留下差距。一旦我滚动,它就会自行修复 - 很确定,因为我有 ScrollSync。
滚动前
2) 列索引 1 永远不会小于默认宽度。这是第一个 non-fixed 列。
然后,主要问题是当我 return 到之前已经显示的选项卡时。发生这种情况时,即使我的新数据标志仍在触发重新测量,但前一个选项卡中存在的列的测量值仍然存在。我想我需要做一些清除缓存的事情,但到目前为止我的尝试导致所有列都变为默认宽度。是否有特定的 CellMeasurerCache.clearAll
、MultiGrid.measureAllCells
和 MultiGrid.recomputeGridSize
顺序适合我?
渲染
render() {
const { tableName, ui } = this.props;
const dataSet = this.getFinalData();
console.log('rendering');
return (
<ScrollSync>
{({
// clientHeight,
// scrollHeight,
// scrollTop,
clientWidth, // width of the grid
scrollWidth, // width of the entire page
scrollLeft, // how far the user has scrolled
onScroll,
}) => {
// if we have new daya, default yo scrolled left
const newData = Ui.getTableNewData(ui, tableName);
const scrolledAllRight = !newData &&
(scrollLeft + clientWidth >= scrollWidth);
const scrolledAllLeft = newData || scrollLeft === 0;
return (
<AutoSizer>
{({ width, height }) => {
const boxShadow = scrolledAllLeft ? false :
'1px -3px 3px #a2a2a2';
return (
<div className="grid-container">
<MultiGrid
cellRenderer={this.cellRenderer}
columnWidth={this.getColumnWidth}
columnCount={this.getColumnCount()}
fixedColumnCount={1}
height={height}
rowHeight={this.getRowHeight}
rowCount={dataSet.length}
fixedRowCount={1}
deferredMeasurementCache={this.cellSizeCache}
noRowsRenderer={DataGrid.emptyRenderer}
width={width}
className={classNames('data-grid', {
'scrolled-left': scrolledAllLeft,
})}
onScroll={onScroll}
styleBottomLeftGrid={{ boxShadow }}
ref={(grid) => {
this.mainGrid = grid;
}}
/>
<div
className={classNames('scroll-x-indicator', {
faded: scrolledAllRight,
})}
>
<i className="fa fa-fw fa-angle-double-right" />
</div>
</div>
);
}}
</AutoSizer>
);
}}
</ScrollSync>
);
}
单元格渲染器
cellRenderer({ columnIndex, rowIndex, style, parent }) {
const data = this.getFinalData(rowIndex);
const column = this.getColumn(columnIndex);
return (
<CellMeasurer
cache={this.cellSizeCache}
columnIndex={columnIndex}
key={`${columnIndex},${rowIndex}`}
parent={parent}
rowIndex={rowIndex}
ref={(cellMeasurer) => {
this.cellMeasurer = cellMeasurer;
}}
>
<div
style={{
...style,
maxWidth: 500,
}}
className={classNames({
'grid-header-cell': rowIndex === 0,
'grid-cell': rowIndex > 0,
'grid-row-even': rowIndex % 2 === 0,
'first-col': columnIndex === 0,
'last-col': columnIndex === this.getColumnCount(),
})}
>
<div className="grid-cell-data">
{data[column.key]}
</div>
</div>
</CellMeasurer>
);
}
组件生命周期
constructor() {
super();
this.cellSizeCache = new CellMeasurerCache({
defaultWidth: 300,
});
// used to update the sizing on command
this.cellMeasurer = null;
this.mainGrid = null;
// this binding for event methods
this.sort = this.sort.bind(this);
this.cellRenderer = this.cellRenderer.bind(this);
this.getColumnWidth = this.getColumnWidth.bind(this);
this.getRowHeight = this.getRowHeight.bind(this);
}
componentDidMount() {
this.componentDidUpdate();
setTimeout(() => {
this.mainGrid.recomputeGridSize();
setTimeout(() => {
this.mainGrid.measureAllCells();
}, 1);
}, 1);
}
componentDidUpdate() {
const { tableName, ui } = this.props;
// if we did have new data, it is now complete
if (Ui.getTableNewData(ui, tableName)) {
console.log('clearing');
setTimeout(() => {
this.mainGrid.measureAllCells();
setTimeout(() => {
this.mainGrid.recomputeGridSize();
}, 1);
}, 1);
this.props.setTableNewData(tableName, false);
}
}
编辑 Here is a plunker。这个例子展示了我所解释的大部分内容。它还为行提供了比预期更高的高度(无法说出与我的其他实现有什么不同)
第一个建议:不要使用 ScrollSync
。直接用MultiGrid
的onScroll
属性就可以了。我认为 ScrollSync
对于这种情况有点矫枉过正。
第二个建议:如果可能,避免使用 CellMeasurer
测量宽度 和 高度,因为这将需要贪婪地测量整个 Grid
计算每列+行中的最大单元格。在您的 Plnkr 中记录了一个关于此的开发警告,但它被其他日志记录所掩盖:
CellMeasurerCache should only measure a cell's width or height. You have configured CellMeasurerCache to measure both. This will result in poor performance.
不幸的是,为了解决您问题的实质 - 我相信您已经发现了 CellMeasurer
和 MultiGrid
之间交互的一些缺陷。
编辑 这些缺陷已在 9.2.3 版本中得到解决。请升级。 :)
您可以看到 CellMeasurer
+ MultiGrid
here and the source code can be seen here 的演示。
也许这个例子对你有帮助。
你可以看看下面的代码。我使用 CellMeasurer 来设置单元格大小。列宽和行高都将在运行时测量。
import classnames from 'classnames';
import React, {Component} from 'react';
import {AutoSizer, CellMeasurer, CellMeasurerCache, MultiGrid} from 'react-virtualized';
import './Spreadsheet.css';
const LETTERS = ' ABCDEFGHIJKLMNOPQRSTUVWXYZ';
export default class MySlide extends Component {
constructor(props, context) {
super(props, context);
this.state = {
cellValues: {},
focusedColumnIndex: null,
focusedRowIndex: null
};
this._cache = new CellMeasurerCache({
defaultHeight: 30,
defaultWidth: 150
});
this._cellRenderer = this._cellRenderer.bind(this);
this._setRef = this._setRef.bind(this);
}
getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
componentWillUpdate(nextProps, nextState) {
const {cellValues, focusedColumnIndex, focusedRowIndex} = this.state;
if (
focusedColumnIndex !== nextState.focusedColumnIndex ||
focusedRowIndex !== nextState.focusedRowIndex
) {
this._multiGrid.forceUpdate();
} else if (cellValues !== nextState.cellValues) {
this._multiGrid.forceUpdate();
}
}
render() {
return (
<AutoSizer disableHeight>
{({width}) => (
<MultiGrid
cellRenderer={this._cellRenderer}
columnCount={LETTERS.length}
fixedColumnCount={1}
fixedRowCount={1}
height={600}
columnWidth={this._cache.columnWidth}
rowHeight={this._cache.rowHeight}
deferredMeasurementCache={this._cache}
overscanColumnCount={0}
overscanRowCount={0}
ref={this._setRef}
rowCount={100}
style={{
border: '1px solid #dadada',
whiteSpace: 'pre',
overflowX: 'hidden',
textOverflow: 'ellipsis'
}}
styleBottomLeftGrid={{
backgroundColor: '#ffffff'
}}
styleTopLeftGrid={{
backgroundColor: '#f3f3f3',
borderBottom: '4px solid #bcbcbc',
borderRight: '4px solid #bcbcbc'
}}
styleTopRightGrid={{
backgroundColor: '#f3f3f3'
}}
width={width}
/>
)}
</AutoSizer>
);
}
_cellRenderer({columnIndex, key, parent, rowIndex, style}) {
if (columnIndex === 0 && rowIndex === 0) {
return <div key={key} style={style}/>
} else if (columnIndex === 0) {
return this._cellRendererLeft({columnIndex, key, parent, rowIndex, style})
} else if (rowIndex === 0) {
return this._cellRendererTop({columnIndex, key, parent, rowIndex, style})
} else {
return this._cellRendererMain({columnIndex, key, parent, rowIndex, style})
}
}
_cellRendererLeft = ({columnIndex, key, parent, rowIndex, style}) => {
const {focusedRowIndex} = this.state;
return (
<CellMeasurer
cache={this._cache}
columnIndex={columnIndex}
key={key}
parent={parent}
rowIndex={rowIndex}>
<div
className={classnames('FixedGridCell', {
FixedGridCellFocused: rowIndex === focusedRowIndex
})}
key={key}
style={{
...style,
whiteSpace: 'nowrap',
padding: '16px'
}}
>
{rowIndex}
</div>
</CellMeasurer>
);
}
_cellRendererMain = ({columnIndex, key, parent, rowIndex, style}) => {
const {cellValues, focusedColumnIndex, focusedRowIndex} = this.state;
const value = cellValues[key] || '';
const isFocused = (
columnIndex === focusedColumnIndex &&
rowIndex === focusedRowIndex
);
return (
<CellMeasurer
cache={this._cache}
columnIndex={columnIndex}
key={key}
parent={parent}
rowIndex={rowIndex}>
<div
key={key}
style={{
...style,
whiteSpace: 'nowrap',
padding: '16px'
}}
className={classnames('MainGridCell', {
MainGridCellFocused: isFocused,
})}
/*onFocus={() => this.setState({
focusedColumnIndex: columnIndex,
focusedRowIndex: rowIndex
})}
onChange={(event) => {
this.setState({
cellValues: {
...cellValues,
[key]: event.target.value
}
})
}}*/>{rowIndex + ',' + columnIndex}
{columnIndex % 3 === 0 && ' This is a long sentence'}<br/>
{rowIndex % 4 === 0 && <br/>}
{rowIndex % 4 === 0 && 'This is a another line'}
{rowIndex % 6 === 0 && <br/>}
{rowIndex % 6 === 0 && 'This is a long sentence'}
</div>
</CellMeasurer>
);
}
_cellRendererTop = ({columnIndex, key, parent, rowIndex, style}) => {
const {focusedColumnIndex} = this.state;
return (
<CellMeasurer
cache={this._cache}
columnIndex={columnIndex}
key={key}
parent={parent}
rowIndex={rowIndex}>
<div
className={classnames('FixedGridCell', {
FixedGridCellFocused: columnIndex === focusedColumnIndex
})}
key={key}
style={{
...style,
whiteSpace: 'nowrap',
}}
>
{LETTERS[columnIndex]}
</div>
</CellMeasurer>
);
}
_setRef(ref) {
this._multiGrid = ref;
}
}
Spreadsheet.css
.GridContainer {
height: 300px;
position: relative;
border: 1px solid #dadada;
overflow: hidden;
}
.TopLeftCell {
height: 40px;
width: 50px;
background-color: #f3f3f3;
border-bottom: 4px solid #bcbcbc;
border-right: 4px solid #bcbcbc;
}
.MainGrid {
position: absolute !important;
left: 50px;
top: 40px;
}
.MainGridCell {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
padding: 0.25rem;
outline: 0;
border: none;
border-right: 1px solid #dadada;
border-bottom: 1px solid #dadada;
background-color: #fff;
font-size: 1rem;
}
.MainGridCellFocused {
box-shadow: 0 0 0 2px #4285FA inset;
}
.LeftGrid {
position: absolute !important;
left: 0;
top: 40px;
overflow: hidden !important;
}
.TopGrid {
position: absolute !important;
left: 50px;
top: 0;
height: 40px;
overflow: hidden !important;
}
.FixedGridCell {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
background-color: #f3f3f3;
border-right: 1px solid #ccc;
border-bottom: 1px solid #ccc;
}
.FixedGridCellFocused {
background-color: #dddddd;
}