Skip to content

Commit

Permalink
[muiStyled] Support default theme when none is available (#22791)
Browse files Browse the repository at this point in the history
  • Loading branch information
mnajdova authored Oct 2, 2020
1 parent 358a811 commit aa68974
Show file tree
Hide file tree
Showing 17 changed files with 376 additions and 44 deletions.
15 changes: 15 additions & 0 deletions docs/pages/api-docs/theme-provider.js
Original file line number Diff line number Diff line change
@@ -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 <MarkdownDocs docs={docs} />;
}

Page.getInitialProps = () => {
const { demos, docs } = prepareMarkdown({ pageFilename, requireRaw });
return { demos, docs };
};
33 changes: 33 additions & 0 deletions docs/pages/api-docs/theme-provider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
filename: /packages/material-ui/src/styles/ThemeProvider.js
---

<!--- This documentation is automatically generated, do not try to edit it. -->

# ThemeProvider API

<p class="description">The API documentation of the ThemeProvider React component. Learn more about the props and the CSS customization points.</p>

## 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.


13 changes: 5 additions & 8 deletions docs/src/pages/components/slider-styled/ContinuousSlider.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,24 @@
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) => {
setValue(newValue);
};

return (
<div className={classes.root}>
<Root>
<Typography id="continuous-slider" gutterBottom>
Volume
</Typography>
Expand All @@ -44,6 +41,6 @@ export default function ContinuousSlider() {
Disabled slider
</Typography>
<Slider disabled defaultValue={30} aria-labelledby="disabled-slider" />
</div>
</Root>
);
}
13 changes: 5 additions & 8 deletions docs/src/pages/components/slider-styled/ContinuousSlider.tsx
Original file line number Diff line number Diff line change
@@ -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<number>(30);

const handleChange = (
Expand All @@ -24,7 +21,7 @@ export default function ContinuousSlider() {
};

return (
<div className={classes.root}>
<Root>
<Typography id="continuous-slider" gutterBottom>
Volume
</Typography>
Expand All @@ -47,6 +44,6 @@ export default function ContinuousSlider() {
Disabled slider
</Typography>
<Slider disabled defaultValue={30} aria-labelledby="disabled-slider" />
</div>
</Root>
);
}
4 changes: 2 additions & 2 deletions packages/material-ui-lab/src/SliderStyled/SliderStyled.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -48,7 +48,7 @@ const overridesResolver = (props, styles, name) => {
return styleOverrides;
};

const SliderRoot = muiStyled(
const SliderRoot = experimentalStyled(
'span',
{},
{ muiName: 'MuiSlider', overridesResolver },
Expand Down
4 changes: 2 additions & 2 deletions packages/material-ui-lab/src/SliderStyled/ValueLabelStyled.js
Original file line number Diff line number Diff line change
@@ -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) => {
Expand All @@ -16,7 +16,7 @@ const overridesResolver = (_, styles) => {
return styleOverrides;
};

const ValueLabelRoot = muiStyled(
const ValueLabelRoot = experimentalStyled(
'span',
{},
{ muiName: 'PrivateValueLabel', overridesResolver },
Expand Down
2 changes: 2 additions & 0 deletions packages/material-ui-styled-engine-sc/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ export default function styled(tag, options) {

return scStyled(tag);
}

export { ThemeContext } from 'styled-components';
1 change: 1 addition & 0 deletions packages/material-ui-styled-engine/src/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from '@emotion/styled';
export { default } from '@emotion/styled';
export { ThemeContext } from '@emotion/core';
1 change: 1 addition & 0 deletions packages/material-ui-styled-engine/src/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default } from '@emotion/styled';
export { ThemeContext } from '@emotion/core';
17 changes: 17 additions & 0 deletions packages/material-ui/src/styles/ThemeProvider.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { DefaultTheme } from '@material-ui/styles';

export interface ThemeProviderProps<Theme = DefaultTheme> {
children?: React.ReactNode;
theme: Partial<Theme> | ((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<T = DefaultTheme>(
props: ThemeProviderProps<T>
): React.ReactElement<ThemeProviderProps<T>>;
53 changes: 53 additions & 0 deletions packages/material-ui/src/styles/ThemeProvider.js
Original file line number Diff line number Diff line change
@@ -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 (
<StyledEngineThemeContext.Provider value={typeof theme === 'object' ? theme : {}}>
{children}
</StyledEngineThemeContext.Provider>
);
}

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 (
<MuiThemeProvider theme={localTheme}>
<InnerThemeProvider>{children}</InnerThemeProvider>
</MuiThemeProvider>
);
}

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;
44 changes: 44 additions & 0 deletions packages/material-ui/src/styles/ThemeProvider.test.js
Original file line number Diff line number Diff line change
@@ -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(
<ThemeProvider theme={{ foo: 'foo' }}>
<Test />
</ThemeProvider>,
);
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(
<ThemeProvider theme={{ foo: 'foo' }}>
<Test />
</ThemeProvider>,
);
expect(theme).to.deep.equal({ foo: 'foo' });
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ interface MuiStyledOptions<Theme extends object = any> {
overridesResolver?: (props: any, styles: string | object, name: string) => string | object;
}

export interface CreateStyled<Theme extends object = any> {
export interface CreateMUIStyled<Theme extends object = any> {
<Tag extends React.ComponentType<any>, ExtraProps = {}>(
tag: Tag,
options?: StyledOptions,
Expand All @@ -198,11 +198,13 @@ export interface CreateStyled<Theme extends object = any> {
}

/**
* 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;
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {};

Expand Down Expand Up @@ -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;
}
});
Expand All @@ -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;
Loading

0 comments on commit aa68974

Please sign in to comment.