Skip to content

Commit eb29941

Browse files
committed
feat[frontend]: monitor query change on system dark mode
1 parent e3be146 commit eb29941

File tree

3 files changed

+71
-41
lines changed

3 files changed

+71
-41
lines changed
Lines changed: 59 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,82 @@
1-
import React, { JSX, useEffect } from "react"
1+
import React, { JSX, useEffect, useState, useSyncExternalStore } from "react"
22
import { ComputerIcon, MoonIcon, SunIcon } from "./icons.js"
33
import { Button, ButtonProps, Tooltip } from "@heroui/react"
44
import { tst } from "../utils/overrides.js"
55

6-
export type DarkMode = "dark" | "light" | "system"
7-
8-
interface MyComponentProps extends ButtonProps {
9-
mode: DarkMode
10-
onModeChange: (newMode: DarkMode) => void
6+
const modeSelections = ["system", "light", "dark"]
7+
type ModeSelection = (typeof modeSelections)[number]
8+
const icons: Record<ModeSelection, JSX.Element> = {
9+
system: <ComputerIcon className="size-6 inline" />,
10+
light: <SunIcon className="size-6 inline" />,
11+
dark: <MoonIcon className="size-6 inline" />,
1112
}
1213

13-
const icons: { name: DarkMode; icon: JSX.Element }[] = [
14-
{ name: "system", icon: <ComputerIcon className="size-6 inline" /> },
15-
{ name: "light", icon: <SunIcon className="size-6 inline" /> },
16-
{ name: "dark", icon: <MoonIcon className="size-6 inline" /> },
17-
]
14+
export function useDarkModeSelection(): [
15+
boolean,
16+
ModeSelection | undefined,
17+
React.Dispatch<React.SetStateAction<ModeSelection | undefined>>,
18+
] {
19+
const [modeSelection, setModeSelection] = useState<ModeSelection | undefined>(undefined)
1820

19-
export function defaultDarkMode(): DarkMode {
20-
const storedDarkModeSelect = localStorage.getItem("darkModeSelect")
21+
const isSystemDark = useSyncExternalStore<boolean>(
22+
(callBack) => {
23+
const mql = window.matchMedia("(prefers-color-scheme: dark)")
24+
mql.addEventListener("change", callBack)
25+
return () => {
26+
mql.removeEventListener("change", callBack)
27+
}
28+
},
29+
() => {
30+
return window.matchMedia("(prefers-color-scheme: dark)").matches
31+
},
32+
() => false,
33+
)
2134

22-
if (storedDarkModeSelect !== null && ["light", "dark", "system"].includes(storedDarkModeSelect)) {
23-
return storedDarkModeSelect as DarkMode
24-
} else {
25-
return "system"
26-
}
27-
}
35+
useEffect(() => {
36+
if (modeSelection) {
37+
localStorage.setItem("darkModeSelect", modeSelection)
38+
}
39+
}, [modeSelection])
2840

29-
export function shouldBeDark(mode: DarkMode): boolean {
30-
// when matchMedia not available (e.g. in tests), set to light mode
31-
const systemDark = window.matchMedia ? window.matchMedia("(prefers-color-scheme: dark)").matches : false
32-
return mode === "system" ? systemDark : mode === "dark"
41+
useEffect(() => {
42+
const item = localStorage.getItem("darkModeSelect")
43+
let storedSelect: ModeSelection | undefined
44+
if (item !== null) {
45+
if (item && modeSelections.includes(item)) {
46+
storedSelect = item
47+
} else {
48+
storedSelect = "system"
49+
}
50+
} else {
51+
storedSelect = "system"
52+
}
53+
setModeSelection(storedSelect)
54+
}, [])
55+
56+
const isDark = modeSelection === undefined || modeSelection === "system" ? isSystemDark : modeSelection === "dark"
57+
return [isDark, modeSelection, setModeSelection]
3358
}
3459

35-
export function DarkModeToggle({ mode, onModeChange, className, ...rest }: MyComponentProps) {
36-
useEffect(() => {
37-
localStorage.setItem("darkModeSelect", mode)
38-
}, [mode])
60+
interface MyComponentProps extends ButtonProps {
61+
modeSelection: ModeSelection | undefined
62+
setModeSelection: React.Dispatch<React.SetStateAction<ModeSelection | undefined>>
63+
}
3964

40-
return (
41-
<Tooltip content={`Toggle dark mode (currently ${mode})`}>
65+
export function DarkModeToggle({ modeSelection, setModeSelection, className, ...rest }: MyComponentProps) {
66+
return modeSelection ? (
67+
<Tooltip content={`Toggle dark mode (currently ${modeSelection} mode)`}>
4268
<Button
4369
isIconOnly
4470
className={`mr-2 rounded-full ${tst} bg-background hover:bg-default-100` + " " + className}
4571
aria-label="Toggle dark mode"
4672
onPress={() => {
47-
const curModeIdx = icons.findIndex(({ name }) => name === mode)
48-
onModeChange(icons[(curModeIdx + 1) % icons.length].name)
73+
const newSelected = modeSelections[(modeSelections.indexOf(modeSelection) + 1) % modeSelections.length]
74+
setModeSelection(newSelected)
4975
}}
5076
{...rest}
5177
>
52-
{icons.find(({ name }) => name === mode)!.icon}
78+
{icons[modeSelection]}
5379
</Button>
5480
</Tooltip>
55-
)
81+
) : null
5682
}

frontend/components/DecryptPaste.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { CheckIcon, CopyIcon, DownloadIcon, HomeIcon } from "./icons.js"
88
import "../style.css"
99
import { parseFilenameFromContentDisposition, parsePath } from "../../shared/parsers.js"
1010
import { formatSize } from "../utils/utils.js"
11-
import { DarkMode, DarkModeToggle, defaultDarkMode, shouldBeDark } from "./DarkModeToggle.js"
11+
import { DarkModeToggle, useDarkModeSelection } from "./DarkModeToggle.js"
1212
import binaryExtensions from "binary-extensions"
1313
import { tst } from "../utils/overrides.js"
1414

@@ -121,7 +121,7 @@ export function DecryptPaste() {
121121
</div>
122122
)
123123

124-
const [darkModeSelect, setDarkModeSelect] = useState<DarkMode>(defaultDarkMode())
124+
const [isDark, modeSelection, setModeSelection] = useDarkModeSelection()
125125

126126
const numOfIssuedCopies = useRef(0)
127127
const [hasIssuedCopies, setHasIssuedCopies] = useState<boolean>(false)
@@ -147,7 +147,7 @@ export function DecryptPaste() {
147147
<main
148148
className={
149149
`flex flex-col items-center min-h-screen transition-transform-background bg-background ${tst} text-foreground w-full p-2` +
150-
(shouldBeDark(darkModeSelect) ? " dark" : " light")
150+
(isDark ? " dark" : " light")
151151
}
152152
>
153153
<div className="w-full max-w-[64rem]">
@@ -179,7 +179,7 @@ export function DecryptPaste() {
179179
</Button>
180180
</Tooltip>
181181
)}
182-
<DarkModeToggle mode={darkModeSelect} onModeChange={setDarkModeSelect} className={buttonClasses} />
182+
<DarkModeToggle modeSelection={modeSelection} setModeSelection={setModeSelection} />
183183
</div>
184184
<div className="my-4">
185185
<div className={`min-h-[30rem] w-full bg-secondary-50 rounded-lg p-3 relative ${tst}`}>

frontend/components/PasteBin.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Button, Link } from "@heroui/react"
55
import { PasteResponse } from "../../shared/interfaces.js"
66
import { parsePath, parseFilenameFromContentDisposition } from "../../shared/parsers.js"
77

8-
import { DarkModeToggle, DarkMode, defaultDarkMode, shouldBeDark } from "./DarkModeToggle.js"
8+
import { DarkModeToggle, useDarkModeSelection } from "./DarkModeToggle.js"
99
import { ErrorModal, ErrorState } from "./ErrorModal.js"
1010
import { PanelSettingsPanel, PasteSetting } from "./PasteSettingPanel.js"
1111

@@ -50,7 +50,7 @@ export function PasteBin() {
5050

5151
const [errorState, setErrorState] = useState<ErrorState>({ isOpen: false, content: "", title: "" })
5252

53-
const [darkModeSelect, setDarkModeSelect] = useState<DarkMode>(defaultDarkMode())
53+
const [isDark, modeSelection, setModeSelection] = useDarkModeSelection()
5454

5555
function showModal(title: string, content: string) {
5656
setErrorState({ title, content, isOpen: true })
@@ -175,7 +175,11 @@ export function PasteBin() {
175175
<div className="mx-4 lg:mx-0">
176176
<div className="mt-8 mb-4 relative">
177177
<h1 className="text-3xl inline">{INDEX_PAGE_TITLE}</h1>
178-
<DarkModeToggle mode={darkModeSelect} onModeChange={setDarkModeSelect} className="absolute right-0" />
178+
<DarkModeToggle
179+
modeSelection={modeSelection}
180+
setModeSelection={setModeSelection}
181+
className="absolute right-0"
182+
/>
179183
</div>
180184
<p className="my-2">An open source pastebin deployed on Cloudflare Workers. </p>
181185
<p className="my-2">
@@ -225,7 +229,7 @@ export function PasteBin() {
225229
<main
226230
className={
227231
`flex flex-col items-center min-h-screen font-sans ${tst} bg-background text-foreground` +
228-
(shouldBeDark(darkModeSelect) ? " dark" : " light")
232+
(isDark ? " dark" : " light")
229233
}
230234
>
231235
<div className="grow w-full max-w-[64rem]">

0 commit comments

Comments
 (0)