Merge pull request #6874 from thornbill/mui-css-vars

Migrate MUI themes to CSS vars
This commit is contained in:
Bill Thornton
2025-06-01 19:14:15 -04:00
committed by GitHub
11 changed files with 145 additions and 164 deletions

View File

@@ -13,7 +13,7 @@ const ElevationScroll = ({ children, elevate = false }: { children: ReactElement
const isElevated = elevate || trigger;
return React.cloneElement(children, {
color: isElevated ? 'primary' : 'transparent',
color: isElevated ? 'default' : 'transparent',
elevation: isElevated ? 4 : 0
});
};

View File

@@ -1,12 +1,14 @@
import { useThemes } from './useThemes';
import { useUserSettings } from './useUserSettings';
const FALLBACK_THEME_ID = 'dark';
export function useUserTheme() {
const { theme, dashboardTheme } = useUserSettings();
const { defaultTheme } = useThemes();
return {
theme: theme || defaultTheme?.id,
dashboardTheme: dashboardTheme || defaultTheme?.id
theme: theme || defaultTheme?.id || FALLBACK_THEME_ID,
dashboardTheme: dashboardTheme || defaultTheme?.id || FALLBACK_THEME_ID
};
}

View File

@@ -1,11 +1,11 @@
import { ThemeProvider } from '@mui/material/styles';
import { type SupportedColorScheme, ThemeProvider, useColorScheme } from '@mui/material/styles';
import React, { type FC, type PropsWithChildren, useState, useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { DASHBOARD_APP_PATHS } from 'apps/dashboard/routes/routes';
import { useUserTheme } from 'hooks/useUserTheme';
import { DEFAULT_THEME, getTheme } from './themes';
import appTheme, { COLOR_SCHEMES } from './themes';
const isDashboardThemePage = (pathname: string) => [
// NOTE: The metadata manager doesn't seem to use the dashboard theme
@@ -13,10 +13,9 @@ const isDashboardThemePage = (pathname: string) => [
DASHBOARD_APP_PATHS.PluginConfig
].some(path => pathname.startsWith(`/${path}`));
const UserThemeProvider: FC<PropsWithChildren<unknown>> = ({ children }) => {
const ColorSchemeSwitcher: FC = () => {
const [ isDashboard, setIsDashboard ] = useState(false);
const [ muiTheme, setMuiTheme ] = useState(DEFAULT_THEME);
const { setColorScheme, setMode } = useColorScheme();
const location = useLocation();
const { theme, dashboardTheme } = useUserTheme();
@@ -26,15 +25,20 @@ const UserThemeProvider: FC<PropsWithChildren<unknown>> = ({ children }) => {
}, [ location.pathname ]);
useEffect(() => {
if (isDashboard) {
setMuiTheme(getTheme(dashboardTheme));
} else {
setMuiTheme(getTheme(theme));
}
}, [ dashboardTheme, isDashboard, theme ]);
const currentSchemeName = (isDashboard ? dashboardTheme : theme) as SupportedColorScheme;
const currentScheme = COLOR_SCHEMES[currentSchemeName];
setColorScheme(currentSchemeName);
setMode(currentScheme.palette?.mode || 'dark');
}, [ dashboardTheme, isDashboard, setColorScheme, setMode, theme ]);
return null;
};
const UserThemeProvider: FC<PropsWithChildren<unknown>> = ({ children }) => {
return (
<ThemeProvider theme={muiTheme}>
<ThemeProvider theme={appTheme} defaultMode='dark'>
<ColorSchemeSwitcher />
{children}
</ThemeProvider>
);

View File

@@ -1,27 +0,0 @@
import createTheme, { type ThemeOptions } from '@mui/material/styles/createTheme';
import merge from 'lodash-es/merge';
import { DEFAULT_THEME_OPTIONS } from 'themes/defaults';
const themeOptions: ThemeOptions = {
palette: {
mode: 'light',
background: {
default: '#d5e9f2',
paper: '#fff'
}
},
components: {
MuiAppBar: {
styleOverrides: {
colorPrimary: {
backgroundColor: '#bcbcbc'
}
}
}
}
};
const theme = createTheme(merge({}, DEFAULT_THEME_OPTIONS, themeOptions));
export default theme;

View File

@@ -1,16 +0,0 @@
import createTheme, { type ThemeOptions } from '@mui/material/styles/createTheme';
import merge from 'lodash-es/merge';
import { DEFAULT_THEME_OPTIONS } from 'themes/defaults';
const options: ThemeOptions = {
palette: {
background: {
paper: '#011432'
}
}
};
const theme = createTheme(merge({}, DEFAULT_THEME_OPTIONS, options));
export default theme;

View File

@@ -1,7 +0,0 @@
import createTheme from '@mui/material/styles/createTheme';
import { DEFAULT_THEME_OPTIONS } from 'themes/defaults';
const theme = createTheme(DEFAULT_THEME_OPTIONS);
export default theme;

View File

@@ -1,8 +1,9 @@
import type { ThemeOptions } from '@mui/material/styles/createTheme';
import type { ColorSystemOptions, ThemeOptions } from '@mui/material/styles';
const LIST_ICON_WIDTH = 36;
export const DEFAULT_THEME_OPTIONS: ThemeOptions = {
/** The default "Dark" color scheme. */
export const DEFAULT_COLOR_SCHEME: ColorSystemOptions = {
palette: {
mode: 'dark',
primary: {
@@ -24,7 +25,11 @@ export const DEFAULT_THEME_OPTIONS: ThemeOptions = {
error: {
main: '#cb272a' // Red color
}
},
}
};
/** The default customizations to the default MUI theme. */
export const DEFAULT_THEME_OPTIONS: ThemeOptions = {
typography: {
fontFamily: '"Noto Sans", sans-serif',
button: {

View File

@@ -1,30 +0,0 @@
import createTheme, { type ThemeOptions } from '@mui/material/styles/createTheme';
import merge from 'lodash-es/merge';
import { DEFAULT_THEME_OPTIONS } from 'themes/defaults';
const options: ThemeOptions = {
palette: {
mode: 'light',
background: {
default: '#f2f2f2',
// NOTE: The original theme uses #303030 for the drawer and app bar but we would need the drawer to use
// dark mode for a color that dark to work properly which would require a separate ThemeProvider just for
// the drawer... which is not worth the trouble in my opinion
paper: '#e8e8e8'
}
},
components: {
MuiAppBar: {
styleOverrides: {
colorPrimary: {
backgroundColor: '#e8e8e8'
}
}
}
}
};
const theme = createTheme(merge({}, DEFAULT_THEME_OPTIONS, options));
export default theme;

View File

@@ -1,22 +0,0 @@
import createTheme, { type ThemeOptions } from '@mui/material/styles/createTheme';
import merge from 'lodash-es/merge';
import { DEFAULT_THEME_OPTIONS } from 'themes/defaults';
const options: ThemeOptions = {
palette: {
background: {
paper: '#000420'
},
primary: {
main: '#48c3c8'
},
secondary: {
main: '#ff77f1'
}
}
};
const theme = createTheme(merge({}, DEFAULT_THEME_OPTIONS, options));
export default theme;

View File

@@ -1,13 +1,17 @@
import { type Theme } from '@mui/material/styles';
import { type ColorSystemOptions, createTheme, extendTheme } from '@mui/material/styles';
import merge from 'lodash-es/merge';
import appletv from './appletv';
import blueradiance from './blueradiance';
import dark from './dark';
import light from './light';
import purplehaze from './purplehaze';
import wmc from './wmc';
import { DEFAULT_COLOR_SCHEME, DEFAULT_THEME_OPTIONS } from './defaults';
/** Extend MUI types to include our customizations. */
declare module '@mui/material/styles' {
interface ColorSchemeOverrides {
appletv: true;
blueradiance: true;
purplehaze: true;
wmc: true;
}
interface Palette {
starIcon: Palette['primary'];
}
@@ -17,32 +21,116 @@ declare module '@mui/material/styles' {
}
}
const ALL_THEMES = {
/** The default built-in MUI theme. */
const defaultMuiTheme = extendTheme({
// @ts-expect-error The default theme does not include our custom color schemes
colorSchemes: { dark: true, light: true }
});
/**
* Default color schemes ('dark' or 'light') will automatically be merged with MUI's corresponding default color
* scheme. For custom schemes, we need to merge these manually.
*/
const buildCustomColorScheme = (options: ColorSystemOptions) => merge(
{},
options.palette?.mode === 'light' ? defaultMuiTheme.colorSchemes.light : defaultMuiTheme.colorSchemes.dark,
DEFAULT_COLOR_SCHEME,
options
);
/** The Apple TV inspired color scheme. */
const appletv = buildCustomColorScheme({
palette: {
mode: 'light',
background: {
default: '#d5e9f2',
paper: '#fff'
},
AppBar: {
defaultBg: '#bcbcbc'
}
}
});
/** The "Blue Radiance" color scheme. */
const blueradiance = buildCustomColorScheme({
palette: {
background: {
paper: '#011432'
},
AppBar: {
defaultBg: '#011432'
}
}
});
/** The "Light" color scheme. */
const light = merge({}, DEFAULT_COLOR_SCHEME, {
palette: {
mode: 'light',
background: {
default: '#f2f2f2',
// NOTE: The original theme uses #303030 for the drawer and app bar but we would need the drawer to use
// dark mode for a color that dark to work properly which would require a separate ThemeProvider just for
// the drawer... which is not worth the trouble in my opinion
paper: '#e8e8e8'
},
AppBar: {
defaultBg: '#e8e8e8'
}
}
});
/** The "Purple Haze" color scheme. */
const purplehaze = buildCustomColorScheme({
palette: {
background: {
paper: '#000420'
},
primary: {
main: '#48c3c8'
},
secondary: {
main: '#ff77f1'
},
AppBar: {
defaultBg: '#000420'
}
}
});
/** The Windows Media Center inspired color scheme. */
const wmc = buildCustomColorScheme({
palette: {
background: {
paper: '#0c2450'
},
AppBar: {
defaultBg: '#0c2450'
}
}
});
/** All color scheme variants in the app. */
export const COLOR_SCHEMES = {
appletv,
blueradiance,
dark,
dark: DEFAULT_COLOR_SCHEME,
light,
purplehaze,
wmc
};
/** The default theme if a user has not selected a preferred theme. */
export const DEFAULT_THEME = dark;
/** The default theme containing all color scheme variants. */
const DEFAULT_THEME = createTheme({
cssVariables: {
cssVarPrefix: 'jf',
colorSchemeSelector: 'data',
disableCssColorScheme: true
},
defaultColorScheme: 'dark',
...DEFAULT_THEME_OPTIONS,
colorSchemes: COLOR_SCHEMES
});
/**
* Gets a MUI Theme by its string id. Returns the default theme if no matching theme is found.
*/
export function getTheme(id?: string): Theme {
if (!id) {
console.info('[getTheme] no theme id; returning default theme');
return DEFAULT_THEME;
}
console.info('[getTheme] getting theme "%s"', id);
if (Object.keys(ALL_THEMES).includes(id)) {
return ALL_THEMES[id as keyof typeof ALL_THEMES];
}
console.warn('[getTheme] theme "%s" not found; returning default theme', id);
return DEFAULT_THEME;
}
export default DEFAULT_THEME;

View File

@@ -1,16 +0,0 @@
import createTheme, { type ThemeOptions } from '@mui/material/styles/createTheme';
import merge from 'lodash-es/merge';
import { DEFAULT_THEME_OPTIONS } from 'themes/defaults';
const options: ThemeOptions = {
palette: {
background: {
paper: '#0c2450'
}
}
};
const theme = createTheme(merge({}, DEFAULT_THEME_OPTIONS, options));
export default theme;