Autoselect the system theme by default

This commit is contained in:
DrMint 2022-02-23 13:13:05 +01:00
parent 60c32fc86d
commit 8842707ae4
7 changed files with 121 additions and 86 deletions

View File

@ -8,7 +8,7 @@ export default function InsetBox(props: InsetBoxProps): JSX.Element {
return ( return (
<div <div
id={props.id} id={props.id}
className={`w-full shadow-inner-sm shadow-shade bg-mid dark:bg-dark-mid rounded-xl p-8 ${props.className}`} className={`w-full shadow-inner-sm shadow-shade dark:shadow-dark-shade bg-mid dark:bg-dark-mid rounded-xl p-8 ${props.className}`}
> >
{props.children} {props.children}
</div> </div>

View File

@ -25,39 +25,14 @@ export default function MainPanel(props: MainPanelProps): JSX.Element {
appLayout.mainPanelReduced && "px-4" appLayout.mainPanelReduced && "px-4"
}`} }`}
> >
{appLayout.mainPanelReduced && isDesktop ? (
<div className="grid place-items-center gap-4">
<Link href="/" passHref>
<div
onClick={() => appLayout.setMainPanelOpen(false)}
className="w-12 cursor-pointer transition-[filter] colorize-black dark:colorize-dark-black hover:colorize-dark dark:hover:colorize-dark-dark"
>
<SVG
src={"/icons/accords.svg"}
alt={"Logo of Accord's Library"}
/>
</div>
</Link>
<Button onClick={() => appLayout.setDarkMode(!appLayout.darkMode)}>
<span className="material-icons !text-sm">
{appLayout.darkMode ? "light_mode" : "dark_mode"}
</span>
</Button>
{router.locale ? (
<div onClick={() => appLayout.setLanguagePanelOpen(true)}>
<Button className="text-xs">{router.locale.toUpperCase()}</Button>
</div>
) : (
""
)}
</div>
) : (
<div> <div>
<div className="grid place-items-center"> <div className="grid place-items-center">
<Link href="/" passHref> <Link href="/" passHref>
<div <div
onClick={() => appLayout.setMainPanelOpen(false)} onClick={() => appLayout.setMainPanelOpen(false)}
className="w-1/2 cursor-pointer transition-[filter] colorize-black dark:colorize-dark-black hover:colorize-dark dark:hover:colorize-dark-dark" className={`${
appLayout.mainPanelReduced && isDesktop ? "w-12" : "w-1/2"
} cursor-pointer transition-[filter] colorize-black dark:colorize-dark-black hover:colorize-dark dark:hover:colorize-dark-dark`}
> >
<SVG <SVG
src={"/icons/accords.svg"} src={"/icons/accords.svg"}
@ -66,12 +41,25 @@ export default function MainPanel(props: MainPanelProps): JSX.Element {
</div> </div>
</Link> </Link>
{appLayout.mainPanelReduced && isDesktop ? (
""
) : (
<h2 className="text-3xl">Accord&rsquo;s Library</h2> <h2 className="text-3xl">Accord&rsquo;s Library</h2>
)}
<div className="flex flex-row flex-wrap gap-2"> <div
className={`flex ${
appLayout.mainPanelReduced && isDesktop ? "flex-col" : "flex-row"
} flex-wrap gap-2`}
>
<Button <Button
onClick={() => appLayout.setDarkMode(!appLayout.darkMode)} onClick={() => {
className="right-0 top-[-1.3em] !py-0.5 !px-2.5" appLayout.setDarkMode(!appLayout.darkMode);
appLayout.setSelectedThemeMode(true);
}}
className={
appLayout.mainPanelReduced && isDesktop ? "" : "!py-0.5 !px-2.5"
}
> >
<span className="material-icons !text-sm"> <span className="material-icons !text-sm">
{appLayout.darkMode ? "dark_mode" : "light_mode"} {appLayout.darkMode ? "dark_mode" : "light_mode"}
@ -81,7 +69,11 @@ export default function MainPanel(props: MainPanelProps): JSX.Element {
{router.locale && ( {router.locale && (
<Button <Button
onClick={() => appLayout.setLanguagePanelOpen(true)} onClick={() => appLayout.setLanguagePanelOpen(true)}
className="right-0 top-[-1.3em] text-sm !py-0.5 !px-2.5" className={
appLayout.mainPanelReduced && isDesktop
? ""
: "!py-0.5 !px-2.5 !text-sm"
}
> >
{router.locale.toUpperCase()} {router.locale.toUpperCase()}
</Button> </Button>
@ -89,7 +81,8 @@ export default function MainPanel(props: MainPanelProps): JSX.Element {
</div> </div>
</div> </div>
</div> </div>
)}
<HorizontalLine />
<NavOption <NavOption
url="/library" url="/library"

View File

@ -1,17 +1,25 @@
import useDarkMode from "hooks/useDarkMode";
import useStateWithLocalStorage from "hooks/useStateWithLocalStorage"; import useStateWithLocalStorage from "hooks/useStateWithLocalStorage";
import React, { ReactNode, useContext } from "react"; import React, { ReactNode, useContext, useEffect } from "react";
export interface AppLayoutState { export interface AppLayoutState {
subPanelOpen: boolean; subPanelOpen: boolean | undefined;
languagePanelOpen: boolean; languagePanelOpen: boolean | undefined;
mainPanelReduced: boolean; mainPanelReduced: boolean | undefined;
mainPanelOpen: boolean; mainPanelOpen: boolean | undefined;
darkMode: boolean; darkMode: boolean | undefined;
setSubPanelOpen: React.Dispatch<React.SetStateAction<boolean>>; setSubPanelOpen: React.Dispatch<React.SetStateAction<boolean | undefined>>;
setLanguagePanelOpen: React.Dispatch<React.SetStateAction<boolean>>; setLanguagePanelOpen: React.Dispatch<
setMainPanelReduced: React.Dispatch<React.SetStateAction<boolean>>; React.SetStateAction<boolean | undefined>
setMainPanelOpen: React.Dispatch<React.SetStateAction<boolean>>; >;
setDarkMode: React.Dispatch<React.SetStateAction<boolean>>; setMainPanelReduced: React.Dispatch<
React.SetStateAction<boolean | undefined>
>;
setMainPanelOpen: React.Dispatch<React.SetStateAction<boolean | undefined>>;
setDarkMode: React.Dispatch<React.SetStateAction<boolean | undefined>>;
setSelectedThemeMode: React.Dispatch<
React.SetStateAction<boolean | undefined>
>;
} }
const initialState: AppLayoutState = { const initialState: AppLayoutState = {
@ -25,6 +33,7 @@ const initialState: AppLayoutState = {
setMainPanelReduced: () => {}, setMainPanelReduced: () => {},
setMainPanelOpen: () => {}, setMainPanelOpen: () => {},
setDarkMode: () => {}, setDarkMode: () => {},
setSelectedThemeMode: () => {},
}; };
const AppContext = React.createContext<AppLayoutState>(initialState); const AppContext = React.createContext<AppLayoutState>(initialState);
@ -40,29 +49,23 @@ type Props = {
}; };
export const AppContextProvider = (props: Props) => { export const AppContextProvider = (props: Props) => {
const [subPanelOpen, setSubPanelOpen] = useStateWithLocalStorage<boolean>( const [subPanelOpen, setSubPanelOpen] = useStateWithLocalStorage<
"subPanelOpen", boolean | undefined
initialState.subPanelOpen >("subPanelOpen", initialState.subPanelOpen);
);
const [languagePanelOpen, setLanguagePanelOpen] = const [languagePanelOpen, setLanguagePanelOpen] = useStateWithLocalStorage<
useStateWithLocalStorage<boolean>( boolean | undefined
"languagePanelOpen", >("languagePanelOpen", initialState.languagePanelOpen);
initialState.languagePanelOpen
);
const [mainPanelReduced, setMainPanelReduced] = const [mainPanelReduced, setMainPanelReduced] = useStateWithLocalStorage<
useStateWithLocalStorage<boolean>( boolean | undefined
"mainPanelReduced", >("mainPanelReduced", initialState.mainPanelReduced);
initialState.mainPanelReduced
);
const [mainPanelOpen, setMainPanelOpen] = useStateWithLocalStorage<boolean>( const [mainPanelOpen, setMainPanelOpen] = useStateWithLocalStorage<
"mainPanelOpen", boolean | undefined
initialState.mainPanelOpen >("mainPanelOpen", initialState.mainPanelOpen);
);
const [darkMode, setDarkMode] = useStateWithLocalStorage<boolean>( const [darkMode, setDarkMode, setSelectedThemeMode] = useDarkMode(
"darkMode", "darkMode",
initialState.darkMode initialState.darkMode
); );
@ -80,6 +83,7 @@ export const AppContextProvider = (props: Props) => {
setMainPanelReduced, setMainPanelReduced,
setMainPanelOpen, setMainPanelOpen,
setDarkMode, setDarkMode,
setSelectedThemeMode,
}} }}
> >
{props.children} {props.children}

27
src/hooks/useDarkMode.ts Normal file
View File

@ -0,0 +1,27 @@
import { useEffect } from "react";
import { usePrefersDarkMode } from "./useMediaQuery";
import useStateWithLocalStorage from "./useStateWithLocalStorage";
export default function useDarkMode(
key: string,
initialValue: boolean | undefined
): [
boolean | undefined,
React.Dispatch<React.SetStateAction<boolean | undefined>>,
React.Dispatch<React.SetStateAction<boolean | undefined>>
] {
const [darkMode, setDarkMode] = useStateWithLocalStorage(key, initialValue);
const prefersDarkMode = usePrefersDarkMode();
const [selectedThemeMode, setSelectedThemeMode] = useStateWithLocalStorage(
"selectedThemeMode",
false
);
useEffect(() => {
if (selectedThemeMode === false) setDarkMode(prefersDarkMode);
}, [selectedThemeMode, prefersDarkMode, setDarkMode]);
return [darkMode, setDarkMode, setSelectedThemeMode];
}

View File

@ -52,3 +52,7 @@ export function useMediaCoarse() {
export function useMediaFine() { export function useMediaFine() {
return useMediaQuery("(pointer: fine)"); return useMediaQuery("(pointer: fine)");
} }
export function usePrefersDarkMode() {
return useMediaQuery("(prefers-color-scheme: dark)");
}

View File

@ -3,17 +3,24 @@ import { useEffect, useState } from "react";
export default function useStateWithLocalStorage<T>( export default function useStateWithLocalStorage<T>(
key: string, key: string,
initialValue: T initialValue: T
): [T, React.Dispatch<React.SetStateAction<T>>] { ): [T | undefined, React.Dispatch<React.SetStateAction<T | undefined>>] {
const [value, setValue] = useState<T>(initialValue); const [value, setValue] = useState<T | undefined>(undefined);
const [, setFromLocaleStorage] = useState<boolean>(false);
useEffect(() => { useEffect(() => {
try { try {
const item = localStorage.getItem(key); const item = localStorage.getItem(key);
if (item) setValue(JSON.parse(item) as T); if (item !== undefined && item !== null) {
setValue(JSON.parse(item) as T);
} else {
setValue(initialValue);
}
setFromLocaleStorage(true);
} catch (error) { } catch (error) {
console.warn(`Error reading localStorage key “${key}”:`, error); console.warn(`Error reading localStorage key “${key}”:`, error);
setValue(initialValue);
} }
}, [setValue, key]); }, [initialValue, key]);
useEffect(() => { useEffect(() => {
localStorage.setItem(key, JSON.stringify(value)); localStorage.setItem(key, JSON.stringify(value));

View File

@ -374,7 +374,7 @@ export default function LibrarySlug(props: LibrarySlugProps): JSX.Element {
<div <div
id={content.attributes.slug} id={content.attributes.slug}
key={content.id} key={content.id}
className="grid gap-2 px-4 rounded-lg target:bg-mid dark:bg-dark-mid target:shadow-inner-sm target:shadow-shade target:h-auto target:py-3 target:my-2 target:[--displaySubContentMenu:grid] [--displaySubContentMenu:none]" className="grid gap-2 px-4 rounded-lg target:bg-mid dark:target:bg-dark-mid target:shadow-inner-sm target:shadow-shade dark:target:shadow-dark-shade target:h-auto target:py-3 target:my-2 target:[--displaySubContentMenu:grid] [--displaySubContentMenu:none]"
> >
<div className="grid gap-4 place-items-center grid-cols-[auto_auto_1fr_auto_12ch] thin:grid-cols-[auto_auto_1fr_auto]"> <div className="grid gap-4 place-items-center grid-cols-[auto_auto_1fr_auto_12ch] thin:grid-cols-[auto_auto_1fr_auto]">
<a href={`#${content.attributes.slug}`}> <a href={`#${content.attributes.slug}`}>