Improve handling of dark mode theme in order to avoid jitter when loading new page

This commit is contained in:
sabaimran
2025-02-06 16:17:58 -08:00
parent 2e01a95cf1
commit 43e032e25a
12 changed files with 166 additions and 54 deletions

View File

@@ -2,6 +2,7 @@ import type { Metadata } from "next";
import { noto_sans, noto_sans_arabic } from "@/app/fonts";
import "../globals.css";
import { ContentSecurityPolicy } from "../common/layoutHelper";
import { ThemeProvider } from "../components/providers/themeProvider";
export const metadata: Metadata = {
title: "Khoj AI - Agents",
@@ -40,8 +41,26 @@ export default function RootLayout({
}>) {
return (
<html lang="en" className={`${noto_sans.variable} ${noto_sans_arabic.variable}`}>
<head>
<script
dangerouslySetInnerHTML={{
__html: `
try {
if (localStorage.getItem('theme') === 'dark' ||
(!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
}
} catch (e) {}
`,
}}
/>
</head>
<ContentSecurityPolicy />
<body>{children}</body>
<body>
<ThemeProvider>
{children}
</ThemeProvider>
</body>
</html>
);
}

View File

@@ -20,7 +20,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTrigger } from "@/components
import LoginPrompt from "../components/loginPrompt/loginPrompt";
import { InlineLoading } from "../components/loading/loading";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { useIsMobileWidth } from "../common/utils";
import { useIsDarkMode, useIsMobileWidth } from "../common/utils";
import {
AgentCard,
EditAgentSchema,

View File

@@ -2,6 +2,7 @@ import type { Metadata } from "next";
import { noto_sans, noto_sans_arabic } from "@/app/fonts";
import "../globals.css";
import { ContentSecurityPolicy } from "../common/layoutHelper";
import { ThemeProvider } from "../components/providers/themeProvider";
export const metadata: Metadata = {
title: "Khoj AI - Chat",
@@ -40,14 +41,30 @@ export default function RootLayout({
}>) {
return (
<html lang="en" className={`${noto_sans.variable} ${noto_sans_arabic.variable}`}>
<ContentSecurityPolicy />
<body>
{children}
<head>
<script
dangerouslySetInnerHTML={{
__html: `window.EXCALIDRAW_ASSET_PATH = 'https://assets.khoj.dev/@excalidraw/excalidraw/dist/';`,
__html: `
try {
if (localStorage.getItem('theme') === 'dark' ||
(!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
}
} catch (e) {}
`,
}}
/>
</head>
<ContentSecurityPolicy />
<body>
<ThemeProvider>
{children}
<script
dangerouslySetInnerHTML={{
__html: `window.EXCALIDRAW_ASSET_PATH = 'https://assets.khoj.dev/@excalidraw/excalidraw/dist/';`,
}}
/>
</ThemeProvider>
</body>
</html>
);

View File

@@ -89,6 +89,40 @@ export const useMutationObserver = (
}, [ref, callback, options])
}
export function useIsDarkMode() {
const [darkMode, setDarkMode] = useState(false);
const [initialLoadDone, setInitialLoadDone] = useState(false);
useEffect(() => {
if (localStorage.getItem("theme") === "dark") {
document.documentElement.classList.add("dark");
setDarkMode(true);
} else if (localStorage.getItem("theme") === "light") {
document.documentElement.classList.remove("dark");
setDarkMode(false);
} else {
const mq = window.matchMedia("(prefers-color-scheme: dark)");
if (mq.matches) {
document.documentElement.classList.add("dark");
setDarkMode(true);
}
}
setInitialLoadDone(true);
}, []);
useEffect(() => {
if (!initialLoadDone) return;
if (darkMode) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
localStorage.setItem("theme", darkMode ? "dark" : "light");
}, [darkMode, initialLoadDone]);
return [darkMode, setDarkMode] as const;
}
export const convertBytesToText = (fileSize: number) => {
if (fileSize < 1024) {
return `${fileSize} B`;

View File

@@ -22,7 +22,7 @@ import { useEffect, useState } from "react";
import AllConversations from "../allConversations/allConversations";
import FooterMenu from "../navMenu/navMenu";
import { useSidebar } from "@/components/ui/sidebar";
import { useIsMobileWidth } from "@/app/common/utils";
import { useIsDarkMode, useIsMobileWidth } from "@/app/common/utils";
import { UserPlusIcon } from "lucide-react";
import { useAuthenticatedData, UserProfile } from "@/app/common/auth";
import LoginPrompt from "../loginPrompt/loginPrompt";

View File

@@ -13,7 +13,7 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Moon, Sun, UserCircle, Question, ArrowRight, Code, BuildingOffice } from "@phosphor-icons/react";
import { useIsMobileWidth } from "@/app/common/utils";
import { useIsDarkMode, useIsMobileWidth } from "@/app/common/utils";
import LoginPrompt from "../loginPrompt/loginPrompt";
import { Button } from "@/components/ui/button";
import { SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "@/components/ui/sidebar";
@@ -49,44 +49,10 @@ export default function FooterMenu({ sideBarIsOpen }: NavMenuProps) {
error: authenticationError,
isLoading: authenticationLoading,
} = useAuthenticatedData();
const [darkMode, setDarkMode] = useState(false);
const [initialLoadDone, setInitialLoadDone] = useState(false);
const [darkMode, setDarkMode] = useIsDarkMode();
const [showLoginPrompt, setShowLoginPrompt] = useState(false);
const isMobileWidth = useIsMobileWidth();
useEffect(() => {
if (localStorage.getItem("theme") === "dark") {
document.documentElement.classList.add("dark");
setDarkMode(true);
} else if (localStorage.getItem("theme") === "light") {
document.documentElement.classList.remove("dark");
setDarkMode(false);
} else {
const mq = window.matchMedia("(prefers-color-scheme: dark)");
if (mq.matches) {
document.documentElement.classList.add("dark");
setDarkMode(true);
}
}
setInitialLoadDone(true);
}, []);
useEffect(() => {
if (!initialLoadDone) return;
toggleDarkMode(darkMode);
}, [darkMode, initialLoadDone]);
function toggleDarkMode(darkMode: boolean) {
if (darkMode) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
localStorage.setItem("theme", darkMode ? "dark" : "light");
}
const menuItems = [
{
title: "Help",

View File

@@ -0,0 +1,8 @@
'use client'
import { useIsDarkMode } from '@/app/common/utils'
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [darkMode, setDarkMode] = useIsDarkMode();
return <>{children}</>;
}

View File

@@ -2,6 +2,7 @@ import type { Metadata } from "next";
import { noto_sans, noto_sans_arabic } from "@/app/fonts";
import "./globals.css";
import { ContentSecurityPolicy } from "./common/layoutHelper";
import { ThemeProvider } from "./components/providers/themeProvider";
export const metadata: Metadata = {
title: "Khoj AI - Ask Anything",
@@ -48,8 +49,26 @@ export default function RootLayout({
}>) {
return (
<html lang="en" className={`${noto_sans.variable} ${noto_sans_arabic.variable}`}>
<head>
<script
dangerouslySetInnerHTML={{
__html: `
try {
if (localStorage.getItem('theme') === 'dark' ||
(!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
}
} catch (e) {}
`,
}}
/>
</head>
<ContentSecurityPolicy />
<body>{children}</body>
<body>
<ThemeProvider>
{children}
</ThemeProvider>
</body>
</html>
);
}

View File

@@ -25,7 +25,6 @@ import {
stepOneSuggestions,
StepTwoSuggestion,
getStepTwoSuggestions,
SuggestionType,
} from "@/app/components/suggestions/suggestionsData";
import LoginPrompt from "@/app/components/loginPrompt/loginPrompt";

View File

@@ -3,6 +3,7 @@ import type { Metadata } from "next";
import "../globals.css";
import { ContentSecurityPolicy } from "../common/layoutHelper";
import { Toaster } from "@/components/ui/toaster";
import { ThemeProvider } from "../components/providers/themeProvider";
export const metadata: Metadata = {
title: "Khoj AI - Search",
@@ -35,10 +36,25 @@ export default function RootLayout({
}>) {
return (
<html>
<head>
<script
dangerouslySetInnerHTML={{
__html: `
try {
if (localStorage.getItem('theme') === 'dark' ||
(!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
}
} catch (e) {}
`,
}}
/>
</head>
<ContentSecurityPolicy />
<body>
{children}
<Toaster />
<ThemeProvider>
{children}
</ThemeProvider>
</body>
</html>
);

View File

@@ -4,6 +4,7 @@ import "../globals.css";
import { Toaster } from "@/components/ui/toaster";
import { ContentSecurityPolicy } from "../common/layoutHelper";
import { ChatwootWidget } from "../components/chatWoot/ChatwootWidget";
import { ThemeProvider } from "../components/providers/themeProvider";
export const metadata: Metadata = {
title: "Khoj AI - Settings",
@@ -40,11 +41,27 @@ export default function RootLayout({
}>) {
return (
<html lang="en" className={`${noto_sans.variable} ${noto_sans_arabic.variable}`}>
<head>
<script
dangerouslySetInnerHTML={{
__html: `
try {
if (localStorage.getItem('theme') === 'dark' ||
(!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
}
} catch (e) {}
`,
}}
/>
</head>
<ContentSecurityPolicy />
<body>
{children}
<Toaster />
<ChatwootWidget />
<ThemeProvider>
{children}
<Toaster />
<ChatwootWidget />
</ThemeProvider>
</body>
</html>
);

View File

@@ -2,6 +2,7 @@ import type { Metadata } from "next";
import { noto_sans, noto_sans_arabic } from "@/app/fonts";
import "../../globals.css";
import { ContentSecurityPolicy } from "@/app/common/layoutHelper";
import { ThemeProvider } from "@/app/components/providers/themeProvider";
export const metadata: Metadata = {
title: "Khoj AI - Ask Anything",
@@ -40,14 +41,30 @@ export default function RootLayout({
}>) {
return (
<html lang="en" className={`${noto_sans.variable} ${noto_sans_arabic.variable}`}>
<ContentSecurityPolicy />
<body>
{children}
<head>
<script
dangerouslySetInnerHTML={{
__html: `window.EXCALIDRAW_ASSET_PATH = 'https://assets.khoj.dev/@excalidraw/excalidraw/dist/';`,
__html: `
try {
if (localStorage.getItem('theme') === 'dark' ||
(!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
}
} catch (e) {}
`,
}}
/>
</head>
<ContentSecurityPolicy />
<body>
<ThemeProvider>
{children}
<script
dangerouslySetInnerHTML={{
__html: `window.EXCALIDRAW_ASSET_PATH = 'https://assets.khoj.dev/@excalidraw/excalidraw/dist/';`,
}}
/>
</ThemeProvider>
</body>
</html>
);