diff --git a/docs/data/data-grid/components/toolbar/GridToolbarStyleReuse.js b/docs/data/data-grid/components/toolbar/GridToolbarStyleReuse.js new file mode 100644 index 0000000000000..e5b7cb0cbeb53 --- /dev/null +++ b/docs/data/data-grid/components/toolbar/GridToolbarStyleReuse.js @@ -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 ( + + + + Commodities + + + +
+ +
+
+ ); +} diff --git a/docs/data/data-grid/components/toolbar/GridToolbarStyleReuse.tsx b/docs/data/data-grid/components/toolbar/GridToolbarStyleReuse.tsx new file mode 100644 index 0000000000000..e5b7cb0cbeb53 --- /dev/null +++ b/docs/data/data-grid/components/toolbar/GridToolbarStyleReuse.tsx @@ -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 ( + + + + Commodities + + + +
+ +
+
+ ); +} diff --git a/docs/data/data-grid/components/toolbar/GridToolbarStyleReuse.tsx.preview b/docs/data/data-grid/components/toolbar/GridToolbarStyleReuse.tsx.preview new file mode 100644 index 0000000000000..96961cf3c9636 --- /dev/null +++ b/docs/data/data-grid/components/toolbar/GridToolbarStyleReuse.tsx.preview @@ -0,0 +1,16 @@ + + + + Commodities + + + +
+ +
+
\ No newline at end of file diff --git a/docs/data/data-grid/components/toolbar/toolbar.md b/docs/data/data-grid/components/toolbar/toolbar.md index 8f74f6f97e139..7c11cc8e781f8 100644 --- a/docs/data/data-grid/components/toolbar/toolbar.md +++ b/docs/data/data-grid/components/toolbar/toolbar.md @@ -39,6 +39,22 @@ It renders a styled `
` element. `` is a button for performing actions from the toolbar. It renders the `baseIconButton` slot. +## Reusing the toolbar styles + +Use the `` 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'; + +Header; +``` + +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. diff --git a/packages/x-data-grid/src/components/toolbarV8/Toolbar.tsx b/packages/x-data-grid/src/components/toolbarV8/Toolbar.tsx index 0a2caddfb40b6..3092ad4c2ecea 100644 --- a/packages/x-data-grid/src/components/toolbarV8/Toolbar.tsx +++ b/packages/x-data-grid/src/components/toolbarV8/Toolbar.tsx @@ -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'; @@ -32,19 +38,38 @@ const useUtilityClasses = (ownerState: OwnerState) => { return composeClasses(slots, getDataGridUtilityClass, classes); }; +/** + * The styled `
` element rendered by the `` 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, + }; }); /** @@ -87,4 +112,4 @@ Toolbar.propTypes /* remove-proptypes */ = { render: PropTypes.oneOfType([PropTypes.element, PropTypes.func]), } as any; -export { Toolbar }; +export { Toolbar, ToolbarRoot }; diff --git a/packages/x-data-grid/src/material/variables.ts b/packages/x-data-grid/src/material/variables.ts index e66fec1ecda89..719f4e32be464 100644 --- a/packages/x-data-grid/src/material/variables.ts +++ b/packages/x-data-grid/src/material/variables.ts @@ -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( @@ -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, @@ -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; } diff --git a/packages/x-data-grid/src/tests/toolbar.DataGrid.test.tsx b/packages/x-data-grid/src/tests/toolbar.DataGrid.test.tsx index c230d6b8ba94d..e729baba9a114 100644 --- a/packages/x-data-grid/src/tests/toolbar.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/toolbar.DataGrid.test.tsx @@ -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'; @@ -413,6 +414,70 @@ describe(' - Toolbar', () => { }); }); + describe('ToolbarRoot', () => { + it('should render a div with the toolbar styles outside of the Data Grid', () => { + render(Header); + + 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( + + + + , + ); + + 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( + + + + , + ); + + 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( +
+ +
, + ); + + // 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( diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index 8a62d2ce11452..1070098bfb1d8 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -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" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index a5a563cfc2010..0188bc9bc13a7 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -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" }, diff --git a/scripts/x-data-grid.exports.json b/scripts/x-data-grid.exports.json index 9602ddf6a461c..c5b4527f912b8 100644 --- a/scripts/x-data-grid.exports.json +++ b/scripts/x-data-grid.exports.json @@ -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" },