From 231b39bae838454001a3285d1dc33f48e6e0c0af Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 31 Dec 2022 12:32:17 +0100 Subject: [PATCH] theme: add v5->v4 component theme adapter Signed-off-by: Patrik Oldsberg --- packages/theme/src/compat/overrides.test.ts | 138 ++++++++++++++++++++ packages/theme/src/compat/overrides.ts | 71 ++++++++++ 2 files changed, 209 insertions(+) create mode 100644 packages/theme/src/compat/overrides.test.ts create mode 100644 packages/theme/src/compat/overrides.ts diff --git a/packages/theme/src/compat/overrides.test.ts b/packages/theme/src/compat/overrides.test.ts new file mode 100644 index 0000000000..68fd883168 --- /dev/null +++ b/packages/theme/src/compat/overrides.test.ts @@ -0,0 +1,138 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Theme } from '@mui/material'; +import { transformV5ComponentThemesToV4 } from './overrides'; + +describe('transformV5ComponentThemesToV4', () => { + const mockTheme = { + palette: { + primary: { + main: 'red', + }, + }, + } as unknown as Theme; + it('transforms empty component themes', () => { + expect(transformV5ComponentThemesToV4(mockTheme)).toEqual({ + overrides: {}, + props: {}, + }); + expect(transformV5ComponentThemesToV4(mockTheme, {})).toEqual({ + overrides: {}, + props: {}, + }); + expect( + transformV5ComponentThemesToV4(mockTheme, { + MuiButton: { + styleOverrides: undefined, + defaultProps: undefined, + }, + }), + ).toEqual({ overrides: {}, props: {} }); + expect( + transformV5ComponentThemesToV4(mockTheme, { + MuiButton: { + styleOverrides: {}, + defaultProps: {}, + }, + }), + ).toEqual({ overrides: { MuiButton: {} }, props: { MuiButton: {} } }); + }); + + it('transforms component themes', () => { + expect( + transformV5ComponentThemesToV4(mockTheme, { + MuiButton: { + styleOverrides: { + root: { + color: 'green', + }, + }, + defaultProps: { + disableRipple: true, + }, + }, + }), + ).toEqual({ + overrides: { + MuiButton: { + root: { + color: 'green', + }, + }, + }, + props: { + MuiButton: { + disableRipple: true, + }, + }, + }); + expect( + transformV5ComponentThemesToV4(mockTheme, { + MuiButton: { + styleOverrides: { + root: ({ theme }) => ({ + color: theme.palette.primary.main, + }), + }, + }, + }), + ).toEqual({ + overrides: { + MuiButton: { + root: { + color: 'red', + }, + }, + }, + props: {}, + }); + }); + + it('transforms CSSBaseline theme', () => { + expect( + transformV5ComponentThemesToV4(mockTheme, { + MuiCssBaseline: { + styleOverrides: theme => ({ + '@global': { + html: { + color: theme.palette.primary.main, + }, + }, + }), + defaultProps: { + enableColorScheme: true, + }, + }, + }), + ).toEqual({ + overrides: { + MuiCssBaseline: { + '@global': { + html: { + color: 'red', + }, + }, + }, + }, + props: { + MuiCssBaseline: { + enableColorScheme: true, + }, + }, + }); + }); +}); diff --git a/packages/theme/src/compat/overrides.ts b/packages/theme/src/compat/overrides.ts new file mode 100644 index 0000000000..0ada593740 --- /dev/null +++ b/packages/theme/src/compat/overrides.ts @@ -0,0 +1,71 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Overrides } from '@material-ui/core/styles/overrides'; +import { ComponentsProps } from '@material-ui/core/styles/props'; +import { ComponentsOverrides, Theme } from '@mui/material/styles'; + +type V5Override = ComponentsOverrides[keyof ComponentsOverrides]; +type V4Override = Overrides[keyof Overrides]; + +// Converts callback-based overrides to static styles, e.g. +// { root: theme => ({ color: theme.color }) } -> { root: { color: 'red' } } +function adaptV5Override(theme: Theme, overrides: V5Override): V4Override { + if (!overrides) { + return overrides as V4Override; + } + if (typeof overrides === 'function') { + return overrides(theme) as V4Override; + } + if (typeof overrides === 'object') { + return Object.fromEntries( + Object.entries(overrides).map(([className, style]) => { + if (typeof style === 'function') { + return [className, style({ theme })]; + } + return [className, style]; + }), + ); + } + return overrides as V4Override; +} + +// Transform v5 theme overrides into a v4 theme, by converting the callback-based overrides +export function transformV5ComponentThemesToV4( + theme: Theme, + components: Theme['components'] = {}, +): { overrides: Overrides; props: ComponentsProps } { + const overrides: Record = {}; + const props: Record = {}; + + for (const name of Object.keys(components)) { + const component = components[name as keyof typeof components]; + if (!component) { + continue; + } + if ('styleOverrides' in component) { + overrides[name] = adaptV5Override( + theme, + component.styleOverrides as V5Override, + ); + } + if ('defaultProps' in component) { + props[name] = component.defaultProps; + } + } + + return { overrides, props }; +}