Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions docs/data/data-grid/components/toolbar/GridToolbarStyleReuse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as React from 'react';
import { DataGrid, ToolbarRoot } from '@mui/x-data-grid';
import { useDemoData } from '@mui/x-data-grid-generator';
import Paper from '@mui/material/Paper';
import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button';

export default function GridToolbarStyleReuse() {
const { data, loading } = useDemoData({
dataSet: 'Commodity',
rowLength: 10,
maxColumns: 10,
visibleFields: ['commodity', 'quantity', 'unitPrice'],
});

return (
<Paper variant="outlined" sx={{ width: '100%' }}>
<ToolbarRoot>
<Typography sx={{ fontWeight: 'medium', flex: 1, mx: 0.5 }}>
Commodities
</Typography>
<Button size="small">Learn more</Button>
</ToolbarRoot>
<div style={{ height: 300 }}>
<DataGrid
{...data}
loading={loading}
sx={{ border: 0, borderRadius: 0 }}
hideFooter
/>
</div>
</Paper>
);
}
34 changes: 34 additions & 0 deletions docs/data/data-grid/components/toolbar/GridToolbarStyleReuse.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as React from 'react';
import { DataGrid, ToolbarRoot } from '@mui/x-data-grid';
import { useDemoData } from '@mui/x-data-grid-generator';
import Paper from '@mui/material/Paper';
import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button';

