Skip to content

Commit

Permalink
[system] Introduce theme scope for using multiple design systems (#36664
Browse files Browse the repository at this point in the history
)
  • Loading branch information
siriwatknp committed Apr 11, 2023
1 parent df6ff62 commit 4890ace
Show file tree
Hide file tree
Showing 50 changed files with 1,073 additions and 449 deletions.

Large diffs are not rendered by default.

87 changes: 87 additions & 0 deletions docs/data/material/guides/styled-engine/styled-engine.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,90 @@ You can use these `styled-component` examples as a reference:
:::

This package-swap approach is identical to the replacement of React with [Preact](https://github.com/preactjs/preact). The Preact team has documented a large number of installation configurations. If you are stuck with Material UI + styled-components, don't hesitate to check out how they solve the problem, as you can likely transfer the solution.

## Theme scoping

:::warning
Having more than one styling libraries could introduce unnecessary complexity to your project. You should have a very good reason to do this.
:::

Material UI can coexist with other libraries that depend on emotion or styled-components. To do that, render Material UI's `ThemeProvider` as an inner provider and use the `THEME_ID` to store the theme.

```js
import { ThemeProvider, THEME_ID, createTheme } from '@mui/material/styles';
import { AnotherThemeProvider } from 'another-ui-library';

const materialTheme = createTheme(…your theme);

function App() {
return (
<AnotherThemeProvider>
<ThemeProvider theme={{ [THEME_ID]: materialTheme }}>
…components from another library and Material UI
</ThemeProvider>
</AnotherThemeProvider>
)
}
```

The theme of Material UI will be separated from the other library, so when you use APIs such as `styled`, `sx` prop, and `useTheme`, you will be able to access Material UI's theme like you normally would.

### Using with [Theme UI](https://theme-ui.com/)

Render Material UI's theme provider below Theme UI's provider and assign the material theme to the `THEME_ID` property.

```js
import { ThemeProvider as ThemeUIThemeProvider } from 'theme-ui';
import { createTheme as materialCreateTheme, THEME_ID } from '@mui/material/styles';

const themeUITheme = {
fonts: {
body: 'system-ui, sans-serif',
heading: '"Avenir Next", sans-serif',
monospace: 'Menlo, monospace',
},
colors: {
text: '#000',
background: '#fff',
primary: '#33e',
},
};

const materialTheme = materialCreateTheme();

function App() {
return (
<ThemeUIThemeProvider theme={themeUITheme}>
<MaterialThemeProvider theme={{ [THEME_ID]: materialTheme }}>
Theme UI components and Material UI components
</MaterialThemeProvider>
</ThemeUIThemeProvider>
);
}
```

### Using with Chakra UI

Render Material UI's theme provider below Chakra UI's provider and assign the material theme to the `THEME_ID` property.

```js
import { ChakraProvider, chakraExtendTheme } from '@chakra-ui/react';
import {
ThemeProvider as MaterialThemeProvider,
createTheme as muiCreateTheme,
THEME_ID,
} from '@mui/material/styles';

const chakraTheme = chakraExtendTheme();
const materialTheme = muiCreateTheme();

function App() {
return (
<ChakraProvider theme={chakraTheme} resetCSS>
<MaterialThemeProvider theme={{ [THEME_ID]: materialTheme }}>
Chakra UI components and Material UI components
</MaterialThemeProvider>
</ChakraProvider>
);
}
```
2 changes: 1 addition & 1 deletion docs/pages/material-ui/api/global-styles.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"styles": {
"type": {
"name": "union",
"description": "func<br>&#124;&nbsp;number<br>&#124;&nbsp;object<br>&#124;&nbsp;{ __emotion_styles: any }<br>&#124;&nbsp;string<br>&#124;&nbsp;bool"
"description": "array<br>&#124;&nbsp;func<br>&#124;&nbsp;number<br>&#124;&nbsp;object<br>&#124;&nbsp;string<br>&#124;&nbsp;bool"
}
}
},
Expand Down
3 changes: 2 additions & 1 deletion packages/api-docs-builder/buildApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,8 @@ async function run(argv: yargs.ArgumentsCamelCase<CommandOptions>) {
// Container's demo isn't ready
// Grid has problem with react-docgen
// Stack has problem with react-docgen
component.filename.match(/(Box|Container|ColorInversion|Grid|Stack)/))
component.filename.match(/(Box|Container|ColorInversion|Grid|Stack)/)) ||
(component.filename.includes('mui-system') && component.filename.match(/GlobalStyles/))
) {
return false;
}
Expand Down
2 changes: 2 additions & 0 deletions packages/mui-joy/src/Box/Box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import PropTypes from 'prop-types';
import { unstable_ClassNameGenerator as ClassNameGenerator } from '../className';
import { Theme } from '../styles/types';
import defaultTheme from '../styles/defaultTheme';
import THEME_ID from '../styles/identifier';

