diff --git a/docs/src/pages/components/data-grid/columns/ColumnFluidWidthGrid.js b/docs/src/pages/components/data-grid/columns/ColumnFluidWidthGrid.js
new file mode 100644
index 000000000000..767f6bca655c
--- /dev/null
+++ b/docs/src/pages/components/data-grid/columns/ColumnFluidWidthGrid.js
@@ -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 (
+
+
+
+ );
+}
diff --git a/docs/src/pages/components/data-grid/columns/ColumnFluidWidthGrid.tsx b/docs/src/pages/components/data-grid/columns/ColumnFluidWidthGrid.tsx
new file mode 100644
index 000000000000..767f6bca655c
--- /dev/null
+++ b/docs/src/pages/components/data-grid/columns/ColumnFluidWidthGrid.tsx
@@ -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 (
+
+
+
+ );
+}
diff --git a/docs/src/pages/components/data-grid/columns/columns.md b/docs/src/pages/components/data-grid/columns/columns.md
index d0ed067d9b3c..f8c112880071 100644
--- a/docs/src/pages/components/data-grid/columns/columns.md
+++ b/docs/src/pages/components/data-grid/columns/columns.md
@@ -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 [⚡️](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.
diff --git a/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts b/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts
index 336d6e659841..1050631096c2 100644
--- a/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts
+++ b/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts
@@ -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': {
diff --git a/packages/grid/_modules_/grid/hooks/features/columns/useColumns.ts b/packages/grid/_modules_/grid/hooks/features/columns/useColumns.ts
index 8f1224824cf5..3442c0a125da 100644
--- a/packages/grid/_modules_/grid/hooks/features/columns/useColumns.ts
+++ b/packages/grid/_modules_/grid/hooks/features/columns/useColumns.ts
@@ -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];
}
@@ -58,6 +91,7 @@ function toMeta(logger: Logger, visibleColumns: Columns): ColumnsMeta {
const resetState = (
columns: Columns,
columnTypes: ColumnTypesRecord,
+ containerWidth: number,
withCheckboxSelection: boolean,
logger: Logger,
): InternalColumns => {
@@ -65,7 +99,7 @@ const resetState = (
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);
@@ -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.');
@@ -134,6 +168,7 @@ export function useColumns(columns: Columns, apiRef: ApiRef): InternalColumns {
const newState = resetState(
columns,
gridState.options.columnTypes,
+ viewportWidth,
!!gridState.options.checkboxSelection,
logger,
);
@@ -142,6 +177,7 @@ export function useColumns(columns: Columns, apiRef: ApiRef): InternalColumns {
columns,
gridState.options.columnTypes,
gridState.options.checkboxSelection,
+ viewportWidth,
logger,
updateState,
]);
diff --git a/packages/grid/_modules_/grid/models/colDef/colDef.ts b/packages/grid/_modules_/grid/models/colDef/colDef.ts
index 089f4cd4363e..5c2a32b55f2f 100644
--- a/packages/grid/_modules_/grid/models/colDef/colDef.ts
+++ b/packages/grid/_modules_/grid/models/colDef/colDef.ts
@@ -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;
diff --git a/packages/grid/data-grid/src/DataGrid.test.tsx b/packages/grid/data-grid/src/DataGrid.test.tsx
index 64edc84b33e7..6fd967c0236f 100644
--- a/packages/grid/data-grid/src/DataGrid.test.tsx
+++ b/packages/grid/data-grid/src/DataGrid.test.tsx
@@ -181,4 +181,122 @@ describe('', () => {
);
});
});
+
+ 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(
+
+
+
,
+ );
+
+ 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(
+
+
+
,
+ );
+
+ 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(
+
+
+
,
+ );
+
+ 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`,
+ });
+ });
+ });
});
diff --git a/packages/storybook/src/stories/grid-columns.stories.tsx b/packages/storybook/src/stories/grid-columns.stories.tsx
index 04499fee39c8..462d60b3100d 100644
--- a/packages/storybook/src/stories/grid-columns.stories.tsx
+++ b/packages/storybook/src/stories/grid-columns.stories.tsx
@@ -221,3 +221,54 @@ export function NewColumnTypes() {
);
}
+
+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 (
+
+
+
+ );
+};
+
+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 (
+
+
+
+ );
+};
+
+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 (
+
+
+
+ );
+};