Skip to content

Commit ec60d5e

Browse files
committed
feature: add light mode toggle and global theme support
1 parent 07ddc29 commit ec60d5e

7 files changed

Lines changed: 275 additions & 4 deletions

File tree

app/components/layout/Header/headerComponent.tsx

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import ProductMenuComponent from '../../headerMenuDropdownComponents/productMenu
1919
import SolutionMenuComponent from '../../headerMenuDropdownComponents/solutionMenuComponent';
2020
import ResourceMenuComponent from '../../headerMenuDropdownComponents/resourceMenuComponent';
2121
import ToolsMenuComponent from '../../headerMenuDropdownComponents/toolsMenuComponent';
22+
import { useTheme } from '@/app/contexts/themeContext';
2223

2324
const responsiveHeader = [
2425
// {
@@ -186,10 +187,49 @@ const CountdownTimer = () => {
186187
);
187188
};
188189

190+
const SunIcon = () => (
191+
<svg
192+
width="16"
193+
height="16"
194+
viewBox="0 0 24 24"
195+
fill="none"
196+
xmlns="http://www.w3.org/2000/svg"
197+
aria-hidden="true"
198+
>
199+
<circle cx="12" cy="12" r="4" stroke="currentColor" strokeWidth="2" />
200+
<path
201+
d="M12 2V5M12 19V22M22 12H19M5 12H2M19.07 4.93L16.95 7.05M7.05 16.95L4.93 19.07M19.07 19.07L16.95 16.95M7.05 7.05L4.93 4.93"
202+
stroke="currentColor"
203+
strokeWidth="2"
204+
strokeLinecap="round"
205+
/>
206+
</svg>
207+
);
208+
209+
const MoonIcon = () => (
210+
<svg
211+
width="16"
212+
height="16"
213+
viewBox="0 0 24 24"
214+
fill="none"
215+
xmlns="http://www.w3.org/2000/svg"
216+
aria-hidden="true"
217+
>
218+
<path
219+
d="M21 14.5A9 9 0 1 1 12.5 3C11.6 4.3 11.1 5.9 11.1 7.6C11.1 12 14.6 15.5 19 15.5C19.7 15.5 20.4 15.4 21 15.2V14.5Z"
220+
stroke="currentColor"
221+
strokeWidth="2"
222+
strokeLinecap="round"
223+
strokeLinejoin="round"
224+
/>
225+
</svg>
226+
);
227+
189228
const HeaderComponent = () => {
190229
const router = usePathname();
191230
const searchParams = useSearchParams();
192231
const utmSource = searchParams.get('utm_source');
232+
const { isLightTheme, setIsLightTheme } = useTheme();
193233
const { collapsed, setCollapsed }: LayoutContextModel =
194234
useContext(LayoutContext);
195235

@@ -390,7 +430,16 @@ const HeaderComponent = () => {
390430
</li>
391431
))}
392432
</ul>
393-
<div className="ml-auto">
433+
<div className="ml-auto flex items-center">
434+
<button
435+
type="button"
436+
className={`${hederStyles.themeToggle} mr-2`}
437+
onClick={() => setIsLightTheme(!isLightTheme)}
438+
aria-label={`Switch to ${isLightTheme ? 'dark' : 'light'} mode`}
439+
aria-pressed={isLightTheme}
440+
>
441+
{isLightTheme ? <MoonIcon /> : <SunIcon />}
442+
</button>
394443
<Link
395444
href={`https://app.betterbugs.io/login${
396445
utmSource ? `?utm_source=${utmSource}` : ''
@@ -402,7 +451,7 @@ const HeaderComponent = () => {
402451
</div>
403452
</Link>
404453
</div>
405-
<div>
454+
<div className="flex items-center">
406455
<Link
407456
href={`https://app.betterbugs.io/login${
408457
utmSource ? `?utm_source=${utmSource}` : ''
@@ -447,7 +496,16 @@ const HeaderComponent = () => {
447496
/>
448497
</Link>
449498
</div>
450-
<div className="ml-auto">
499+
<div className="ml-auto flex items-center">
500+
<button
501+
type="button"
502+
className={`${hederStyles.themeToggle} mr-3`}
503+
onClick={() => setIsLightTheme(!isLightTheme)}
504+
aria-label={`Switch to ${isLightTheme ? 'dark' : 'light'} mode`}
505+
aria-pressed={isLightTheme}
506+
>
507+
{isLightTheme ? <MoonIcon /> : <SunIcon />}
508+
</button>
451509
<Link
452510
href={`https://app.betterbugs.io/login${
453511
utmSource ? `?utm_source=${utmSource}` : ''

app/components/layout/Header/headerStyles.module.scss

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,31 @@
8282
border-radius: 15px;
8383
}
8484
}
85+
86+
.themeToggle {
87+
align-items: center;
88+
background: rgba(255, 255, 255, 0.08);
89+
border: 1px solid rgba(255, 255, 255, 0.2);
90+
border-radius: 999px;
91+
color: white;
92+
cursor: pointer;
93+
display: inline-flex;
94+
height: 36px;
95+
justify-content: center;
96+
transition: background-color 0.2s ease, color 0.2s ease;
97+
width: 36px;
98+
99+
&:hover {
100+
background: rgba(255, 255, 255, 0.16);
101+
}
102+
103+
:global(html[data-theme="light"]) & {
104+
background: rgba(17, 24, 39, 0.06);
105+
border-color: rgba(17, 24, 39, 0.2);
106+
color: #111827;
107+
108+
&:hover {
109+
background: rgba(17, 24, 39, 0.12);
110+
}
111+
}
112+
}

app/contexts/themeContext.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ export const ThemeProvider = ({ children }: { children: ReactNode }) => {
3333
localStorage.setItem("nestify-theme", isLight ? "light" : "dark");
3434
};
3535

36+
useEffect(() => {
37+
document.documentElement.setAttribute(
38+
"data-theme",
39+
isLightTheme ? "light" : "dark"
40+
);
41+
}, [isLightTheme]);
42+
3643
// Don't render until theme is loaded to prevent hydration mismatch
3744
if (!isLoaded) {
3845
return null;

app/developmentToolsStyles.module.scss

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
input {
1010
background: #ffffff1a;
11+
border: 1px solid #ffffff66;
12+
color: #ffffff;
1113
border-radius: 50px;
1214
padding: 2px 24px;
1315
height: 40px;
@@ -43,6 +45,17 @@
4345
}
4446
}
4547

48+
:global(html[data-theme="light"]) .searchInput {
49+
input {
50+
border: 1px solid #000000;
51+
color: #000000;
52+
53+
&::placeholder {
54+
color: #00000099;
55+
}
56+
}
57+
}
58+
4659
// tab background color
4760
.tabBackgroundColor {
4861
background: linear-gradient(91.15deg, #11e498 11.3%, #05bae2 101.69%);

app/layout.tsx

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
LayoutContextProvider,
1111
} from './contexts/layoutContexts';
1212
import { ThemeProvider } from './contexts/themeContext';
13+
import { useTheme } from './contexts/themeContext';
1314
import FooterComponent from './components/layout/footer/footerComponent';
1415
import AnimatedCursor from 'react-animated-cursor';
1516
import { useMediaQuery } from 'react-responsive';
@@ -22,6 +23,60 @@ const inter = Poppins({
2223
weight: ['100', '200', '300', '400', '500', '600', '700', '800', '900'],
2324
});
2425

26+
const SunIcon = () => (
27+
<svg
28+
width="16"
29+
height="16"
30+
viewBox="0 0 24 24"
31+
fill="none"
32+
xmlns="http://www.w3.org/2000/svg"
33+
aria-hidden="true"
34+
>
35+
<circle cx="12" cy="12" r="4" stroke="currentColor" strokeWidth="2" />
36+
<path
37+
d="M12 2V5M12 19V22M22 12H19M5 12H2M19.07 4.93L16.95 7.05M7.05 16.95L4.93 19.07M19.07 19.07L16.95 16.95M7.05 7.05L4.93 4.93"
38+
stroke="currentColor"
39+
strokeWidth="2"
40+
strokeLinecap="round"
41+
/>
42+
</svg>
43+
);
44+
45+
const MoonIcon = () => (
46+
<svg
47+
width="16"
48+
height="16"
49+
viewBox="0 0 24 24"
50+
fill="none"
51+
xmlns="http://www.w3.org/2000/svg"
52+
aria-hidden="true"
53+
>
54+
<path
55+
d="M21 14.5A9 9 0 1 1 12.5 3C11.6 4.3 11.1 5.9 11.1 7.6C11.1 12 14.6 15.5 19 15.5C19.7 15.5 20.4 15.4 21 15.2V14.5Z"
56+
stroke="currentColor"
57+
strokeWidth="2"
58+
strokeLinecap="round"
59+
strokeLinejoin="round"
60+
/>
61+
</svg>
62+
);
63+
64+
const GlobalThemeToggle = () => {
65+
const { isLightTheme, setIsLightTheme } = useTheme();
66+
67+
return (
68+
<button
69+
type="button"
70+
className="global-theme-toggle"
71+
onClick={() => setIsLightTheme(!isLightTheme)}
72+
aria-label={`Switch to ${isLightTheme ? 'dark' : 'light'} mode`}
73+
aria-pressed={isLightTheme}
74+
>
75+
{isLightTheme ? <MoonIcon /> : <SunIcon />}
76+
</button>
77+
);
78+
};
79+
2580
const MyApp = ({ children }: { children: JSX.Element }): JSX.Element => {
2681
const { isClient }: LayoutContextModel = useContext(LayoutContext);
2782

@@ -176,6 +231,7 @@ window.gtag =
176231
<Suspense>
177232
<HeaderComponent />
178233
</Suspense>
234+
<GlobalThemeToggle />
179235
{children}
180236
<FooterComponent />
181237
</div>

app/page.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import CodeForgeIcon from "./components/theme/Icon/codeForgeIcon";
1515
import ConvertXIcon from "./components/theme/Icon/convertXIcon";
1616
import GenieIcon from "./components/theme/Icon/genieIcon";
1717
import DevUtilsIcon from "./components/theme/Icon/devUtilsIcon";
18+
import { useTheme } from "./contexts/themeContext";
1819

1920
const CATEGORY_GROUPS = [
2021
"Text Lab",
@@ -124,6 +125,7 @@ const classifyBasis = (title: string, url: string): BasisType => {
124125
};
125126

126127
const Page = () => {
128+
const { isLightTheme } = useTheme();
127129
const { register, formState, setValue } = useForm<any>({});
128130
const [isSearch, setIsSearch] = useState(false);
129131
const [searchTerm, setSearchTerm] = useState("");
@@ -220,7 +222,7 @@ const Page = () => {
220222
</div>
221223
) : (
222224
<div className="absolute lg:right-[210px] lg:top-4 right-11 top-4 2xl:right-[13rem] 2xl:top-4">
223-
<SearchIcon className="text-white" />
225+
<SearchIcon className={isLightTheme ? "text-black" : "text-white"} />
224226
</div>
225227
)}
226228
</div>

app/styles/global.scss

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,113 @@ body {
3131
}
3232
}
3333

34+
:root {
35+
color-scheme: dark;
36+
}
37+
38+
html[data-theme="light"] {
39+
color-scheme: light;
40+
}
41+
42+
html[data-theme="light"],
43+
html[data-theme="light"] body {
44+
background-color: #ffffff !important;
45+
color: #111827 !important;
46+
}
47+
48+
html[data-theme="light"] .bg-black,
49+
html[data-theme="light"] .bg-\[\#111111\] {
50+
background-color: #ffffff !important;
51+
}
52+
53+
html[data-theme="light"] [class*="bg-black"] {
54+
background-color: #ffffff !important;
55+
}
56+
57+
html[data-theme="light"] [class*="bg-\\[\\#"] {
58+
background-color: #ffffff !important;
59+
}
60+
61+
html[data-theme="light"] [class*="bg-white/"] {
62+
background-color: #f3f4f6 !important;
63+
}
64+
65+
html[data-theme="light"] .text-white,
66+
html[data-theme="light"] .text-white\/80,
67+
html[data-theme="light"] .text-white\/70,
68+
html[data-theme="light"] .text-white\/60 {
69+
color: #111827 !important;
70+
}
71+
72+
html[data-theme="light"] [class*="text-white"] {
73+
color: #111827 !important;
74+
}
75+
76+
html[data-theme="light"] [class*="!text-white"] {
77+
color: #111827 !important;
78+
}
79+
80+
html[data-theme="light"] .border-light-primary,
81+
html[data-theme="light"] .border-t-light-primary,
82+
html[data-theme="light"] .\!border-light-primary {
83+
border-color: #d1d5db !important;
84+
}
85+
86+
html[data-theme="light"] [class*="border-white"],
87+
html[data-theme="light"] [class*="border-\\[\\#222"],
88+
html[data-theme="light"] [class*="border-black"] {
89+
border-color: #d1d5db !important;
90+
}
91+
92+
html[data-theme="light"] .tab-gradient,
93+
html[data-theme="light"] .grey-gradient,
94+
html[data-theme="light"] .feature-gradient,
95+
html[data-theme="light"] .cardGradient,
96+
html[data-theme="light"] .boxGradient {
97+
background: #f3f4f6 !important;
98+
}
99+
100+
html[data-theme="light"] section,
101+
html[data-theme="light"] main,
102+
html[data-theme="light"] article,
103+
html[data-theme="light"] aside,
104+
html[data-theme="light"] header,
105+
html[data-theme="light"] footer,
106+
html[data-theme="light"] nav,
107+
html[data-theme="light"] div {
108+
color: #111827;
109+
}
110+
111+
html[data-theme="light"] input,
112+
html[data-theme="light"] textarea,
113+
html[data-theme="light"] select {
114+
color: #111827 !important;
115+
border-color: #d1d5db !important;
116+
}
117+
118+
.global-theme-toggle {
119+
align-items: center;
120+
background: rgba(255, 255, 255, 0.1);
121+
border: 1px solid rgba(255, 255, 255, 0.2);
122+
border-radius: 999px;
123+
bottom: 24px;
124+
color: #ffffff;
125+
cursor: pointer;
126+
display: inline-flex;
127+
height: 42px;
128+
justify-content: center;
129+
position: fixed;
130+
right: 24px;
131+
width: 42px;
132+
z-index: 120;
133+
}
134+
135+
html[data-theme="light"] .global-theme-toggle {
136+
background: rgba(17, 24, 39, 0.06);
137+
border-color: rgba(17, 24, 39, 0.2);
138+
color: #111827;
139+
}
140+
34141
h1,
35142
h2,
36143
h3,

0 commit comments

Comments
 (0)