diff --git a/docs/pages/api-docs/theme-provider.js b/docs/pages/api-docs/theme-provider.js
new file mode 100644
index 00000000000000..ab8127ad82ccab
--- /dev/null
+++ b/docs/pages/api-docs/theme-provider.js
@@ -0,0 +1,15 @@
+import React from 'react';
+import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs';
+import { prepareMarkdown } from 'docs/src/modules/utils/parseMarkdown';
+
+const pageFilename = 'api/theme-provider';
+const requireRaw = require.context('!raw-loader!./', false, /\/theme-provider\.md$/);
+
+export default function Page({ docs }) {
+ return ;
+}
+
+Page.getInitialProps = () => {
+ const { demos, docs } = prepareMarkdown({ pageFilename, requireRaw });
+ return { demos, docs };
+};
diff --git a/docs/pages/api-docs/theme-provider.md b/docs/pages/api-docs/theme-provider.md
new file mode 100644
index 00000000000000..93fc56a82c7d06
--- /dev/null
+++ b/docs/pages/api-docs/theme-provider.md
@@ -0,0 +1,33 @@
+---
+filename: /packages/material-ui/src/styles/ThemeProvider.js
+---
+
+
+
+# ThemeProvider API
+
+
The API documentation of the ThemeProvider React component. Learn more about the props and the CSS customization points.
+
+## Import
+
+```js
+import ThemeProvider from '@material-ui/core/styles/ThemeProvider.js/ThemeProvider';
+// or
+import { ThemeProvider } from '@material-ui/core/styles/ThemeProvider.js';
+```
+
+You can learn more about the difference by [reading this guide](/guides/minimizing-bundle-size/).
+
+This component makes the `theme` available down the React tree.
+It should preferably be used at **the root of your component tree**.
+
+
+
+## Props
+
+| Name | Type | Default | Description |
+|:-----|:-----|:--------|:------------|
+
+The component cannot hold a ref.
+
+
diff --git a/docs/src/pages/components/slider-styled/ContinuousSlider.js b/docs/src/pages/components/slider-styled/ContinuousSlider.js
index 6b14f7b682ebe4..bae03e4997e2f8 100644
--- a/docs/src/pages/components/slider-styled/ContinuousSlider.js
+++ b/docs/src/pages/components/slider-styled/ContinuousSlider.js
@@ -1,19 +1,16 @@
import * as React from 'react';
-import { makeStyles } from '@material-ui/core/styles';
+import { experimentalStyled as styled } from '@material-ui/core/styles';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import Slider from '@material-ui/lab/SliderStyled';
import VolumeDown from '@material-ui/icons/VolumeDown';
import VolumeUp from '@material-ui/icons/VolumeUp';
-const useStyles = makeStyles({
- root: {
- width: 200,
- },
+const Root = styled('div')({
+ width: 200,
});
export default function ContinuousSlider() {
- const classes = useStyles();
const [value, setValue] = React.useState(30);
const handleChange = (event, newValue) => {
@@ -21,7 +18,7 @@ export default function ContinuousSlider() {
};
return (
-
+
Volume
@@ -44,6 +41,6 @@ export default function ContinuousSlider() {
Disabled slider
-
+
);
}
diff --git a/docs/src/pages/components/slider-styled/ContinuousSlider.tsx b/docs/src/pages/components/slider-styled/ContinuousSlider.tsx
index cd52b4eab9c878..2fbd5b89d16ea8 100644
--- a/docs/src/pages/components/slider-styled/ContinuousSlider.tsx
+++ b/docs/src/pages/components/slider-styled/ContinuousSlider.tsx
@@ -1,19 +1,16 @@
import * as React from 'react';
-import { makeStyles } from '@material-ui/core/styles';
+import { experimentalStyled as styled } from '@material-ui/core/styles';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import Slider from '@material-ui/lab/SliderStyled';
import VolumeDown from '@material-ui/icons/VolumeDown';
import VolumeUp from '@material-ui/icons/VolumeUp';
-const useStyles = makeStyles({
- root: {
- width: 200,
- },
+const Root = styled('div')({
+ width: 200,
});
export default function ContinuousSlider() {
- const classes = useStyles();
const [value, setValue] = React.useState(30);
const handleChange = (
@@ -24,7 +21,7 @@ export default function ContinuousSlider() {
};
return (
-
+
Volume
@@ -47,6 +44,6 @@ export default function ContinuousSlider() {
Disabled slider
-
+
);
}
diff --git a/packages/material-ui-lab/src/SliderStyled/SliderStyled.js b/packages/material-ui-lab/src/SliderStyled/SliderStyled.js
index 87732e35f6a06a..0944da1033d177 100644
--- a/packages/material-ui-lab/src/SliderStyled/SliderStyled.js
+++ b/packages/material-ui-lab/src/SliderStyled/SliderStyled.js
@@ -1,6 +1,6 @@
import * as React from 'react';
import PropTypes from 'prop-types';
-import { useThemeProps, muiStyled, fade, lighten, darken } from '@material-ui/core/styles';
+import { useThemeProps, experimentalStyled, fade, lighten, darken } from '@material-ui/core/styles';
import { capitalize } from '@material-ui/core/utils';
import SliderUnstyled from '../SliderUnstyled';
import ValueLabelStyled from './ValueLabelStyled';
@@ -48,7 +48,7 @@ const overridesResolver = (props, styles, name) => {
return styleOverrides;
};
-const SliderRoot = muiStyled(
+const SliderRoot = experimentalStyled(
'span',
{},
{ muiName: 'MuiSlider', overridesResolver },
diff --git a/packages/material-ui-lab/src/SliderStyled/ValueLabelStyled.js b/packages/material-ui-lab/src/SliderStyled/ValueLabelStyled.js
index 1ac066f95062a8..622049d96b48cb 100644
--- a/packages/material-ui-lab/src/SliderStyled/ValueLabelStyled.js
+++ b/packages/material-ui-lab/src/SliderStyled/ValueLabelStyled.js
@@ -1,5 +1,5 @@
import * as React from 'react';
-import { useThemeProps, muiStyled } from '@material-ui/core/styles';
+import { useThemeProps, experimentalStyled } from '@material-ui/core/styles';
import ValueLabelUnstyled from '../SliderUnstyled/ValueLabelUnstyled';
const overridesResolver = (_, styles) => {
@@ -16,7 +16,7 @@ const overridesResolver = (_, styles) => {
return styleOverrides;
};
-const ValueLabelRoot = muiStyled(
+const ValueLabelRoot = experimentalStyled(
'span',
{},
{ muiName: 'PrivateValueLabel', overridesResolver },
diff --git a/packages/material-ui-styled-engine-sc/src/index.js b/packages/material-ui-styled-engine-sc/src/index.js
index 126204d13924d9..8fb1b642d592be 100644
--- a/packages/material-ui-styled-engine-sc/src/index.js
+++ b/packages/material-ui-styled-engine-sc/src/index.js
@@ -10,3 +10,5 @@ export default function styled(tag, options) {
return scStyled(tag);
}
+
+export { ThemeContext } from 'styled-components';
diff --git a/packages/material-ui-styled-engine/src/index.d.ts b/packages/material-ui-styled-engine/src/index.d.ts
index 53bc27a1a267b7..3d3d28286b3347 100644
--- a/packages/material-ui-styled-engine/src/index.d.ts
+++ b/packages/material-ui-styled-engine/src/index.d.ts
@@ -1,2 +1,3 @@
export * from '@emotion/styled';
export { default } from '@emotion/styled';
+export { ThemeContext } from '@emotion/core';
diff --git a/packages/material-ui-styled-engine/src/index.js b/packages/material-ui-styled-engine/src/index.js
index 9b24cbd7fcb37d..a0befe0bc7e71d 100644
--- a/packages/material-ui-styled-engine/src/index.js
+++ b/packages/material-ui-styled-engine/src/index.js
@@ -1 +1,2 @@
export { default } from '@emotion/styled';
+export { ThemeContext } from '@emotion/core';
diff --git a/packages/material-ui/src/styles/ThemeProvider.d.ts b/packages/material-ui/src/styles/ThemeProvider.d.ts
new file mode 100644
index 00000000000000..0fef50ac9aa015
--- /dev/null
+++ b/packages/material-ui/src/styles/ThemeProvider.d.ts
@@ -0,0 +1,17 @@
+import { DefaultTheme } from '@material-ui/styles';
+
+export interface ThemeProviderProps {
+ children?: React.ReactNode;
+ theme: Partial | ((outerTheme: Theme) => Theme);
+}
+
+/**
+ * This component makes the `theme` available down the React tree.
+ * It should preferably be used at **the root of your component tree**.
+ * API:
+ *
+ * - [ThemeProvider API](https://material-ui.com/api/theme-provider/)
+ */
+export default function ThemeProvider(
+ props: ThemeProviderProps
+): React.ReactElement>;
diff --git a/packages/material-ui/src/styles/ThemeProvider.js b/packages/material-ui/src/styles/ThemeProvider.js
new file mode 100644
index 00000000000000..aec494b37905bb
--- /dev/null
+++ b/packages/material-ui/src/styles/ThemeProvider.js
@@ -0,0 +1,53 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { ThemeProvider as MuiThemeProvider } from '@material-ui/styles';
+import { exactProp } from '@material-ui/utils';
+import { ThemeContext as StyledEngineThemeContext } from '@material-ui/styled-engine';
+import useTheme from './useTheme';
+
+function InnerThemeProvider({ children }) {
+ const theme = useTheme();
+ return (
+
+ {children}
+
+ );
+}
+
+InnerThemeProvider.propTypes = {
+ /**
+ * Your component tree.
+ */
+ children: PropTypes.node,
+};
+
+/**
+ * This component makes the `theme` available down the React tree.
+ * It should preferably be used at **the root of your component tree**.
+ */
+function ThemeProvider(props) {
+ const { children, theme: localTheme } = props;
+
+ return (
+
+ {children}
+
+ );
+}
+
+ThemeProvider.propTypes = {
+ /**
+ * Your component tree.
+ */
+ children: PropTypes.node,
+ /**
+ * A theme object. You can provide a function to extend the outer theme.
+ */
+ theme: PropTypes.oneOfType([PropTypes.object, PropTypes.func]).isRequired,
+};
+
+if (process.env.NODE_ENV !== 'production') {
+ ThemeProvider.propTypes = exactProp(ThemeProvider.propTypes);
+}
+
+export default ThemeProvider;
diff --git a/packages/material-ui/src/styles/ThemeProvider.test.js b/packages/material-ui/src/styles/ThemeProvider.test.js
new file mode 100644
index 00000000000000..a5edad67ff5042
--- /dev/null
+++ b/packages/material-ui/src/styles/ThemeProvider.test.js
@@ -0,0 +1,44 @@
+import React from 'react';
+import { expect } from 'chai';
+import { createClientRender } from 'test/utils';
+import { useTheme } from '@material-ui/styles';
+import { ThemeContext } from '@material-ui/styled-engine';
+import ThemeProvider from './ThemeProvider';
+
+describe('ThemeProvider', () => {
+ const render = createClientRender();
+
+ it('should provide the theme to the mui theme context', () => {
+ let theme;
+
+ function Test() {
+ theme = useTheme();
+
+ return null;
+ }
+
+ render(
+
+
+ ,
+ );
+ expect(theme).to.deep.equal({ foo: 'foo' });
+ });
+
+ it('should provide the theme to the styled engine theme context', () => {
+ let theme;
+
+ function Test() {
+ theme = React.useContext(ThemeContext);
+
+ return null;
+ }
+
+ render(
+
+
+ ,
+ );
+ expect(theme).to.deep.equal({ foo: 'foo' });
+ });
+});
diff --git a/packages/material-ui/src/styles/muiStyled.d.ts b/packages/material-ui/src/styles/experimentalStyled.d.ts
similarity index 93%
rename from packages/material-ui/src/styles/muiStyled.d.ts
rename to packages/material-ui/src/styles/experimentalStyled.d.ts
index bd9aca8a85a51e..11e0b8256aae21 100644
--- a/packages/material-ui/src/styles/muiStyled.d.ts
+++ b/packages/material-ui/src/styles/experimentalStyled.d.ts
@@ -183,7 +183,7 @@ interface MuiStyledOptions {
overridesResolver?: (props: any, styles: string | object, name: string) => string | object;
}
-export interface CreateStyled {
+export interface CreateMUIStyled {
, ExtraProps = {}>(
tag: Tag,
options?: StyledOptions,
@@ -198,11 +198,13 @@ export interface CreateStyled {
}
/**
- * Cutom styled functionality that support mui specific config.
+ * Custom styled utility that has a default MUI theme.
*
- * @param options Takes an incomplete theme object and adds the missing parts.
- * @returns A complete, ready to use theme object.
+ * @param tag HTML tag or component that should serve as base.
+ * @param options Styled options for the created component.
+ * @param muiOptions Material-UI specific style options.
+ * @returns React component that has styles attached to it.
*/
-declare const muiStyled: CreateStyled;
+declare const experimentalStyled: CreateMUIStyled;
-export default muiStyled;
+export default experimentalStyled;
diff --git a/packages/material-ui/src/styles/muiStyled.js b/packages/material-ui/src/styles/experimentalStyled.js
similarity index 64%
rename from packages/material-ui/src/styles/muiStyled.js
rename to packages/material-ui/src/styles/experimentalStyled.js
index d86f27239a4752..4e7aff61c7d6ba 100644
--- a/packages/material-ui/src/styles/muiStyled.js
+++ b/packages/material-ui/src/styles/experimentalStyled.js
@@ -2,6 +2,10 @@ import styled from '@material-ui/styled-engine';
import { propsToClassKey } from '@material-ui/styles';
import defaultTheme from './defaultTheme';
+function isEmpty(obj) {
+ return Object.keys(obj).length === 0;
+}
+
const getStyleOverrides = (name, theme) => {
let styleOverrides = {};
@@ -41,7 +45,7 @@ const variantsResolver = (props, styles, theme, name) => {
themeVariants.forEach((themeVariant) => {
let isMatch = true;
Object.keys(themeVariant.props).forEach((key) => {
- if (styleProps[key] !== themeVariant.props[key]) {
+ if (styleProps[key] !== themeVariant.props[key] && props[key] !== themeVariant.props[key]) {
isMatch = false;
}
});
@@ -56,25 +60,34 @@ const variantsResolver = (props, styles, theme, name) => {
const shouldForwardProp = (prop) => prop !== 'styleProps' && prop !== 'theme';
-const muiStyled = (tag, options, muiOptions) => {
+const experimentalStyled = (tag, options, muiOptions = {}) => {
const name = muiOptions.muiName;
const defaultStyledResolver = styled(tag, { shouldForwardProp, label: name, ...options });
const muiStyledResolver = (...styles) => {
- if (muiOptions.overridesResolver) {
- styles.push((props) => {
- const theme = props.theme || defaultTheme;
+ const stylesWithDefaultTheme = styles.map((stylesArg) => {
+ return typeof stylesArg === 'function'
+ ? ({ theme: themeInput, ...rest }) =>
+ stylesArg({ theme: isEmpty(themeInput) ? defaultTheme : themeInput, ...rest })
+ : stylesArg;
+ });
+
+ if (name && muiOptions.overridesResolver) {
+ stylesWithDefaultTheme.push((props) => {
+ const theme = isEmpty(props.theme) ? defaultTheme : props.theme;
return muiOptions.overridesResolver(props, getStyleOverrides(name, theme), name);
});
}
- styles.push((props) => {
- const theme = props.theme || defaultTheme;
- return variantsResolver(props, getVariantStyles(name, theme), theme, name);
- });
+ if (name) {
+ stylesWithDefaultTheme.push((props) => {
+ const theme = isEmpty(props.theme) ? defaultTheme : props.theme;
+ return variantsResolver(props, getVariantStyles(name, theme), theme, name);
+ });
+ }
- return defaultStyledResolver(...styles);
+ return defaultStyledResolver(...stylesWithDefaultTheme);
};
return muiStyledResolver;
};
-export default muiStyled;
+export default experimentalStyled;
diff --git a/packages/material-ui/src/styles/experimentalStyled.test.js b/packages/material-ui/src/styles/experimentalStyled.test.js
new file mode 100644
index 00000000000000..7d6ad23a3d2c53
--- /dev/null
+++ b/packages/material-ui/src/styles/experimentalStyled.test.js
@@ -0,0 +1,156 @@
+import React from 'react';
+import { expect } from 'chai';
+import { createClientRender, screen } from 'test/utils';
+import createMuiTheme from './createMuiTheme';
+import styled from './experimentalStyled';
+import ThemeProvider from './ThemeProvider';
+
+describe('experimentalStyled', () => {
+ const render = createClientRender();
+ it('should work', () => {
+ const Div = styled('div')({
+ width: '200px',
+ });
+
+ render(Test
);
+
+ const style = window.getComputedStyle(screen.getByTestId('component'));
+ expect(style.getPropertyValue('width')).to.equal('200px');
+ });
+
+ it('should use defaultTheme if no theme is provided', () => {
+ const Div = styled('div')((props) => ({
+ width: props.theme.spacing(1),
+ }));
+
+ render(Test
);
+
+ const style = window.getComputedStyle(screen.getByTestId('component'));
+ expect(style.getPropertyValue('width')).to.equal('8px');
+ });
+
+ it('should use theme from context if available', () => {
+ const Div = styled('div')((props) => ({
+ width: props.theme.spacing(1),
+ }));
+
+ const theme = createMuiTheme({
+ spacing: 10,
+ });
+
+ render(
+
+ Test
+ ,
+ );
+
+ const style = window.getComputedStyle(screen.getByTestId('component'));
+ expect(style.getPropertyValue('width')).to.equal('10px');
+ });
+
+ describe('muiOptions', () => {
+ const theme = createMuiTheme({
+ components: {
+ MuiTest: {
+ variants: [
+ {
+ props: { variant: 'rect', size: 'large' },
+ style: {
+ width: '400px',
+ height: '400px',
+ },
+ },
+ ],
+ styleOverrides: {
+ root: {
+ width: '250px',
+ },
+ rect: {
+ height: '250px',
+ },
+ },
+ },
+ },
+ });
+
+ const testOverridesResolver = (props, styles) => ({
+ ...styles.root,
+ ...(props.variant && styles[props.variant]),
+ });
+
+ const Test = styled(
+ 'div',
+ { shouldForwardProp: (prop) => prop !== 'variant' && prop !== 'size' },
+ { muiName: 'MuiTest', overridesResolver: testOverridesResolver },
+ )`
+ width: 200px;
+ height: 300px;
+ `;
+
+ it('should work with specified muiOptions', () => {
+ render(Test);
+
+ const style = window.getComputedStyle(screen.getByTestId('component'));
+ expect(style.getPropertyValue('width')).to.equal('200px');
+ expect(style.getPropertyValue('height')).to.equal('300px');
+ });
+
+ it('overrides should be respected', () => {
+ render(
+
+ Test
+ ,
+ );
+
+ const style = window.getComputedStyle(screen.getByTestId('component'));
+ expect(style.getPropertyValue('width')).to.equal('250px');
+ expect(style.getPropertyValue('height')).to.equal('300px');
+ });
+
+ it('overrides should be respected when prop is specified', () => {
+ render(
+
+
+ Test
+
+ ,
+ );
+
+ const style = window.getComputedStyle(screen.getByTestId('component'));
+ expect(style.getPropertyValue('width')).to.equal('250px');
+ expect(style.getPropertyValue('height')).to.equal('250px');
+ });
+
+ it('variants should win over overrides', () => {
+ render(
+
+
+ Test
+
+ ,
+ );
+
+ const style = window.getComputedStyle(screen.getByTestId('component'));
+ expect(style.getPropertyValue('width')).to.equal('400px');
+ expect(style.getPropertyValue('height')).to.equal('400px');
+ });
+
+ it('styled wrapper should win over variants', () => {
+ const CustomTest = styled(Test)`
+ width: 500px;
+ `;
+
+ render(
+
+
+ Test
+
+ ,
+ );
+
+ const style = window.getComputedStyle(screen.getByTestId('component'));
+ expect(style.getPropertyValue('width')).to.equal('500px');
+ expect(style.getPropertyValue('height')).to.equal('400px');
+ });
+ });
+});
diff --git a/packages/material-ui/src/styles/index.d.ts b/packages/material-ui/src/styles/index.d.ts
index 8006e6928cbee4..16ba132723f684 100644
--- a/packages/material-ui/src/styles/index.d.ts
+++ b/packages/material-ui/src/styles/index.d.ts
@@ -24,16 +24,18 @@ export {
StyledComponentProps,
} from './withStyles';
export { default as withTheme, WithTheme } from './withTheme';
-export { default as muiStyled } from './muiStyled';
+export { default as experimentalStyled, CreateMUIStyled } from './experimentalStyled';
export { default as styled, ComponentCreator, StyledProps } from './styled';
+export {
+ default as MuiThemeProvider,
+ default as ThemeProvider,
+ ThemeProviderProps,
+} from './ThemeProvider';
export {
createGenerateClassName,
jssPreset,
ServerStyleSheets,
StylesProvider,
- ThemeProvider as MuiThemeProvider,
- ThemeProvider,
- ThemeProviderProps,
} from '@material-ui/styles';
export { ComponentsProps } from './props';
export { ComponentsVariants } from './variants';
diff --git a/packages/material-ui/src/styles/index.js b/packages/material-ui/src/styles/index.js
index 6607732a24f0e1..b2e5dd8f49ff96 100644
--- a/packages/material-ui/src/styles/index.js
+++ b/packages/material-ui/src/styles/index.js
@@ -12,13 +12,12 @@ export { default as useTheme } from './useTheme';
export { default as useThemeProps } from './useThemeProps';
export { default as withStyles } from './withStyles';
export { default as withTheme } from './withTheme';
-export { default as muiStyled } from './muiStyled';
+export { default as experimentalStyled } from './experimentalStyled';
+export { default as MuiThemeProvider, default as ThemeProvider } from './ThemeProvider';
export {
createGenerateClassName,
jssPreset,
ServerStyleSheets,
StylesProvider,
- ThemeProvider as MuiThemeProvider,
- ThemeProvider,
useThemeVariants,
} from '@material-ui/styles';