Skip to content

Commit

Permalink
[DataGrid] Add fluid columns width support (#480)
Browse files Browse the repository at this point in the history
* [DataGrid] Add fluid columns width support

* Fix doc formatting

* Fix documentation so that it is complient to the MUI documentation standarts

* Add unit tests

* Update packages/grid/_modules_/grid/hooks/root/useColumns.ts

Co-authored-by: Olivier Tassinari <olivier.tassinari@gmail.com>

* Update packages/grid/_modules_/grid/models/colDef/colDef.ts

Co-authored-by: Olivier Tassinari <olivier.tassinari@gmail.com>

* Fix PR comments

* Fix unit tests

* Resolve TS issues

* Add // @ts-expect-error need to migrate helpers to TypeScript before toHaveInlineStyle

* Add storybook examples

* Update docs/src/pages/components/data-grid/columns/columns.md

Co-authored-by: Matt <github@nospam.33m.co>

* Update docs/src/pages/components/data-grid/columns/columns.md

Co-authored-by: Matt <github@nospam.33m.co>

* Update docs/src/pages/components/data-grid/columns/columns.md

Co-authored-by: Matt <github@nospam.33m.co>

* Update docs/src/pages/components/data-grid/columns/columns.md

Co-authored-by: Matt <github@nospam.33m.co>

* Update docs/src/pages/components/data-grid/columns/columns.md

Co-authored-by: Matt <github@nospam.33m.co>

* Update packages/grid/data-grid/src/DataGrid.test.tsx

Co-authored-by: Matt <github@nospam.33m.co>

* Fix storybook flex col width examples

* rerun ci

* Fix formatting

* Update docs/src/pages/components/data-grid/columns/columns.md

* Trigger CI

Co-authored-by: Olivier Tassinari <olivier.tassinari@gmail.com>
Co-authored-by: Matt <github@nospam.33m.co>
  • Loading branch information
3 people authored Nov 4, 2020
1 parent 1071158 commit 4cbde43
Show file tree
Hide file tree
Showing 8 changed files with 300 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as React from 'react';
import { DataGrid } from '@material-ui/data-grid';

const rows = [
{
id: 1,
username: 'defunkt',
age: 38,
},
];

export default function ColumnFluidWidthGrid() {
return (
<div style={{ height: 250, width: '100%' }}>
<DataGrid
columns={[
{
field: 'id',
flex: 1,
},
{
field: 'username',
width: 200,
},
{
field: 'age',
flex: 0.3,
},
]}
rows={rows}
/>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as React from 'react';
import { DataGrid } from '@material-ui/data-grid';

const rows = [
{
id: 1,
username: 'defunkt',
age: 38,
},
];

export default function ColumnFluidWidthGrid() {
return (
<div style={{ height: 250, width: '100%' }}>
<DataGrid
columns={[
{
field: 'id',
flex: 1,
},
{
field: 'username',
width: 200,
},
{
field: 'age',
flex: 0.3,
},
]}
rows={rows}
/>
</div>
);
}
16 changes: 16 additions & 0 deletions docs/src/pages/components/data-grid/columns/columns.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,22 @@ To change the width of a column, use the `width` property available in `ColDef`.

{{"demo": "pages/components/data-grid/columns/ColumnWidthGrid.js", "bg": "inline"}}

### Fluid width

Each column has a fixed width of 100 pixels by default, but column fluidity (responsiveness) can be by achieved by setting the `flex` property in `ColDef`.

The `flex` property accepts a value between 0 and ∞.

The `flex` property works by dividing the remaining space in the grid among all flex columns in proportion to their `flex` value.
For example, consider a grid with a total width of 500px that has three columns: the first with `width: 200`; the second with `flex: 1`; and third with `flex: 0.5`.
The first column will be 200px wide, leaving 300px remaining. The column with `flex: 1` is twice the size of `flex: 0.5`, which means that final sizes will be: 200px, 200px, 100px.

Note that `flex` doesn't work together with `width`. If you set both `flex` and `width` in `ColDef`, `flex` will override `width`.

In addition, `flex` does not work if the combined width of the columns that have `width` is more than the width of the grid itself. If that is the case a scroll bar will be visible, and the columns that have `flex` will default back to their base value of 100px.

{{"demo": "pages/components/data-grid/columns/ColumnFluidWidthGrid.js", "bg": "inline"}}

## Column resizing [<span role="img" title="Enterprise">⚡️</span>](https://material-ui.com/store/items/material-ui-x/)

By default, `XGrid` allows all columns to be resized by dragging the right portion of the column separator.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ export const useStyles = makeStyles(
cursor: 'col-resize',
'&:hover, &.Mui-resizing': {
color: theme.palette.text.primary,
// Reset on touch devices, it doesn't add specificity
'@media (hover: none)': {
color: borderColor,
},
},
},
'& .MuiDataGrid-iconSeparator': {
Expand Down
42 changes: 39 additions & 3 deletions packages/grid/_modules_/grid/hooks/features/columns/useColumns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,47 @@ import { useApiMethod } from '../../root/useApiMethod';
import { Logger, useLogger } from '../../utils/useLogger';
import { useGridState } from '../core/useGridState';

function mapColumns(
columns: Columns,
columnTypes: ColumnTypesRecord,
containerWidth: number,
): Columns {
let extendedColumns = columns.map((c) => ({ ...getColDef(columnTypes, c.type), ...c }));
const numberOfFluidColumns = columns.filter((column) => !!column.flex).length;
let flexDivider = 0;

if (numberOfFluidColumns && containerWidth) {
extendedColumns.forEach((column) => {
if (!column.flex) {
containerWidth -= column.width!;
} else {
flexDivider += column.flex;
}
});
}

if (containerWidth > 0 && numberOfFluidColumns) {
const flexMultiplier = containerWidth / flexDivider;
extendedColumns = extendedColumns.map((column) => {
return {
...column,
width: column.flex! ? Math.floor(flexMultiplier * column.flex!) : column.width,
};
});
}

return extendedColumns;
}

function hydrateColumns(
columns: Columns,
columnTypes: ColumnTypesRecord,
containerWidth: number,
withCheckboxSelection: boolean,
logger: Logger,
): Columns {
logger.debug('Hydrating Columns with default definitions');
let mappedCols = columns.map((c) => ({ ...getColDef(columnTypes, c.type), ...c }));
let mappedCols = mapColumns(columns, columnTypes, containerWidth);
if (withCheckboxSelection) {
mappedCols = [checkboxSelectionColDef, ...mappedCols];
}
Expand Down Expand Up @@ -58,14 +91,15 @@ function toMeta(logger: Logger, visibleColumns: Columns): ColumnsMeta {
const resetState = (
columns: Columns,
columnTypes: ColumnTypesRecord,
containerWidth: number,
withCheckboxSelection: boolean,
logger: Logger,
): InternalColumns => {
if (columns.length === 0) {
return getInitialColumnsState();
}

const all = hydrateColumns(columns, columnTypes, withCheckboxSelection, logger);
const all = hydrateColumns(columns, columnTypes, containerWidth, withCheckboxSelection, logger);
const visible = filterVisible(logger, all);
const meta = toMeta(logger, visible);
const lookup = toLookup(logger, all);
Expand Down Expand Up @@ -115,7 +149,7 @@ const getUpdatedColumnState = (
export function useColumns(columns: Columns, apiRef: ApiRef): InternalColumns {
const logger = useLogger('useColumns');
const [gridState, setGridState, forceUpdate] = useGridState(apiRef);

const viewportWidth = gridState.containerSizes ? gridState.containerSizes.viewportSize.width : 0;
const updateState = React.useCallback(
(newState: InternalColumns, emit = true) => {
logger.debug('Updating columns state.');
Expand All @@ -134,6 +168,7 @@ export function useColumns(columns: Columns, apiRef: ApiRef): InternalColumns {
const newState = resetState(
columns,
gridState.options.columnTypes,
viewportWidth,
!!gridState.options.checkboxSelection,
logger,
);
Expand All @@ -142,6 +177,7 @@ export function useColumns(columns: Columns, apiRef: ApiRef): InternalColumns {
columns,
gridState.options.columnTypes,
gridState.options.checkboxSelection,
viewportWidth,
logger,
updateState,
]);
Expand Down
4 changes: 4 additions & 0 deletions packages/grid/_modules_/grid/models/colDef/colDef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ export interface ColDef {
* @default 100
*/
width?: number;
/**
* If set, it indicates that a column has fluid width. Range [0, ∞].
*/
flex?: number;
/**
* If `true`, hide the column.
* @default false;
Expand Down
118 changes: 118 additions & 0 deletions packages/grid/data-grid/src/DataGrid.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -181,4 +181,122 @@ describe('<DataGrid />', () => {
);
});
});

describe('column width', () => {
before(function beforeHook() {
if (/jsdom/.test(window.navigator.userAgent)) {
// Need layouting
this.skip();
}
});

it('should set the columns width to 100px by default', () => {
const rows = [
{
id: 1,
username: 'John Doe',
age: 30,
},
];

const columns = [
{
field: 'id',
},
{
field: 'name',
},
{
field: 'age',
},
];

const { getAllByRole } = render(
<div style={{ width: 300, height: 300 }}>
<DataGrid columns={columns} rows={rows} />
</div>,
);

const DOMColumns = getAllByRole('columnheader');
DOMColumns.forEach((col) => {
// @ts-expect-error need to migrate helpers to TypeScript
expect(col).toHaveInlineStyle({ width: '100px' });
});
});

it('should set the columns width value to what is provided', () => {
const rows = [
{
id: 1,
username: 'John Doe',
age: 30,
},
];

const colWidthValues = [50, 50, 200];
const columns = [
{
field: 'id',
width: colWidthValues[0],
},
{
field: 'name',
width: colWidthValues[1],
},
{
field: 'age',
width: colWidthValues[2],
},
];

const { getAllByRole } = render(
<div style={{ width: 300, height: 300 }}>
<DataGrid columns={columns} rows={rows} />
</div>,
);

const DOMColumns = getAllByRole('columnheader');
DOMColumns.forEach((col, index) => {
// @ts-expect-error need to migrate helpers to TypeScript
expect(col).toHaveInlineStyle({ width: `${colWidthValues[index]}px` });
});
});

it('should set the first column to be twice as wide as the second one', () => {
const rows = [
{
id: 1,
username: 'John Doe',
age: 30,
},
];

const columns = [
{
field: 'id',
flex: 1,
},
{
field: 'name',
flex: 0.5,
},
];

render(
<div style={{ width: 200, height: 300 }}>
<DataGrid columns={columns} rows={rows} />
</div>,
);

const firstColumn = document.querySelector('[role="columnheader"][aria-colindex="1"]');
const secondColumn: HTMLElement | null = document.querySelector(
'[role="columnheader"][aria-colindex="2"]',
);
const secondColumnWidthVal = secondColumn!.style.width.split('px')[0];
// @ts-expect-error need to migrate helpers to TypeScript
expect(firstColumn).toHaveInlineStyle({
width: `${2 * parseInt(secondColumnWidthVal, 10)}px`,
});
});
});
});
51 changes: 51 additions & 0 deletions packages/storybook/src/stories/grid-columns.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,54 @@ export function NewColumnTypes() {
</div>
);
}

export const FewFlexColumns = () => {
const data = useData(20, 3);
const transformColSizes = React.useCallback(
(columns: ColDef[]) =>
columns.map((col, index) =>
index % 2 === 0 ? { ...col, flex: index + 1 } : { ...col, width: 200 },
),
[],
);

return (
<div className="grid-container">
<XGrid rows={data.rows} columns={transformColSizes(data.columns)} />
</div>
);
};

export const SeveralFlexColumn = () => {
const data = useData(20, 7);
const transformColSizes = React.useCallback(
(columns: ColDef[]) =>
columns.map((col, index) =>
index % 3 !== 0 ? { ...col, flex: index } : { ...col, flex: 1 },
),
[],
);

return (
<div className="grid-container">
<XGrid rows={data.rows} columns={transformColSizes(data.columns)} />
</div>
);
};

export const FlexColumnWidth2000 = () => {
const data = useData(20, 3);
const transformColSizes = React.useCallback(
(columns: ColDef[]) =>
columns.map((col, index) =>
index % 2 !== 0 ? { ...col, width: 2000 } : { ...col, flex: index + 1 },
),
[],
);

return (
<div className="grid-container">
<XGrid rows={data.rows} columns={transformColSizes(data.columns)} />
</div>
);
};

0 comments on commit 4cbde43

Please sign in to comment.