From 3454d16f3685c291d8e2710b7977f830362cca48 Mon Sep 17 00:00:00 2001 From: Carter Date: Mon, 14 Apr 2025 16:25:33 -0600 Subject: [PATCH] got new theme system to work without re-rendering --- package-lock.json | 6 +- src/app/App.tsx | 47 ++-- src/app/Header.tsx | 14 +- src/app/UserWrapper.tsx | 18 +- src/navigation/BottomNavigator.tsx | 1 - src/navigation/NavigationContainer.tsx | 14 +- src/navigation/Router.tsx | 14 +- src/services/whiteLabel.ts | 4 + src/theme/index.ts | 2 +- src/theme/theme.ts | 321 ++++--------------------- src/theme/themeType.ts | 7 +- src/user/UserMenu.tsx | 13 +- 12 files changed, 100 insertions(+), 361 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2ca4730..6c8e30d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14126,9 +14126,9 @@ } }, "node_modules/vite": { - "version": "6.2.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.5.tgz", - "integrity": "sha512-j023J/hCAa4pRIUH6J9HemwYfjB5llR2Ps0CWeikOtdR8+pAURAk0DoJC5/mm9kd+UgdnIy7d6HE4EAvlYhPhA==", + "version": "6.2.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.6.tgz", + "integrity": "sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/src/app/App.tsx b/src/app/App.tsx index a5e062d..62da61b 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -5,14 +5,13 @@ import HTTPProvider from 'providers/http' import { useState } from 'react' import LoadingScreen from './LoadingScreen' import UserWrapper from './UserWrapper' -import { CssBaseline, Theme, ThemeProvider } from '@mui/material' -import { CreateTheme } from '../theme/theme' -import { getThemeType, setThemeType } from '../theme/themeType' +import { CssBaseline } from '@mui/material' import { getWhitelabel } from 'services/whiteLabel' +import { AppThemeProvider } from 'theme/AppThemeProvider' function App() { const [token, setToken] = useState(undefined) - const [palette, setPalette] = useState(CreateTheme(getThemeType())); + // const [palette, setPalette] = useState(CreateTheme(getThemeType())); const whiteLabel = getWhitelabel() const manifestPath = "/" + whiteLabel.name.replace(/\s/g, "") + "/manifest.json" @@ -45,33 +44,23 @@ function App() { let client_id = import.meta.env.VITE_AUTH0_DEV_CLIENT_ID; - const toggleTheme = () => { - if (getThemeType() === "light") { - setPalette(CreateTheme("dark")); - setThemeType("dark"); - } else { - setPalette(CreateTheme("light")); - setThemeType("light"); - } - }; - return ( - - - + + + {/* */} { token ? - + : } - - + + ) } diff --git a/src/app/Header.tsx b/src/app/Header.tsx index 998639e..e75048a 100644 --- a/src/app/Header.tsx +++ b/src/app/Header.tsx @@ -15,7 +15,7 @@ const useStyles = makeStyles((theme: Theme) => ({ appBar: { zIndex: theme.zIndex.drawer + 1, backgroundImage: "none", // This prevents de-saturation of header in dark mode - // backgroundColor: "#272727" + backgroundColor: "#272727" }, toolbar: { marginLeft: theme.spacing(0.5), @@ -77,16 +77,8 @@ const useStyles = makeStyles((theme: Theme) => ({ }, })); -interface Props { - toggleTheme: () => void; - // teams: Team[]; - // setTeams: React.Dispatch>; -} +export default function Header() { -export default function Header(props: Props) { - - // const { sideIsOpen, openSide, toggleTheme } = props; - const { toggleTheme } = props; const themeType = useThemeType(); const classes = useStyles() const isMobile = useMobile(); @@ -125,7 +117,7 @@ export default function Header(props: Props) { )} - + diff --git a/src/app/UserWrapper.tsx b/src/app/UserWrapper.tsx index 80960d4..e273c58 100644 --- a/src/app/UserWrapper.tsx +++ b/src/app/UserWrapper.tsx @@ -9,7 +9,8 @@ import { GlobalState, GlobalStateAction, StateProvider } from '../providers/Stat import { User } from '../models/user' import { Team } from '../models/team' import { makeStyles } from '@mui/styles' -import { Theme } from '@mui/material' +import { CssBaseline, Theme } from '@mui/material' +import { AppThemeProvider } from 'theme/AppThemeProvider' // import FirmwareLoader from './FirmwareLoader' const reducer = (state: GlobalState, action: GlobalStateAction): GlobalState => { @@ -50,12 +51,7 @@ const globalDefault = { firmware: new Map() } -interface Props { - toggleTheme: () => void; -} - -export default function UserWrapper(props: Props) { - const { toggleTheme } = props; +export default function UserWrapper() { const [loading, setLoading] = useState(false) const userAPI = useUserAPI(); const classes = useStyles(); @@ -99,10 +95,14 @@ export default function UserWrapper(props: Props) { return ( - {/* */} + +
- +
+
+ {/* */} + {/* */}
) diff --git a/src/navigation/BottomNavigator.tsx b/src/navigation/BottomNavigator.tsx index 9003d97..d7bf295 100644 --- a/src/navigation/BottomNavigator.tsx +++ b/src/navigation/BottomNavigator.tsx @@ -10,7 +10,6 @@ import { import { usePrevious } from "hooks"; import BindaptIcon from "products/Bindapt/BindaptIcon"; import BinsIcon from "products/Bindapt/BinsIcon"; -import VentilationIcon from "products/ventilation/VentilationIcon"; import { useGlobalState } from "providers"; import { useCallback, useEffect, useState } from "react"; import { useNavigate, useLocation } from "react-router-dom"; diff --git a/src/navigation/NavigationContainer.tsx b/src/navigation/NavigationContainer.tsx index 6516783..8590f9b 100644 --- a/src/navigation/NavigationContainer.tsx +++ b/src/navigation/NavigationContainer.tsx @@ -1,22 +1,12 @@ import React, { PropsWithChildren } from "react"; import Router from "./Router"; -interface Props extends PropsWithChildren { - toggleTheme: () => void; -// teams: Team[]; -// setTeams: React.Dispatch>; -} - -export default function NavigationContainer(props: Props) { -// const { toggleTheme, teams, setTeams } = props; - const { toggleTheme } = props; +export default function NavigationContainer() { return ( {/* */} - + ); } diff --git a/src/navigation/Router.tsx b/src/navigation/Router.tsx index 9fc066a..6976cc8 100644 --- a/src/navigation/Router.tsx +++ b/src/navigation/Router.tsx @@ -37,19 +37,13 @@ const BinCableEstimator = lazy(() => import("pages/BinCableEstimator")); const APIDocs = lazy(() => import("pages/APIDocs")); const Fields = lazy(()=> import("pages/Fields")); const Marketplace = lazy(() => import("userFeatures/UserFeatures")) - -interface Props { - toggleTheme: () => void; -} - export const appendToUrl = (appendage: number | string) => { const basePath = location.pathname.replace(/\/$/, ""); return(`${basePath}/${appendage}`); }; -export default function Router(props: Props) { +export default function Router() { - const { toggleTheme } = props; const { isAuthenticated, loginWithRedirect, isLoading } = useAuth0(); const whiteLabel = getWhitelabel(); const [{ user }] = useGlobalState(); @@ -290,11 +284,7 @@ export default function Router(props: Props) { return ( }> -
+
{/* Redirects */} diff --git a/src/services/whiteLabel.ts b/src/services/whiteLabel.ts index 9101cfe..e04fa27 100644 --- a/src/services/whiteLabel.ts +++ b/src/services/whiteLabel.ts @@ -92,7 +92,9 @@ const STAGING_WHITELABEL: WhiteLabel = { const BXT_WHITE_LABEL: WhiteLabel = { name: "Brand X Technologies", primaryColour: "blue", + // primaryColour: "#0000FF", secondaryColour: "yellow", + // secondaryColour: "#FFFF00", signatureColour: "#272727", // signatureColour: "#005bb0", signatureAccentColour: "#fff", @@ -116,7 +118,9 @@ export function isBXT(): boolean { const ADAPTIVE_AGRICULTURE_WHITE_LABEL: WhiteLabel = { name: "Adaptive Agriculture", primaryColour: "green", + // primaryColour: "#008000", secondaryColour: "yellow", + // secondaryColour: "#FFFF00", signatureColour: "#272727", signatureAccentColour: "#fff", auth0ClientId: import.meta.env.REACT_APP_AUTH0_ADAPTIVE_AGRICULTURE_CLIENT_ID, diff --git a/src/theme/index.ts b/src/theme/index.ts index dce14f6..bbaa471 100644 --- a/src/theme/index.ts +++ b/src/theme/index.ts @@ -1,3 +1,3 @@ export * from "./text"; -export { CreateTheme } from "./theme"; +// export { CreateTheme } from "./theme"; export * from "./themeType"; diff --git a/src/theme/theme.ts b/src/theme/theme.ts index c2852e5..7b75bc0 100644 --- a/src/theme/theme.ts +++ b/src/theme/theme.ts @@ -1,5 +1,6 @@ +// src/theme.ts import * as Colours from "@mui/material/colors" -import { createTheme, responsiveFontSizes, Theme, ThemeOptions } from '@mui/material/styles'; +import { createTheme, ThemeOptions } from '@mui/material/styles'; import { getPrimaryColour, getSecondaryColour, @@ -7,283 +8,55 @@ import { getSignatureColour } from "../services/whiteLabel"; -export function CreateTheme(themeType: "light" | "dark"): Theme { - return responsiveFontSizes(createTheme(options(themeType))); -} - -function generateBackgroundShades(themeType: "light" | "dark") { - if(themeType==="light") return ({ - default: "#e9e9e9", - paper: "#fafafa", - }) +const baseTheme: ThemeOptions = { + palette: { + mode: 'light', // default mode; will be overridden by system if needed + primary: { + main: '#1976d2', + }, + background: { + default: '#ffffff', + paper: '#f5f5f5', + }, + }, + // Add other options if needed +}; +function makePaletteColor(name: keyof typeof Colours) { + if (name.startsWith("#")) { + return { main: name }; + } + const ramp = Colours[name] as Record; // e.g., blue[500], blue[300], etc. + // if (!ramp) { + + // } return { - default: "#303030", - paper: "#373737", + main: ramp[500], + light: ramp[300], + dark: ramp[700], }; } -function options(themeType: "light" | "dark"): ThemeOptions { - - const signature = getSignatureColour(); - const accent = getSignatureAccentColour(); - const highlight = themeType === "light" ? "black" : "white"; - const bg = generateBackgroundShades(themeType); - return { - cssVariables: true, - zIndex: { - modal: 1300, - popover: 1350 - }, +export const getTheme = (mode: 'light' | 'dark') => + createTheme({ + ...baseTheme, palette: { - primary: Colours[getPrimaryColour() as keyof typeof Colours], - secondary: Colours[getSecondaryColour() as keyof typeof Colours], - background: bg, - mode: themeType, - bxt: { - primaryBlue: "#005bb0", - lightBlue: "#1a86ec", - darkBlue: "#081d55", - rubberDuckYellow: "#ffd45c" - }, - status: { - succes: "#4caf50", - unknown: "0288d1", - unstable: "rgb(189, 200, 33)", - risk: "#ffd642", - warning: "#ffb74d", - alert: "#f44336" - } + ...baseTheme.palette, + primary: makePaletteColor(getPrimaryColour()), + secondary: makePaletteColor(getSecondaryColour()), + mode, + ...(mode === 'dark' + ? { + background: { + default: '#121212', + paper: '#1e1e1e', + }, + } + : { + background: { + default: "#e9e9e9", + paper: "#fafafa", + } + }), }, - typography: { - fontFamily: [ - "Open Sans", - "-apple-system", - "BlinkMacSystemFont", - '"Segoe UI"', - '"Helvetica Neue"', - "Arial", - "sans-serif", - '"Apple Color Emoji"', - '"Segoe UI Emoji"', - '"Segoe UI Symbol"' - ].join(",") - }, - components: { - MuiAppBar: { - styleOverrides: { - colorPrimary: { - backgroundColor: signature, - color: accent, - }, - }, - }, - MuiBottomNavigation: { - styleOverrides: { - root: { - backgroundColor: signature - } - } - }, - MuiBottomNavigationAction: { - styleOverrides: { - root: { - backgroundColor: signature, - color: Colours["grey"][500], - "&$selected": { - color: accent - } - } - }, - }, - MuiCard: { - styleOverrides: { - root: { - borderRadius: "5px", - "@media (min-width: 600px)": { - borderRadius: "7px" - } - } - } - }, - MuiChip: { - styleOverrides: { - root: { - "@media (max-width: 600px)": { - height: "24px" - } - }, - avatar: { - "@media (max-width: 600px)": { - height: "24px", - width: "24px" - } - } - }, - }, - MuiDialog: { - styleOverrides: { - paperFullScreen: { - overflowX: "hidden" - }, - root: { - // zIndex: 1500 - } - }, - }, - MuiDrawer: { - styleOverrides: { - root: { - zIndex: 1300 - } - }, - }, - MuiSlider: { - styleOverrides: { - root: { - height: 5 - }, - thumb: { - height: 18, - width: 18, - marginTop: -6, - marginLeft: -9, - "&:focus,&:hover,&$active": { - boxShadow: "inherit" - } - }, - active: {}, - valueLabel: { - left: "calc(-50% + 2px)" - }, - track: { - height: 7, - borderRadius: 4 - }, - rail: { - height: 7, - borderRadius: 4 - }, - vertical: { - "& .MuiSlider-thumb": { - marginLeft: "-8px !important" - } - } - }, - }, - MuiStepper: { - styleOverrides: { - root: { - background: "transparent" - } - }, - }, - MuiSwitch: { - styleOverrides: { - switchBase: { - color: Colours.grey[400], - "&$checked": { - color: Colours.grey[100] - }, - "&$checked + $track": { - color: Colours.grey[100] - } - }, - checked: {}, - track: {} - } - }, - MuiButton: { - styleOverrides: { - root: { - '& .MuiTouchRipple-root': { - color: accent, // Customize ripple color here - // color: "white" - }, - '&:hover': { - backgroundColor: "rgba(125, 125, 125, 0.2)", - color: accent, - }, - }, - }, - }, - MuiListItemButton: { - styleOverrides: { - root: { - '& .MuiTouchRipple-root': { - color: highlight, - }, - '&:hover': { - color: highlight - }, - }, - }, - }, - MuiCssBaseline: { - styleOverrides: { - body: { - backgroundColor: bg, // Set your desired background color - // color: '#333', // Optionally, you can also set the default text color - }, - }, - }, - MuiSvgIcon: { - styleOverrides: { - root: { - // Set the default icon color for light mode - // color: "black", // Dark color for icons in light mode - }, - }, - }, - MuiLink: { - styleOverrides: { - root: { - color: 'inherit', // Default link color - textDecoration: 'none', // Remove underline if desired - '&:hover': { - // color: 'red !important', // Your hover color - }, - }, - }, - }, - MuiToggleButtonGroup: { - styleOverrides: { - root: { - borderRadius: 25 - }, - middleButton: { - borderRadius: 24, - marginRight: 5, - marginLeft: 5, - }, - lastButton: { - borderRadius: 24, - }, - firstButton: { - borderRadius: 24, - }, - grouped: { - border: "none", - color: "gray" - } - } - }, - // MuiToggleButton: { - // styleOverrides: { - // root: { - // "&:hover": { - // backgroundColor: getPrimaryColour(), - // } - // } - // } - // } - // MuiDataGrid: { - // styleOverrides: { - // root: { - // backgroundColor: "black" - // }, - // }, - // }, - } - } as unknown as ThemeOptions; -} + }); diff --git a/src/theme/themeType.ts b/src/theme/themeType.ts index d494282..eba1431 100644 --- a/src/theme/themeType.ts +++ b/src/theme/themeType.ts @@ -1,4 +1,4 @@ -export type ThemeType = "light" | "dark"; +export type ThemeType = "light" | "dark" | "system"; export function getThemeType(): ThemeType { let theme = localStorage.getItem("theme"); @@ -8,3 +8,8 @@ export function getThemeType(): ThemeType { export function setThemeType(theme: ThemeType) { localStorage.setItem("theme", theme); } + +export function getThemeMode(): ThemeType { + let theme = localStorage.getItem("theme"); + return theme === "light" ? "light" : "dark"; +} \ No newline at end of file diff --git a/src/user/UserMenu.tsx b/src/user/UserMenu.tsx index 2139b2c..ebcbe5d 100644 --- a/src/user/UserMenu.tsx +++ b/src/user/UserMenu.tsx @@ -14,6 +14,7 @@ import { useNavigate } from "react-router-dom"; import TeamDialog from "teams/TeamDialog"; import AccessObject from "./AccessObject"; import { useSnackbar, useUserAPI } from "hooks"; +import { useThemeMode } from "theme/AppThemeProvider"; const useStyles = makeStyles((theme: Theme) => ({ accessIcon: { @@ -72,14 +73,10 @@ const useStyles = makeStyles((theme: Theme) => ({ borderColor: getSignatureAccentColour() }, })); - -interface Props { - toggleTheme: () => void; -} -export default function UserMenu(props: Props) { +export default function UserMenu() { - const { toggleTheme } = props; + const { toggleMode } = useThemeMode() const [{ user, team, as }, dispatch] = useGlobalState(); const { loginWithRedirect } = useAuth0(); const classes = useStyles(); @@ -167,7 +164,7 @@ export default function UserMenu(props: Props) { )} - + {}} className={classes.rightIcon} aria-label="Toggle Theme"> @@ -229,7 +226,7 @@ export default function UserMenu(props: Props) { - +