export default function GridToolbarStyleReuse() {
const { data, loading } = useDemoData({
dataSet: 'Commodity',
rowLength: 10,
maxColumns: 10,
visibleFields: ['commodity', 'quantity', 'unitPrice'],
});

return (
<Paper variant="outlined" sx={{ width: '100%' }}>
<ToolbarRoot>
<Typography sx={{ fontWeight: 'medium', flex: 1, mx: 0.5 }}>
Commodities
</Typography>
<Button size="small">Learn more</Button>
</ToolbarRoot>
<div style={{ height: 300 }}>
<DataGrid
{...data}
loading={loading}
sx={{ border: 0, borderRadius: 0 }}
hideFooter
/>
</div>
</Paper>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Paper variant="outlined" sx={{ width: '100%' }}>
<ToolbarRoot>
<Typography sx={{ fontWeight: 'medium', flex: 1, mx: 0.5 }}>
Commodities
</Typography>
<Button size="small">Learn more</Button>
</ToolbarRoot>
<div style={{ height: 300 }}>
<DataGrid
{...data}
loading={loading}
sx={{ border: 0, borderRadius: 0 }}
hideFooter
/>
</div>
</Paper>
16 changes: 16 additions & 0 deletions docs/data/data-grid/components/toolbar/toolbar.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,22 @@ It renders a styled `<div />` element.
`<ToolbarButton />` is a button for performing actions from the toolbar.
It renders the `baseIconButton` slot.

## Reusing the toolbar styles

Use the `<ToolbarRoot />` component to apply the toolbar styles to elements outside of the toolbar, so they stay in sync with the Data Grid.

```tsx
import { ToolbarRoot } from '@mui/x-data-grid';

<ToolbarRoot>Header</ToolbarRoot>;
```

It can be rendered anywhere, including outside of a Data Grid, and picks up the theme values the Data Grid uses.
It only applies the styles.
The toolbar subcomponents such as [Toolbar Button](#toolbar-button) render Data Grid slots and register with the toolbar context, so they need a [Toolbar](#toolbar) inside a Data Grid.

{{"demo": "GridToolbarStyleReuse.js", "bg": "inline", "defaultCodeOpen": false}}

## Recipes

Below are some ways the Toolbar component can be used.
Expand Down
47 changes: 36 additions & 11 deletions packages/x-data-grid/src/components/toolbarV8/Toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ import { useComponentRenderer } from '@mui/x-internals/useComponentRenderer';
import type { RenderProp } from '@mui/x-internals/useComponentRenderer';
import { ToolbarContextProvider } from '@mui/x-internals/ToolbarContext';
import { vars } from '../../constants/cssVariables';
import {
getBackgroundBase,
getBorderColor,
getFontBody,
getSpacingUnit,
} from '../../material/variables';
import { getDataGridUtilityClass } from '../../constants/gridClasses';
import { useGridRootProps } from '../../hooks/utils/useGridRootProps';
import type { DataGridProcessedProps } from '../../models/props/DataGridProps';
Expand All @@ -32,19 +38,38 @@ const useUtilityClasses = (ownerState: OwnerState) => {
return composeClasses(slots, getDataGridUtilityClass, classes);
};

/**
* The styled `<div />` element rendered by the `<Toolbar />` component.
* Use it to apply the toolbar styles to other elements, inside or outside of a Data Grid.
*/
const ToolbarRoot = styled('div', {
name: 'MuiDataGrid',
slot: 'Toolbar',
})({
flex: '0 1 1px',
display: 'flex',
alignItems: 'center',
justifyContent: 'end',
gap: vars.spacing(0.25),
padding: vars.spacing(0.75),
minHeight: 52,
boxSizing: 'border-box',
borderBottom: `1px solid ${vars.colors.border.base}`,
})(({ theme }) => {
// The Data Grid CSS variables are only defined on the Data Grid root element, so they are
// declared with a fallback to keep the styles working when rendered outside of a Data Grid.
const spacingUnit = `var(${vars.keys.spacingUnit}, ${getSpacingUnit(theme)})`;
const borderColor = `var(${vars.keys.colors.border.base}, ${getBorderColor(theme)})`;
const background = `var(${vars.keys.colors.background.base}, ${getBackgroundBase(theme)})`;
const foreground = `var(${vars.keys.colors.foreground.base}, ${(theme.vars || theme).palette.text.primary})`;
const fontBody = getFontBody(theme);

return {
flex: '0 1 1px',
display: 'flex',
alignItems: 'center',
justifyContent: 'end',
gap: `calc(${spacingUnit} * 0.25)`,
padding: `calc(${spacingUnit} * 0.75)`,
minHeight: 52,
boxSizing: 'border-box',
borderBottom: `1px solid ${borderColor}`,
// Inherited from the Data Grid root inside a grid, declared here so the component keeps
// matching it when rendered on its own.
backgroundColor: background,
color: foreground,
font: fontBody ? `var(${vars.keys.typography.font.body}, ${fontBody})` : undefined,
};
});

/**
Expand Down Expand Up @@ -87,4 +112,4 @@ Toolbar.propTypes /* remove-proptypes */ = {
render: PropTypes.oneOfType([PropTypes.element, PropTypes.func]),
} as any;

export { Toolbar };
export { Toolbar, ToolbarRoot };
30 changes: 22 additions & 8 deletions packages/x-data-grid/src/material/variables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,7 @@ function transformTheme(t: Theme): GridCSSVariablesInterface {

const paperColor = (t.vars || t).palette.background.paper;

const backgroundBase =
dataGridPalette?.bg ??
(t.palette.mode === 'dark'
? colorMixIfSupported(`color-mix(in srgb, ${paperColor} 95%, #fff)`, paperColor)
: paperColor);
const backgroundBase = getBackgroundBase(t);
const backgroundHeader = dataGridPalette?.headerBg ?? backgroundBase;
const backgroundPinned = dataGridPalette?.pinnedBg ?? backgroundBase;
const backgroundBackdrop = t.alpha(
Expand All @@ -41,13 +37,13 @@ function transformTheme(t: Theme): GridCSSVariablesInterface {

const radius = getRadius(t);

const fontBody = (t.vars as any)?.font?.body2 ?? formatFont(t.typography.body2);
const fontBody = getFontBody(t);
const fontSmall = (t.vars as any)?.font?.caption ?? formatFont(t.typography.caption);
const fontLarge = (t.vars as any)?.font?.body1 ?? formatFont(t.typography.body1);
const k = vars.keys;

return {
[k.spacingUnit]: t.vars ? ((t.vars as any).spacing ?? t.spacing(1)) : t.spacing(1),
[k.spacingUnit]: getSpacingUnit(t),

[k.colors.border.base]: borderColor,
[k.colors.background.base]: backgroundBase,
Expand Down Expand Up @@ -111,7 +107,25 @@ function getRadius(theme: Theme) {
: theme.shape.borderRadius;
}

function getBorderColor(theme: Theme) {
export function getBackgroundBase(theme: Theme) {
const paperColor = (theme.vars || theme).palette.background.paper;
return (
(theme.vars || theme).palette.DataGrid?.bg ??
(theme.palette.mode === 'dark'
? colorMixIfSupported(`color-mix(in srgb, ${paperColor} 95%, #fff)`, paperColor)
: paperColor)
);
}

export function getFontBody(theme: Theme) {
return (theme.vars as any)?.font?.body2 ?? formatFont(theme.typography.body2);
}

export function getSpacingUnit(theme: Theme) {
return theme.vars ? ((theme.vars as any).spacing ?? theme.spacing(1)) : theme.spacing(1);
}

export function getBorderColor(theme: Theme) {
if (theme.vars) {
return theme.vars.palette.TableCell.border;
}
Expand Down
67 changes: 66 additions & 1 deletion packages/x-data-grid/src/tests/toolbar.DataGrid.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react';
import { createRenderer, screen, act } from '@mui/internal-test-utils';
import { getColumnHeadersTextContent } from 'test/utils/helperFn';
import { DataGrid, Toolbar, ToolbarButton } from '@mui/x-data-grid';
import { DataGrid, Toolbar, ToolbarButton, ToolbarRoot } from '@mui/x-data-grid';
import type { GridColumnsManagementProps } from '@mui/x-data-grid';
import { isJSDOM } from 'test/utils/skipIf';
import { describe, it, expect } from 'vitest';
Expand Down Expand Up @@ -413,6 +414,70 @@ describe('<DataGrid /> - Toolbar', () => {
});
});

describe('ToolbarRoot', () => {
it('should render a div with the toolbar styles outside of the Data Grid', () => {
render(<ToolbarRoot data-testid="custom-header">Header</ToolbarRoot>);

const element = screen.getByTestId('custom-header');
expect(element.tagName).to.equal('DIV');
expect(element).toHaveComputedStyle({
display: 'flex',
alignItems: 'center',
minHeight: '52px',
});
});

it('should apply the same styles as the element rendered by Toolbar', () => {
render(
<React.Fragment>
<ToolbarRoot data-testid="custom-header" />
<DataGrid {...baselineProps} slots={{ toolbar: CustomToolbar }} showToolbar />
</React.Fragment>,
);

const toolbarClasses = new Set(screen.getByRole('toolbar').classList);
const rootClasses = Array.from(screen.getByTestId('custom-header').classList);
// The styled component class is shared between both elements.
expect(rootClasses.some((className) => toolbarClasses.has(className))).to.equal(true);
});

// The CSS variable fallbacks are resolved by the browser, jsdom keeps them unresolved.
it.skipIf(isJSDOM)(
'should fall back to the theme values for the variables the Data Grid defines',
() => {
render(
<React.Fragment>
<ToolbarRoot data-testid="custom-header" />
<DataGrid {...baselineProps} slots={{ toolbar: CustomToolbar }} showToolbar />
</React.Fragment>,
);

const toolbarStyle = getComputedStyle(screen.getByRole('toolbar'));
const rootStyle = getComputedStyle(screen.getByTestId('custom-header'));

expect(rootStyle.padding).not.to.equal('0px');
expect(rootStyle.padding).to.equal(toolbarStyle.padding);
expect(rootStyle.borderBottomColor).to.equal(toolbarStyle.borderBottomColor);
expect(rootStyle.borderBottomWidth).to.equal(toolbarStyle.borderBottomWidth);
expect(rootStyle.backgroundColor).to.equal(toolbarStyle.backgroundColor);
expect(rootStyle.color).to.equal(toolbarStyle.color);
expect(rootStyle.font).to.equal(toolbarStyle.font);
},
);

// The CSS variable fallbacks are resolved by the browser, jsdom keeps them unresolved.
it.skipIf(isJSDOM)('should prefer the Data Grid variables over the fallbacks', () => {
render(
<div style={{ '--DataGrid-t-spacing-unit': '20px' } as React.CSSProperties}>
<ToolbarRoot data-testid="custom-header" />
</div>,
);

// 20 * 0.75 = 15px, coming from the inherited variable rather than from the fallback.
expect(getComputedStyle(screen.getByTestId('custom-header')).padding).to.equal('15px');
});
});

describe('column selector', () => {
it('should hide "id" column when hiding it from the column selector', async () => {
const { user } = render(
Expand Down
1 change: 1 addition & 0 deletions scripts/x-data-grid-premium.exports.json
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,7 @@
{ "name": "ToolbarButtonProps", "kind": "TypeAlias" },
{ "name": "ToolbarProps", "kind": "TypeAlias" },
{ "name": "ToolbarPropsOverrides", "kind": "Interface" },
{ "name": "ToolbarRoot", "kind": "Variable" },
{ "name": "unstable_gridDefaultPromptResolver", "kind": "Function" },
{ "name": "unstable_resetCleanupTracking", "kind": "Function" },
{ "name": "unstable_useGridPivoting", "kind": "Variable" },
Expand Down
1 change: 1 addition & 0 deletions scripts/x-data-grid-pro.exports.json
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,7 @@
{ "name": "ToolbarButtonProps", "kind": "TypeAlias" },
{ "name": "ToolbarProps", "kind": "TypeAlias" },
{ "name": "ToolbarPropsOverrides", "kind": "Interface" },
{ "name": "ToolbarRoot", "kind": "Variable" },
{ "name": "unstable_resetCleanupTracking", "kind": "Function" },
{ "name": "useFirstRender", "kind": "Function" },
{ "name": "useGridApiContext", "kind": "Variable" },
Expand Down
1 change: 1 addition & 0 deletions scripts/x-data-grid.exports.json
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,7 @@
{ "name": "ToolbarButtonProps", "kind": "TypeAlias" },
{ "name": "ToolbarProps", "kind": "TypeAlias" },
{ "name": "ToolbarPropsOverrides", "kind": "Interface" },
{ "name": "ToolbarRoot", "kind": "Variable" },
{ "name": "unstable_resetCleanupTracking", "kind": "Function" },
{ "name": "useFirstRender", "kind": "Function" },
{ "name": "useGridApiContext", "kind": "Function" },
Expand Down
Loading