Skip to content

Commit 76b522e

Browse files
committed
Portal: Use next-themes for theme management instead of custom code (#6739)
<!-- ## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes" If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000): ## Notes for the reviewer Anything important to call out? Be sure to also clarify these in your comments. ## How to test Unit tests, playground, etc. --> <!-- start pr-codex --> --- ## PR-Codex overview This PR focuses on enhancing the theme management in the application by integrating `next-themes` for better theme switching functionality and adding a new `Skeleton` component for loading states. ### Detailed summary - Removed `theme.tsx` and its usage. - Added `next-themes` dependency in `package.json`. - Introduced `Skeleton` component in `skeleton.tsx`. - Updated `ThemeSwitcher` to use `next-themes` for theme management. - Implemented `useIsClientMounted` for client-side rendering checks. - Wrapped the application in `ThemeProvider` in `layout.tsx`. - Modified CSS for dark and light themes. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent 4978640 commit 76b522e

File tree

7 files changed

+140
-140
lines changed

7 files changed

+140
-140
lines changed

apps/portal/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"he": "^1.2.0",
3838
"lucide-react": "0.487.0",
3939
"next": "15.2.4",
40+
"next-themes": "^0.4.6",
4041
"nextjs-toploader": "^1.6.12",
4142
"node-html-markdown": "^1.3.0",
4243
"node-html-parser": "^6.1.13",

apps/portal/src/app/globals.css

Lines changed: 42 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -19,44 +19,7 @@ html {
1919
--sticky-top-height: 70px;
2020
}
2121

22-
:root,
23-
[data-theme="dark"] {
24-
/* bg - neutral */
25-
--background: 0 0% 0%;
26-
--card: 0 0% 3.92%;
27-
--popover: 0 0% 0%;
28-
--secondary: 0 0% 11%;
29-
--muted: 0 0% 11%;
30-
--accent: 0 0% 11%;
31-
--inverted: 0 0% 100%;
32-
33-
/* bg - colorful */
34-
--primary: 221 83% 54%;
35-
--destructive: 360 72% 51%;
36-
37-
/* Text */
38-
--foreground: 0 0% 98%;
39-
--card-foreground: 0 0% 98%;
40-
--popover-foreground: 0 0% 98%;
41-
--primary-foreground: 0 0% 100%;
42-
--secondary-foreground: 0 0% 98%;
43-
--muted-foreground: 0 0% 63%;
44-
--accent-foreground: 0 0% 98%;
45-
--destructive-foreground: 0 0% 100%;
46-
--link-foreground: 215.88 100% 65%;
47-
--warning-text: 38 92% 50%;
48-
--destructive-text: 360 72% 55%;
49-
--success-text: 142 75% 50%;
50-
--inverted-foreground: 0 0% 0%;
51-
52-
/* Borders */
53-
--border: 0 0% 15%;
54-
--active-border: 0 0% 22%;
55-
--ring: 0 0% 30%;
56-
--input: 0 0% 15%;
57-
}
58-
59-
[data-theme="light"] {
22+
:root {
6023
/* bg - neutral */
6124
--background: 0 0% 98%;
6225
--popover: 0 0% 100%;
@@ -91,23 +54,58 @@ html {
9154
--input: 0 0% 85%;
9255
--ring: 0 0% 80%;
9356
}
57+
58+
.dark {
59+
/* bg - neutral */
60+
--background: 0 0% 0%;
61+
--card: 0 0% 3.92%;
62+
--popover: 0 0% 0%;
63+
--secondary: 0 0% 11%;
64+
--muted: 0 0% 11%;
65+
--accent: 0 0% 11%;
66+
--inverted: 0 0% 100%;
67+
68+
/* bg - colorful */
69+
--primary: 221 83% 54%;
70+
--destructive: 360 72% 51%;
71+
72+
/* Text */
73+
--foreground: 0 0% 98%;
74+
--card-foreground: 0 0% 98%;
75+
--popover-foreground: 0 0% 98%;
76+
--primary-foreground: 0 0% 100%;
77+
--secondary-foreground: 0 0% 98%;
78+
--muted-foreground: 0 0% 63%;
79+
--accent-foreground: 0 0% 98%;
80+
--destructive-foreground: 0 0% 100%;
81+
--link-foreground: 215.88 100% 65%;
82+
--warning-text: 38 92% 50%;
83+
--destructive-text: 360 72% 55%;
84+
--success-text: 142 75% 50%;
85+
--inverted-foreground: 0 0% 0%;
86+
87+
/* Borders */
88+
--border: 0 0% 15%;
89+
--active-border: 0 0% 22%;
90+
--ring: 0 0% 30%;
91+
--input: 0 0% 15%;
92+
}
9493
}
9594

96-
/* If no data-theme is added in body, its in dark theme */
97-
body:not([data-theme="light"]) .light-only {
95+
.dark .light-only {
9896
display: none;
9997
}
10098

101-
[data-theme="light"] .dark-only {
99+
html:not(.dark) .dark-only {
102100
display: none;
103101
}
104102

105103
code span {
106-
color: var(--code-dark-color);
104+
color: var(--code-light-color);
107105
}
108106

109-
body[data-theme="light"] code span {
110-
color: var(--code-light-color);
107+
.dark code span {
108+
color: var(--code-dark-color);
111109
}
112110

113111
@layer base {

apps/portal/src/app/layout.tsx

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import "./globals.css";
22
import { createMetadata } from "@/components/Document";
33
import { PosthogHeadSetup } from "@/lib/posthog/PosthogHeadSetup";
4+
import { ThemeProvider } from "next-themes";
45
import { Fira_Code, Inter } from "next/font/google";
56
import Script from "next/script";
67
import NextTopLoader from "nextjs-toploader";
78
import { StickyTopContainer } from "../components/Document/StickyTopContainer";
89
import { Banner } from "../components/others/Banner";
910
import { EnableSmoothScroll } from "../components/others/SmoothScroll";
10-
import { SetStoredTheme } from "../components/others/theme/theme";
1111
import { PHProvider } from "../lib/posthog/Posthog";
1212
import { PostHogPageView } from "../lib/posthog/PosthogPageView";
1313
import { cn } from "../lib/utils";
@@ -48,32 +48,39 @@ export default function RootLayout({
4848
</head>
4949
<PHProvider>
5050
<PostHogPageView />
51+
5152
<body
5253
className={cn(sansFont.variable, monoFont.variable, "font-sans")}
5354
suppressHydrationWarning
5455
>
55-
<SetStoredTheme />
56-
<NextTopLoader
57-
color="hsl(var(--link-foreground))"
58-
height={2}
59-
shadow={false}
60-
showSpinner={false}
61-
/>
62-
<EnableSmoothScroll />
56+
<ThemeProvider
57+
attribute="class"
58+
disableTransitionOnChange
59+
enableSystem={false}
60+
defaultTheme="dark"
61+
>
62+
<NextTopLoader
63+
color="hsl(var(--link-foreground))"
64+
height={2}
65+
shadow={false}
66+
showSpinner={false}
67+
/>
68+
<EnableSmoothScroll />
6369

64-
<div className="relative flex min-h-screen flex-col">
65-
<StickyTopContainer>
66-
{/* Note: Please change id as well when changing text or href so that new banner is shown to user even if user dismissed the older one */}
67-
<Banner
68-
id="ub-launch"
69-
text="Let users pay with whatever they have without leaving your app"
70-
href="https://thirdweb.com/connect/universal-bridge"
71-
/>
72-
<Header />
73-
</StickyTopContainer>
70+
<div className="relative flex min-h-screen flex-col">
71+
<StickyTopContainer>
72+
{/* Note: Please change id as well when changing text or href so that new banner is shown to user even if user dismissed the older one */}
73+
<Banner
74+
id="ub-launch"
75+
text="Let users pay with whatever they have without leaving your app"
76+
href="https://thirdweb.com/connect/universal-bridge"
77+
/>
78+
<Header />
79+
</StickyTopContainer>
7480

75-
{children}
76-
</div>
81+
{children}
82+
</div>
83+
</ThemeProvider>
7784
</body>
7885
</PHProvider>
7986
</html>
Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,44 @@
11
"use client";
22

3+
import { Button } from "@/components/ui/button";
4+
import { Skeleton } from "@/components/ui/skeleton";
5+
import { cn } from "@/lib/utils";
36
import { MoonIcon, SunIcon } from "lucide-react";
7+
import { useTheme } from "next-themes";
48
import { useEffect, useState } from "react";
5-
import { cn } from "../../../lib/utils";
6-
import { Button } from "../../ui/button";
79

810
export function ThemeSwitcher(props: { className?: string }) {
9-
const [theme, setTheme] = useState<"light" | "dark">("dark");
10-
11-
useEffect(() => {
12-
try {
13-
const storedTheme = localStorage.getItem("website-theme");
14-
if (storedTheme === "dark" || storedTheme === "light") {
15-
setTheme(storedTheme);
16-
}
17-
} catch {
18-
// ignore
19-
}
20-
}, []);
11+
const { theme, setTheme } = useTheme();
12+
const hasMounted = useIsClientMounted();
2113

2214
return (
2315
<Button
2416
aria-label="Toggle theme"
2517
onClick={() => {
2618
const newTheme = theme === "light" ? "dark" : "light";
2719
setTheme(newTheme);
28-
document.body.dataset.theme = newTheme;
29-
try {
30-
localStorage.setItem("website-theme", newTheme);
31-
} catch {
32-
// ignore
33-
}
3420
}}
3521
variant="outline"
3622
className={cn("p-2", props.className)}
3723
>
38-
{theme === "light" ? (
24+
{!hasMounted ? (
25+
<Skeleton className="size-6 lg:size-5" />
26+
) : theme === "light" ? (
3927
<SunIcon className="size-6 lg:size-5" />
4028
) : (
4129
<MoonIcon className="size-6 lg:size-5" />
4230
)}
4331
</Button>
4432
);
4533
}
34+
35+
function useIsClientMounted() {
36+
const [hasMounted, setHasMounted] = useState(false);
37+
38+
// eslint-disable-next-line no-restricted-syntax
39+
useEffect(() => {
40+
setHasMounted(true);
41+
}, []);
42+
43+
return hasMounted;
44+
}

apps/portal/src/components/others/theme/theme.tsx

Lines changed: 0 additions & 22 deletions
This file was deleted.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { cn } from "@/lib/utils";
2+
3+
function Skeleton({
4+
className,
5+
...props
6+
}: React.HTMLAttributes<HTMLDivElement>) {
7+
return (
8+
<div
9+
className={cn("animate-skeleton rounded-md bg-muted", className)}
10+
{...props}
11+
/>
12+
);
13+
}
14+
15+
export { Skeleton };

0 commit comments

Comments
 (0)