diff --git a/.eslintrc.js b/.eslintrc.js
index 670e229c62378b..c716674a2f2128 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -120,6 +120,10 @@ module.exports = /** @type {Config} */ ({
variables: true,
},
],
+ '@typescript-eslint/no-unused-vars': [
+ 'error',
+ { vars: 'all', args: 'after-used', ignoreRestSiblings: true, argsIgnorePattern: '^_' },
+ ],
'no-use-before-define': 'off',
// disabled type-aware linting due to performance considerations
diff --git a/docs/data/material/getting-started/templates/shared-theme/AppTheme.js b/docs/data/material/getting-started/templates/shared-theme/AppTheme.js
index b1d55ca4408a14..6fde1fc9fd9be3 100644
--- a/docs/data/material/getting-started/templates/shared-theme/AppTheme.js
+++ b/docs/data/material/getting-started/templates/shared-theme/AppTheme.js
@@ -9,7 +9,8 @@ import { navigationCustomizations } from './customizations/navigation';
import { surfacesCustomizations } from './customizations/surfaces';
import { colorSchemes, typography, shadows, shape } from './themePrimitives';
-function AppTheme({ children, disableCustomTheme, themeComponents }) {
+function AppTheme(props) {
+ const { children, disableCustomTheme, themeComponents } = props;
const theme = React.useMemo(() => {
return disableCustomTheme
? {}
diff --git a/docs/data/material/getting-started/templates/shared-theme/AppTheme.tsx b/docs/data/material/getting-started/templates/shared-theme/AppTheme.tsx
index 1fe35d6faeef48..a4a512c30aef59 100644
--- a/docs/data/material/getting-started/templates/shared-theme/AppTheme.tsx
+++ b/docs/data/material/getting-started/templates/shared-theme/AppTheme.tsx
@@ -17,11 +17,8 @@ interface AppThemeProps {
themeComponents?: ThemeOptions['components'];
}
-export default function AppTheme({
- children,
- disableCustomTheme,
- themeComponents,
-}: AppThemeProps) {
+export default function AppTheme(props: AppThemeProps) {
+ const { children, disableCustomTheme, themeComponents } = props;
const theme = React.useMemo(() => {
return disableCustomTheme
? {}
diff --git a/docs/pages/joy-ui/api/stack.json b/docs/pages/joy-ui/api/stack.json
index 008afff7737bbc..8826d1eebc8a85 100644
--- a/docs/pages/joy-ui/api/stack.json
+++ b/docs/pages/joy-ui/api/stack.json
@@ -6,14 +6,16 @@
"type": {
"name": "union",
"description": "'column-reverse'
| 'column'
| 'row-reverse'
| 'row'
| Array<'column-reverse'
| 'column'
| 'row-reverse'
| 'row'>
| object"
- }
+ },
+ "default": "'column'"
},
"divider": { "type": { "name": "node" } },
"spacing": {
"type": {
"name": "union",
"description": "Array<number
| string>
| number
| object
| string"
- }
+ },
+ "default": "0"
},
"sx": {
"type": {
@@ -22,7 +24,7 @@
},
"additionalInfo": { "sx": true }
},
- "useFlexGap": { "type": { "name": "bool" } }
+ "useFlexGap": { "type": { "name": "bool" }, "default": "false" }
},
"name": "Stack",
"imports": ["import Stack from '@mui/joy/Stack';", "import { Stack } from '@mui/joy';"],
diff --git a/docs/pages/material-ui/api/container.json b/docs/pages/material-ui/api/container.json
index 29b78f807d6157..0f60f58e920597 100644
--- a/docs/pages/material-ui/api/container.json
+++ b/docs/pages/material-ui/api/container.json
@@ -2,13 +2,14 @@
"props": {
"classes": { "type": { "name": "object" }, "additionalInfo": { "cssApi": true } },
"component": { "type": { "name": "elementType" } },
- "disableGutters": { "type": { "name": "bool" } },
- "fixed": { "type": { "name": "bool" } },
+ "disableGutters": { "type": { "name": "bool" }, "default": "false" },
+ "fixed": { "type": { "name": "bool" }, "default": "false" },
"maxWidth": {
"type": {
"name": "union",
"description": "'xs'
| 'sm'
| 'md'
| 'lg'
| 'xl'
| false
| string"
- }
+ },
+ "default": "'lg'"
},
"sx": {
"type": {
diff --git a/docs/pages/material-ui/api/grid-2.json b/docs/pages/material-ui/api/grid-2.json
index c4b4b854151cfd..7f74fa6b051eef 100644
--- a/docs/pages/material-ui/api/grid-2.json
+++ b/docs/pages/material-ui/api/grid-2.json
@@ -5,7 +5,8 @@
"type": {
"name": "union",
"description": "Array<number>
| number
| object"
- }
+ },
+ "default": "12"
},
"columnSpacing": {
"type": {
@@ -13,12 +14,13 @@
"description": "Array<number
| string>
| number
| object
| string"
}
},
- "container": { "type": { "name": "bool" } },
+ "container": { "type": { "name": "bool" }, "default": "false" },
"direction": {
"type": {
"name": "union",
"description": "'column-reverse'
| 'column'
| 'row-reverse'
| 'row'
| Array<'column-reverse'
| 'column'
| 'row-reverse'
| 'row'>
| object"
- }
+ },
+ "default": "'row'"
},
"offset": {
"type": {
@@ -42,13 +44,15 @@
"type": {
"name": "union",
"description": "Array<number
| string>
| number
| object
| string"
- }
+ },
+ "default": "0"
},
"wrap": {
"type": {
"name": "enum",
"description": "'nowrap'
| 'wrap-reverse'
| 'wrap'"
- }
+ },
+ "default": "'wrap'"
}
},
"name": "Grid2",
diff --git a/docs/pages/material-ui/api/stack.json b/docs/pages/material-ui/api/stack.json
index ae424d7b57eea6..65e1dc1c8a28d2 100644
--- a/docs/pages/material-ui/api/stack.json
+++ b/docs/pages/material-ui/api/stack.json
@@ -6,14 +6,16 @@
"type": {
"name": "union",
"description": "'column-reverse'
| 'column'
| 'row-reverse'
| 'row'
| Array<'column-reverse'
| 'column'
| 'row-reverse'
| 'row'>
| object"
- }
+ },
+ "default": "'column'"
},
"divider": { "type": { "name": "node" } },
"spacing": {
"type": {
"name": "union",
"description": "Array<number
| string>
| number
| object
| string"
- }
+ },
+ "default": "0"
},
"sx": {
"type": {
@@ -22,7 +24,7 @@
},
"additionalInfo": { "sx": true }
},
- "useFlexGap": { "type": { "name": "bool" } }
+ "useFlexGap": { "type": { "name": "bool" }, "default": "false" }
},
"name": "Stack",
"imports": ["import Stack from '@mui/material/Stack';", "import { Stack } from '@mui/material';"],
diff --git a/docs/pages/system/api/container.json b/docs/pages/system/api/container.json
index 3f094034d61076..ae5bd4660c2576 100644
--- a/docs/pages/system/api/container.json
+++ b/docs/pages/system/api/container.json
@@ -2,13 +2,14 @@
"props": {
"classes": { "type": { "name": "object" }, "additionalInfo": { "cssApi": true } },
"component": { "type": { "name": "elementType" } },
- "disableGutters": { "type": { "name": "bool" } },
- "fixed": { "type": { "name": "bool" } },
+ "disableGutters": { "type": { "name": "bool" }, "default": "false" },
+ "fixed": { "type": { "name": "bool" }, "default": "false" },
"maxWidth": {
"type": {
"name": "union",
"description": "'xs'
| 'sm'
| 'md'
| 'lg'
| 'xl'
| false
| string"
- }
+ },
+ "default": "'lg'"
},
"sx": {
"type": {
diff --git a/docs/pages/system/api/grid.json b/docs/pages/system/api/grid.json
index 3a3c9d23850287..4cd038309b7d7b 100644
--- a/docs/pages/system/api/grid.json
+++ b/docs/pages/system/api/grid.json
@@ -5,7 +5,8 @@
"type": {
"name": "union",
"description": "Array<number>
| number
| object"
- }
+ },
+ "default": "12"
},
"columnSpacing": {
"type": {
@@ -13,12 +14,13 @@
"description": "Array<number
| string>
| number
| object
| string"
}
},
- "container": { "type": { "name": "bool" } },
+ "container": { "type": { "name": "bool" }, "default": "false" },
"direction": {
"type": {
"name": "union",
"description": "'column-reverse'
| 'column'
| 'row-reverse'
| 'row'
| Array<'column-reverse'
| 'column'
| 'row-reverse'
| 'row'>
| object"
- }
+ },
+ "default": "'row'"
},
"offset": {
"type": {
@@ -42,13 +44,15 @@
"type": {
"name": "union",
"description": "Array<number
| string>
| number
| object
| string"
- }
+ },
+ "default": "0"
},
"wrap": {
"type": {
"name": "enum",
"description": "'nowrap'
| 'wrap-reverse'
| 'wrap'"
- }
+ },
+ "default": "'wrap'"
}
},
"name": "Grid",
diff --git a/docs/pages/system/api/stack.json b/docs/pages/system/api/stack.json
index a5d69b0c81f265..2c838e2175b73d 100644
--- a/docs/pages/system/api/stack.json
+++ b/docs/pages/system/api/stack.json
@@ -6,14 +6,16 @@
"type": {
"name": "union",
"description": "'column-reverse'
| 'column'
| 'row-reverse'
| 'row'
| Array<'column-reverse'
| 'column'
| 'row-reverse'
| 'row'>
| object"
- }
+ },
+ "default": "'column'"
},
"divider": { "type": { "name": "node" } },
"spacing": {
"type": {
"name": "union",
"description": "Array<number
| string>
| number
| object
| string"
- }
+ },
+ "default": "0"
},
"sx": {
"type": {
@@ -22,7 +24,7 @@
},
"additionalInfo": { "sx": true }
},
- "useFlexGap": { "type": { "name": "bool" } }
+ "useFlexGap": { "type": { "name": "bool" }, "default": "false" }
},
"name": "Stack",
"imports": ["import Stack from '@mui/system/Stack';", "import { Stack } from '@mui/system';"],
diff --git a/docs/src/modules/components/JoyThemeBuilder.tsx b/docs/src/modules/components/JoyThemeBuilder.tsx
index 6f0feae3ffa79e..67753753411c4b 100644
--- a/docs/src/modules/components/JoyThemeBuilder.tsx
+++ b/docs/src/modules/components/JoyThemeBuilder.tsx
@@ -1268,7 +1268,15 @@ function getAvailableTokens(colorSchemes: any, colorMode: 'light' | 'dark') {
return tokens;
}
-function TemplatesDialog({ children, data }: { children: React.ReactElement; data: any }) {
+function TemplatesDialog({
+ children,
+ data,
+}: {
+ children: React.ReactElement<{
+ onClick?: React.MouseEventHandler;
+ }>;
+ data: any;
+}) {
const [open, setOpen] = React.useState(false);
const { map: templateMap } = sourceJoyTemplates();
const renderItem = (name: string, item: TemplateData) => {
@@ -1339,9 +1347,9 @@ function TemplatesDialog({ children, data }: { children: React.ReactElement
return (
{React.cloneElement(children, {
- onClick: () => {
+ onClick: (event: React.MouseEvent) => {
setOpen(true);
- children.props.onClick?.();
+ children.props.onClick?.(event);
},
})}
setOpen(false)}>
diff --git a/packages/api-docs-builder/ApiBuilders/ComponentApiBuilder.ts b/packages/api-docs-builder/ApiBuilders/ComponentApiBuilder.ts
index 577dfae4a22407..02a81285bb3ddb 100644
--- a/packages/api-docs-builder/ApiBuilders/ComponentApiBuilder.ts
+++ b/packages/api-docs-builder/ApiBuilders/ComponentApiBuilder.ts
@@ -697,13 +697,26 @@ export default async function generateComponentApi(
const filename = componentInfo.filename;
let reactApi: ComponentReactApi;
- if (componentInfo.isSystemComponent || componentInfo.name === 'Grid2') {
- try {
+ try {
+ reactApi = docgenParse(src, null, defaultHandlers.concat(muiDefaultPropsHandler), {
+ filename,
+ });
+ } catch (error) {
+ // fallback to default logic if there is no `create*` definition.
+ if ((error as Error).message === 'No suitable component definition found.') {
reactApi = docgenParse(
src,
(ast) => {
let node;
+ // TODO migrate to react-docgen v6, using Babel AST now
astTypes.visit(ast, {
+ visitFunctionDeclaration: (functionPath) => {
+ // @ts-ignore
+ if (functionPath.node.params[0].name === 'props') {
+ node = functionPath;
+ }
+ return false;
+ },
visitVariableDeclaration: (variablePath) => {
const definitions: any[] = [];
if (variablePath.node.declarations) {
@@ -711,7 +724,6 @@ export default async function generateComponentApi(
.get('declarations')
.each((declarator: any) => definitions.push(declarator.get('init')));
}
-
definitions.forEach((definition) => {
// definition.value.expression is defined when the source is in TypeScript.
const expression = definition.value?.expression
@@ -719,36 +731,25 @@ export default async function generateComponentApi(
: definition;
if (expression.value?.callee) {
const definitionName = expression.value.callee.name;
-
if (definitionName === `create${componentInfo.name}`) {
node = expression;
}
}
});
-
return false;
},
});
return node;
},
- defaultHandlers,
- { filename },
- );
- } catch (error) {
- // fallback to default logic if there is no `create*` definition.
- if ((error as Error).message === 'No suitable component definition found.') {
- reactApi = docgenParse(src, null, defaultHandlers.concat(muiDefaultPropsHandler), {
+ defaultHandlers.concat(muiDefaultPropsHandler),
+ {
filename,
- });
- } else {
- throw error;
- }
+ },
+ );
+ } else {
+ throw error;
}
- } else {
- reactApi = docgenParse(src, null, defaultHandlers.concat(muiDefaultPropsHandler), {
- filename,
- });
}
if (!reactApi.props) {
diff --git a/packages/api-docs-builder/utils/defaultPropsHandler.ts b/packages/api-docs-builder/utils/defaultPropsHandler.ts
index 9b49c019f1af88..6836e85cb677ec 100644
--- a/packages/api-docs-builder/utils/defaultPropsHandler.ts
+++ b/packages/api-docs-builder/utils/defaultPropsHandler.ts
@@ -131,6 +131,11 @@ function getExplicitPropsDeclaration(
): NodePath | undefined {
const functionNode = getRenderBody(componentDefinition, importer);
+ // No function body available to inspect.
+ if (!functionNode.value) {
+ return undefined;
+ }
+
let propsPath: NodePath | undefined;
// visitVariableDeclarator, can't use visit body.node since it looses scope information
functionNode
diff --git a/packages/api-docs-builder/utils/getPropsFromComponentNode.ts b/packages/api-docs-builder/utils/getPropsFromComponentNode.ts
index d6af8aa4274a41..f3e1c63e3d4eb6 100644
--- a/packages/api-docs-builder/utils/getPropsFromComponentNode.ts
+++ b/packages/api-docs-builder/utils/getPropsFromComponentNode.ts
@@ -51,6 +51,7 @@ function isStyledFunction(node: ts.VariableDeclaration): boolean {
);
}
+// TODO update to reflect https://github.com/DefinitelyTyped/DefinitelyTyped/pull/65135
function getJSXLikeReturnValueFromFunction(type: ts.Type, project: TypeScriptProject) {
return type
.getCallSignatures()
diff --git a/packages/mui-base/src/ClickAwayListener/ClickAwayListener.tsx b/packages/mui-base/src/ClickAwayListener/ClickAwayListener.tsx
index e84ca9b1382b42..5f8f4da604c046 100644
--- a/packages/mui-base/src/ClickAwayListener/ClickAwayListener.tsx
+++ b/packages/mui-base/src/ClickAwayListener/ClickAwayListener.tsx
@@ -209,7 +209,7 @@ function ClickAwayListener(props: ClickAwayListenerProps): React.JSX.Element {
return undefined;
}, [handleClickAway, mouseEvent]);
- return {React.cloneElement(children, childrenProps)};
+ return React.cloneElement(children, childrenProps);
}
ClickAwayListener.propTypes /* remove-proptypes */ = {
diff --git a/packages/mui-base/src/NoSsr/NoSsr.tsx b/packages/mui-base/src/NoSsr/NoSsr.tsx
index c637df94e70b61..8468d401c64a86 100644
--- a/packages/mui-base/src/NoSsr/NoSsr.tsx
+++ b/packages/mui-base/src/NoSsr/NoSsr.tsx
@@ -38,8 +38,8 @@ function NoSsr(props: NoSsrProps): React.JSX.Element {
}
}, [defer]);
- // We need the Fragment here to force react-docgen to recognise NoSsr as a component.
- return {mountedState ? children : fallback};
+ // TODO casting won't be needed at one point https://github.com/DefinitelyTyped/DefinitelyTyped/pull/65135
+ return (mountedState ? children : fallback) as React.JSX.Element;
}
NoSsr.propTypes /* remove-proptypes */ = {
diff --git a/packages/mui-base/src/Portal/Portal.tsx b/packages/mui-base/src/Portal/Portal.tsx
index 830b229d8eb175..af607b51532adc 100644
--- a/packages/mui-base/src/Portal/Portal.tsx
+++ b/packages/mui-base/src/Portal/Portal.tsx
@@ -64,14 +64,10 @@ const Portal = React.forwardRef(function Portal(
};
return React.cloneElement(children, newProps);
}
- return {children};
+ return children;
}
- return (
-
- {mountNode ? ReactDOM.createPortal(children, mountNode) : mountNode}
-
- );
+ return mountNode ? ReactDOM.createPortal(children, mountNode) : mountNode;
}) as React.ForwardRefExoticComponent>;
Portal.propTypes /* remove-proptypes */ = {
diff --git a/packages/mui-material/src/Avatar/Avatar.js b/packages/mui-material/src/Avatar/Avatar.js
index 50157ee01ef149..fbf36c82d5fa3a 100644
--- a/packages/mui-material/src/Avatar/Avatar.js
+++ b/packages/mui-material/src/Avatar/Avatar.js
@@ -163,17 +163,23 @@ const Avatar = React.forwardRef(function Avatar(inProps, ref) {
let children = null;
- // Use a hook instead of onError on the img element to support server-side rendering.
- const loaded = useLoaded({ ...imgProps, src, srcSet });
- const hasImg = src || srcSet;
- const hasImgNotFailing = hasImg && loaded !== 'error';
-
const ownerState = {
...props,
- colorDefault: !hasImgNotFailing,
component,
variant,
};
+
+ // Use a hook instead of onError on the img element to support server-side rendering.
+ const loaded = useLoaded({
+ ...imgProps,
+ ...(typeof slotProps.img === 'function' ? slotProps.img(ownerState) : slotProps.img),
+ src,
+ srcSet,
+ });
+ const hasImg = src || srcSet;
+ const hasImgNotFailing = hasImg && loaded !== 'error';
+
+ ownerState.colorDefault = !hasImgNotFailing;
// This issue explains why this is required: https://github.com/mui/material-ui/issues/42184
delete ownerState.ownerState;
diff --git a/packages/mui-material/src/Avatar/Avatar.test.js b/packages/mui-material/src/Avatar/Avatar.test.js
index 5f3fb5796106ee..7feddbbd36731a 100644
--- a/packages/mui-material/src/Avatar/Avatar.test.js
+++ b/packages/mui-material/src/Avatar/Avatar.test.js
@@ -62,6 +62,20 @@ describe('', () => {
fireEvent.error(img);
expect(onError.callCount).to.equal(1);
});
+
+ it('should pass slots.img to `useLoaded` hook', () => {
+ const originalImage = global.Image;
+ const image = {};
+ global.Image = function Image() {
+ return image;
+ };
+
+ render();
+
+ expect(image.crossOrigin).to.equal('anonymous');
+
+ global.Image = originalImage;
+ });
});
describe('image avatar with unrendered children', () => {
diff --git a/packages/mui-material/src/ClickAwayListener/ClickAwayListener.tsx b/packages/mui-material/src/ClickAwayListener/ClickAwayListener.tsx
index 6a695e84f78352..5987a8f04dc1c8 100644
--- a/packages/mui-material/src/ClickAwayListener/ClickAwayListener.tsx
+++ b/packages/mui-material/src/ClickAwayListener/ClickAwayListener.tsx
@@ -210,7 +210,7 @@ function ClickAwayListener(props: ClickAwayListenerProps): React.JSX.Element {
return undefined;
}, [handleClickAway, mouseEvent]);
- return {React.cloneElement(children, childrenProps)};
+ return React.cloneElement(children, childrenProps);
}
ClickAwayListener.propTypes /* remove-proptypes */ = {
diff --git a/packages/mui-material/src/Grid2/Grid2.test.js b/packages/mui-material/src/Grid2/Grid2.test.js
index 87c952bca83cd7..533a3f0502d9e4 100644
--- a/packages/mui-material/src/Grid2/Grid2.test.js
+++ b/packages/mui-material/src/Grid2/Grid2.test.js
@@ -1,6 +1,8 @@
import * as React from 'react';
-import { createRenderer } from '@mui/internal-test-utils';
+import { createRenderer, screen } from '@mui/internal-test-utils';
import Grid2, { grid2Classes as classes } from '@mui/material/Grid2';
+import { createTheme, ThemeProvider } from '@mui/material/styles';
+import { expect } from 'chai';
import describeConformance from '../../test/describeConformance';
// The main tests are in mui-system Grid folder
@@ -20,4 +22,31 @@ describe('', () => {
testVariantProps: { container: true, spacing: 5 },
skip: ['componentsProp', 'classesRoot'],
}));
+
+ it('should render with the container class', () => {
+ render();
+ expect(screen.getByTestId('grid')).to.have.class(classes.container);
+ });
+
+ it('should have container styles passed from theme', () => {
+ const theme = createTheme({
+ components: {
+ MuiGrid2: {
+ styleOverrides: {
+ container: {
+ padding: '11px',
+ },
+ },
+ },
+ },
+ });
+ render(
+
+
+ hello
+
+ ,
+ );
+ expect(screen.getByTestId('grid')).to.have.style('padding', '11px');
+ });
});
diff --git a/packages/mui-material/src/Grid2/Grid2.tsx b/packages/mui-material/src/Grid2/Grid2.tsx
index 1d2841bafa74c1..99c4148d33f2ef 100644
--- a/packages/mui-material/src/Grid2/Grid2.tsx
+++ b/packages/mui-material/src/Grid2/Grid2.tsx
@@ -127,7 +127,10 @@ const Grid2 = createGrid2({
createStyledComponent: styled('div', {
name: 'MuiGrid2',
slot: 'Root',
- overridesResolver: (props, styles) => styles.root,
+ overridesResolver: (props, styles) => {
+ const { ownerState } = props;
+ return [styles.root, ownerState.container && styles.container];
+ },
}),
componentName: 'MuiGrid2',
useThemeProps: (inProps) => useDefaultProps({ props: inProps, name: 'MuiGrid2' }),
diff --git a/packages/mui-material/src/Hidden/HiddenJs.js b/packages/mui-material/src/Hidden/HiddenJs.js
index b591452cef6299..a9ab0d0a965ee2 100644
--- a/packages/mui-material/src/Hidden/HiddenJs.js
+++ b/packages/mui-material/src/Hidden/HiddenJs.js
@@ -1,5 +1,4 @@
'use client';
-import * as React from 'react';
import PropTypes from 'prop-types';
import exactProp from '@mui/utils/exactProp';
import withWidth, { isWidthDown, isWidthUp } from './withWidth';
@@ -50,7 +49,7 @@ function HiddenJs(props) {
return null;
}
- return {children};
+ return children;
}
HiddenJs.propTypes = {
diff --git a/packages/mui-material/src/Modal/useModal.ts b/packages/mui-material/src/Modal/useModal.ts
index 4cb2b992beae60..6a14ec7b7eb9c7 100644
--- a/packages/mui-material/src/Modal/useModal.ts
+++ b/packages/mui-material/src/Modal/useModal.ts
@@ -24,6 +24,8 @@ function getHasTransition(children: UseModalParameters['children']) {
return children ? children.props.hasOwnProperty('in') : false;
}
+const noop = () => {};
+
// A modal manager used to track and manage the state of open Modals.
// Modals don't open on the server so this won't conflict with concurrent requests.
const manager = new ModalManager();
@@ -227,8 +229,8 @@ function useModal(parameters: UseModalParameters): UseModalReturnValue {
};
return {
- onEnter: createChainedFunction(handleEnter, children?.props.onEnter),
- onExited: createChainedFunction(handleExited, children?.props.onExited),
+ onEnter: createChainedFunction(handleEnter, children?.props.onEnter ?? noop),
+ onExited: createChainedFunction(handleExited, children?.props.onExited ?? noop),
};
};
diff --git a/packages/mui-material/src/Modal/useModal.types.ts b/packages/mui-material/src/Modal/useModal.types.ts
index a67b240a9c5708..9b384f7688f70f 100644
--- a/packages/mui-material/src/Modal/useModal.types.ts
+++ b/packages/mui-material/src/Modal/useModal.types.ts
@@ -22,7 +22,14 @@ export type UseModalParameters = {
/**
* A single child content element.
*/
- children: React.ReactElement | undefined | null;
+ children:
+ | React.ReactElement<{
+ in?: boolean;
+ onEnter?: (this: unknown) => void;
+ onExited?: (this: unknown) => void;
+ }>
+ | undefined
+ | null;
/**
* When set to true the Modal waits until a nested Transition is completed before closing.
* @default false
diff --git a/packages/mui-material/src/NoSsr/NoSsr.tsx b/packages/mui-material/src/NoSsr/NoSsr.tsx
index c7e2dfbc191a6e..0b18a597a751ae 100644
--- a/packages/mui-material/src/NoSsr/NoSsr.tsx
+++ b/packages/mui-material/src/NoSsr/NoSsr.tsx
@@ -38,8 +38,8 @@ function NoSsr(props: NoSsrProps): React.JSX.Element {
}
}, [defer]);
- // We need the Fragment here to force react-docgen to recognise NoSsr as a component.
- return {mountedState ? children : fallback};
+ // TODO casting won't be needed at one point https://github.com/DefinitelyTyped/DefinitelyTyped/pull/65135
+ return (mountedState ? children : fallback) as React.JSX.Element;
}
NoSsr.propTypes /* remove-proptypes */ = {
diff --git a/packages/mui-material/src/Portal/Portal.tsx b/packages/mui-material/src/Portal/Portal.tsx
index 8bd141d8a5f858..a028eb856836e0 100644
--- a/packages/mui-material/src/Portal/Portal.tsx
+++ b/packages/mui-material/src/Portal/Portal.tsx
@@ -64,14 +64,10 @@ const Portal = React.forwardRef(function Portal(
};
return React.cloneElement(children, newProps);
}
- return {children};
+ return children;
}
- return (
-
- {mountNode ? ReactDOM.createPortal(children, mountNode) : mountNode}
-
- );
+ return mountNode ? ReactDOM.createPortal(children, mountNode) : mountNode;
}) as React.ForwardRefExoticComponent>;
Portal.propTypes /* remove-proptypes */ = {
diff --git a/packages/mui-material/src/Slider/SliderValueLabel.types.ts b/packages/mui-material/src/Slider/SliderValueLabel.types.ts
index 8fac93f872dcb8..378930f97e345e 100644
--- a/packages/mui-material/src/Slider/SliderValueLabel.types.ts
+++ b/packages/mui-material/src/Slider/SliderValueLabel.types.ts
@@ -1,5 +1,5 @@
export interface SliderValueLabelProps {
- children?: React.ReactElement;
+ children?: React.ReactElement<{ className?: string; children?: React.ReactNode }>;
className?: string;
style?: React.CSSProperties;
/**
diff --git a/packages/mui-material/src/Tabs/ScrollbarSize.js b/packages/mui-material/src/Tabs/ScrollbarSize.js
index da7ec73c0c741b..4f5676cc428d13 100644
--- a/packages/mui-material/src/Tabs/ScrollbarSize.js
+++ b/packages/mui-material/src/Tabs/ScrollbarSize.js
@@ -49,7 +49,7 @@ export default function ScrollbarSize(props) {
onChange(scrollbarHeight.current);
}, [onChange]);
- return ;
+ return ;
}
ScrollbarSize.propTypes = {
diff --git a/packages/mui-material/src/Unstable_TrapFocus/FocusTrap.tsx b/packages/mui-material/src/Unstable_TrapFocus/FocusTrap.tsx
index 0f6085f5b9f1d9..398a7b37421019 100644
--- a/packages/mui-material/src/Unstable_TrapFocus/FocusTrap.tsx
+++ b/packages/mui-material/src/Unstable_TrapFocus/FocusTrap.tsx
@@ -324,7 +324,7 @@ function FocusTrap(props: FocusTrapProps): React.JSX.Element {
};
}, [disableAutoFocus, disableEnforceFocus, disableRestoreFocus, isEnabled, open, getTabbable]);
- const onFocus = (event: FocusEvent) => {
+ const onFocus = (event: React.FocusEvent) => {
if (nodeToRestore.current === null) {
nodeToRestore.current = event.relatedTarget;
}
diff --git a/packages/mui-material/src/Unstable_TrapFocus/FocusTrap.types.ts b/packages/mui-material/src/Unstable_TrapFocus/FocusTrap.types.ts
index 462100ec696479..399ddd84ea210a 100644
--- a/packages/mui-material/src/Unstable_TrapFocus/FocusTrap.types.ts
+++ b/packages/mui-material/src/Unstable_TrapFocus/FocusTrap.types.ts
@@ -24,7 +24,10 @@ export interface FocusTrapProps {
/**
* A single child content element.
*/
- children: React.ReactElement;
+ children: React.ReactElement<{
+ onFocus?: React.FocusEventHandler;
+ ref?: React.RefCallback | null;
+ }>;
/**
* If `true`, the focus trap will not automatically shift focus to itself when it opens, and
* replace it to the last focused element when it closes.
diff --git a/packages/mui-material/src/styles/ThemeProviderWithVars.test.js b/packages/mui-material/src/styles/ThemeProviderWithVars.test.js
index 57cc558313c691..e06a6d304d63dd 100644
--- a/packages/mui-material/src/styles/ThemeProviderWithVars.test.js
+++ b/packages/mui-material/src/styles/ThemeProviderWithVars.test.js
@@ -408,4 +408,35 @@ describe('[Material UI] ThemeProviderWithVars', () => {
fireEvent.click(screen.getByText('Dark'));
}).not.toErrorDev();
});
+
+ it('theme should remain the same when ThemeProvider rerenders', () => {
+ const theme = createTheme({ cssVariables: true });
+
+ function Inner() {
+ const upperTheme = useTheme();
+ const themeRef = React.useRef(upperTheme);
+ const [changed, setChanged] = React.useState(false);
+ React.useEffect(() => {
+ if (themeRef.current !== upperTheme) {
+ setChanged(true);
+ }
+ }, [upperTheme]);
+ return changed ? : null;
+ }
+ function App() {
+ const [, setState] = React.useState({});
+ const rerender = () => setState({});
+ return (
+
+
+
+
+ );
+ }
+ render();
+
+ fireEvent.click(screen.getByRole('button'));
+
+ expect(screen.queryByTestId('theme-changed')).to.equal(null);
+ });
});
diff --git a/packages/mui-material/src/styles/createTheme.test.js b/packages/mui-material/src/styles/createTheme.test.js
index 26df7f1fdf6398..e4351045dffa8f 100644
--- a/packages/mui-material/src/styles/createTheme.test.js
+++ b/packages/mui-material/src/styles/createTheme.test.js
@@ -221,6 +221,22 @@ describe('createTheme', () => {
expect(theme.colorSchemes.dark).to.not.equal(undefined);
});
+ it('should be able to customize tonal offset', () => {
+ const theme = createTheme({
+ cssVariables: true,
+ palette: {
+ primary: {
+ main: green[500],
+ },
+ tonalOffset: {
+ light: 0.1,
+ dark: 0.9,
+ },
+ },
+ });
+ expect(theme.palette.primary.main).to.equal('#4caf50');
+ });
+
describe('spacing', () => {
it('should provide the default spacing', () => {
const theme = createTheme({ cssVariables: true });
diff --git a/packages/mui-material/src/styles/createThemeWithVars.js b/packages/mui-material/src/styles/createThemeWithVars.js
index 9e1fd9f76707e9..fde30948676edf 100644
--- a/packages/mui-material/src/styles/createThemeWithVars.js
+++ b/packages/mui-material/src/styles/createThemeWithVars.js
@@ -40,7 +40,7 @@ function setColor(obj, key, defaultValue) {
}
function toRgb(color) {
- if (!color || !color.startsWith('hsl')) {
+ if (typeof color !== 'string' || !color.startsWith('hsl')) {
return color;
}
return hslToRgb(color);
@@ -421,7 +421,7 @@ export default function createThemeWithVars(options = {}, ...args) {
// The default palettes (primary, secondary, error, info, success, and warning) errors are handled by the above `createTheme(...)`.
- if (colors && typeof colors === 'object') {
+ if (color !== 'tonalOffset' && colors && typeof colors === 'object') {
// Silent the error for custom palettes.
if (colors.main) {
setColor(palette[color], 'mainChannel', safeColorChannel(toRgb(colors.main)));
diff --git a/packages/mui-styles/test/styles.spec.tsx b/packages/mui-styles/test/styles.spec.tsx
index 8ed6a1d215f263..34ab5cac0f3711 100644
--- a/packages/mui-styles/test/styles.spec.tsx
+++ b/packages/mui-styles/test/styles.spec.tsx
@@ -215,7 +215,7 @@ withStyles((theme) =>
});
interface ListItemContentProps extends WithStyles {
- children?: React.ReactElement;
+ children?: React.ReactElement;
inset?: boolean;
row?: boolean;
}
diff --git a/packages/mui-system/src/cssVars/createCssVarsProvider.js b/packages/mui-system/src/cssVars/createCssVarsProvider.js
index e7a2a21febb207..64ed9f9fc4037d 100644
--- a/packages/mui-system/src/cssVars/createCssVarsProvider.js
+++ b/packages/mui-system/src/cssVars/createCssVarsProvider.js
@@ -48,6 +48,9 @@ export default function createCssVarsProvider(options) {
const useColorScheme = () => React.useContext(ColorSchemeContext) || defaultContext;
+ const defaultColorSchemes = {};
+ const defaultComponents = {};
+
function CssVarsProvider(props) {
const {
children,
@@ -75,12 +78,12 @@ export default function createCssVarsProvider(options) {
return typeof defaultTheme === 'function' ? defaultTheme() : defaultTheme;
}, [themeProp]);
const scopedTheme = initialTheme[themeId];
+ const restThemeProp = scopedTheme || initialTheme;
const {
- colorSchemes = {},
- components = {},
+ colorSchemes = defaultColorSchemes,
+ components = defaultComponents,
cssVarPrefix,
- ...restThemeProp
- } = scopedTheme || initialTheme;
+ } = restThemeProp;
const joinedColorSchemes = Object.keys(colorSchemes)
.filter((k) => !!colorSchemes[k])
.join(',');
@@ -126,42 +129,46 @@ export default function createCssVarsProvider(options) {
colorScheme = ctx.colorScheme;
}
- // `colorScheme` is undefined on the server and hydration phase
- const calculatedColorScheme = colorScheme || restThemeProp.defaultColorScheme;
+ const memoTheme = React.useMemo(() => {
+ // `colorScheme` is undefined on the server and hydration phase
+ const calculatedColorScheme = colorScheme || restThemeProp.defaultColorScheme;
- // 2. get the `vars` object that refers to the CSS custom properties
- const themeVars = restThemeProp.generateThemeVars?.() || restThemeProp.vars;
+ // 2. get the `vars` object that refers to the CSS custom properties
+ const themeVars = restThemeProp.generateThemeVars?.() || restThemeProp.vars;
- // 3. Start composing the theme object
- const theme = {
- ...restThemeProp,
- components,
- colorSchemes,
- cssVarPrefix,
- vars: themeVars,
- };
- if (typeof theme.generateSpacing === 'function') {
- theme.spacing = theme.generateSpacing();
- }
+ // 3. Start composing the theme object
+ const theme = {
+ ...restThemeProp,
+ components,
+ colorSchemes,
+ cssVarPrefix,
+ vars: themeVars,
+ };
+ if (typeof theme.generateSpacing === 'function') {
+ theme.spacing = theme.generateSpacing();
+ }
- // 4. Resolve the color scheme and merge it to the theme
- if (calculatedColorScheme) {
- const scheme = colorSchemes[calculatedColorScheme];
- if (scheme && typeof scheme === 'object') {
- // 4.1 Merge the selected color scheme to the theme
- Object.keys(scheme).forEach((schemeKey) => {
- if (scheme[schemeKey] && typeof scheme[schemeKey] === 'object') {
- // shallow merge the 1st level structure of the theme.
- theme[schemeKey] = {
- ...theme[schemeKey],
- ...scheme[schemeKey],
- };
- } else {
- theme[schemeKey] = scheme[schemeKey];
- }
- });
+ // 4. Resolve the color scheme and merge it to the theme
+ if (calculatedColorScheme) {
+ const scheme = colorSchemes[calculatedColorScheme];
+ if (scheme && typeof scheme === 'object') {
+ // 4.1 Merge the selected color scheme to the theme
+ Object.keys(scheme).forEach((schemeKey) => {
+ if (scheme[schemeKey] && typeof scheme[schemeKey] === 'object') {
+ // shallow merge the 1st level structure of the theme.
+ theme[schemeKey] = {
+ ...theme[schemeKey],
+ ...scheme[schemeKey],
+ };
+ } else {
+ theme[schemeKey] = scheme[schemeKey];
+ }
+ });
+ }
}
- }
+
+ return resolveTheme ? resolveTheme(theme) : theme;
+ }, [restThemeProp, colorScheme, components, colorSchemes, cssVarPrefix]);
// 5. Declaring effects
// 5.1 Updates the selector value to use the current color scheme which tells CSS to use the proper stylesheet.
@@ -248,7 +255,7 @@ export default function createCssVarsProvider(options) {
process.env.NODE_ENV === 'production'
? setMode
: (newMode) => {
- if (theme.colorSchemeSelector === 'media') {
+ if (memoTheme.colorSchemeSelector === 'media') {
console.error(
[
'MUI: The `setMode` function has no effect if `colorSchemeSelector` is `media` (`media` is the default value).',
@@ -270,7 +277,7 @@ export default function createCssVarsProvider(options) {
setColorScheme,
setMode,
systemMode,
- theme.colorSchemeSelector,
+ memoTheme.colorSchemeSelector,
],
);
@@ -285,13 +292,12 @@ export default function createCssVarsProvider(options) {
const element = (
-
+
{children}
- {shouldGenerateStyleSheet && }
+ {shouldGenerateStyleSheet && (
+
+ )}
);