fix: dark mode flickering by using native css vars

This commit is contained in:
Johannes Kirschbauer 2024-05-17 13:52:46 +02:00 committed by Johannes Kirschbauer
parent eb0f3a19f8
commit 9c04f9559f
15 changed files with 150 additions and 185 deletions

View File

@ -1,4 +1,3 @@
import { CssBaseline } from "@mui/material";
import "../styles/globals.css";
import { AppRouterCacheProvider } from "@mui/material-nextjs/v13-appRouter";
import localFont from "next/font/local";
@ -26,7 +25,7 @@ export default function RootLayout({
children: React.ReactNode;
}) {
return (
<html lang="en" className={inter.className}>
<html lang="en" className={inter.className} data-color-scheme="light">
<head>
{/* <link rel="icon" href="/favicon.png" /> */}
<link
@ -43,7 +42,7 @@ export default function RootLayout({
}}
>
<ClientSideLayoutContext>
<CssBaseline />
{/* <CssBaseline /> */}
{children}
</ClientSideLayoutContext>
</AppRouterCacheProvider>

View File

@ -4,7 +4,7 @@ import { LandingPageLayout } from "@/components/layout";
import { FilterProvider } from "@/components/layout/filterContext";
import { SearchInput } from "@/components/searchInput";
import { Box, Typography, Link } from "@mui/material";
import type {} from "@mui/material/themeCssVarsAugmentation";
import localFont from "next/font/local";
import { Suspense } from "react";

View File

@ -1,28 +1,70 @@
"use client";
import {
CssBaseline,
ThemeProvider,
createTheme,
useMediaQuery,
} from "@mui/material";
import { darkThemeOptions, lightThemeOptions } from "@/styles/theme";
import { ReactNode } from "react";
import { Button, CssBaseline, useMediaQuery } from "@mui/material";
import { cssThemeOptions } from "@/styles/theme";
import { ReactNode, useEffect, useState } from "react";
import { Toaster } from "react-hot-toast";
import {
Experimental_CssVarsProvider as CssVarsProvider,
experimental_extendTheme as extendTheme,
getInitColorSchemeScript,
useColorScheme,
} from "@mui/material/styles";
const darkTheme = createTheme(darkThemeOptions);
const lightTheme = createTheme(lightThemeOptions);
const theme = extendTheme(cssThemeOptions);
const Mode = () => {
const userPrefersDarkmode = useMediaQuery("(prefers-color-scheme: dark)");
const { mode, setMode } = useColorScheme();
const [mounted, setMounted] = useState(false);
useEffect(
() => {
setMounted(true);
setMode(userPrefersDarkmode ? "dark" : "light");
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[userPrefersDarkmode]
);
if (!mounted) {
// for server-side rendering
// learn more at https://github.com/pacocoursey/next-themes#avoid-hydration-mismatch
return null;
}
return (
<Button
variant="outlined"
onClick={() => {
if (mode === "light") {
setMode("dark");
} else {
setMode("light");
}
}}
>
{mode === "light" ? "Change to Dark" : "Change to Light"}
</Button>
);
};
export const ClientSideLayoutContext = ({
children,
}: {
children: ReactNode;
}) => {
const userPrefersDarkmode = useMediaQuery("(prefers-color-scheme: dark)");
return (
<ThemeProvider theme={userPrefersDarkmode ? darkTheme : lightTheme}>
<CssBaseline />
<Toaster />
{children}
</ThemeProvider>
// <ThemeProvider theme={userPrefersDarkmode ? darkTheme : lightTheme}>
<>
{getInitColorSchemeScript()}
<CssVarsProvider theme={theme}>
<CssBaseline />
<Mode />
<Toaster />
{children}
</CssVarsProvider>
</>
// </ThemeProvider>
);
};

View File

@ -1,17 +1,18 @@
"use client";
import { useTheme } from "@mui/material";
import { useColorScheme } from "@mui/material";
import { useEffect } from "react";
export const HighlightBaseline = () => {
const theme = useTheme();
const { mode } = useColorScheme();
useEffect(() => {
if (theme.palette.mode === "dark") {
// @ts-ignore - don't check type of css module
import("highlight.js/styles/github-dark.css");
console.log({ mode });
// @ts-ignore - don't check type of css module
import("highlight.js/styles/github-dark.css");
if (mode === "dark") {
} else {
// @ts-ignore - don't check type of css module
import("highlight.js/styles/github.css");
// import("highlight.js/styles/github.css");
}
}, [theme]);
}, [mode]);
return <></>;
};

View File

@ -6,7 +6,7 @@ import {
CardContent,
CardHeader,
Divider,
useTheme,
styled,
} from "@mui/material";
import { useMemo, useState } from "react";
import seedrandom from "seedrandom";
@ -37,10 +37,20 @@ function getRandomIntInclusive(min: number, max: number, config?: Config) {
return Math.floor(randomNumber * (max - min + 1) + min); // The maximum is inclusive and the minimum is inclusive
}
const FunctionCard = styled(
Card,
{}
)(({ theme }) => ({
width: "100%",
borderImageSlice: 1,
borderImageSource: `linear-gradient(to left, ${theme.vars.palette.info.light},${theme.vars.palette.error.main})`,
// : `linear-gradient(to left, ${info.light},${info.dark})`,
borderWidth: 4,
borderStyle: "solid",
}));
export const FunctionOfTheDay = () => {
const {
palette: { info, error },
} = useTheme();
// const { mode } = useColorScheme();
const todaysIdx = useMemo(
() => getRandomIntInclusive(0, data.length - 1),
@ -74,20 +84,7 @@ export const FunctionOfTheDay = () => {
};
return (
<Card
elevation={0}
sx={{
width: "100%",
my: 5,
borderImageSlice: 1,
borderImageSource:
idx === todaysIdx
? `linear-gradient(to left, ${info.light},${error.main})`
: `linear-gradient(to left, ${info.light},${info.dark})`,
borderWidth: 4,
borderStyle: "solid",
}}
>
<FunctionCard sx={{ my: 5 }}>
<CardHeader
component="h2"
sx={{ py: 0 }}
@ -129,6 +126,6 @@ export const FunctionOfTheDay = () => {
Next
</Button>
</CardActions>
</Card>
</FunctionCard>
);
};

View File

@ -1,9 +1,8 @@
"use client";
import { Box, useTheme } from "@mui/material";
import { Box } from "@mui/material";
import { ReactNode } from "react";
export const Background = ({ children }: { children: ReactNode }) => {
const theme = useTheme();
return (
<Box
sx={{
@ -12,8 +11,7 @@ export const Background = ({ children }: { children: ReactNode }) => {
flexDirection: "column",
overflowX: "hidden",
overflowY: "scroll",
bgcolor:
theme.palette.mode === "light" ? "rgb(242, 248, 253)" : "#070c16",
bgcolor: "background.paper",
}}
>
{children}

View File

@ -1,5 +1,5 @@
"use client";
import { Box, LinearProgress, Link, useTheme } from "@mui/material";
import { Box, LinearProgress, Link } from "@mui/material";
import { SearchInput } from "../searchInput";
import { Filter } from "../filter";
import { Suspense } from "react";
@ -13,8 +13,6 @@ const fira = localFont({
});
export const Header = () => {
const theme = useTheme();
return (
<>
<Box
@ -24,8 +22,7 @@ export const Header = () => {
width: "100%",
py: 1.2,
zIndex: 1000,
backgroundColor:
theme.palette.mode === "light" ? "primary.main" : "#101010",
backgroundColor: "header.default",
display: "grid",
gridTemplateColumns: {
xs: "1fr",

View File

@ -1,5 +1,5 @@
"use client";
import { useTheme } from "@mui/material";
import { useColorScheme } from "@mui/material";
import nix from "highlight.js/lib/languages/nix";
import haskell from "highlight.js/lib/languages/haskell";
import bash from "highlight.js/lib/languages/bash";
@ -23,16 +23,17 @@ interface MarkdownPreviewProps {
}
export const MarkdownPreview = (props: MarkdownPreviewProps) => {
const { description } = props;
const theme = useTheme();
const { mode } = useColorScheme();
useEffect(() => {
if (theme.palette.mode === "dark") {
// @ts-ignore - don't check type of css module
import("highlight.js/styles/github-dark.css");
console.log({ mode });
// @ts-ignore - don't check type of css module
import("highlight.js/styles/github-dark.css");
if (mode === "dark") {
} else {
// @ts-ignore - don't check type of css module
import("highlight.js/styles/github.css");
// import("highlight.js/styles/github.css");
}
}, [theme]);
}, [mode]);
return (
<>

View File

@ -1,72 +0,0 @@
"use client";
import { darkThemeOptions, lightThemeOptions } from "@/styles/theme";
import createCache from "@emotion/cache";
import { CacheProvider } from "@emotion/react";
import { useMediaQuery } from "@mui/material";
import CssBaseline from "@mui/material/CssBaseline";
import { ThemeProvider, createTheme } from "@mui/material/styles";
import { useServerInsertedHTML } from "next/navigation";
import React, { ReactNode } from "react";
const lightTheme = createTheme(lightThemeOptions);
const darkTheme = createTheme(darkThemeOptions);
// This implementation is from emotion-js
// https://github.com/emotion-js/emotion/issues/2928#issuecomment-1319747902
export default function ThemeRegistry(props: {
children: ReactNode;
options: any;
}) {
const userPrefersDarkmode = useMediaQuery("(prefers-color-scheme: dark)");
const { options, children } = props;
const [{ cache, flush }] = React.useState(() => {
const cache = createCache(options);
cache.compat = true;
const prevInsert = cache.insert;
let inserted: string[] = [];
cache.insert = (...args) => {
const serialized = args[1];
if (cache.inserted[serialized.name] === undefined) {
inserted.push(serialized.name);
}
return prevInsert(...args);
};
const flush = () => {
const prevInserted = inserted;
inserted = [];
return prevInserted;
};
return { cache, flush };
});
useServerInsertedHTML(() => {
const names = flush();
if (names.length === 0) {
return null;
}
let styles = "";
for (const name of names) {
styles += cache.inserted[name];
}
return (
<style
key={cache.key}
data-emotion={`${cache.key} ${names.join(" ")}`}
dangerouslySetInnerHTML={{
__html: options.prepend ? `@layer emotion {${styles}}` : styles,
}}
/>
);
});
return (
<CacheProvider value={cache}>
<ThemeProvider theme={userPrefersDarkmode ? darkTheme : lightTheme}>
<CssBaseline />
{children}
</ThemeProvider>
</CacheProvider>
);
}

View File

@ -189,14 +189,9 @@ mark.noogle-marker {
code {
/* padding: 0.3rem; */
background-color: #f0f1f2;
}
pre {
/* padding: 0.3rem; */
background-color: #f0f1f2;
}
code.hljs {
background-color: #f0f1f2;
}

View File

@ -1,6 +1,6 @@
import { ThemeOptions } from "@mui/material/styles";
import { CssVarsThemeOptions } from "@mui/material";
const commonOptions: Partial<ThemeOptions> = {
const commonOptions: Partial<CssVarsThemeOptions> = {
typography: {
fontFamily: "inherit",
h1: {

View File

@ -1,22 +0,0 @@
import { ThemeOptions } from "@mui/material/styles";
import { commonOptions } from "./common";
const darkThemeOptions: ThemeOptions = {
...commonOptions,
palette: {
mode: "dark",
background: {
// default: "#0f192c",
default: "#1b1d22",
paper: "#17181c",
},
primary: {
main: "#6586c8",
},
secondary: {
main: "#40224e",
},
},
};
export { darkThemeOptions };

View File

@ -1,2 +1 @@
export { lightThemeOptions } from "./lightThemeOptions";
export { darkThemeOptions } from "./darkThemeOptions";
export { cssThemeOptions } from "./themeOptions";

View File

@ -1,20 +0,0 @@
import { ThemeOptions } from "@mui/material/styles";
import { commonOptions } from "./common";
const lightThemeOptions: ThemeOptions = {
...commonOptions,
palette: {
mode: "light",
primary: {
main: "#6586c8",
},
secondary: {
main: "#6ad541",
},
background: {
paper: "#fafafa",
default: "#f0f1f2",
},
},
};
export { lightThemeOptions };

View File

@ -0,0 +1,50 @@
import { CssVarsThemeOptions } from "@mui/material/styles";
import { commonOptions } from "./common";
import type {} from "@mui/material/themeCssVarsAugmentation";
const cssThemeOptions: CssVarsThemeOptions = {
...commonOptions,
colorSchemes: {
light: {
palette: {
mode: "light",
primary: {
main: "#6586c8",
},
secondary: {
main: "#6ad541",
},
background: {
paper: "#fafafa",
default: "#f0f1f2",
},
// @ts-expect-error
header: {
default: "var(--mui-palette-primary-main)",
},
},
},
dark: {
palette: {
mode: "dark",
background: {
// default: "#0f192c",
default: "#1b1d22",
paper: "#17181c",
},
primary: {
main: "#6586c8",
},
secondary: {
main: "#40224e",
},
// @ts-expect-error
header: {
default: "#101010",
},
},
},
},
};
export { cssThemeOptions };