got new theme system to work without re-rendering

This commit is contained in:
Carter 2025-04-14 16:25:33 -06:00
parent a452cf9444
commit 3454d16f36
12 changed files with 100 additions and 361 deletions

6
package-lock.json generated
View file

@ -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": {

View file

@ -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<string | undefined>(undefined)
const [palette, setPalette] = useState<Theme>(CreateTheme(getThemeType()));
// const [palette, setPalette] = useState<Theme>(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 (
<Auth0Provider
domain={or(url, "")}
clientId={or(client_id, "")}
authorizationParams={{
audience: or(audience, ""),
redirect_uri: window.location.origin + "/callback"
}}
useRefreshTokens={true}
cacheLocation='localstorage'
>
<ThemeProvider theme={palette}>
<CssBaseline />
<AppThemeProvider>
<Auth0Provider
domain={or(url, "")}
clientId={or(client_id, "")}
authorizationParams={{
audience: or(audience, ""),
redirect_uri: window.location.origin + "/callback"
}}
useRefreshTokens={true}
cacheLocation='localstorage'
>
{/* <CssBaseline /> */}
<AuthWrapper setToken={setToken}>
{ token ?
<HTTPProvider token={token}>
<UserWrapper toggleTheme={toggleTheme}/>
<UserWrapper />
</HTTPProvider>
:
<LoadingScreen
@ -79,8 +68,8 @@ function App() {
/>
}
</AuthWrapper>
</ThemeProvider>
</Auth0Provider>
</Auth0Provider>
</AppThemeProvider>
)
}

View file

@ -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<React.SetStateAction<Team[]>>;
}
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) {
)}
</Box>
<Box className={classes.appBarRight}>
<UserMenu toggleTheme={toggleTheme} />
<UserMenu />
<HeaderButtons hasTeams={hasTeams} team={team} user={user} />
</Box>
</Toolbar>

View file

@ -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 (
<StateProvider state={global} reducer={reducer}>
{/* <FirmwareLoader> */}
<AppThemeProvider >
<CssBaseline/>
<main className={classes.appContent}>
<NavigationContainer toggleTheme={toggleTheme} />
<NavigationContainer />
</main>
</AppThemeProvider>
{/* <FirmwareLoader> */}
{/* </FirmwareLoader> */}
</StateProvider>
)

View file

@ -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";

View file

@ -1,22 +1,12 @@
import React, { PropsWithChildren } from "react";
import Router from "./Router";
interface Props extends PropsWithChildren {
toggleTheme: () => void;
// teams: Team[];
// setTeams: React.Dispatch<React.SetStateAction<Team[]>>;
}
export default function NavigationContainer(props: Props) {
// const { toggleTheme, teams, setTeams } = props;
const { toggleTheme } = props;
export default function NavigationContainer() {
return (
<React.Fragment>
{/* <NotificationBanner /> */}
<Router
toggleTheme={toggleTheme}
/>
<Router />
</React.Fragment>
);
}

View file

@ -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 (
<Suspense fallback={<LoadingScreen />}>
<BrowserRouter>
<Header
toggleTheme={toggleTheme}
// teams={teams}
// setTeams={setTeams}
/>
<Header />
<Routes>
{/* Redirects */}

View file

@ -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,

View file

@ -1,3 +1,3 @@
export * from "./text";
export { CreateTheme } from "./theme";
// export { CreateTheme } from "./theme";
export * from "./themeType";

View file

@ -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<number, string>; // 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;
}
});

View file

@ -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";
}

View file

@ -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) {
)}
</Button>
<IconButton onClick={toggleTheme} className={classes.rightIcon} aria-label="Toggle Theme">
<IconButton onClick={/*toggleMode*/ () => {}} className={classes.rightIcon} aria-label="Toggle Theme">
<ThemeIcon />
</IconButton>
</>
@ -229,7 +226,7 @@ export default function UserMenu(props: Props) {
</ListItemIcon>
<ListItemText primary="User Settings" />
</MenuItem>
<MenuItem onClick={toggleTheme} aria-label="Toggle Theme" dense>
<MenuItem onClick={toggleMode} aria-label="Toggle Theme" dense>
<ListItemIcon>
<ThemeIcon />
</ListItemIcon>