Use localStorage hook

This commit is contained in:
DrMint 2022-08-16 01:25:01 +02:00
parent 625f436163
commit 1bbf3b164a
9 changed files with 106 additions and 154 deletions

View File

@ -109,7 +109,7 @@ export const AppLayout = ({
onSwipedLeft: (SwipeEventData) => {
if (menuGestures) {
if (SwipeEventData.velocity < SENSIBILITY_SWIPE) return;
if (mainPanelOpen === true) {
if (mainPanelOpen) {
setMainPanelOpen(false);
} else if (isDefined(subPanel) && isDefined(contentPanel)) {
setSubPanelOpen(true);
@ -119,7 +119,7 @@ export const AppLayout = ({
onSwipedRight: (SwipeEventData) => {
if (menuGestures) {
if (SwipeEventData.velocity < SENSIBILITY_SWIPE) return;
if (subPanelOpen === true) {
if (subPanelOpen) {
setSubPanelOpen(false);
} else {
setMainPanelOpen(true);
@ -135,7 +135,7 @@ export const AppLayout = ({
useLayoutEffect(() => {
document.getElementsByTagName("html")[0].style.fontSize = `${
(fontSize ?? 1) * 100
fontSize * 100
}%`;
}, [fontSize]);
@ -159,18 +159,16 @@ export const AppLayout = ({
}, [currencyOptions, currencySelect, setCurrency]);
useEffect(() => {
if (preferredLanguages) {
if (preferredLanguages.length === 0) {
if (isDefinedAndNotEmpty(router.locale) && router.locales) {
setPreferredLanguages(
getDefaultPreferredLanguages(router.locale, router.locales)
);
}
} else if (router.locale !== preferredLanguages[0]) {
router.replace(router.asPath, router.asPath, {
locale: preferredLanguages[0],
});
if (preferredLanguages.length === 0) {
if (isDefinedAndNotEmpty(router.locale) && router.locales) {
setPreferredLanguages(
getDefaultPreferredLanguages(router.locale, router.locales)
);
}
} else if (router.locale !== preferredLanguages[0]) {
router.replace(router.asPath, router.asPath, {
locale: preferredLanguages[0],
});
}
}, [
preferredLanguages,
@ -182,11 +180,11 @@ export const AppLayout = ({
const gridCol = useMemo(() => {
if (isDefined(subPanel)) {
if (mainPanelReduced === true) {
if (mainPanelReduced) {
return "grid-cols-[6rem_20rem_1fr]";
}
return "grid-cols-[20rem_20rem_1fr]";
} else if (mainPanelReduced === true) {
} else if (mainPanelReduced) {
return "grid-cols-[6rem_0px_1fr]";
}
return "grid-cols-[20rem_0px_1fr]";
@ -262,7 +260,7 @@ export const AppLayout = ({
`absolute inset-0 transition-[backdrop-filter] duration-500 [grid-area:content]
mobile:z-10`,
cIf(
(mainPanelOpen === true || subPanelOpen === true) && isMobile,
(mainPanelOpen || subPanelOpen) && isMobile,
"[backdrop-filter:blur(2px)]",
"pointer-events-none touch-none"
)
@ -272,7 +270,7 @@ export const AppLayout = ({
className={cJoin(
"absolute inset-0 bg-shade transition-opacity duration-500",
cIf(
(mainPanelOpen === true || subPanelOpen === true) && isMobile,
(mainPanelOpen || subPanelOpen) && isMobile,
"opacity-60",
"opacity-0"
)
@ -312,7 +310,7 @@ export const AppLayout = ({
mobile:border-r-0 mobile:border-l-[1px] mobile:[grid-area:content]`,
turnSubIntoContent
? "mobile:w-full mobile:border-l-0"
: subPanelOpen === true
: subPanelOpen
? ""
: "mobile:translate-x-[100vw]"
)}
@ -328,7 +326,7 @@ export const AppLayout = ({
transition-transform duration-300 [grid-area:main] [scrollbar-width:none]
webkit-scrollbar:w-0 mobile:z-10 mobile:w-[90%] mobile:justify-self-start
mobile:[grid-area:content]`,
cIf(mainPanelOpen === false, "mobile:-translate-x-full")
cIf(!mainPanelOpen, "mobile:-translate-x-full")
)}
>
<MainPanel langui={langui} />
@ -340,7 +338,7 @@ export const AppLayout = ({
border-t-[1px] border-dotted border-black bg-light [grid-area:navbar] desktop:hidden"
>
<Ico
icon={mainPanelOpen === true ? Icon.Close : Icon.Menu}
icon={mainPanelOpen ? Icon.Close : Icon.Menu}
className="mt-[.1em] cursor-pointer !text-2xl"
onClick={() => {
toggleMainPanelOpen();
@ -365,7 +363,7 @@ export const AppLayout = ({
</p>
{isDefined(subPanel) && !turnSubIntoContent && (
<Ico
icon={subPanelOpen === true ? Icon.Close : subPanelIcon}
icon={subPanelOpen ? Icon.Close : subPanelIcon}
className="mt-[.1em] cursor-pointer !text-2xl"
onClick={() => {
toggleSubPanelOpen();
@ -404,7 +402,7 @@ export const AppLayout = ({
</Popup>
<Popup
state={configPanelOpen ?? false}
state={configPanelOpen}
onClose={() => setConfigPanelOpen(false)}
>
<h2 className="text-2xl">{langui.settings}</h2>
@ -416,7 +414,7 @@ export const AppLayout = ({
{router.locales && (
<div>
<h3 className="text-xl">{langui.languages}</h3>
{preferredLanguages && preferredLanguages.length > 0 && (
{preferredLanguages.length > 0 && (
<OrderableList
items={
new Map(
@ -453,14 +451,14 @@ export const AppLayout = ({
setDarkMode(false);
setSelectedThemeMode(true);
},
active: selectedThemeMode === true && darkMode === false,
active: selectedThemeMode && darkMode,
text: langui.light,
},
{
onClick: () => {
setSelectedThemeMode(false);
},
active: selectedThemeMode === false,
active: selectedThemeMode,
text: langui.auto,
},
{
@ -468,7 +466,7 @@ export const AppLayout = ({
setDarkMode(true);
setSelectedThemeMode(true);
},
active: selectedThemeMode === true && darkMode === true,
active: selectedThemeMode && darkMode,
text: langui.dark,
},
]}
@ -492,20 +490,17 @@ export const AppLayout = ({
<ButtonGroup
buttonsProps={[
{
onClick: () => setFontSize((fontSize ?? 1) / 1.05),
onClick: () => setFontSize(fontSize / 1.05),
icon: Icon.TextDecrease,
},
{
onClick: () => setFontSize(1),
text: `${((fontSize ?? 1) * 100).toLocaleString(
undefined,
{
maximumFractionDigits: 0,
}
)}%`,
text: `${(fontSize * 100).toLocaleString(undefined, {
maximumFractionDigits: 0,
})}%`,
},
{
onClick: () => setFontSize((fontSize ?? 1) * 1.05),
onClick: () => setFontSize(fontSize * 1.05),
icon: Icon.TextIncrease,
},
]}
@ -516,13 +511,13 @@ export const AppLayout = ({
<h3 className="text-xl">{langui.font}</h3>
<div className="grid gap-2">
<Button
active={dyslexic === false}
active={!dyslexic}
onClick={() => setDyslexic(false)}
className="font-zenMaruGothic"
text="Zen Maru Gothic"
/>
<Button
active={dyslexic === true}
active={dyslexic}
onClick={() => setDyslexic(true)}
className="font-openDyslexic"
text="OpenDyslexic"
@ -535,7 +530,7 @@ export const AppLayout = ({
<TextInput
placeholder="<player>"
className="w-48"
value={playerName ?? ""}
value={playerName}
onChange={setPlayerName}
/>
</div>

View File

@ -1,5 +1,5 @@
import { Fragment, useCallback } from "react";
import { useBoolean } from "usehooks-ts";
import { Fragment, useCallback, useRef } from "react";
import { useBoolean, useOnClickOutside } from "usehooks-ts";
import { Ico, Icon } from "components/Ico";
import { cIf, cJoin } from "helpers/className";
@ -37,11 +37,12 @@ export const Select = ({
if (optionCount > 1) toggleOpened();
}, [options.length, value, toggleOpened]);
const ref = useRef<HTMLDivElement>(null);
useOnClickOutside(ref, setClosed);
return (
<div
onClickCapture={() => {
setClosed();
}}
ref={ref}
className={cJoin(
"relative text-center transition-[filter]",
cIf(isOpened, "z-10 drop-shadow-shade-lg"),

View File

@ -35,7 +35,7 @@ export const PreviewCardCTAs = ({
<Button
icon={Icon.Favorite}
text={expand ? langui.want_it : undefined}
active={libraryItemUserStatus?.[id] === LibraryItemUserStatus.Want}
active={libraryItemUserStatus[id] === LibraryItemUserStatus.Want}
onClick={(event) => {
event.preventDefault();
setLibraryItemUserStatus((current) => {
@ -53,7 +53,7 @@ export const PreviewCardCTAs = ({
<Button
icon={Icon.BackHand}
text={expand ? langui.have_it : undefined}
active={libraryItemUserStatus?.[id] === LibraryItemUserStatus.Have}
active={libraryItemUserStatus[id] === LibraryItemUserStatus.Have}
onClick={(event) => {
event.preventDefault();
setLibraryItemUserStatus((current) => {

View File

@ -1,64 +1,77 @@
import React, { ReactNode, useContext, useState } from "react";
import { useLocalStorage } from "usehooks-ts";
import { isDefined } from "helpers/others";
import { LibraryItemUserStatus, RequiredNonNullable } from "helpers/types";
import { useDarkMode } from "hooks/useDarkMode";
import { useStateWithLocalStorage } from "hooks/useStateWithLocalStorage";
interface AppLayoutState {
subPanelOpen: boolean | undefined;
subPanelOpen: boolean;
toggleSubPanelOpen: () => void;
setSubPanelOpen: React.Dispatch<
React.SetStateAction<AppLayoutState["subPanelOpen"]>
>;
configPanelOpen: boolean | undefined;
configPanelOpen: boolean;
toggleConfigPanelOpen: () => void;
setConfigPanelOpen: React.Dispatch<
React.SetStateAction<AppLayoutState["configPanelOpen"]>
>;
searchPanelOpen: boolean | undefined;
searchPanelOpen: boolean;
toggleSearchPanelOpen: () => void;
setSearchPanelOpen: React.Dispatch<
React.SetStateAction<AppLayoutState["searchPanelOpen"]>
>;
mainPanelReduced: boolean | undefined;
mainPanelReduced: boolean;
toggleMainPanelReduced: () => void;
setMainPanelReduced: React.Dispatch<
React.SetStateAction<AppLayoutState["mainPanelReduced"]>
>;
mainPanelOpen: boolean | undefined;
mainPanelOpen: boolean;
toggleMainPanelOpen: () => void;
setMainPanelOpen: React.Dispatch<
React.SetStateAction<AppLayoutState["mainPanelOpen"]>
>;
darkMode: boolean | undefined;
darkMode: boolean;
toggleDarkMode: () => void;
setDarkMode: React.Dispatch<React.SetStateAction<AppLayoutState["darkMode"]>>;
selectedThemeMode: boolean | undefined;
selectedThemeMode: boolean;
toggleSelectedThemeMode: () => void;
setSelectedThemeMode: React.Dispatch<
React.SetStateAction<AppLayoutState["selectedThemeMode"]>
>;
fontSize: number | undefined;
fontSize: number;
setFontSize: React.Dispatch<React.SetStateAction<AppLayoutState["fontSize"]>>;
dyslexic: boolean | undefined;
dyslexic: boolean;
toggleDyslexic: () => void;
setDyslexic: React.Dispatch<React.SetStateAction<AppLayoutState["dyslexic"]>>;
currency: string | undefined;
currency: string;
setCurrency: React.Dispatch<React.SetStateAction<AppLayoutState["currency"]>>;
playerName: string | undefined;
playerName: string;
setPlayerName: React.Dispatch<
React.SetStateAction<AppLayoutState["playerName"]>
>;
preferredLanguages: string[] | undefined;
preferredLanguages: string[];
setPreferredLanguages: React.Dispatch<
React.SetStateAction<AppLayoutState["preferredLanguages"]>
>;
menuGestures: boolean;
toggleMenuGestures: () => void;
setMenuGestures: React.Dispatch<
React.SetStateAction<AppLayoutState["menuGestures"]>
>;
libraryItemUserStatus: Record<string, LibraryItemUserStatus> | undefined;
libraryItemUserStatus: Record<string, LibraryItemUserStatus>;
setLibraryItemUserStatus: React.Dispatch<
React.SetStateAction<AppLayoutState["libraryItemUserStatus"]>
>;
@ -68,38 +81,51 @@ const initialState: RequiredNonNullable<AppLayoutState> = {
subPanelOpen: false,
toggleSubPanelOpen: () => null,
setSubPanelOpen: () => null,
configPanelOpen: false,
setConfigPanelOpen: () => null,
toggleConfigPanelOpen: () => null,
searchPanelOpen: false,
setSearchPanelOpen: () => null,
toggleSearchPanelOpen: () => null,
mainPanelReduced: false,
setMainPanelReduced: () => null,
toggleMainPanelReduced: () => null,
mainPanelOpen: false,
toggleMainPanelOpen: () => null,
setMainPanelOpen: () => null,
darkMode: false,
toggleDarkMode: () => null,
setDarkMode: () => null,
selectedThemeMode: false,
toggleSelectedThemeMode: () => null,
setSelectedThemeMode: () => null,
fontSize: 1,
setFontSize: () => null,
dyslexic: false,
toggleDyslexic: () => null,
setDyslexic: () => null,
currency: "USD",
setCurrency: () => null,
playerName: "",
setPlayerName: () => null,
preferredLanguages: [],
setPreferredLanguages: () => null,
menuGestures: true,
toggleMenuGestures: () => null,
setMenuGestures: () => null,
libraryItemUserStatus: {},
setLibraryItemUserStatus: () => null,
};
@ -113,22 +139,22 @@ interface Props {
}
export const AppContextProvider = (props: Props): JSX.Element => {
const [subPanelOpen, setSubPanelOpen] = useStateWithLocalStorage(
const [subPanelOpen, setSubPanelOpen] = useLocalStorage(
"subPanelOpen",
initialState.subPanelOpen
);
const [configPanelOpen, setConfigPanelOpen] = useStateWithLocalStorage(
const [configPanelOpen, setConfigPanelOpen] = useLocalStorage(
"configPanelOpen",
initialState.configPanelOpen
);
const [mainPanelReduced, setMainPanelReduced] = useStateWithLocalStorage(
const [mainPanelReduced, setMainPanelReduced] = useLocalStorage(
"mainPanelReduced",
initialState.mainPanelReduced
);
const [mainPanelOpen, setMainPanelOpen] = useStateWithLocalStorage(
const [mainPanelOpen, setMainPanelOpen] = useLocalStorage(
"mainPanelOpen",
initialState.mainPanelOpen
);
@ -136,43 +162,42 @@ export const AppContextProvider = (props: Props): JSX.Element => {
const [darkMode, selectedThemeMode, setDarkMode, setSelectedThemeMode] =
useDarkMode("darkMode", initialState.darkMode);
const [fontSize, setFontSize] = useStateWithLocalStorage(
const [fontSize, setFontSize] = useLocalStorage(
"fontSize",
initialState.fontSize
);
const [dyslexic, setDyslexic] = useStateWithLocalStorage(
const [dyslexic, setDyslexic] = useLocalStorage(
"dyslexic",
initialState.dyslexic
);
const [currency, setCurrency] = useStateWithLocalStorage(
const [currency, setCurrency] = useLocalStorage(
"currency",
initialState.currency
);
const [playerName, setPlayerName] = useStateWithLocalStorage(
const [playerName, setPlayerName] = useLocalStorage(
"playerName",
initialState.playerName
);
const [preferredLanguages, setPreferredLanguages] = useStateWithLocalStorage(
const [preferredLanguages, setPreferredLanguages] = useLocalStorage(
"preferredLanguages",
initialState.preferredLanguages
);
const [menuGestures, setMenuGestures] = useState(false);
const [searchPanelOpen, setSearchPanelOpen] = useStateWithLocalStorage(
const [searchPanelOpen, setSearchPanelOpen] = useLocalStorage(
"searchPanelOpen",
initialState.searchPanelOpen
);
const [libraryItemUserStatus, setLibraryItemUserStatus] =
useStateWithLocalStorage(
"libraryItemUserStatus",
initialState.libraryItemUserStatus
);
const [libraryItemUserStatus, setLibraryItemUserStatus] = useLocalStorage(
"libraryItemUserStatus",
initialState.libraryItemUserStatus
);
const toggleSubPanelOpen = () => {
setSubPanelOpen((current) => (isDefined(current) ? !current : current));

View File

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

View File

@ -42,7 +42,7 @@ export const useSmartLanguage = <T>({
useEffect(() => {
setSelectedTranslationIndex(
getPreferredLanguage(
preferredLanguages ?? [router.locale],
preferredLanguages,
availableLocales
)
);

View File

@ -1,31 +0,0 @@
import { useEffect, useState } from "react";
import { isDefined } from "helpers/others";
export const useStateWithLocalStorage = <T>(
key: string,
initialValue: T
): [T | undefined, React.Dispatch<React.SetStateAction<T | undefined>>] => {
const [value, setValue] = useState<T | undefined>(undefined);
const [, setFromLocaleStorage] = useState<boolean>(false);
useEffect(() => {
try {
const item = localStorage.getItem(key);
if (isDefined(item)) {
setValue(JSON.parse(item) as T);
} else {
setValue(initialValue);
}
setFromLocaleStorage(true);
} catch (error) {
console.warn(`Error reading localStorage key “${key}”:`, error);
setValue(initialValue);
}
}, [initialValue, key]);
useEffect(() => {
if (isDefined(value)) localStorage.setItem(key, JSON.stringify(value));
}, [value, key]);
return [value, setValue];
};

View File

@ -1,38 +0,0 @@
import { useMemo } from "react";
import { usePrefersDarkMode } from "./useMediaQuery";
import { useStateWithLocalStorage } from "./useStateWithLocalStorage";
export enum TernaryDarkMode {
Dark = "dark",
Auto = "auto",
Light = "light",
}
export const useTernaryDarkMode = (
key: string,
initialValue: TernaryDarkMode
): {
isDarkMode: boolean;
ternaryDarkMode: TernaryDarkMode | undefined;
setTernaryDarkMode: React.Dispatch<
React.SetStateAction<TernaryDarkMode | undefined>
>;
} => {
const [ternaryDarkMode, setTernaryDarkMode] = useStateWithLocalStorage(
key,
initialValue
);
const prefersDarkMode = usePrefersDarkMode();
const isDarkMode = useMemo(() => {
switch (ternaryDarkMode) {
case TernaryDarkMode.Light:
return false;
case TernaryDarkMode.Dark:
return true;
default:
return prefersDarkMode;
}
}, [prefersDarkMode, ternaryDarkMode]);
return { isDarkMode, ternaryDarkMode, setTernaryDarkMode };
};

View File

@ -120,7 +120,7 @@ const Library = ({
if (item.attributes.primary && !showPrimaryItems) return false;
if (!item.attributes.primary && !showSecondaryItems) return false;
if (isDefined(filterUserStatus) && item.id && libraryItemUserStatus) {
if (isDefined(filterUserStatus) && item.id) {
if (isUntangibleGroupItem(item.attributes.metadata?.[0])) {
return false;
}