Skip to content

Commit 43e032e

Browse files
committed
Improve handling of dark mode theme in order to avoid jitter when loading new page
1 parent 2e01a95 commit 43e032e

File tree

12 files changed

+166
-54
lines changed

12 files changed

+166
-54
lines changed

src/interface/web/app/agents/layout.tsx

+20-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
22
import { noto_sans, noto_sans_arabic } from "@/app/fonts";
33
import "../globals.css";
44
import { ContentSecurityPolicy } from "../common/layoutHelper";
5+
import { ThemeProvider } from "../components/providers/themeProvider";
56

67
export const metadata: Metadata = {
78
title: "Khoj AI - Agents",
@@ -40,8 +41,26 @@ export default function RootLayout({
4041
}>) {
4142
return (
4243
<html lang="en" className={`${noto_sans.variable} ${noto_sans_arabic.variable}`}>
44+
<head>
45+
<script
46+
dangerouslySetInnerHTML={{
47+
__html: `
48+
try {
49+
if (localStorage.getItem('theme') === 'dark' ||
50+
(!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
51+
document.documentElement.classList.add('dark');
52+
}
53+
} catch (e) {}
54+
`,
55+
}}
56+
/>
57+
</head>
4358
<ContentSecurityPolicy />
44-
<body>{children}</body>
59+
<body>
60+
<ThemeProvider>
61+
{children}
62+
</ThemeProvider>
63+
</body>
4564
</html>
4665
);
4766
}

src/interface/web/app/agents/page.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTrigger } from "@/components
2020
import LoginPrompt from "../components/Prompt/Prompt";
2121
import { InlineLoading } from "../components/loading/loading";
2222
import { Alert, AlertDescription } from "@/components/ui/alert";
23-
import { useIsMobileWidth } from "../common/utils";
23+
import { useIsDarkMode, useIsMobileWidth } from "../common/utils";
2424
import {
2525
AgentCard,
2626
EditAgentSchema,

src/interface/web/app/chat/layout.tsx

+21-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
22
import { noto_sans, noto_sans_arabic } from "@/app/fonts";
33
import "../globals.css";
44
import { ContentSecurityPolicy } from "../common/layoutHelper";
5+
import { ThemeProvider } from "../components/providers/themeProvider";
56

67
export const metadata: Metadata = {
78
title: "Khoj AI - Chat",
@@ -40,14 +41,30 @@ export default function RootLayout({
4041
}>) {
4142
return (
4243
<html lang="en" className={`${noto_sans.variable} ${noto_sans_arabic.variable}`}>
43-
<ContentSecurityPolicy />
44-
<body>
45-
{children}
44+
<head>
4645
<script
4746
dangerouslySetInnerHTML={{
48-
__html: `window.EXCALIDRAW_ASSET_PATH = 'https://assets.khoj.dev/@excalidraw/excalidraw/dist/';`,
47+
__html: `
48+
try {
49+
if (localStorage.getItem('theme') === 'dark' ||
50+
(!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
51+
document.documentElement.classList.add('dark');
52+
}
53+
} catch (e) {}
54+
`,
4955
}}
5056
/>
57+
</head>
58+
<ContentSecurityPolicy />
59+
<body>
60+
<ThemeProvider>
61+
{children}
62+
<script
63+
dangerouslySetInnerHTML={{
64+
__html: `window.EXCALIDRAW_ASSET_PATH = 'https://assets.khoj.dev/@excalidraw/excalidraw/dist/';`,
65+
}}
66+
/>
67+
</ThemeProvider>
5168
</body>
5269
</html>
5370
);

src/interface/web/app/common/utils.ts

+34
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,40 @@ export const useMutationObserver = (
8989
}, [ref, callback, options])
9090
}
9191

92+
export function useIsDarkMode() {
93+
const [darkMode, setDarkMode] = useState(false);
94+
const [initialLoadDone, setInitialLoadDone] = useState(false);
95+
96+
useEffect(() => {
97+
if (localStorage.getItem("theme") === "dark") {
98+
document.documentElement.classList.add("dark");
99+
setDarkMode(true);
100+
} else if (localStorage.getItem("theme") === "light") {
101+
document.documentElement.classList.remove("dark");
102+
setDarkMode(false);
103+
} else {
104+
const mq = window.matchMedia("(prefers-color-scheme: dark)");
105+
if (mq.matches) {
106+
document.documentElement.classList.add("dark");
107+
setDarkMode(true);
108+
}
109+
}
110+
setInitialLoadDone(true);
111+
}, []);
112+
113+
useEffect(() => {
114+
if (!initialLoadDone) return;
115+
if (darkMode) {
116+
document.documentElement.classList.add("dark");
117+
} else {
118+
document.documentElement.classList.remove("dark");
119+
}
120+
localStorage.setItem("theme", darkMode ? "dark" : "light");
121+
}, [darkMode, initialLoadDone]);
122+
123+
return [darkMode, setDarkMode] as const;
124+
}
125+
92126
export const convertBytesToText = (fileSize: number) => {
93127
if (fileSize < 1024) {
94128
return `${fileSize} B`;

src/interface/web/app/components/appSidebar/appSidebar.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { useEffect, useState } from "react";
2222
import AllConversations from "../allConversations/allConversations";
2323
import FooterMenu from "../navMenu/navMenu";
2424
import { useSidebar } from "@/components/ui/sidebar";
25-
import { useIsMobileWidth } from "@/app/common/utils";
25+
import { useIsDarkMode, useIsMobileWidth } from "@/app/common/utils";
2626
import { UserPlusIcon } from "lucide-react";
2727
import { useAuthenticatedData, UserProfile } from "@/app/common/auth";
2828
import LoginPrompt from "../Prompt/Prompt";

src/interface/web/app/components/navMenu/navMenu.tsx

+2-36
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
DropdownMenuTrigger,
1414
} from "@/components/ui/dropdown-menu";
1515
import { Moon, Sun, UserCircle, Question, ArrowRight, Code, BuildingOffice } from "@phosphor-icons/react";
16-
import { useIsMobileWidth } from "@/app/common/utils";
16+
import { useIsDarkMode, useIsMobileWidth } from "@/app/common/utils";
1717
import LoginPrompt from "../Prompt/Prompt";
1818
import { Button } from "@/components/ui/button";
1919
import { SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "@/components/ui/sidebar";
@@ -49,44 +49,10 @@ export default function FooterMenu({ sideBarIsOpen }: NavMenuProps) {
4949
error: authenticationError,
5050
isLoading: authenticationLoading,
5151
} = useAuthenticatedData();
52-
const [darkMode, setDarkMode] = useState(false);
53-
const [initialLoadDone, setInitialLoadDone] = useState(false);
52+
const [darkMode, setDarkMode] = useIsDarkMode();
5453
const [showLoginPrompt, setShowLoginPrompt] = useState(false);
5554
const isMobileWidth = useIsMobileWidth();
5655

57-
useEffect(() => {
58-
if (localStorage.getItem("theme") === "dark") {
59-
document.documentElement.classList.add("dark");
60-
setDarkMode(true);
61-
} else if (localStorage.getItem("theme") === "light") {
62-
document.documentElement.classList.remove("dark");
63-
setDarkMode(false);
64-
} else {
65-
const mq = window.matchMedia("(prefers-color-scheme: dark)");
66-
67-
if (mq.matches) {
68-
document.documentElement.classList.add("dark");
69-
setDarkMode(true);
70-
}
71-
}
72-
73-
setInitialLoadDone(true);
74-
}, []);
75-
76-
useEffect(() => {
77-
if (!initialLoadDone) return;
78-
toggleDarkMode(darkMode);
79-
}, [darkMode, initialLoadDone]);
80-
81-
function toggleDarkMode(darkMode: boolean) {
82-
if (darkMode) {
83-
document.documentElement.classList.add("dark");
84-
} else {
85-
document.documentElement.classList.remove("dark");
86-
}
87-
localStorage.setItem("theme", darkMode ? "dark" : "light");
88-
}
89-
9056
const menuItems = [
9157
{
9258
title: "Help",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
'use client'
2+
3+
import { useIsDarkMode } from '@/app/common/utils'
4+
5+
export function ThemeProvider({ children }: { children: React.ReactNode }) {
6+
const [darkMode, setDarkMode] = useIsDarkMode();
7+
return <>{children}</>;
8+
}

src/interface/web/app/layout.tsx

+20-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
22
import { noto_sans, noto_sans_arabic } from "@/app/fonts";
33
import "./globals.css";
44
import { ContentSecurityPolicy } from "./common/layoutHelper";
5+
import { ThemeProvider } from "./components/providers/themeProvider";
56

67
export const metadata: Metadata = {
78
title: "Khoj AI - Ask Anything",
@@ -48,8 +49,26 @@ export default function RootLayout({
4849
}>) {
4950
return (
5051
<html lang="en" className={`${noto_sans.variable} ${noto_sans_arabic.variable}`}>
52+
<head>
53+
<script
54+
dangerouslySetInnerHTML={{
55+
__html: `
56+
try {
57+
if (localStorage.getItem('theme') === 'dark' ||
58+
(!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
59+
document.documentElement.classList.add('dark');
60+
}
61+
} catch (e) {}
62+
`,
63+
}}
64+
/>
65+
</head>
5166
<ContentSecurityPolicy />
52-
<body>{children}</body>
67+
<body>
68+
<ThemeProvider>
69+
{children}
70+
</ThemeProvider>
71+
</body>
5372
</html>
5473
);
5574
}

src/interface/web/app/page.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import {
2525
stepOneSuggestions,
2626
StepTwoSuggestion,
2727
getStepTwoSuggestions,
28-
SuggestionType,
2928
} from "@/app/components/suggestions/suggestionsData";
3029
import LoginPrompt from "@/app/components/Prompt/Prompt";
3130

src/interface/web/app/search/layout.tsx

+18-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { Metadata } from "next";
33
import "../globals.css";
44
import { ContentSecurityPolicy } from "../common/layoutHelper";
55
import { Toaster } from "@/components/ui/toaster";
6+
import { ThemeProvider } from "../components/providers/themeProvider";
67

78
export const metadata: Metadata = {
89
title: "Khoj AI - Search",
@@ -35,10 +36,25 @@ export default function RootLayout({
3536
}>) {
3637
return (
3738
<html>
39+
<head>
40+
<script
41+
dangerouslySetInnerHTML={{
42+
__html: `
43+
try {
44+
if (localStorage.getItem('theme') === 'dark' ||
45+
(!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
46+
document.documentElement.classList.add('dark');
47+
}
48+
} catch (e) {}
49+
`,
50+
}}
51+
/>
52+
</head>
3853
<ContentSecurityPolicy />
3954
<body>
40-
{children}
41-
<Toaster />
55+
<ThemeProvider>
56+
{children}
57+
</ThemeProvider>
4258
</body>
4359
</html>
4460
);

src/interface/web/app/settings/layout.tsx

+20-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import "../globals.css";
44
import { Toaster } from "@/components/ui/toaster";
55
import { ContentSecurityPolicy } from "../common/layoutHelper";
66
import { ChatwootWidget } from "../components/chatWoot/ChatwootWidget";
7+
import { ThemeProvider } from "../components/providers/themeProvider";
78

89
export const metadata: Metadata = {
910
title: "Khoj AI - Settings",
@@ -40,11 +41,27 @@ export default function RootLayout({
4041
}>) {
4142
return (
4243
<html lang="en" className={`${noto_sans.variable} ${noto_sans_arabic.variable}`}>
44+
<head>
45+
<script
46+
dangerouslySetInnerHTML={{
47+
__html: `
48+
try {
49+
if (localStorage.getItem('theme') === 'dark' ||
50+
(!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
51+
document.documentElement.classList.add('dark');
52+
}
53+
} catch (e) {}
54+
`,
55+
}}
56+
/>
57+
</head>
4358
<ContentSecurityPolicy />
4459
<body>
45-
{children}
46-
<Toaster />
47-
<ChatwootWidget />
60+
<ThemeProvider>
61+
{children}
62+
<Toaster />
63+
<ChatwootWidget />
64+
</ThemeProvider>
4865
</body>
4966
</html>
5067
);

src/interface/web/app/share/chat/layout.tsx

+21-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
22
import { noto_sans, noto_sans_arabic } from "@/app/fonts";
33
import "../../globals.css";
44
import { ContentSecurityPolicy } from "@/app/common/layoutHelper";
5+
import { ThemeProvider } from "@/app/components/providers/themeProvider";
56

67
export const metadata: Metadata = {
78
title: "Khoj AI - Ask Anything",
@@ -40,14 +41,30 @@ export default function RootLayout({
4041
}>) {
4142
return (
4243
<html lang="en" className={`${noto_sans.variable} ${noto_sans_arabic.variable}`}>
43-
<ContentSecurityPolicy />
44-
<body>
45-
{children}
44+
<head>
4645
<script
4746
dangerouslySetInnerHTML={{
48-
__html: `window.EXCALIDRAW_ASSET_PATH = 'https://assets.khoj.dev/@excalidraw/excalidraw/dist/';`,
47+
__html: `
48+
try {
49+
if (localStorage.getItem('theme') === 'dark' ||
50+
(!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
51+
document.documentElement.classList.add('dark');
52+
}
53+
} catch (e) {}
54+
`,
4955
}}
5056
/>
57+
</head>
58+
<ContentSecurityPolicy />
59+
<body>
60+
<ThemeProvider>
61+
{children}
62+
<script
63+
dangerouslySetInnerHTML={{
64+
__html: `window.EXCALIDRAW_ASSET_PATH = 'https://assets.khoj.dev/@excalidraw/excalidraw/dist/';`,
65+
}}
66+
/>
67+
</ThemeProvider>
5168
</body>
5269
</html>
5370
);

0 commit comments

Comments
 (0)