Skip to content

Commit 6a9723f

Browse files
committed
Fix #521: add smooth theme transition animation
1 parent 6c6bc3e commit 6a9723f

3 files changed

Lines changed: 46 additions & 6 deletions

File tree

src/components/Navbar.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,9 @@ const Navbar: React.FC = () => {
6464
aria-label="Toggle Theme"
6565
>
6666
{mode === "dark" ? (
67-
<Sun className="h-5 w-5 text-yellow-400" />
67+
<Sun key="sun-icon" className="theme-toggle-icon h-5 w-5 text-yellow-400" />
6868
) : (
69-
<Moon className="h-5 w-5 text-slate-700" />
69+
<Moon key="moon-icon" className="theme-toggle-icon h-5 w-5 text-slate-700 dark:text-white" />
7070
)}
7171
</button>
7272
</div>
@@ -83,7 +83,7 @@ const Navbar: React.FC = () => {
8383
{mode === "dark" ? (
8484
<Sun className="h-5 w-5 text-yellow-400" />
8585
) : (
86-
<Moon className="h-5 w-5 text-white" />
86+
<Moon key="moon-icon-mobile" className="theme-toggle-icon h-5 w-5 text-slate-700 dark:text-white" />
8787
)}
8888
</button>
8989

src/context/ThemeContext.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,15 @@ const ThemeWrapper = ({ children }: { children: ReactNode }) => {
2727
localStorage.setItem(THEME_STORAGE_KEY, mode);
2828
}, [mode]);
2929

30-
const toggleTheme = () => {
31-
setMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light'));
32-
};
30+
const toggleTheme = () => {
31+
document.documentElement.classList.add("theme-transitioning");
32+
33+
window.setTimeout(() => {
34+
document.documentElement.classList.remove("theme-transitioning");
35+
}, 650);
36+
37+
setMode((prevMode) => (prevMode === "light" ? "dark" : "light"));
38+
};
3339

3440
const muiTheme: Theme = useMemo(
3541
() => createTheme({ palette: { mode } }),

src/index.css

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,37 @@
8989
.icon-issue-closed {
9090
color: #cf222e;
9191
}
92+
93+
html.theme-transitioning,
94+
html.theme-transitioning *,
95+
html.theme-transitioning *::before,
96+
html.theme-transitioning *::after {
97+
transition-property: background-color, border-color, color, fill, stroke, box-shadow, opacity, transform;
98+
transition-duration: 600ms;
99+
transition-timing-function: ease-in-out;
100+
}
101+
102+
@media (prefers-reduced-motion: reduce) {
103+
html.theme-transitioning,
104+
html.theme-transitioning *,
105+
html.theme-transitioning *::before,
106+
html.theme-transitioning *::after {
107+
transition-duration: 0ms;
108+
}
109+
}
110+
111+
@keyframes theme-icon-pop {
112+
0% {
113+
opacity: 0;
114+
transform: rotate(-90deg) scale(0.75);
115+
}
116+
117+
100% {
118+
opacity: 1;
119+
transform: rotate(0deg) scale(1);
120+
}
121+
}
122+
123+
.theme-toggle-icon {
124+
animation: theme-icon-pop 500ms ease-out;
125+
}

0 commit comments

Comments
 (0)