Skip to content

Commit 24b8e46

Browse files
authored
fix[dark-mode-toggle]: fix dark/light mode toggle on mobile (#1332)
* fix[dark-mode-toggle]: fix dark/light mode toggle on mobile * fix[dark-mode-toggle]: add tests
1 parent 9b13c17 commit 24b8e46

File tree

2 files changed

+55
-15
lines changed

2 files changed

+55
-15
lines changed

components/DarkModeToggle.tsx

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useTheme } from 'next-themes';
2-
import { useEffect, useState } from 'react';
2+
import { useEffect, useRef, useState } from 'react';
33
import React from 'react';
44
import Image from 'next/image';
55

@@ -35,6 +35,7 @@ export default function DarkModeToggle() {
3535
const [activeThemeIcon, setActiveThemeIcon] = useState(
3636
'/icons/theme-switch.svg',
3737
);
38+
3839
useEffect(() => {
3940
switch (theme) {
4041
case 'system':
@@ -46,8 +47,30 @@ export default function DarkModeToggle() {
4647
}
4748
}, [theme, resolvedTheme]);
4849

50+
const dropdownRef = useRef<HTMLDivElement>(null);
51+
52+
useEffect(() => {
53+
const handleClickOutside = (event: MouseEvent) => {
54+
if (
55+
dropdownRef.current &&
56+
!dropdownRef.current.contains(event.target as Node)
57+
) {
58+
setShowSelect(false);
59+
}
60+
};
61+
62+
document.addEventListener('mousedown', handleClickOutside);
63+
64+
return () => {
65+
document.removeEventListener('mousedown', handleClickOutside);
66+
};
67+
}, []);
68+
4969
return (
50-
<div className='relative w-10 h-10 dark-mode-toggle-container'>
70+
<div
71+
ref={dropdownRef}
72+
className='relative w-10 h-10 dark-mode-toggle-container'
73+
>
5174
<button
5275
onClick={() => setShowSelect(!showSelect)}
5376
className='dark-mode-toggle rounded-md dark:hover:bg-gray-700 p-1.5 hover:bg-gray-100 transition duration-150 '
@@ -66,11 +89,10 @@ export default function DarkModeToggle() {
6689
/>
6790
</button>
6891
<div
69-
className='absolute right-0 p-2 bg-white dark:bg-gray-800 rounded-lg border dark:border-gray-700 z-10 w-max'
70-
style={{ display: showSelect ? 'block' : 'none' }}
71-
onMouseLeave={() => {
72-
setShowSelect(false);
73-
}}
92+
onMouseLeave={() => setShowSelect(false)}
93+
className={`absolute right-0 p-2 bg-white dark:bg-gray-800 rounded-lg border dark:border-gray-700 z-10 w-max ${
94+
showSelect ? 'block' : 'hidden'
95+
}`}
7496
tabIndex={0}
7597
data-test='theme-dropdown'
7698
>
@@ -93,7 +115,7 @@ export default function DarkModeToggle() {
93115
>
94116
<Image
95117
src={'/icons/sun.svg'}
96-
alt='System theme'
118+
alt='Light theme'
97119
width={18}
98120
height={18}
99121
style={{ filter: isDarkMode ? 'invert(1)' : 'invert(0)' }}
@@ -106,7 +128,7 @@ export default function DarkModeToggle() {
106128
>
107129
<Image
108130
src={'/icons/moon.svg'}
109-
alt='System theme'
131+
alt='Dark theme'
110132
width={18}
111133
height={18}
112134
style={{ filter: isDarkMode ? 'invert(1)' : 'invert(0)' }}

cypress/components/DarkModeToggle.cy.tsx

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,13 @@ describe('DarkModeToggle Component', () => {
4343
cy.get(TOGGLE_BUTTON).click();
4444

4545
// check if the menu is open
46-
cy.get(THEME_DROPDOWN).should('have.css', 'display', 'block');
46+
cy.get(THEME_DROPDOWN).should('be.visible');
4747

4848
// click on the toggle button again to close the menu
4949
cy.get(TOGGLE_BUTTON).click();
5050

5151
// check if the menu is closed
52-
cy.get(THEME_DROPDOWN).should('have.css', 'display', 'none');
52+
cy.get(THEME_DROPDOWN).should('not.be.visible');
5353
});
5454

5555
// Should close the menu on mouse leave
@@ -58,13 +58,31 @@ describe('DarkModeToggle Component', () => {
5858
cy.get(TOGGLE_BUTTON).click();
5959

6060
// check if the menu is open
61-
cy.get(THEME_DROPDOWN).should('have.css', 'display', 'block');
61+
cy.get(THEME_DROPDOWN).should('be.visible');
6262

63-
// trigger mouse leave event on the menu
64-
cy.get(THEME_DROPDOWN).trigger('mouseout');
63+
// simulate mouse leave event on the menu
64+
cy.get(THEME_DROPDOWN).then(($el) => {
65+
const mouseOutEvent = new Event('mouseout', { bubbles: true });
66+
$el[0].dispatchEvent(mouseOutEvent);
67+
});
6568

6669
// check if the menu is closed
67-
cy.get(THEME_DROPDOWN).should('have.css', 'display', 'none');
70+
cy.get(THEME_DROPDOWN).should('not.be.visible');
71+
});
72+
73+
// Should close the menu when clicking outside
74+
it('should close the menu when clicking outside', () => {
75+
// Click on the toggle button to open the menu
76+
cy.get(TOGGLE_BUTTON).click();
77+
78+
// Check if the menu is open
79+
cy.get(THEME_DROPDOWN).should('be.visible');
80+
81+
// Simulate clicking outside the dropdown
82+
cy.get('body').click(0, 0); // Click at the top-left corner of the body
83+
84+
// Check if the menu is closed
85+
cy.get(THEME_DROPDOWN).should('not.be.visible');
6886
});
6987
});
7088

0 commit comments

Comments
 (0)