const Box = createBox<Theme>({
themeId: THEME_ID,
defaultTheme,
defaultClassName: 'MuiBox-root',
generateClassName: ClassNameGenerator.generate,
Expand Down
6 changes: 0 additions & 6 deletions packages/mui-joy/src/GlobalStyles/index.ts

This file was deleted.

11 changes: 11 additions & 0 deletions packages/mui-joy/src/GlobalStyles/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as React from 'react';
import { GlobalStyles as SystemGlobalStyles, GlobalStylesProps } from '@mui/system';
import defaultTheme from '../styles/defaultTheme';
import THEME_ID from '../styles/identifier';
import { Theme } from '../styles/types';

function GlobalStyles(props: GlobalStylesProps<Theme>) {
return <SystemGlobalStyles {...props} defaultTheme={defaultTheme} themeId={THEME_ID} />;
}

export default GlobalStyles;
5 changes: 4 additions & 1 deletion packages/mui-joy/src/styles/CssVarsProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import defaultTheme from './defaultTheme';
import { CssVarsThemeOptions } from './extendTheme';
import { createSoftInversion, createSolidInversion } from './variantUtils';
import type { Theme, DefaultColorScheme, ExtendedColorScheme } from './types';
import THEME_ID from './identifier';

const { CssVarsProvider, useColorScheme, getInitColorSchemeScript } = createCssVarsProvider<
DefaultColorScheme | ExtendedColorScheme
DefaultColorScheme | ExtendedColorScheme,
typeof THEME_ID
>({
themeId: THEME_ID,
theme: defaultTheme,
attribute: 'data-joy-color-scheme',
modeStorageKey: 'joy-mode',
Expand Down
25 changes: 21 additions & 4 deletions packages/mui-joy/src/styles/ThemeProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,37 @@ import * as React from 'react';
import { ThemeProvider as SystemThemeProvider, useTheme as useSystemTheme } from '@mui/system';
import defaultTheme from './defaultTheme';
import extendTheme from './extendTheme';
import THEME_ID from './identifier';
import type { Theme } from './types';
import type { CssVarsThemeOptions } from './extendTheme';

export const useTheme = () => {
return useSystemTheme(defaultTheme);
export const useTheme = (): Theme => {
const theme = useSystemTheme(defaultTheme);

if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line react-hooks/rules-of-hooks
React.useDebugValue(theme);
}

// @ts-ignore internal logic
return theme[THEME_ID] || theme;
};

export default function ThemeProvider({
children,
theme: themeInput,
}: React.PropsWithChildren<{
theme?: CssVarsThemeOptions;
theme?: CssVarsThemeOptions | { [k in typeof THEME_ID]: CssVarsThemeOptions };
}>) {
let theme = defaultTheme;
if (themeInput) {
theme = extendTheme(THEME_ID in themeInput ? themeInput[THEME_ID] : themeInput);
}
return (
<SystemThemeProvider theme={themeInput ? extendTheme(themeInput) : defaultTheme}>
<SystemThemeProvider
theme={theme}
themeId={themeInput && THEME_ID in themeInput ? THEME_ID : undefined}
>
{children}
</SystemThemeProvider>
);
Expand Down
1 change: 1 addition & 0 deletions packages/mui-joy/src/styles/identifier.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default '$$joy';
1 change: 1 addition & 0 deletions packages/mui-joy/src/styles/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export type {
ThemeCssVar,
ThemeCssVarOverrides,
} from './types/theme';
export { default as THEME_ID } from './identifier';
export { CssVarsProvider, useColorScheme, getInitColorSchemeScript } from './CssVarsProvider';
export { default as shouldSkipGeneratingVar } from './shouldSkipGeneratingVar';
export { default as styled } from './styled';
Expand Down
3 changes: 2 additions & 1 deletion packages/mui-joy/src/styles/styled.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { createStyled } from '@mui/system';
import { Theme } from './types';
import defaultTheme from './defaultTheme';
import THEME_ID from './identifier';

const styled = createStyled<Theme>({ defaultTheme });
const styled = createStyled<Theme>({ defaultTheme, themeId: THEME_ID });

export default styled;
8 changes: 7 additions & 1 deletion packages/mui-joy/src/styles/useThemeProps.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useThemeProps as systemUseThemeProps } from '@mui/system';
import defaultTheme from './defaultTheme';
import THEME_ID from './identifier';

export default function useThemeProps<T extends {}>({
props,
Expand All @@ -8,5 +9,10 @@ export default function useThemeProps<T extends {}>({
props: T & {};
name: string;
}) {
return systemUseThemeProps({ props, name, defaultTheme: { ...defaultTheme, components: {} } });
return systemUseThemeProps({
props,
name,
defaultTheme: { ...defaultTheme, components: {} },
themeId: THEME_ID,
});
}
2 changes: 2 additions & 0 deletions packages/mui-material-next/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ export * from './Tab';

export { default as TabScrollButton } from './TabScrollButton';
export * from './TabScrollButton';

export * from './styles';
48 changes: 26 additions & 22 deletions packages/mui-material-next/src/styles/CssVarsProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,39 @@ import {
SxProps,
} from '@mui/system';
import {
THEME_ID,
SupportedColorScheme,
private_createTypography as createTypography,
private_excludeVariablesFromRoot as excludeVariablesFromRoot,
} from '@mui/material/styles';
import { Theme } from './Theme.types';
import defaultTheme from './defaultTheme';

const { CssVarsProvider, useColorScheme, getInitColorSchemeScript } =
createCssVarsProvider<SupportedColorScheme>({
theme: defaultTheme,
attribute: 'data-mui-color-scheme',
modeStorageKey: 'mui-mode',
colorSchemeStorageKey: 'mui-color-scheme',
defaultColorScheme: {
light: 'light',
dark: 'dark',
},
resolveTheme: (theme) => {
const newTheme = {
...theme,
typography: createTypography(theme.palette, theme.typography),
};
const { CssVarsProvider, useColorScheme, getInitColorSchemeScript } = createCssVarsProvider<
SupportedColorScheme,
typeof THEME_ID
>({
themeId: THEME_ID,
theme: defaultTheme,
attribute: 'data-mui-color-scheme',
modeStorageKey: 'mui-mode',
colorSchemeStorageKey: 'mui-color-scheme',
defaultColorScheme: {
light: 'light',
dark: 'dark',
},
resolveTheme: (theme) => {
const newTheme = {
...theme,
typography: createTypography(theme.palette, theme.typography),
};

newTheme.unstable_sx = function sx(props: SxProps<Theme>) {
return styleFunctionSx({ sx: props, theme: this });
};
return newTheme;
},
excludeVariablesFromRoot,
});
newTheme.unstable_sx = function sx(props: SxProps<Theme>) {
return styleFunctionSx({ sx: props, theme: this });
};
return newTheme;
},
excludeVariablesFromRoot,
});

export { useColorScheme, getInitColorSchemeScript, CssVarsProvider };
3 changes: 2 additions & 1 deletion packages/mui-material-next/src/styles/styled.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { createStyled } from '@mui/system';
import { THEME_ID } from '@mui/material/styles';
import { Theme } from './Theme.types';
import defaultTheme from './defaultTheme';

const styled = createStyled<Theme>({ defaultTheme });
const styled = createStyled<Theme>({ defaultTheme, themeId: THEME_ID });

export default styled;
2 changes: 2 additions & 0 deletions packages/mui-material/src/Box/Box.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import { createBox } from '@mui/system';
import PropTypes from 'prop-types';
import { unstable_ClassNameGenerator as ClassNameGenerator } from '../className';
import { createTheme } from '../styles';
import THEME_ID from '../styles/identifier';

const defaultTheme = createTheme();

const Box = createBox({
themeId: THEME_ID,
defaultTheme,
defaultClassName: 'MuiBox-root',
generateClassName: ClassNameGenerator.generate,
Expand Down
9 changes: 4 additions & 5 deletions packages/mui-material/src/GlobalStyles/GlobalStyles.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import * as React from 'react';
import PropTypes from 'prop-types';
import { GlobalStyles as SystemGlobalStyles } from '@mui/system';
import defaultTheme from '../styles/defaultTheme';
import THEME_ID from '../styles/identifier';

function GlobalStyles(props) {
return <SystemGlobalStyles {...props} defaultTheme={defaultTheme} />;
return <SystemGlobalStyles {...props} defaultTheme={defaultTheme} themeId={THEME_ID} />;
}

GlobalStyles.propTypes /* remove-proptypes */ = {
Expand All @@ -15,13 +16,11 @@ GlobalStyles.propTypes /* remove-proptypes */ = {
/**
* The styles you want to apply globally.
*/
styles: PropTypes.oneOfType([
styles: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([
PropTypes.array,
PropTypes.func,
PropTypes.number,
PropTypes.object,
PropTypes.shape({
__emotion_styles: PropTypes.any.isRequired,
}),
PropTypes.string,
PropTypes.bool,
]),
Expand Down
Loading

0 comments on commit 4890ace

Please sign in to comment.