-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Add light mode toggle and implementation #114
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
c77174d
47a8ae5
7b4799f
aa25132
c777d88
a8d7f98
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| // Example of updating a common layout component | ||
| export default function Layout({ children }) { | ||
| return ( | ||
| <div className="min-h-screen bg-white dark:bg-gray-900"> | ||
| <nav className="bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700"> | ||
| {/* Navigation content with light/dark mode support */} | ||
| <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> | ||
| <div className="flex justify-between h-16"> | ||
| {/* Logo */} | ||
| <div className="flex items-center"> | ||
| <h1 className="text-xl font-bold text-gray-900 dark:text-white">st0x</h1> | ||
| </div> | ||
| {/* Nav items */} | ||
| <div className="flex items-center space-x-4"> | ||
| <a href="/dashboard" className="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white"> | ||
| Dashboard | ||
| </a> | ||
| <a href="/dashboard/settings" className="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white"> | ||
| Settings | ||
| </a> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </nav> | ||
| <main className="flex-1">{children}</main> | ||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import { useState, useEffect } from 'react'; | ||
|
|
||
| export default function ThemeToggle() { | ||
| const [isDark, setIsDark] = useState(true); | ||
|
|
||
| useEffect(() => { | ||
| // Check for saved theme preference or default to dark mode | ||
| const savedTheme = localStorage.getItem('theme'); | ||
| const prefersDark = savedTheme ? savedTheme === 'dark' : true; | ||
| setIsDark(prefersDark); | ||
|
|
||
| if (prefersDark) { | ||
| document.documentElement.classList.add('dark'); | ||
| } else { | ||
| document.documentElement.classList.remove('dark'); | ||
| } | ||
| }, []); | ||
|
Comment on lines
+4
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Initial state mismatch causes toggle UI flash. The initial Consider initializing state lazily or using a loading state: 🛠️ Proposed fix with lazy initialization export default function ThemeToggle() {
- const [isDark, setIsDark] = useState(true);
+ const [isDark, setIsDark] = useState(() => {
+ if (typeof window === 'undefined') return true;
+ const savedTheme = localStorage.getItem('theme');
+ return savedTheme ? savedTheme === 'dark' : true;
+ });
useEffect(() => {
- // Check for saved theme preference or default to dark mode
- const savedTheme = localStorage.getItem('theme');
- const prefersDark = savedTheme ? savedTheme === 'dark' : true;
- setIsDark(prefersDark);
-
- if (prefersDark) {
+ // Apply theme class on mount
+ if (isDark) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
- }, []);
+ }, [isDark]);🤖 Prompt for AI Agents |
||
|
|
||
| const toggleTheme = () => { | ||
| const newIsDark = !isDark; | ||
| setIsDark(newIsDark); | ||
|
|
||
| if (newIsDark) { | ||
| document.documentElement.classList.add('dark'); | ||
| localStorage.setItem('theme', 'dark'); | ||
| } else { | ||
| document.documentElement.classList.remove('dark'); | ||
| localStorage.setItem('theme', 'light'); | ||
| } | ||
| }; | ||
|
|
||
| return ( | ||
| <button | ||
| onClick={toggleTheme} | ||
| className="relative inline-flex items-center h-6 rounded-full w-11 transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-900 bg-gray-200 dark:bg-gray-700" | ||
| aria-label="Toggle theme" | ||
| > | ||
| <span | ||
| className={`${ | ||
| isDark ? 'translate-x-6' : 'translate-x-1' | ||
| } inline-block w-4 h-4 transform bg-white rounded-full transition-transform`} | ||
| /> | ||
| <span className="sr-only">{isDark ? 'Dark' : 'Light'} mode</span> | ||
| </button> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import { useEffect } from 'react'; | ||
| import '../styles/globals.css'; | ||
|
|
||
| function MyApp({ Component, pageProps }) { | ||
| useEffect(() => { | ||
| // Initialize theme on app load | ||
| const savedTheme = localStorage.getItem('theme'); | ||
| if (savedTheme === 'light') { | ||
| document.documentElement.classList.remove('dark'); | ||
| } else { | ||
| document.documentElement.classList.add('dark'); | ||
| } | ||
| }, []); | ||
|
Comment on lines
+5
to
+13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Theme initialization in Since The standard fix is to add a blocking inline script in 🛠️ Recommended fix using _document.jsxCreate or update import { Html, Head, Main, NextScript } from 'next/document';
export default function Document() {
return (
<Html>
<Head />
<body>
<script
dangerouslySetInnerHTML={{
__html: `
(function() {
const theme = localStorage.getItem('theme');
if (theme === 'light') {
document.documentElement.classList.remove('dark');
} else {
document.documentElement.classList.add('dark');
}
})();
`,
}}
/>
<Main />
<NextScript />
</body>
</Html>
);
}Then you can simplify -import { useEffect } from 'react';
import '../styles/globals.css';
function MyApp({ Component, pageProps }) {
- useEffect(() => {
- // Initialize theme on app load
- const savedTheme = localStorage.getItem('theme');
- if (savedTheme === 'light') {
- document.documentElement.classList.remove('dark');
- } else {
- document.documentElement.classList.add('dark');
- }
- }, []);
-
return <Component {...pageProps} />;
}🤖 Prompt for AI Agents |
||
|
|
||
| return <Component {...pageProps} />; | ||
| } | ||
|
|
||
| export default MyApp; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| import ThemeToggle from '../../components/ThemeToggle'; | ||
| // ... existing imports | ||
|
|
||
| export default function Settings() { | ||
| // ... existing code | ||
|
|
||
| return ( | ||
| <div className="min-h-screen bg-white dark:bg-gray-900 text-gray-900 dark:text-white"> | ||
| <div className="max-w-4xl mx-auto p-6"> | ||
| <h1 className="text-3xl font-bold mb-8 text-gray-900 dark:text-white">Settings</h1> | ||
|
|
||
| {/* Theme Settings Section */} | ||
| <div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-6 mb-6"> | ||
| <h2 className="text-xl font-semibold mb-4 text-gray-900 dark:text-white">Appearance</h2> | ||
| <div className="flex items-center justify-between"> | ||
| <div> | ||
| <p className="text-sm font-medium text-gray-900 dark:text-white">Theme</p> | ||
| <p className="text-sm text-gray-500 dark:text-gray-400">Switch between light and dark mode</p> | ||
| </div> | ||
| <ThemeToggle /> | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* ... rest of existing settings content */} | ||
| </div> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| @import 'tailwindcss/base'; | ||
| @import 'tailwindcss/components'; | ||
| @import 'tailwindcss/utilities'; | ||
|
|
||
| /* Base styles for smooth theme transitions */ | ||
| * { | ||
| @apply transition-colors duration-200; | ||
| } | ||
|
|
||
| /* Scrollbar styling for both themes */ | ||
| ::-webkit-scrollbar { | ||
| @apply w-2; | ||
| } | ||
|
|
||
| ::-webkit-scrollbar-track { | ||
| @apply bg-gray-100 dark:bg-gray-800; | ||
| } | ||
|
|
||
| ::-webkit-scrollbar-thumb { | ||
| @apply bg-gray-400 dark:bg-gray-600 rounded-full; | ||
| } | ||
|
|
||
| ::-webkit-scrollbar-thumb:hover { | ||
| @apply bg-gray-500 dark:bg-gray-500; | ||
| } | ||
|
|
||
| /* Custom focus styles */ | ||
| *:focus { | ||
| @apply outline-none ring-2 ring-indigo-500 dark:ring-indigo-400 ring-offset-2 dark:ring-offset-gray-900; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| /** @type {import('tailwindcss').Config} */ | ||
| module.exports = { | ||
| content: [ | ||
| './pages/**/*.{js,ts,jsx,tsx,mdx}', | ||
| './components/**/*.{js,ts,jsx,tsx,mdx}', | ||
| './app/**/*.{js,ts,jsx,tsx,mdx}', | ||
| ], | ||
| darkMode: 'class', | ||
| theme: { | ||
| extend: { | ||
| colors: { | ||
| // Custom colors for light mode if needed | ||
| light: { | ||
| primary: '#3B82F6', | ||
| secondary: '#10B981', | ||
| background: '#F9FAFB', | ||
| surface: '#FFFFFF', | ||
| text: '#111827', | ||
| } | ||
| }, | ||
| }, | ||
| }, | ||
| plugins: [], | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
flex-1on<main>has no effect without flex container.The
flex-1class on Line 25 won't work because the parent<div>doesn't havedisplay: flex. The main content won't expand to fill remaining height.🛠️ Proposed fix
🤖 Prompt for AI Agents