diff --git a/src/app/Theme.ts b/src/app/Theme.ts index 1f03d78e..c080ab2a 100644 --- a/src/app/Theme.ts +++ b/src/app/Theme.ts @@ -1,10 +1,7 @@ 'use client'; -import { - type PaletteColor, - type Theme, - createTheme, -} from '@mui/material/styles'; +import { type PaletteColor, createTheme } from '@mui/material/styles'; +import type {} from '@mui/material/themeCssVarsAugmentation'; import { type Property } from 'csstype'; declare module '@mui/material/styles' { @@ -14,23 +11,6 @@ declare module '@mui/material/styles' { interface PaletteOptions { boxShadow?: string; } - interface Theme { - map: { - basemapTileUrl: string; - basemapTileOverallColor?: string; - routeColor: string; - routeTextColor: string; - }; - } - - interface ThemeOptions { - map?: { - basemapTileUrl?: string; - basemapTileOverallColor?: string; - routeColor?: string; - routeTextColor?: string; - }; - } interface TypeText { lightContrast?: string; @@ -65,7 +45,7 @@ export const fontFamily = { secondary: 'var(--font-ibm-plex-mono)', }; -const palette = { +const lightPalette = { primary: { main: '#3959fa', dark: '#002eea', @@ -131,140 +111,170 @@ const darkPalette = { boxShadow: '0px 1px 4px 2px rgba(0,0,0,0.6)', }; -export const getTheme = (mode: ThemeModeEnum): Theme => { - const isLightMode = mode === ThemeModeEnum.light; - const chosenPalette = !isLightMode ? darkPalette : palette; - return createTheme({ - palette: { ...chosenPalette, mode }, - map: { - basemapTileUrl: isLightMode - ? 'https://a.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png' - : 'https://a.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', - basemapTileOverallColor: isLightMode ? '#f6f6f6' : '#0d0d0d', - routeColor: chosenPalette.background.default, - routeTextColor: chosenPalette.text.primary, +/** + * Map configuration per color scheme. + * Extracted from the theme because map tile URLs and canvas colors + * need resolved JS values (not CSS variable references). + * Use with `useColorScheme()` to pick the right config. + */ +export const mapConfig = { + light: { + basemapTileUrl: + 'https://a.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', + basemapTileOverallColor: '#f6f6f6', + routeColor: lightPalette.background.default, + routeTextColor: lightPalette.text.primary, + textPrimary: lightPalette.text.primary, + backgroundPaper: lightPalette.background.paper, + primaryMain: lightPalette.primary.main, + }, + dark: { + basemapTileUrl: + 'https://a.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', + basemapTileOverallColor: '#0d0d0d', + routeColor: darkPalette.background.default, + routeTextColor: darkPalette.text.primary, + textPrimary: darkPalette.text.primary, + backgroundPaper: darkPalette.background.paper, + primaryMain: darkPalette.primary.main, + }, +} as const; + +export type MapConfig = (typeof mapConfig)[keyof typeof mapConfig]; + +export const theme = createTheme({ + cssVariables: { + colorSchemeSelector: 'class', + }, + colorSchemes: { + light: { + palette: lightPalette, }, - mixins: { - code: { - contrastText: '#f1fa8c', - command: { - fontWeight: 'bold', - color: '#ff79c6', - }, + dark: { + palette: darkPalette, + }, + }, + mixins: { + code: { + contrastText: '#f1fa8c', + command: { + fontWeight: 'bold', + color: '#ff79c6', }, }, - typography: { - fontFamily: fontFamily.primary, + }, + typography: { + fontFamily: fontFamily.primary, + }, + components: { + MuiInputAdornment: { + styleOverrides: { + root: { + color: 'inherit', + }, + }, }, - components: { - MuiInputAdornment: { - styleOverrides: { - root: { - color: 'inherit', - }, + MuiFormLabel: { + styleOverrides: { + root: { + color: 'var(--mui-palette-text-primary)', + fontWeight: 'bold', }, }, - MuiFormLabel: { - styleOverrides: { - root: { - color: chosenPalette.text.primary, - fontWeight: 'bold', + }, + MuiTextField: { + styleOverrides: { + root: { + '&.md-small-input': { + input: { paddingTop: '7px', paddingBottom: '7px' }, + }, + '.MuiOutlinedInput-root fieldset': { + borderColor: 'var(--mui-palette-divider)', }, }, }, - MuiTextField: { - styleOverrides: { - root: { - '&.md-small-input': { - input: { paddingTop: '7px', paddingBottom: '7px' }, - }, - '.MuiOutlinedInput-root fieldset': { - borderColor: chosenPalette.divider, - }, + }, + MuiSelect: { + styleOverrides: { + root: { + '.MuiSelect-select': { paddingTop: '7px', paddingBottom: '7px' }, + '.MuiSvgIcon-root': { color: 'var(--mui-palette-text-primary)' }, + '&.MuiInputBase-root fieldset': { + borderColor: 'var(--mui-palette-divider)', }, }, }, - MuiSelect: { - styleOverrides: { - root: { - '.MuiSelect-select': { paddingTop: '7px', paddingBottom: '7px' }, - '.MuiSvgIcon-root': { color: chosenPalette.text.primary }, - '&.MuiInputBase-root fieldset': { - borderColor: chosenPalette.divider, + }, + MuiButton: { + styleOverrides: { + root: ({ theme }) => ({ + textTransform: 'none' as const, + boxShadow: 'none', + fontFamily: fontFamily.secondary, + boxSizing: 'border-box' as const, + '&.MuiButton-contained': { + border: '2px solid transparent', + color: 'var(--mui-palette-background-default)', + '&.Mui-disabled': { + backgroundColor: 'var(--mui-palette-text-disabled)', }, }, - }, - }, - MuiButton: { - styleOverrides: { - root: { - textTransform: 'none', + '&.MuiButton-containedPrimary:hover': { boxShadow: 'none', - fontFamily: fontFamily.secondary, - boxSizing: 'border-box', - '&.MuiButton-contained': { - border: '2px solid transparent', - color: chosenPalette.background.default, - '&.Mui-disabled': { - backgroundColor: chosenPalette.text.disabled, - }, - }, - '&.MuiButton-containedPrimary:hover': { - boxShadow: 'none', - backgroundColor: 'transparent', - border: `2px solid ${chosenPalette.primary.main}`, - color: chosenPalette.primary.main, - }, - '&.MuiButton-outlinedPrimary': { - border: `2px solid ${chosenPalette.primary.main}`, - padding: '6px 16px', - }, - '&.MuiButton-outlinedPrimary:hover': { - backgroundColor: chosenPalette.primary.main, - color: isLightMode - ? chosenPalette.primary.contrastText - : chosenPalette.background.default, + backgroundColor: 'transparent', + border: '2px solid var(--mui-palette-primary-main)', + color: 'var(--mui-palette-primary-main)', + }, + '&.MuiButton-outlinedPrimary': { + border: '2px solid var(--mui-palette-primary-main)', + padding: '6px 16px', + }, + '&.MuiButton-outlinedPrimary:hover': { + backgroundColor: 'var(--mui-palette-primary-main)', + color: 'var(--mui-palette-primary-contrastText)', + ...theme.applyStyles('dark', { + color: 'var(--mui-palette-background-default)', + }), + }, + '&.MuiButton-text.inline': { + fontFamily: fontFamily.primary, + fontSize: 'inherit', + padding: '0 8px', + lineHeight: 'normal', + verticalAlign: 'baseline', + '&.line-start': { + paddingLeft: 0, }, - '&.MuiButton-text.inline': { - fontFamily: fontFamily.primary, - fontSize: 'inherit', - padding: `0 8px`, - lineHeight: 'normal', - verticalAlign: 'baseline', - '&.line-start': { - paddingLeft: 0, - }, - '.MuiButton-endIcon': { - marginRight: 0, - svg: { - color: 'inherit', - }, + '.MuiButton-endIcon': { + marginRight: 0, + svg: { + color: 'inherit', }, }, }, - }, + }), }, - MuiTypography: { - variants: [ - { - props: { variant: 'sectionTitle' }, - style: { - color: chosenPalette.primary?.main, - fontWeight: 'bold', - fontSize: '1.5rem', - marginBottom: '0.5rem', - marginTop: '1rem', - }, - }, - ], - styleOverrides: { - h1: { - fontWeight: 700, - color: chosenPalette.primary.main, - fontSize: '2.125rem', // h4 size + }, + MuiTypography: { + variants: [ + { + props: { variant: 'sectionTitle' }, + style: { + color: 'var(--mui-palette-primary-main)', + fontWeight: 'bold', + fontSize: '1.5rem', + marginBottom: '0.5rem', + marginTop: '1rem', }, }, + ], + styleOverrides: { + h1: { + fontWeight: 700, + color: 'var(--mui-palette-primary-main)', + fontSize: '2.125rem', // h4 size + }, }, }, - }); -}; + }, +}); diff --git a/src/app/[locale]/account/Account.styles.ts b/src/app/[locale]/account/Account.styles.ts index 047804c4..7bbf5f4a 100644 --- a/src/app/[locale]/account/Account.styles.ts +++ b/src/app/[locale]/account/Account.styles.ts @@ -25,10 +25,10 @@ export const tokenDisplayElementSx: SxProps = (theme) => ({ wordWrap: 'break-word', width: '100%', maxWidth: '610px', - backgroundColor: theme.palette.background.paper, + backgroundColor: theme.vars.palette.background.paper, p: 2, borderRadius: '6px', - border: `1px solid ${theme.palette.primary.main}`, + border: `1px solid ${theme.vars.palette.primary.main}`, }); export const tokenActionButtonsSx: SxProps = { diff --git a/src/app/[locale]/account/Account.tsx b/src/app/[locale]/account/Account.tsx index c970792e..97e4d8a1 100644 --- a/src/app/[locale]/account/Account.tsx +++ b/src/app/[locale]/account/Account.tsx @@ -325,7 +325,7 @@ export default function APIAccount(): React.ReactElement { > + + diff --git a/src/app/[locale]/sign-in/SignIn.tsx b/src/app/[locale]/sign-in/SignIn.tsx index d31cf18d..e804c075 100644 --- a/src/app/[locale]/sign-in/SignIn.tsx +++ b/src/app/[locale]/sign-in/SignIn.tsx @@ -280,7 +280,7 @@ export default function SignIn(): React.ReactElement { component='h5' sx={{ zIndex: 1, - backgroundColor: theme.palette.background.default, + backgroundColor: theme.vars.palette.background.default, px: 2, }} > diff --git a/src/app/components/CoveredAreaMap.tsx b/src/app/components/CoveredAreaMap.tsx index 2b098ac5..a16e18ce 100644 --- a/src/app/components/CoveredAreaMap.tsx +++ b/src/app/components/CoveredAreaMap.tsx @@ -299,7 +299,7 @@ const CoveredAreaMap: React.FC = ({ maxHeight: '90vh', minHeight: '50vh', p: 2, - backgroundColor: theme.palette.background.default, + backgroundColor: theme.vars.palette.background.default, borderRadius: '5px', border: 'none', }} diff --git a/src/app/components/FeedStatus.tsx b/src/app/components/FeedStatus.tsx index f56330cc..a05c7a32 100644 --- a/src/app/components/FeedStatus.tsx +++ b/src/app/components/FeedStatus.tsx @@ -22,7 +22,7 @@ export const FeedStatusIndicator = ( { const theme = useTheme(); + const { colorScheme } = useColorScheme(); const t = useTranslations('footer'); const { config } = useRemoteConfig(); const FOOTER_COLUMN_WIDTH = '185px'; @@ -34,7 +36,7 @@ const Footer: React.FC = () => { { { { {/* Bottom bar */} { { /> { = ({ target={external === true ? '_blank' : undefined} rel={external === true ? 'noopener noreferrer' : undefined} sx={{ - color: theme.palette.text.secondary, + color: theme.vars.palette.text.secondary, textDecoration: 'none', fontSize: theme.typography.body2.fontSize, fontFamily: fontFamily.secondary, @@ -31,7 +31,7 @@ export const FooterLink: React.FC = ({ marginBottom: 1.5, transition: 'color 0.2s', '&:hover': { - color: theme.palette.text.primary, + color: theme.vars.palette.text.primary, }, }} > diff --git a/src/app/components/GtfsVisualizationMap.layers.tsx b/src/app/components/GtfsVisualizationMap.layers.tsx index fa73d270..71d0e676 100644 --- a/src/app/components/GtfsVisualizationMap.layers.tsx +++ b/src/app/components/GtfsVisualizationMap.layers.tsx @@ -9,7 +9,8 @@ import { generateStopColorExpression, generateRouteOutlineColorExpression, } from './GtfsVisualizationMap.functions'; -import { type Theme } from '@mui/material'; +import { type MapConfig } from '../Theme'; +import { grey } from '@mui/material/colors'; // layer helpers @@ -45,7 +46,7 @@ export const stopsBaseFilter = ( export const RoutesWhiteLayer = ( filteredRouteTypeIds: string[], - theme: Theme, + mapCfg: MapConfig, ): LayerSpecification => { return { id: 'routes-white', @@ -55,8 +56,8 @@ export const RoutesWhiteLayer = ( type: 'line', paint: { 'line-color': generateRouteOutlineColorExpression( - theme.map.basemapTileOverallColor ?? '#ffffff', - theme.palette.grey[500], + mapCfg.basemapTileOverallColor ?? '#ffffff', + grey[500], ), 'line-opacity': 1, 'line-width': ['match', ['get', 'route_type'], '3', 5, '1', 10, 7], @@ -105,7 +106,7 @@ export const RoutesWhiteHighlightLayer = ( routeId: string | undefined, hoverInfo: string[], filteredRoutes: string[], - theme: Theme, + mapCfg: MapConfig, ): LayerSpecification => { return { id: 'routes-white-highlight', @@ -114,8 +115,8 @@ export const RoutesWhiteHighlightLayer = ( type: 'line', paint: { 'line-color': generateRouteOutlineColorExpression( - theme.map.basemapTileOverallColor ?? '#ffffff', - theme.palette.grey[500], + mapCfg.basemapTileOverallColor ?? '#ffffff', + grey[500], ), 'line-opacity': 1, 'line-width': ['match', ['get', 'route_type'], '3', 10, '1', 14, 10], @@ -162,7 +163,7 @@ export const StopLayer = ( hideStops: boolean, allSelectedRouteIds: string[], stopRadius: number, - theme: Theme, + mapCfg: MapConfig, ): LayerSpecification => { return { id: 'stops', @@ -172,7 +173,7 @@ export const StopLayer = ( type: 'circle', paint: { 'circle-radius': stopRadius, - 'circle-color': theme.palette.text.primary, + 'circle-color': mapCfg.textPrimary, 'circle-opacity': 0.4, }, minzoom: 12, @@ -186,7 +187,7 @@ export const StopsHighlightLayer = ( filteredRoutes: string[], stopId: string | undefined, stopHighlightColorMap: Record, - theme: Theme, + mapCfg: MapConfig, ): LayerSpecification => { return { id: 'stops-highlight', @@ -197,8 +198,8 @@ export const StopsHighlightLayer = ( 'circle-radius': 7, 'circle-color': generateStopColorExpression( stopHighlightColorMap, - theme.map.basemapTileOverallColor ?? '#ffffff', - theme.palette.grey[500], + mapCfg.basemapTileOverallColor ?? '#ffffff', + grey[500], ), 'circle-opacity': 1, }, @@ -238,7 +239,7 @@ export const StopsHighlightOuterLayer = ( hoverInfo: string[], hideStops: boolean, filteredRoutes: string[], - theme: Theme, + mapCfg: MapConfig, ): LayerSpecification => { return { id: 'stops-highlight-outer', @@ -247,7 +248,7 @@ export const StopsHighlightOuterLayer = ( type: 'circle', paint: { 'circle-radius': 3, - 'circle-color': theme.palette.background.paper, + 'circle-color': mapCfg.backgroundPaper, 'circle-opacity': 1, }, minzoom: 11, diff --git a/src/app/components/GtfsVisualizationMap.tsx b/src/app/components/GtfsVisualizationMap.tsx index 61cabecb..d9f0ac45 100644 --- a/src/app/components/GtfsVisualizationMap.tsx +++ b/src/app/components/GtfsVisualizationMap.tsx @@ -11,7 +11,8 @@ import maplibregl, { type LngLatBoundsLike } from 'maplibre-gl'; import 'maplibre-gl/dist/maplibre-gl.css'; import { Protocol } from 'pmtiles'; import { type LngLatTuple, type GtfsRoute } from '../types'; -import { Box, useTheme } from '@mui/material'; +import { Box } from '@mui/material'; +import { useMapConfig } from '../hooks/useMapConfig'; import { MapElement, @@ -68,7 +69,7 @@ export const GtfsVisualizationMap = ({ stopRadius = 3, preview = true, }: GtfsVisualizationMapProps): React.ReactElement => { - const theme = useTheme(); + const mapCfg = useMapConfig(); const [hoverInfo, setHoverInfo] = useState([]); const [mapElements, setMapElements] = useState([]); const [mapClickRouteData, setMapClickRouteData] = useState ({ width: '100%', height: '100%', position: 'relative', - borderColor: theme.palette.primary.main, + borderColor: theme.vars.palette.primary.main, borderRadius: '5px', - }} + })} > {/* Hover/click info (top-left) */} OpenStreetMap contributors', @@ -589,15 +590,15 @@ export const GtfsVisualizationMap = ({ minzoom: 0, maxzoom: 22, }, - RoutesWhiteLayer(filteredRouteTypeIds, theme), + RoutesWhiteLayer(filteredRouteTypeIds, mapCfg), RouteLayer(filteredRoutes, filteredRouteTypeIds), RoutesWhiteHighlightLayer( mapClickRouteData?.route_id, hoverInfo, filteredRoutes, - theme, + mapCfg, ), - StopLayer(hideStops, allSelectedRouteIds, stopRadius, theme), + StopLayer(hideStops, allSelectedRouteIds, stopRadius, mapCfg), RouteHighlightLayer( mapClickRouteData?.route_id, hoverInfo, @@ -609,13 +610,13 @@ export const GtfsVisualizationMap = ({ filteredRoutes, mapClickStopData?.stop_id, stopHighlightColorMap, - theme, + mapCfg, ), StopsHighlightOuterLayer( hoverInfo, hideStops, filteredRoutes, - theme, + mapCfg, ), StopsIndexLayer(), ], diff --git a/src/app/components/Header.style.ts b/src/app/components/Header.style.ts index d12fc9d6..7cde0766 100644 --- a/src/app/components/Header.style.ts +++ b/src/app/components/Header.style.ts @@ -8,7 +8,7 @@ export const mobileNavElementStyle = ( width: '100%', justifyContent: 'flex-start', pl: 3, - color: theme.palette.text.primary, + color: theme.vars.palette.text.primary, }); export const animatedButtonStyling = ( @@ -39,7 +39,7 @@ export const animatedButtonStyling = ( left: 0, right: 0, bottom: 0, - backgroundColor: theme.palette.primary.main, + backgroundColor: theme.vars.palette.primary.main, opacity: 0.7, transition: 'transform 0.9s cubic-bezier(0.19, 1, 0.22, 1)', transform: 'scaleX(0)', diff --git a/src/app/components/Header.tsx b/src/app/components/Header.tsx index f5f228b2..6be8221e 100644 --- a/src/app/components/Header.tsx +++ b/src/app/components/Header.tsx @@ -16,10 +16,10 @@ import { Menu, MenuItem, Select, - useTheme, Alert, AlertTitle, } from '@mui/material'; +import { useColorScheme } from '@mui/material/styles'; import MenuIcon from '@mui/icons-material/Menu'; import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; import { @@ -81,7 +81,7 @@ export default function DrawerAppBar(): React.ReactElement { const hasTransitFeedsRedirectParam = clientSearchParams?.get('utm_source') === 'transitfeeds'; - const theme = useTheme(); + const { colorScheme } = useColorScheme(); const pathname = usePathname(); const [mobileOpen, setMobileOpen] = React.useState(false); const [hasTransitFeedsRedirect, setHasTransitFeedsRedirect] = @@ -189,33 +189,33 @@ export default function DrawerAppBar(): React.ReactElement { component='nav' color='inherit' elevation={0} - sx={{ - background: theme.palette.background.paper, + sx={(theme) => ({ + backgroundColor: theme.vars.palette.background.paper, fontFamily: fontFamily.secondary, - }} + })} > @@ -206,13 +198,13 @@ export const MapGeoJSON = ( }} /> ({ display: 'flex', justifyContent: 'space-between', fontSize: '0.75rem', gap: 2, - color: theme.palette.text.secondary, - }} + color: theme.vars.palette.text.secondary, + })} > {t('heatmapLower')} {t('heatmapHigher')} diff --git a/src/app/components/NestedCheckboxList.tsx b/src/app/components/NestedCheckboxList.tsx index ee6e881b..3d417007 100644 --- a/src/app/components/NestedCheckboxList.tsx +++ b/src/app/components/NestedCheckboxList.tsx @@ -120,7 +120,7 @@ export default function NestedCheckboxList({ display: 'block', borderBottom: checkboxData.children !== undefined - ? `1px solid ${theme.palette.text.primary}` + ? `1px solid ${theme.vars.palette.text.primary}` : 'none', '.MuiListItemSecondaryAction-root': { top: checkboxData.type === 'checkbox' ? '22px' : '11px', diff --git a/src/app/components/PopupTable.tsx b/src/app/components/PopupTable.tsx index 61961d3b..c4977efc 100644 --- a/src/app/components/PopupTable.tsx +++ b/src/app/components/PopupTable.tsx @@ -10,11 +10,8 @@ import { TableContainer, Typography, } from '@mui/material'; -import { type Theme } from '@mui/material/styles'; - interface PopupTableProps { properties: Record; - theme: Theme; } const fieldDescriptions: Record = { @@ -28,10 +25,7 @@ const fieldDescriptions: Record = { }, }; -export const PopupTable: React.FC = ({ - properties, - theme, -}) => { +export const PopupTable: React.FC = ({ properties }) => { const displayName = properties?.display_name ?? 'Details'; // Create rows for each property (exclude 'color' and 'display_name') @@ -45,7 +39,7 @@ export const PopupTable: React.FC = ({ return ( - + theme.vars.palette.text.primary }}> {formattedKey} {fieldInfo.description != null && ( @@ -53,7 +47,7 @@ export const PopupTable: React.FC = ({ )} - + theme.vars.palette.text.primary }}> {properties[key]} @@ -62,14 +56,14 @@ export const PopupTable: React.FC = ({ return ( ({ + background: theme.vars.palette.background.paper, + color: theme.vars.palette.text.primary, maxWidth: '300px', - border: `1px solid ${theme.palette.divider}`, + border: `1px solid ${theme.vars.palette.divider}`, borderRadius: theme.shape.borderRadius, padding: theme.spacing(1), - }} + })} > = ({ ({ + background: theme.vars.palette.background.default, + color: theme.vars.palette.text.primary, + })} > {rows} diff --git a/src/app/context/ThemeProvider.tsx b/src/app/context/ThemeProvider.tsx index f9790436..16bf9276 100644 --- a/src/app/context/ThemeProvider.tsx +++ b/src/app/context/ThemeProvider.tsx @@ -1,63 +1,35 @@ 'use client'; -import { createContext, useState, useMemo, useContext, useEffect } from 'react'; -import { - ThemeProvider as MuiThemeProvider, - CssBaseline, - useMediaQuery, -} from '@mui/material'; -import { getTheme, ThemeModeEnum } from '../Theme'; +import { ThemeProvider as MuiThemeProvider, CssBaseline } from '@mui/material'; +import { useColorScheme } from '@mui/material/styles'; +import { theme, ThemeModeEnum } from '../Theme'; import type ContextProviderProps from '../interface/ContextProviderProps'; -// TODO: Revisit theme for best SSR practices - -const ThemeContext = createContext({ - mode: ThemeModeEnum.light, - toggleTheme: () => {}, -}); - export const ThemeProvider: React.FC = ({ children }) => { - const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)'); - - // Initialize with system preference for SSR, then check localStorage on client - const [mode, setMode] = useState( - prefersDarkMode ? ThemeModeEnum.dark : ThemeModeEnum.light, + return ( + + + {children} + ); +}; - // Load theme from localStorage only on client side - useEffect(() => { - if (typeof window !== 'undefined') { - const savedTheme = localStorage.getItem('theme'); - if ( - savedTheme != null && - savedTheme !== '' && - Object.values(ThemeModeEnum).includes(savedTheme as ThemeModeEnum) - ) { - setMode(savedTheme as ThemeModeEnum); - } - } - }, []); - +/** + * Hook to access the current color mode and toggle between light/dark. + * Must be used within a component rendered inside ``. + * Delegates to MUI's built-in `useColorScheme` which handles + * system preference detection and localStorage persistence. + */ +export const useTheme = (): { + mode: ThemeModeEnum; + toggleTheme: () => void; +} => { + const { mode, setMode, systemMode } = useColorScheme(); + const resolvedMode = (mode === 'system' ? systemMode : mode) ?? 'light'; + const themeMode = + resolvedMode === 'dark' ? ThemeModeEnum.dark : ThemeModeEnum.light; const toggleTheme = (): void => { - const newMode = - mode === ThemeModeEnum.light ? ThemeModeEnum.dark : ThemeModeEnum.light; - setMode(newMode); - if (typeof window !== 'undefined') { - localStorage.setItem('theme', newMode); - } + setMode(resolvedMode === 'dark' ? 'light' : 'dark'); }; - - const theme = useMemo(() => getTheme(mode), [mode]); - - return ( - - - - {children} - - - ); + return { mode: themeMode, toggleTheme }; }; - -export const useTheme = (): { mode: ThemeModeEnum; toggleTheme: () => void } => - useContext(ThemeContext); diff --git a/src/app/hooks/useMapConfig.ts b/src/app/hooks/useMapConfig.ts new file mode 100644 index 00000000..7af9db08 --- /dev/null +++ b/src/app/hooks/useMapConfig.ts @@ -0,0 +1,15 @@ +'use client'; + +import { useColorScheme } from '@mui/material/styles'; +import { mapConfig, type MapConfig } from '../Theme'; + +/** + * Returns the map configuration resolved for the current color scheme. + * Map canvas components (MapLibre, deck.gl) need actual color values, + * not CSS variable references, so this hook provides resolved values. + */ +export function useMapConfig(): MapConfig { + const { mode, systemMode } = useColorScheme(); + const resolvedMode = (mode === 'system' ? systemMode : mode) ?? 'light'; + return mapConfig[resolvedMode === 'dark' ? 'dark' : 'light']; +} diff --git a/src/app/screens/Analytics/GBFSFeedAnalytics/DetailPanel.tsx b/src/app/screens/Analytics/GBFSFeedAnalytics/DetailPanel.tsx index 51fd1cdc..d04b36f1 100644 --- a/src/app/screens/Analytics/GBFSFeedAnalytics/DetailPanel.tsx +++ b/src/app/screens/Analytics/GBFSFeedAnalytics/DetailPanel.tsx @@ -122,11 +122,11 @@ const DetailPanel: React.FC = ({ row }) => { diff --git a/src/app/screens/Analytics/GBFSNoticeAnalytics/index.tsx b/src/app/screens/Analytics/GBFSNoticeAnalytics/index.tsx index d22ce419..28c4ebae 100644 --- a/src/app/screens/Analytics/GBFSNoticeAnalytics/index.tsx +++ b/src/app/screens/Analytics/GBFSNoticeAnalytics/index.tsx @@ -163,12 +163,12 @@ export default function GBFSNoticeAnalytics(): React.ReactElement { diff --git a/src/app/screens/Analytics/GBFSVersionAnalytics/index.tsx b/src/app/screens/Analytics/GBFSVersionAnalytics/index.tsx index 0beb652a..438ed5f3 100644 --- a/src/app/screens/Analytics/GBFSVersionAnalytics/index.tsx +++ b/src/app/screens/Analytics/GBFSVersionAnalytics/index.tsx @@ -187,12 +187,12 @@ export default function GBFSVersionAnalytics(): React.ReactElement { diff --git a/src/app/screens/Analytics/GTFSFeatureAnalytics/index.tsx b/src/app/screens/Analytics/GTFSFeatureAnalytics/index.tsx index c83bdfa9..4ef16a8f 100644 --- a/src/app/screens/Analytics/GTFSFeatureAnalytics/index.tsx +++ b/src/app/screens/Analytics/GTFSFeatureAnalytics/index.tsx @@ -253,12 +253,12 @@ export default function GTFSFeatureAnalytics(): React.ReactElement { diff --git a/src/app/screens/Analytics/GTFSFeedAnalytics/DetailPanel.tsx b/src/app/screens/Analytics/GTFSFeedAnalytics/DetailPanel.tsx index 5b2edac7..c9b0e094 100644 --- a/src/app/screens/Analytics/GTFSFeedAnalytics/DetailPanel.tsx +++ b/src/app/screens/Analytics/GTFSFeedAnalytics/DetailPanel.tsx @@ -89,11 +89,11 @@ const DetailPanel: React.FC = ({ row }) => { @@ -105,9 +105,9 @@ const DetailPanel: React.FC = ({ row }) => { diff --git a/src/app/screens/Analytics/GTFSNoticeAnalytics/index.tsx b/src/app/screens/Analytics/GTFSNoticeAnalytics/index.tsx index ee5dfb47..43aa064f 100644 --- a/src/app/screens/Analytics/GTFSNoticeAnalytics/index.tsx +++ b/src/app/screens/Analytics/GTFSNoticeAnalytics/index.tsx @@ -223,12 +223,12 @@ export default function GTFSNoticeAnalytics(): React.ReactElement { diff --git a/src/app/screens/ContactUs.tsx b/src/app/screens/ContactUs.tsx index 6b4e2481..b7647270 100644 --- a/src/app/screens/ContactUs.tsx +++ b/src/app/screens/ContactUs.tsx @@ -43,7 +43,7 @@ export default function ContactUs(): React.ReactElement { viewBox='0 0 24 24' > diff --git a/src/app/screens/Contribute.tsx b/src/app/screens/Contribute.tsx index 648219a0..add976dd 100644 --- a/src/app/screens/Contribute.tsx +++ b/src/app/screens/Contribute.tsx @@ -21,7 +21,7 @@ export default function Contribute(): React.ReactElement { = (theme) => ({ display: 'flex', flexWrap: { xs: 'wrap', sm: 'nowrap' }, gap: 1, - borderTop: `1px solid ${theme.palette.divider}`, + borderTop: `1px solid ${theme.vars.palette.divider}`, pt: 3, }); export const featureChipsStyle: SxProps = (theme) => ({ - color: theme.palette.secondary.contrastText, - backgroundColor: theme.palette.secondary.dark, + color: theme.vars.palette.secondary.contrastText, + backgroundColor: theme.vars.palette.secondary.dark, border: `2px solid transparent`, ':hover': { opacity: 0.95, diff --git a/src/app/screens/Feed/FeedSummary.styles.ts b/src/app/screens/Feed/FeedSummary.styles.ts index 0348eb09..cdf0079c 100644 --- a/src/app/screens/Feed/FeedSummary.styles.ts +++ b/src/app/screens/Feed/FeedSummary.styles.ts @@ -2,7 +2,7 @@ import { Box, Card, styled, Typography } from '@mui/material'; import type { ElementType } from 'react'; export const GroupCard = styled(Card)(({ theme }) => ({ - background: theme.palette.background.default, + background: theme.vars.palette.background.default, border: 'none', padding: theme.spacing(2), marginBottom: theme.spacing(2), @@ -17,7 +17,7 @@ export const GroupHeader = styled(Typography)<{ component?: ElementType }>( gap: theme.spacing(1), marginBottom: theme.spacing(1), alignItems: 'center', - color: theme.palette.text.secondary, + color: theme.vars.palette.text.secondary, }), ); diff --git a/src/app/screens/Feed/Map.styles.ts b/src/app/screens/Feed/Map.styles.ts index f0fa59d0..dcfd783d 100644 --- a/src/app/screens/Feed/Map.styles.ts +++ b/src/app/screens/Feed/Map.styles.ts @@ -11,7 +11,7 @@ export const StyledMapControlPanel = styled(Box, { paddingTop: '100px', // to account for the fixed header on mobile flexDirection: 'column', flexWrap: 'nowrap', - backgroundColor: theme.palette.background.paper, + backgroundColor: theme.vars.palette.background.paper, zIndex: 10000, display: showMapControlMobile ? 'flex' : 'none', width: '100%', diff --git a/src/app/screens/Feed/components/AssociatedFeeds.tsx b/src/app/screens/Feed/components/AssociatedFeeds.tsx index 0d8bdb09..116220e5 100644 --- a/src/app/screens/Feed/components/AssociatedFeeds.tsx +++ b/src/app/screens/Feed/components/AssociatedFeeds.tsx @@ -41,7 +41,7 @@ const renderAssociatedGTFSFeedRow = ( sx={{ textDecoration: 'none', '&:hover, &:focus': { - backgroundColor: theme.palette.background.paper, + backgroundColor: theme.vars.palette.background.paper, }, }} > @@ -84,7 +84,7 @@ const renderAssociatedGTFSRTFeedRow = ( sx={{ textDecoration: 'none', '&:hover, &:focus': { - backgroundColor: theme.palette.background.paper, + backgroundColor: theme.vars.palette.background.paper, }, }} > @@ -124,7 +124,7 @@ export default function AssociatedGTFSRTFeeds({ @@ -148,7 +148,7 @@ export default function AssociatedGTFSRTFeeds({ {gtfsRtFeeds === undefined && Loading...} diff --git a/src/app/screens/Feed/components/FeedSummary.tsx b/src/app/screens/Feed/components/FeedSummary.tsx index 93e66f1a..b91eaca0 100644 --- a/src/app/screens/Feed/components/FeedSummary.tsx +++ b/src/app/screens/Feed/components/FeedSummary.tsx @@ -368,8 +368,8 @@ export default function FeedSummary({ whiteSpace: 'nowrap', overflowX: 'auto', borderRadius: '5px', - backgroundColor: theme.palette.secondary.light, - color: theme.palette.text.lightContrast, + backgroundColor: theme.vars.palette.secondary.light, + color: theme.vars.palette.text.lightContrast, py: 1.5, px: 2, fontSize: '0.875em', @@ -856,7 +856,7 @@ export default function FeedSummary({ position: 'absolute', right: 8, top: 8, - color: (theme) => theme.palette.grey[500], + color: (theme) => theme.vars.palette.grey[500], })} > diff --git a/src/app/screens/Feed/components/FullMapView.tsx b/src/app/screens/Feed/components/FullMapView.tsx index 391874ad..3073c181 100644 --- a/src/app/screens/Feed/components/FullMapView.tsx +++ b/src/app/screens/Feed/components/FullMapView.tsx @@ -215,7 +215,7 @@ export default function FullMapView({ display: 'flex', alignItems: 'center', justifyContent: 'center', - backgroundColor: theme.palette.background.default, + backgroundColor: theme.vars.palette.background.default, }} > @@ -252,7 +252,7 @@ export default function FullMapView({ {t('fullMapView.style.stopSize')} @@ -522,7 +522,7 @@ export default function FullMapView({ /> {t('fullMapView.style.radius', { px: customStopRadius, diff --git a/src/app/screens/Feed/components/GbfsVersions.tsx b/src/app/screens/Feed/components/GbfsVersions.tsx index c15cff80..345ab1bb 100644 --- a/src/app/screens/Feed/components/GbfsVersions.tsx +++ b/src/app/screens/Feed/components/GbfsVersions.tsx @@ -120,7 +120,7 @@ export default function GbfsVersions({ display: 'flex', flexDirection: 'column', justifyContent: 'space-between', - backgroundColor: theme.palette.background.default, + backgroundColor: theme.vars.palette.background.default, }} width={{ xs: '100%' }} > @@ -143,8 +143,8 @@ export default function GbfsVersions({ diff --git a/src/app/screens/Feed/components/LicenseDialog.tsx b/src/app/screens/Feed/components/LicenseDialog.tsx index 28ac441a..b89bf707 100644 --- a/src/app/screens/Feed/components/LicenseDialog.tsx +++ b/src/app/screens/Feed/components/LicenseDialog.tsx @@ -99,7 +99,7 @@ export default function LicenseDialog({ position: 'absolute', right: 8, top: 8, - color: (theme) => theme.palette.grey[500], + color: (theme) => theme.vars.palette.grey[500], }} > diff --git a/src/app/screens/FeedSubmissionFAQ.tsx b/src/app/screens/FeedSubmissionFAQ.tsx index 96079c68..e14ecb6a 100644 --- a/src/app/screens/FeedSubmissionFAQ.tsx +++ b/src/app/screens/FeedSubmissionFAQ.tsx @@ -24,9 +24,9 @@ export default function FeedSubmissionFAQ(): React.ReactElement { boxShadow: 'none', background: 'transparent', borderBottom: '2px solid', - borderColor: theme.palette.divider, + borderColor: theme.vars.palette.divider, '&:before': { display: 'none' }, - svg: { color: theme.palette.divider }, + svg: { color: theme.vars.palette.divider }, }; return ( @@ -42,7 +42,7 @@ export default function FeedSubmissionFAQ(): React.ReactElement { diff --git a/src/app/screens/Feeds/AdvancedSearchTable.tsx b/src/app/screens/Feeds/AdvancedSearchTable.tsx index d13d512b..650efadf 100644 --- a/src/app/screens/Feeds/AdvancedSearchTable.tsx +++ b/src/app/screens/Feeds/AdvancedSearchTable.tsx @@ -154,7 +154,7 @@ const renderGTFSDetails = ( sx={{ background: featureData.color, border: selectedFeatures.includes(feature) - ? `2px solid ${theme.palette.primary.main}` + ? `2px solid ${theme.vars.palette.primary.main}` : 'none', color: 'black', }} @@ -205,9 +205,9 @@ const renderGBFSDetails = ( sx={{ mr: 1, border: selectedGbfsVersions.includes('v' + version) - ? `2px solid ${theme.palette.primary.main}` + ? `2px solid ${theme.vars.palette.primary.main}` : '', - color: theme.palette.text.primary, + color: theme.vars.palette.text.primary, }} /> ))} @@ -250,7 +250,7 @@ export default function AdvancedSearchTable({ const descriptionDividerStyle: SxProps = { py: 1, - borderTop: `1px solid ${theme.palette.divider}`, + borderTop: `1px solid ${theme.vars.palette.divider}`, mt: 1, display: 'flex', flexDirection: 'column', diff --git a/src/app/screens/Feeds/Feeds.styles.ts b/src/app/screens/Feeds/Feeds.styles.ts index 0ff1103d..262dce9e 100644 --- a/src/app/screens/Feeds/Feeds.styles.ts +++ b/src/app/screens/Feeds/Feeds.styles.ts @@ -24,7 +24,7 @@ export const stickyHeaderStyles = (props: { xs: props.headerBannerVisible ? '113px' : '56px', md: props.headerBannerVisible ? '140px' : '64px', }, - background: props.theme.palette.background.default, + background: props.theme.vars.palette.background.default, transition: 'box-shadow 0.3s ease-in-out', mx: { xs: '-16px', diff --git a/src/app/screens/Feeds/PopoverList.tsx b/src/app/screens/Feeds/PopoverList.tsx index 19cf3722..1051c121 100644 --- a/src/app/screens/Feeds/PopoverList.tsx +++ b/src/app/screens/Feeds/PopoverList.tsx @@ -20,8 +20,8 @@ export default function PopoverList({ anchorEl={anchorEl} placement='top' sx={{ - backgroundColor: theme.palette.background.paper, - boxShadow: theme.palette.boxShadow, + backgroundColor: theme.vars.palette.background.paper, + boxShadow: theme.vars.palette.boxShadow, zIndex: 1000, }} > diff --git a/src/app/screens/Feeds/ProviderTitle.tsx b/src/app/screens/Feeds/ProviderTitle.tsx index 5b9278dc..ede6c249 100644 --- a/src/app/screens/Feeds/ProviderTitle.tsx +++ b/src/app/screens/Feeds/ProviderTitle.tsx @@ -33,7 +33,7 @@ export default function ProviderTitle({ fontStyle: 'italic', fontSize: '14px', fontWeight: 'bold', - color: theme.palette.primary.main, + color: theme.vars.palette.primary.main, padding: 2, }} onMouseEnter={(event) => { diff --git a/src/app/screens/Feeds/SearchTable.spec.tsx b/src/app/screens/Feeds/SearchTable.spec.tsx index 5c89092f..53453aae 100644 --- a/src/app/screens/Feeds/SearchTable.spec.tsx +++ b/src/app/screens/Feeds/SearchTable.spec.tsx @@ -2,6 +2,8 @@ import SearchTable, { getDataTypeElement } from './SearchTable'; import { render, cleanup, screen, within } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import { type AllFeedsType } from '../../services/feeds/utils'; +import { ThemeProvider } from '@mui/material/styles'; +import { theme } from '../../Theme'; jest.mock('../../../i18n/navigation', () => ({ useRouter: () => ({ push: jest.fn() }), @@ -143,9 +145,11 @@ describe.only('getProviderElement', () => { it('should display the correct number of transit providers in table row', () => { render( - - - , + + + + + , ); expect(screen.getByText('Utah Transit Authority (UTA)')).toBeTruthy(); diff --git a/src/app/screens/Feeds/SearchTable.tsx b/src/app/screens/Feeds/SearchTable.tsx index 5564bc9c..ad5ddad7 100644 --- a/src/app/screens/Feeds/SearchTable.tsx +++ b/src/app/screens/Feeds/SearchTable.tsx @@ -148,7 +148,7 @@ export default function SearchTable({ borderTopLeftRadius: '6px', }, '.feed-row:first-of-type .feed-column': { - borderTop: `1px solid ${theme.palette.divider}`, + borderTop: `1px solid ${theme.vars.palette.divider}`, }, '.feed-row:last-child .feed-column:last-child': { borderBottomRightRadius: '6px', @@ -157,13 +157,13 @@ export default function SearchTable({ borderBottomLeftRadius: '6px', }, '.feed-row:last-child .feed-column': { - borderBottom: `1px solid ${theme.palette.divider}`, + borderBottom: `1px solid ${theme.vars.palette.divider}`, }, '.feed-row .feed-column:first-of-type': { - borderLeft: `1px solid ${theme.palette.divider}`, + borderLeft: `1px solid ${theme.vars.palette.divider}`, }, '.feed-row .feed-column:last-child': { - borderRight: `1px solid ${theme.palette.divider}`, + borderRight: `1px solid ${theme.vars.palette.divider}`, minWidth: '180px', }, }} @@ -188,13 +188,13 @@ export default function SearchTable({ }} sx={{ textDecoration: 'none', - backgroundColor: theme.palette.background.default, + backgroundColor: theme.vars.palette.background.default, '.feed-column': { fontSize: '16px', - borderBottom: `1px solid ${theme.palette.divider}`, + borderBottom: `1px solid ${theme.vars.palette.divider}`, }, '&:hover, &:focus': { - backgroundColor: theme.palette.background.paper, + backgroundColor: theme.vars.palette.background.paper, cursor: 'pointer', }, }} @@ -286,8 +286,8 @@ export default function SearchTable({ anchorEl={anchorEl} placement='top' sx={{ - backgroundColor: theme.palette.background.paper, - boxShadow: theme.palette.boxShadow, + backgroundColor: theme.vars.palette.background.paper, + boxShadow: theme.vars.palette.boxShadow, zIndex: 1000, }} > diff --git a/src/app/screens/GbfsValidator/GbfsFeedSearchInput.tsx b/src/app/screens/GbfsValidator/GbfsFeedSearchInput.tsx index 31e0fbb2..3fa621d3 100644 --- a/src/app/screens/GbfsValidator/GbfsFeedSearchInput.tsx +++ b/src/app/screens/GbfsValidator/GbfsFeedSearchInput.tsx @@ -158,8 +158,8 @@ export default function GbfsFeedSearchInput({ padding: 2, pt: 3, marginTop: 2, - backgroundColor: theme.palette.background.default, - border: '3px solid ' + theme.palette.text.primary, + backgroundColor: theme.vars.palette.background.default, + border: '3px solid ' + theme.vars.palette.text.primary, borderRadius: '5px', }} > diff --git a/src/app/screens/GbfsValidator/ValidationReport.styles.ts b/src/app/screens/GbfsValidator/ValidationReport.styles.ts index 1e4886e6..f84e5736 100644 --- a/src/app/screens/GbfsValidator/ValidationReport.styles.ts +++ b/src/app/screens/GbfsValidator/ValidationReport.styles.ts @@ -44,7 +44,7 @@ export const PromotionTextColumn = styled(Box)(({ theme }) => ({ })); export const ValidationReportTableStyles: SxProps = (theme) => ({ - backgroundColor: theme.palette.background.paper, + backgroundColor: theme.vars.palette.background.paper, height: 'fit-content', position: 'sticky', top: theme.spacing(10), @@ -77,7 +77,7 @@ export const AlertErrorBoxStyles = ( whiteSpace: 'pre-wrap', fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace', - bgcolor: theme.palette.action.hover, + bgcolor: theme.vars.palette.action.hover, p: 1.5, borderRadius: 1, maxHeight: showDetails ? 400 : 140, @@ -87,7 +87,7 @@ export const AlertErrorBoxStyles = ( }); export const ValidationErrorPathStyles = (theme: Theme): CSSProperties => ({ - background: theme.palette.background.paper, + background: theme.vars.palette.background.paper, width: '100%', overflowX: 'auto', fontSize: '0.875em', @@ -100,7 +100,7 @@ export const ValidationErrorPathStyles = (theme: Theme): CSSProperties => ({ }); export const ContentTitle = styled(Typography)(({ theme }) => ({ - color: theme.palette.text.secondary, + color: theme.vars.palette.text.secondary, fontSize: theme.typography.subtitle2.fontSize, padding: `0 ${theme.spacing(2)}`, lineHeight: '48px', @@ -117,7 +117,7 @@ export const highlightedPreSx: SxProps = (theme: Theme) => ({ m: 0, p: 1, borderRadius: 1, - backgroundColor: theme.palette.action.hover, + backgroundColor: theme.vars.palette.action.hover, maxHeight: 300, overflow: 'auto', whiteSpace: 'pre-wrap', @@ -127,18 +127,18 @@ export const highlightedPreSx: SxProps = (theme: Theme) => ({ export const highlightedContainerSx: SxProps = (theme: Theme) => ({ border: '1px solid', - borderColor: theme.palette.divider, + borderColor: theme.vars.palette.divider, borderRadius: 2, overflow: 'hidden', }); export const highlightedTitleSx: SxProps = (theme: Theme) => ({ width: '100%', - backgroundColor: theme.palette.background.default, - borderBottom: `1px solid ${theme.palette.divider}`, + backgroundColor: theme.vars.palette.background.default, + borderBottom: `1px solid ${theme.vars.palette.divider}`, p: 1, px: 1, - color: theme.palette.text.primary, + color: theme.vars.palette.text.primary, display: 'flex', alignItems: 'center', gap: 2, @@ -148,7 +148,7 @@ export const highlightedInnerSx: SxProps = (theme: Theme) => ({ m: 0, p: 1, borderRadius: 1, - backgroundColor: theme.palette.action.hover, + backgroundColor: theme.vars.palette.action.hover, maxHeight: 300, overflow: 'auto', fontFamily: 'monospace', @@ -163,7 +163,7 @@ export const entryRowSx = ( alignItems: 'flex-start', px: 0.5, borderLeft: isHitProp ? '3px solid' : undefined, - borderColor: isHitProp ? theme.palette.error.main : undefined, + borderColor: isHitProp ? theme.vars.palette.error.main : undefined, backgroundColor: isHitProp ? 'rgba(244,67,54,0.08)' : undefined, borderRadius: 0.5, }); @@ -174,7 +174,7 @@ export const keyTypographySx = ( ): SxProps => ({ fontFamily: 'inherit', fontWeight: isHitProp ? 700 : 400, - color: isHitProp ? theme.palette.error.main : 'inherit', + color: isHitProp ? theme.vars.palette.error.main : 'inherit', }); export const listItemSx = ( @@ -183,7 +183,7 @@ export const listItemSx = ( ): SxProps => ({ backgroundColor: isOffender ? 'rgba(244,67,54,0.08)' : '', borderLeft: isOffender ? '3px solid' : '', - borderColor: isOffender ? theme.palette.error.main : '', + borderColor: isOffender ? theme.vars.palette.error.main : '', pl: isOffender ? 1 : 0, borderRadius: 0.5, wordBreak: 'break-word', @@ -199,12 +199,12 @@ export const outlinePreSx: SxProps = (theme: Theme) => ({ m: 0, p: 1, borderRadius: 1, - backgroundColor: theme.palette.action.hover, + backgroundColor: theme.vars.palette.action.hover, maxHeight: 300, overflow: 'auto', whiteSpace: 'pre-wrap', wordBreak: 'break-word', - outline: `2px solid ${theme.palette.error.main}`, + outline: `2px solid ${theme.vars.palette.error.main}`, outlineOffset: '-2px', }); @@ -219,14 +219,14 @@ export const rowButtonOutlineErrorSx: SxProps = (theme: Theme) => ({ px: theme.spacing(1), py: theme.spacing(0.5), borderRadius: '5px', - border: `1px solid ${theme.palette.error.main}`, - color: theme.palette.error.main, + border: `1px solid ${theme.vars.palette.error.main}`, + color: theme.vars.palette.error.main, fontSize: '0.8125rem', fontFamily: fontFamily.secondary, fontWeight: 500, background: 'transparent', '&:hover': { - backgroundColor: theme.palette.error.light, - color: theme.palette.error.contrastText, + backgroundColor: theme.vars.palette.error.light, + color: theme.vars.palette.error.contrastText, }, }); diff --git a/src/app/screens/GbfsValidator/ValidationReport.tsx b/src/app/screens/GbfsValidator/ValidationReport.tsx index 2c2543e5..087f4150 100644 --- a/src/app/screens/GbfsValidator/ValidationReport.tsx +++ b/src/app/screens/GbfsValidator/ValidationReport.tsx @@ -200,14 +200,14 @@ export default function ValidationReport({ {hasErrors ? ( ) : ( @@ -215,7 +215,10 @@ export default function ValidationReport({ ) : ( )} @@ -224,10 +227,10 @@ export default function ValidationReport({ secondary={secondary} sx={{ color: hasErrors - ? theme.palette.error.main + ? theme.vars.palette.error.main : hasSystemErrors - ? theme.palette.warning.main - : theme.palette.text.primary, + ? theme.vars.palette.warning.main + : theme.vars.palette.text.primary, }} /> @@ -242,7 +245,7 @@ export default function ValidationReport({ height: '100%', width: '100%', borderRadius: '5px', - backgroundColor: theme.palette.background.paper, + backgroundColor: theme.vars.palette.background.paper, p: 0, }} > @@ -326,7 +329,7 @@ export default function ValidationReport({ (fg.systemErrors?.length ?? 0) === 0 && ( Valid no errors @@ -387,7 +390,7 @@ export default function ValidationReport({ }, '&:hover': { backgroundColor: - theme.palette.action.hover, + theme.vars.palette.action.hover, }, '&:hover .hover-details-btn, &:focus-visible .hover-details-btn': { @@ -526,11 +529,11 @@ export default function ValidationReport({ 'background-color 120ms, box-shadow 120ms', cursor: 'pointer', '&:hover': { - boxShadow: `0 0 0 2px ${theme.palette.error.light}`, + boxShadow: `0 0 0 2px ${theme.vars.palette.error.light}`, }, '&:focus-visible': { outline: 'none', - boxShadow: `0 0 0 3px ${theme.palette.error.main}`, + boxShadow: `0 0 0 3px ${theme.vars.palette.error.main}`, }, '&:hover .hover-details-btn, &:focus-visible .hover-details-btn': { diff --git a/src/app/screens/GbfsValidator/ValidationReportSkeletonLoading.tsx b/src/app/screens/GbfsValidator/ValidationReportSkeletonLoading.tsx index b2312419..6a648257 100644 --- a/src/app/screens/GbfsValidator/ValidationReportSkeletonLoading.tsx +++ b/src/app/screens/GbfsValidator/ValidationReportSkeletonLoading.tsx @@ -50,7 +50,7 @@ export function ValidationReportSkeletonLoading(): ReactElement { height: '100%', width: '100%', borderRadius: '5px', - backgroundColor: theme.palette.background.paper, + backgroundColor: theme.vars.palette.background.paper, p: 0, }} > diff --git a/src/app/screens/GbfsValidator/ValidationState.tsx b/src/app/screens/GbfsValidator/ValidationState.tsx index c2f1ce7a..15129c4f 100644 --- a/src/app/screens/GbfsValidator/ValidationState.tsx +++ b/src/app/screens/GbfsValidator/ValidationState.tsx @@ -136,7 +136,7 @@ export default function ValidationState(): ReactElement { sx={{ fontWeight: 700, mb: 3, - color: theme.palette.primary.main, + color: theme.vars.palette.primary.main, overflowWrap: 'break-word', }} > @@ -223,7 +223,7 @@ export default function ValidationState(): ReactElement { {/* TODO: Disabled until map data is implemented @@ -294,7 +296,7 @@ export function ErrorDetailsDialog({ Instance path @@ -305,7 +307,10 @@ export function ErrorDetailsDialog({ Schema path @@ -316,7 +321,10 @@ export function ErrorDetailsDialog({ Error Message @@ -397,7 +405,7 @@ export function ErrorDetailsDialog({ p: 1, borderRadius: 1, backgroundColor: 'rgba(244,67,54,0.08)', - borderLeft: `3px solid ${theme.palette.error.main}`, + borderLeft: `3px solid ${theme.vars.palette.error.main}`, fontFamily: 'monospace', }} > @@ -406,7 +414,7 @@ export function ErrorDetailsDialog({ sx={{ fontFamily: 'inherit', fontWeight: 700, - color: theme.palette.error.main, + color: theme.vars.palette.error.main, }} > "{missingKey}": diff --git a/src/app/screens/GbfsValidator/index.tsx b/src/app/screens/GbfsValidator/index.tsx index 46449fe0..13334e20 100644 --- a/src/app/screens/GbfsValidator/index.tsx +++ b/src/app/screens/GbfsValidator/index.tsx @@ -34,7 +34,7 @@ export default function GbfsValidator(): React.ReactElement { sx={{ ...gbfsValidatorHeroBg, padding: 2, - color: theme.palette.common.black, + color: theme.vars.palette.common.black, marginTop: '-32px', height: '400px', display: 'flex', diff --git a/src/app/screens/PrivacyPolicy.tsx b/src/app/screens/PrivacyPolicy.tsx index c955246a..3f43d4ec 100644 --- a/src/app/screens/PrivacyPolicy.tsx +++ b/src/app/screens/PrivacyPolicy.tsx @@ -13,7 +13,7 @@ export default function PrivacyPolicy(): React.ReactElement { display: 'flex', flexDirection: 'column', width: '100%', - background: theme.palette.background.paper, + background: theme.vars.palette.background.paper, }} > ({ display: 'block', height: '3px', width: '104px', - background: theme.palette.text.primary, + background: theme.vars.palette.text.primary, }, })); diff --git a/src/app/styles/PageLayout.style.ts b/src/app/styles/PageLayout.style.ts index 357e8dce..1e711074 100644 --- a/src/app/styles/PageLayout.style.ts +++ b/src/app/styles/PageLayout.style.ts @@ -1,7 +1,7 @@ import { Container, styled } from '@mui/material'; export const ColoredContainer = styled(Container)(({ theme }) => ({ - background: theme.palette.background.paper, + background: theme.vars.palette.background.paper, borderRadius: '6px', paddingTop: theme.spacing(3), paddingBottom: theme.spacing(3), diff --git a/src/app/styles/VerificationBadge.styles.ts b/src/app/styles/VerificationBadge.styles.ts index 886a4482..4d46207e 100644 --- a/src/app/styles/VerificationBadge.styles.ts +++ b/src/app/styles/VerificationBadge.styles.ts @@ -4,6 +4,6 @@ import { type SystemStyleObject } from '@mui/system'; export const verificationBadgeStyle = ( theme: Theme, ): SystemStyleObject => ({ - background: `linear-gradient(25deg, ${theme.palette.primary.light}, ${theme.palette.primary.dark})`, + background: `linear-gradient(25deg, ${theme.vars.palette.primary.light}, ${theme.vars.palette.primary.dark})`, color: 'white', }); diff --git a/src/app/utils/feedStatusConsts.tsx b/src/app/utils/feedStatusConsts.tsx index 9fda1dec..97e82042 100644 --- a/src/app/utils/feedStatusConsts.tsx +++ b/src/app/utils/feedStatusConsts.tsx @@ -18,28 +18,28 @@ export function getFeedStatusData( toolTip: t('feedStatus.active.toolTip'), label: t('feedStatus.active.label'), themeColor: 'success', - color: theme.palette.success.main, + color: theme.vars.palette.success.main, toolTipLong: t('feedStatus.active.toolTipLong'), }, future: { toolTip: t('feedStatus.future.toolTip'), label: t('feedStatus.future.label'), themeColor: 'info', - color: theme.palette.info.main, + color: theme.vars.palette.info.main, toolTipLong: t('feedStatus.future.toolTipLong'), }, inactive: { toolTip: t('feedStatus.inactive.toolTip'), label: t('feedStatus.inactive.label'), themeColor: 'warning', - color: theme.palette.warning.main, + color: theme.vars.palette.warning.main, toolTipLong: t('feedStatus.inactive.toolTipLong'), }, deprecated: { toolTip: t('feedStatus.deprecated.toolTip'), label: t('feedStatus.deprecated.label'), themeColor: 'error', - color: theme.palette.error.main, + color: theme.vars.palette.error.main, toolTipLong: t('feedStatus.deprecated.toolTipLong'), }, };