Skip to content

Commit 8fe26fd

Browse files
authored
fix: theme loading issue (#84)
* Load theme earlier to avoid fouc * fix system preference * Renaming variables
1 parent 5b4c066 commit 8fe26fd

File tree

5 files changed

+50
-44
lines changed

5 files changed

+50
-44
lines changed

cypress/e2e/darkmode.spec.cy.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ describe("DarkMode test", () => {
22
beforeEach(() => {
33
cy.visit("http://localhost:3000");
44
cy.wait(3000);
5-
cy.get('[data-testid="darkModeButton"]').click();
5+
cy.get('[data-testid="themeSelectorButton"]').click();
66
});
77

88
it("should select light Mode", () => {
@@ -27,7 +27,7 @@ describe("DarkMode test", () => {
2727
},
2828
});
2929
cy.wait(3000);
30-
cy.get('[data-testid="darkModeButton"]').click();
30+
cy.get('[data-testid="themeSelectorButton"]').click();
3131
cy.get('[data-testid="system-mode-option"]').click();
3232
cy.get("html").should("have.data", "theme", "custom-dark");
3333
});

src/components/Header.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { MAIN_LOGIN_PROVIDER } from "@/pages/api/auth/[...nextauth]";
22
import { signIn, signOut, useSession } from "next-auth/react";
33
import Image from "next/image";
44
import Link from "next/link";
5-
import { DarkModeDropdown } from "./DarkModeDropdown";
5+
import { ThemeSelector } from "./ThemeSelector";
66

77
export const Header = () => {
88
const { data: session, status } = useSession();
@@ -60,7 +60,7 @@ export const Header = () => {
6060
</ul>
6161
</div>
6262
<div className="navbar-end">
63-
<DarkModeDropdown />
63+
<ThemeSelector />
6464
{status === "authenticated" ? (
6565
<div className="dropdown dropdown-end">
6666
<label tabIndex={0} className="btn btn-ghost btn-circle avatar">

src/components/DarkModeDropdown.test.tsx renamed to src/components/ThemeSelector.test.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import { fireEvent, render, screen } from "@testing-library/react";
22
import { describe, test, expect } from "vitest";
3-
import { DarkModeDropdown } from "./DarkModeDropdown";
3+
import { ThemeSelector } from "./ThemeSelector";
44

5-
describe("DarkModeDropdown", () => {
5+
describe("ThemeSelector", () => {
66
test("should change light Icon if click Dark mode", () => {
7-
render(<DarkModeDropdown />);
7+
render(<ThemeSelector />);
88

9-
const button = screen.getByTestId("darkModeButton");
9+
const button = screen.getByTestId("themeSelectorButton");
1010
fireEvent.click(button);
1111

1212
const darkModeItem = screen.getByTestId("dark-mode-option");
1313
fireEvent.click(darkModeItem);
1414

15-
const buttonEdited = screen.getByTestId("darkModeButton");
15+
const buttonEdited = screen.getByTestId("themeSelectorButton");
1616
const darkModeSvg = buttonEdited.firstElementChild as SVGElement;
1717
const testidValue = darkModeSvg.dataset.testid;
1818

@@ -25,14 +25,14 @@ describe("DarkModeDropdown", () => {
2525
});
2626

2727
test("should change to System Preference", () => {
28-
render(<DarkModeDropdown />);
28+
render(<ThemeSelector />);
2929

30-
const button = screen.getByTestId("darkModeButton");
30+
const button = screen.getByTestId("themeSelectorButton");
3131
fireEvent.click(button);
3232
const systemItem = screen.getByTestId("system-mode-option");
3333
fireEvent.click(systemItem);
3434

35-
const buttonEdited = screen.getByTestId("darkModeButton");
35+
const buttonEdited = screen.getByTestId("themeSelectorButton");
3636
const systemSvg = buttonEdited.firstElementChild as SVGAElement;
3737
const testidValue = systemSvg.dataset.testid;
3838

src/components/DarkModeDropdown.tsx renamed to src/components/ThemeSelector.tsx

Lines changed: 22 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,39 @@
11
import { useEffect, useState } from "react";
22

3-
type DarkModeOptions = "custom-dark" | "light" | "system";
3+
type ThemeOptions = "custom-dark" | "light" | "system";
44

5-
export function DarkModeDropdown() {
6-
const [darkModeOption, setDarkModeOption] =
7-
useState<DarkModeOptions>("light");
5+
export function ThemeSelector() {
6+
const [selectedTheme, setSelectedTheme] = useState<ThemeOptions | undefined>(
7+
undefined
8+
);
89

910
useEffect(() => {
10-
if (
11-
localStorage.theme === "light" ||
12-
(!("theme" in localStorage) &&
13-
!window.matchMedia("(prefers-color-scheme:dark)").matches)
14-
) {
15-
setDocumentElement("light");
16-
} else {
17-
setDocumentElement("custom-dark");
11+
const theme = localStorage.getItem("theme") as ThemeOptions;
12+
if (theme) {
13+
setSelectedTheme(theme);
1814
}
1915
}, []);
2016

2117
function onClick($event: React.MouseEvent<HTMLLIElement>) {
2218
$event.stopPropagation();
2319
const li = $event.currentTarget as HTMLLIElement;
24-
setDocumentElement(li.id as DarkModeOptions);
20+
setDocumentElement(li.id as ThemeOptions);
2521
}
2622

27-
const setDocumentElement = (theme: DarkModeOptions) => {
28-
setDarkModeOption(theme);
29-
theme === "system"
30-
? setSystemPreferenceTheme()
31-
: setLightOrDarkTheme(theme);
23+
const setDocumentElement = (theme: ThemeOptions) => {
24+
setSelectedTheme(theme);
25+
theme === "system" ? setSystemPreferenceTheme() : setTheme(theme);
3226
};
3327

34-
const buttonIcon = getButtonIconByOption(darkModeOption);
28+
const buttonIcon = getButtonIconByOption(selectedTheme);
3529

3630
return (
3731
<>
3832
<div className="dropdown dropdown-bottom dropdown-end">
3933
<label
4034
tabIndex={0}
4135
className="btn btn-circle btn-ghost m-1"
42-
data-testid="darkModeButton"
36+
data-testid="themeSelectorButton"
4337
>
4438
{buttonIcon}
4539
</label>
@@ -119,30 +113,26 @@ const SystemPreference = () => (
119113
</svg>
120114
);
121115

122-
const getButtonIconByOption = (option: DarkModeOptions) => {
116+
const getButtonIconByOption = (option: ThemeOptions | undefined) => {
123117
switch (option) {
124-
case "light": {
118+
case "light":
125119
return <LightMode />;
126-
}
127-
case "custom-dark": {
120+
121+
case "custom-dark":
128122
return <DarkMode />;
129-
}
130-
case "system": {
123+
124+
default:
131125
return <SystemPreference />;
132-
}
133126
}
134127
};
135128

136129
const setSystemPreferenceTheme = () => {
137130
const isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
131+
setTheme(isDark ? "custom-dark" : "light");
138132
localStorage.removeItem("theme");
139-
document.documentElement.dataset.theme = isDark ? "custom-dark" : "light";
140-
isDark
141-
? document.documentElement.classList.add("dark")
142-
: document.documentElement.classList.remove("dark");
143133
};
144134

145-
const setLightOrDarkTheme = (theme: "custom-dark" | "light") => {
135+
const setTheme = (theme: "custom-dark" | "light") => {
146136
localStorage.theme = theme;
147137
document.documentElement.dataset.theme = theme;
148138
theme === "custom-dark"

src/pages/_document.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@ export default function Document() {
55
<Html lang="en" data-theme="light">
66
<Head />
77
<body className="light-mode dark:dark-mode">
8+
<script
9+
dangerouslySetInnerHTML={{
10+
__html: `
11+
(function() {
12+
var theme = localStorage.getItem('theme');
13+
if (theme === "custom-dark" || (!theme && window.matchMedia("(prefers-color-scheme:dark)").matches)) {
14+
document.documentElement.dataset.theme = "custom-dark";
15+
document.documentElement.classList.add("dark")
16+
} else {
17+
document.documentElement.dataset.theme = "light";
18+
document.documentElement.classList.remove("dark");
19+
}
20+
})()
21+
`,
22+
}}
23+
/>
824
<Main />
925
<NextScript />
1026
</body>

0 commit comments

Comments
 (0)