From 17180e8232b605e896c5ef2d7d02bfb35f77bcb4 Mon Sep 17 00:00:00 2001
From: pharmpy-dev-123 <101794402+pharmpy-dev-123@users.noreply.github.com>
Date: Mon, 17 Oct 2022 19:20:59 +0200
Subject: [PATCH] Get rid of flash of erroneously styled content
Using MUI's experimental `useColorScheme`. Fixes #1.
---
gatsby-ssr.js | 5 ++
src/layouts/default.tsx | 13 +++--
src/ui/Header.tsx | 70 ++++++++++++++++++++------
src/ui/lib/component/useForceUpdate.ts | 11 ++++
src/ui/lib/component/useIsMounted.ts | 25 +++++++++
src/ui/lib/text/HighlightGrammar.tsx | 7 ++-
src/ui/theme/ModeContext.ts | 19 -------
src/ui/theme/ModeProvider.tsx | 27 ----------
src/ui/theme/useMode.ts | 7 ++-
src/ui/theme/useNavigatorMode.ts | 7 ---
src/ui/useTheme.ts | 13 +----
11 files changed, 113 insertions(+), 91 deletions(-)
create mode 100644 gatsby-ssr.js
create mode 100644 src/ui/lib/component/useForceUpdate.ts
create mode 100644 src/ui/lib/component/useIsMounted.ts
delete mode 100644 src/ui/theme/ModeContext.ts
delete mode 100644 src/ui/theme/ModeProvider.tsx
delete mode 100644 src/ui/theme/useNavigatorMode.ts
diff --git a/gatsby-ssr.js b/gatsby-ssr.js
new file mode 100644
index 0000000..27e0c09
--- /dev/null
+++ b/gatsby-ssr.js
@@ -0,0 +1,5 @@
+import {getInitColorSchemeScript} from '@mui/material/styles';
+
+export function onRenderBody({setPreBodyComponents}) {
+ setPreBodyComponents([getInitColorSchemeScript()]);
+}
diff --git a/src/layouts/default.tsx b/src/layouts/default.tsx
index 15184e9..fb1481b 100644
--- a/src/layouts/default.tsx
+++ b/src/layouts/default.tsx
@@ -7,11 +7,14 @@ import '@fontsource/roboto/700.css';
import type {PropsOf} from '@emotion/react/types/helper';
-import {ThemeProvider} from '@mui/material/styles';
+import {
+ ThemeProvider,
+ Experimental_CssVarsProvider as CssVarsProvider,
+} from '@mui/material/styles';
+// eslint-disable-next-line import/no-unassigned-import
+import type {} from '@mui/material/themeCssVarsAugmentation';
import CssBaseline from '@mui/material/CssBaseline';
-import ModeProvider from '../ui/theme/ModeProvider';
-
import useTheme from '../ui/useTheme';
import Header from '../ui/Header';
import Main from '../ui/Main';
@@ -33,9 +36,9 @@ function Layout({path, ...rest}: LayoutProps) {
function App(props: LayoutProps) {
return (
-
+
-
+
);
}
diff --git a/src/ui/Header.tsx b/src/ui/Header.tsx
index 7f9ec2e..991b5fc 100644
--- a/src/ui/Header.tsx
+++ b/src/ui/Header.tsx
@@ -1,6 +1,6 @@
import React from 'react';
-import {useTheme} from '@mui/material/styles';
+import {useTheme, useColorScheme} from '@mui/material/styles';
import AppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar';
@@ -9,9 +9,12 @@ import IconButton from '@mui/material/IconButton';
import Box from '@mui/material/Box';
import EditIcon from '@mui/icons-material/Edit';
+import CircleIcon from '@mui/icons-material/Circle';
import LightModeIcon from '@mui/icons-material/LightMode';
import DarkModeIcon from '@mui/icons-material/DarkMode';
+import SystemModeIcon from '@mui/icons-material/Brightness4';
+import useIsMounted from './lib/component/useIsMounted';
import Breadcrumbs from './navigation/BreadCrumbs';
import useMode from './theme/useMode';
@@ -19,27 +22,64 @@ type HeaderProps = {
path: string;
};
+type Mode = 'system' | 'light' | 'dark';
+
+type ModeMapOptions = {
+ system: T;
+ light: T;
+ dark: T;
+};
+
+function modeMap(mode: Mode, options: ModeMapOptions): T {
+ return options[mode];
+}
+
+function ModeSwitch() {
+ const isMounted = useIsMounted();
+ const {mode, setMode} = useColorScheme();
+
+ let onClick;
+ let ariaLabel;
+ let Icon = CircleIcon;
+ if (mode !== undefined && isMounted()) {
+ const nextMode = modeMap(mode, {
+ system: 'light',
+ light: 'dark',
+ dark: 'system',
+ });
+ onClick = () => {
+ setMode(nextMode);
+ };
+
+ ariaLabel = `switch to ${nextMode} mode`;
+ Icon = modeMap(mode, {
+ system: SystemModeIcon,
+ light: LightModeIcon,
+ dark: DarkModeIcon,
+ });
+ }
+
+ return (
+
+
+
+ );
+}
+
function Header({path}: HeaderProps) {
- const {
- palette: {mode},
- } = useTheme();
- const [, setMode] = useMode();
return (
- {
- setMode(mode === 'dark' ? 'light' : 'dark');
- }}
- >
- {mode === 'dark' ? : }
-
+
{
+ // eslint-disable-next-line react/hook-use-state
+ const [, updateState] = useState>();
+ return useCallback(() => {
+ updateState({});
+ }, []);
+};
+
+export default useForceUpdate;
diff --git a/src/ui/lib/component/useIsMounted.ts b/src/ui/lib/component/useIsMounted.ts
new file mode 100644
index 0000000..2253077
--- /dev/null
+++ b/src/ui/lib/component/useIsMounted.ts
@@ -0,0 +1,25 @@
+import {useRef, useEffect, useState} from 'react';
+import useForceUpdate from './useForceUpdate';
+
+/**
+ * See https://gist.github.com/jaydenseric/a67cfb1b809b1b789daa17dfe6f83daa
+ *
+ * Do not use to avoid warning when calling setState on an unmounted component.
+ * See https://github.com/facebook/react/pull/22114
+ */
+const useIsMounted = () => {
+ const componentIsMounted = useRef(false);
+ const forceUpdate = useForceUpdate();
+
+ useEffect(() => {
+ componentIsMounted.current = true;
+ forceUpdate();
+ return () => {
+ componentIsMounted.current = false;
+ };
+ }, [forceUpdate]);
+
+ return () => componentIsMounted.current;
+};
+
+export default useIsMounted;
diff --git a/src/ui/lib/text/HighlightGrammar.tsx b/src/ui/lib/text/HighlightGrammar.tsx
index 2b9b5e4..e50bf2a 100644
--- a/src/ui/lib/text/HighlightGrammar.tsx
+++ b/src/ui/lib/text/HighlightGrammar.tsx
@@ -13,7 +13,8 @@ import r from 'react-syntax-highlighter/dist/esm/languages/prism/r';
import bash from 'react-syntax-highlighter/dist/esm/languages/prism/bash';
import dark from 'react-syntax-highlighter/dist/esm/styles/prism/material-dark';
import light from 'react-syntax-highlighter/dist/esm/styles/prism/material-light';
-import {useTheme} from '@mui/material/styles';
+
+import useMode from '../../theme/useMode';
import saveTextToClipboard from '../output/saveTextToClipboard';
SyntaxHighlighter.registerLanguage('python', python);
@@ -60,9 +61,7 @@ const customStyle = {margin: 0, display: 'flex', flex: '1'};
type Style = typeof dark | typeof light;
function HighlightGrammar({language, word}: Props) {
- const {
- palette: {mode},
- } = useTheme();
+ const mode = useMode();
const style: Style = mode === 'dark' ? dark : light;
const [tooltipText, setTooltipText] = useState(init);
const [open, setOpen] = useState(false);
diff --git a/src/ui/theme/ModeContext.ts b/src/ui/theme/ModeContext.ts
deleted file mode 100644
index a29ffbd..0000000
--- a/src/ui/theme/ModeContext.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import type {SetStateAction} from 'react';
-import type React from 'react';
-import {createContext} from 'react';
-
-import type {PaletteMode} from '@mui/material';
-
-export type State = PaletteMode | undefined;
-
-export type Dispatch = React.Dispatch>;
-
-// eslint-disable-next-line @typescript-eslint/naming-convention
-const ModeContext = createContext<[State, Dispatch]>([
- undefined,
- () => {
- // NOTE no-op by default
- },
-]);
-
-export default ModeContext;
diff --git a/src/ui/theme/ModeProvider.tsx b/src/ui/theme/ModeProvider.tsx
deleted file mode 100644
index 6f717a2..0000000
--- a/src/ui/theme/ModeProvider.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import React, {useEffect, useMemo, useState} from 'react';
-import type {PropsOf} from '@emotion/react/types/helper';
-import type {PaletteMode} from '@mui/material';
-import type {Dispatch, State} from './ModeContext';
-import ModeContext from './ModeContext';
-import useNavigatorMode from './useNavigatorMode';
-
-type ModeProviderProps = Record &
- Omit, 'value'>;
-
-function ModeProvider(props: ModeProviderProps) {
- const init = useNavigatorMode();
- const [mode, setMode] = useState(init);
-
- useEffect(() => {
- setMode(init);
- }, [init]);
-
- const value = useMemo<[State, Dispatch]>(
- () => [mode, setMode],
- [mode, setMode],
- );
-
- return ;
-}
-
-export default ModeProvider;
diff --git a/src/ui/theme/useMode.ts b/src/ui/theme/useMode.ts
index 55a72d0..fb6ec85 100644
--- a/src/ui/theme/useMode.ts
+++ b/src/ui/theme/useMode.ts
@@ -1,6 +1,9 @@
import {useContext} from 'react';
-import ModeContext from './ModeContext';
+import {useColorScheme} from '@mui/material/styles';
-const useMode = () => useContext(ModeContext);
+const useMode = () => {
+ const {mode: colorSchemeMode, systemMode} = useColorScheme();
+ return colorSchemeMode === 'system' ? systemMode : colorSchemeMode;
+};
export default useMode;
diff --git a/src/ui/theme/useNavigatorMode.ts b/src/ui/theme/useNavigatorMode.ts
deleted file mode 100644
index f99b157..0000000
--- a/src/ui/theme/useNavigatorMode.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import type {PaletteMode} from '@mui/material';
-import {useMediaQuery} from '@mui/material';
-
-const useNavigatorMode = (): PaletteMode =>
- useMediaQuery('(prefers-color-scheme: dark)') ? 'dark' : 'light';
-
-export default useNavigatorMode;
diff --git a/src/ui/useTheme.ts b/src/ui/useTheme.ts
index b7c31e2..2041cbe 100644
--- a/src/ui/useTheme.ts
+++ b/src/ui/useTheme.ts
@@ -1,19 +1,8 @@
import {useMemo} from 'react';
import {createTheme} from '@mui/material/styles';
-import useMode from './theme/useMode';
-
const useTheme = () => {
- const [mode] = useMode();
- return useMemo(
- () =>
- createTheme({
- palette: {
- mode,
- },
- }),
- [mode],
- );
+ return useMemo(() => createTheme(), []);
};
export default useTheme;