From e2a22830520fcbb045a6817b669fff2a1cee678f Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Fri, 16 May 2025 12:54:58 -0400 Subject: [PATCH] Migrate mui themes to css vars --- src/hooks/useUserTheme.ts | 6 +- src/themes/UserThemeProvider.tsx | 28 ++++--- src/themes/appletv/index.ts | 27 ------- src/themes/blueradiance/index.ts | 16 ---- src/themes/dark/index.ts | 7 -- src/themes/defaults.ts | 11 ++- src/themes/light/index.ts | 30 ------- src/themes/purplehaze/index.ts | 22 ----- src/themes/themes.ts | 135 ++++++++++++++++++++++++------- src/themes/wmc/index.ts | 16 ---- 10 files changed, 135 insertions(+), 163 deletions(-) delete mode 100644 src/themes/appletv/index.ts delete mode 100644 src/themes/blueradiance/index.ts delete mode 100644 src/themes/dark/index.ts delete mode 100644 src/themes/light/index.ts delete mode 100644 src/themes/purplehaze/index.ts delete mode 100644 src/themes/wmc/index.ts diff --git a/src/hooks/useUserTheme.ts b/src/hooks/useUserTheme.ts index 29593ddca..2d60c173c 100644 --- a/src/hooks/useUserTheme.ts +++ b/src/hooks/useUserTheme.ts @@ -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 }; } diff --git a/src/themes/UserThemeProvider.tsx b/src/themes/UserThemeProvider.tsx index bdffd2748..e3ea9eb17 100644 --- a/src/themes/UserThemeProvider.tsx +++ b/src/themes/UserThemeProvider.tsx @@ -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> = ({ 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> = ({ 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> = ({ children }) => { return ( - + + {children} ); diff --git a/src/themes/appletv/index.ts b/src/themes/appletv/index.ts deleted file mode 100644 index 9acba52cf..000000000 --- a/src/themes/appletv/index.ts +++ /dev/null @@ -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; diff --git a/src/themes/blueradiance/index.ts b/src/themes/blueradiance/index.ts deleted file mode 100644 index 2b2634309..000000000 --- a/src/themes/blueradiance/index.ts +++ /dev/null @@ -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; diff --git a/src/themes/dark/index.ts b/src/themes/dark/index.ts deleted file mode 100644 index 71fcaa425..000000000 --- a/src/themes/dark/index.ts +++ /dev/null @@ -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; diff --git a/src/themes/defaults.ts b/src/themes/defaults.ts index 7f92f0c4b..5150ec003 100644 --- a/src/themes/defaults.ts +++ b/src/themes/defaults.ts @@ -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: { diff --git a/src/themes/light/index.ts b/src/themes/light/index.ts deleted file mode 100644 index f6a84e9bd..000000000 --- a/src/themes/light/index.ts +++ /dev/null @@ -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; diff --git a/src/themes/purplehaze/index.ts b/src/themes/purplehaze/index.ts deleted file mode 100644 index 407178422..000000000 --- a/src/themes/purplehaze/index.ts +++ /dev/null @@ -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; diff --git a/src/themes/themes.ts b/src/themes/themes.ts index b5ad17f98..d6780dfda 100644 --- a/src/themes/themes.ts +++ b/src/themes/themes.ts @@ -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,107 @@ 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' + } + } +}); + +/** 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' + } + } +}); + +/** The Windows Media Center inspired color scheme. */ +const wmc = buildCustomColorScheme({ + palette: { + background: { + paper: '#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; diff --git a/src/themes/wmc/index.ts b/src/themes/wmc/index.ts deleted file mode 100644 index 105ac10d6..000000000 --- a/src/themes/wmc/index.ts +++ /dev/null @@ -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;