Skip to content

Commit 5118e20

Browse files
committed
fix language picker and weird menu closing behavior
1 parent c1b4dbb commit 5118e20

File tree

3 files changed

+158
-21
lines changed

3 files changed

+158
-21
lines changed

src/components/MobileSideBarMenu/Content.jsx

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useEffect, useRef } from 'react';
1+
import React, { useState, useEffect } from 'react';
22
import styles from './styles.module.scss'
33
import IconClose from '@theme/Icon/Close';
44
import IconArrowLeft from '@site/static/img/arrowleft.svg';
@@ -13,7 +13,7 @@ import { useLocation } from '@docusaurus/router';
1313
import { useHistory } from '@docusaurus/router';
1414
import MobileLanguagePicker from "./MobileLanguagePicker";
1515

16-
const MobileSideBarMenuContents = ({ className, onClick, onClose, sidebar, path, menu, isVisible = true }) => {
16+
const MobileSideBarMenuContents = ({ className, onClick, onClose, sidebar, path, menu, isVisible = true, onLanguageChange }) => {
1717
const [showTopLevel, setShowTopLevel] = useState(false);
1818
const location = useLocation();
1919
const history = useHistory();
@@ -95,6 +95,17 @@ const MobileSideBarMenuContents = ({ className, onClick, onClose, sidebar, path,
9595

9696
const currentCategory = getCurrentCategory();
9797

98+
// Handle language change - notify parent and then navigate
99+
const handleLanguageChange = (locale, href) => {
100+
// Notify parent component about language change FIRST
101+
if (onLanguageChange) {
102+
onLanguageChange(locale, href);
103+
}
104+
105+
// Then navigate (parent has already set the language change flag)
106+
history.push(href);
107+
};
108+
98109
// Handle item click - navigate and potentially close the mobile sidebar
99110
const handleItemClick = (item) => {
100111
// Handle navigation for items with href
@@ -109,23 +120,13 @@ const MobileSideBarMenuContents = ({ className, onClick, onClose, sidebar, path,
109120
!itemHref.startsWith('/docs/') &&
110121
!itemHref.startsWith('http');
111122

112-
console.log('Navigation debug:', {
113-
item: item.label,
114-
originalHref: itemHref,
115-
isInMainMenu,
116-
isDocsContext,
117-
shouldAddDocsPrefix,
118-
currentPath: location.pathname
119-
});
120-
121123
if (shouldAddDocsPrefix) {
122124
const currentLocale = getCurrentLocale();
123125
if (currentLocale !== 'en') {
124126
itemHref = `/docs/${currentLocale}${itemHref}`;
125127
} else {
126128
itemHref = `/docs${itemHref}`;
127129
}
128-
console.log('Fixed href:', itemHref);
129130
}
130131

131132
// Navigate to the href
@@ -183,7 +184,7 @@ const MobileSideBarMenuContents = ({ className, onClick, onClose, sidebar, path,
183184

184185
{/* Right Side - Controls */}
185186
<div className={styles.headerActions}>
186-
<MobileLanguagePicker/>
187+
<MobileLanguagePicker onLanguageChange={handleLanguageChange} />
187188
<ColorModeToggle/>
188189
<IconClose width={10} height={10} onClick={onClose || onClick} style={{"align-self":"center"}}/>
189190
</div>
@@ -222,7 +223,7 @@ const MobileSideBarMenuContents = ({ className, onClick, onClose, sidebar, path,
222223
return (
223224
<>
224225
{renderHeader()}
225-
{renderDocSidebarItems(menu.dropdownCategories || [], location.pathname, true)}
226+
{renderDocSidebarItems(menu?.dropdownCategories || menu || [], location.pathname, true)}
226227
</>
227228
);
228229
};
@@ -253,4 +254,4 @@ const MobileSideBarMenuContents = ({ className, onClick, onClose, sidebar, path,
253254
);
254255
};
255256

256-
export default MobileSideBarMenuContents;
257+
export default MobileSideBarMenuContents;

src/components/MobileSideBarMenu/MobileLanguagePicker.jsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
44
import Link from '@docusaurus/Link';
55
import styles from './mobileLanguagePicker.module.scss';
66

7-
const MobileLanguagePicker = () => {
7+
const MobileLanguagePicker = ({ onLanguageChange }) => {
88
const [isOpen, setIsOpen] = useState(false);
99
const dropdownRef = useRef(null);
1010
const location = useLocation();
@@ -85,6 +85,19 @@ const MobileLanguagePicker = () => {
8585
return localeNames[locale] || locale;
8686
};
8787

88+
// Handle language selection
89+
const handleLanguageSelect = (locale, href) => {
90+
setIsOpen(false);
91+
92+
// Notify parent component about language change if callback provided
93+
if (onLanguageChange) {
94+
onLanguageChange(locale, href);
95+
} else {
96+
// Fallback navigation if no callback
97+
window.location.href = href;
98+
}
99+
};
100+
88101
// If only one locale is configured, don't show the picker
89102
if (locales.length <= 1) {
90103
return null;
@@ -189,7 +202,10 @@ const MobileLanguagePicker = () => {
189202
key={locale}
190203
to={href}
191204
className={`${styles.languageOption} ${isCurrentLocale ? styles.languageOptionActive : ''}`}
192-
onClick={() => setIsOpen(false)}
205+
onClick={(e) => {
206+
e.preventDefault(); // Prevent the Link from navigating
207+
handleLanguageSelect(locale, href);
208+
}}
193209
aria-label={`Switch to ${localeFullName}`}
194210
>
195211
<span className={styles.languageName}>{localeFullName}</span>

src/components/MobileSideBarMenu/index.jsx

Lines changed: 124 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,108 @@
1-
import React, {useState, useEffect} from 'react';
1+
import React, {useState, useEffect, useRef} from 'react';
22
import Hamburger from "./Hamburger";
33
import MobileSideBarMenuContents from './Content';
44
import styles from './styles.module.scss';
5+
import { useLocation, useHistory } from '@docusaurus/router';
56

67
const MobileSideBarMenu = ({sidebar, menu}) => {
7-
const [currentMenuState, setMenuState] = useState(false);
8+
const [currentMenuState, setMenuState] = useState(() => {
9+
// Try to restore menu state from sessionStorage on mount
10+
const savedMenuState = sessionStorage.getItem('mobilemenu_open');
11+
return savedMenuState === 'true';
12+
});
13+
const location = useLocation();
14+
const history = useHistory();
15+
const previousLocationRef = useRef(null);
16+
const isLanguageChangeRef = useRef(false);
17+
const languageChangeTimeoutRef = useRef(null);
18+
19+
// Save menu state to sessionStorage whenever it changes
20+
useEffect(() => {
21+
sessionStorage.setItem('mobilemenu_open', currentMenuState.toString());
22+
}, [currentMenuState]);
823

924
// Define the breakpoint where mobile menu should be hidden (laptop breakpoint)
1025
const LAPTOP_BREAKPOINT = 1330;
1126

27+
// Initialize the previous location ref on first render and monitor location changes
28+
useEffect(() => {
29+
const currentPath = location.pathname;
30+
const previousPath = previousLocationRef.current;
31+
32+
// On first render, check if we can get previous path from sessionStorage
33+
if (previousPath === null) {
34+
const storedPreviousPath = sessionStorage.getItem('mobilemenu_previous_path');
35+
36+
if (storedPreviousPath && storedPreviousPath !== currentPath) {
37+
// We have a stored previous path that's different from current
38+
previousLocationRef.current = storedPreviousPath;
39+
// Continue with path change detection below
40+
} else {
41+
// No stored path or it's the same - initialize and skip detection
42+
previousLocationRef.current = currentPath;
43+
sessionStorage.setItem('mobilemenu_previous_path', currentPath);
44+
return; // Skip path change detection on first render
45+
}
46+
}
47+
48+
// Check if this is a language change (same base path, different locale)
49+
const isLanguageChange = () => {
50+
// More comprehensive language change detection
51+
const normalizePathForComparison = (path) => {
52+
// Handle docs root cases first
53+
if (path === '/docs' || path === '/docs/') {
54+
return '/docs/';
55+
}
56+
if (path.match(/^\/docs\/(jp|ja|ru|zh|zh-CN)\/?$/)) {
57+
return '/docs/';
58+
}
59+
60+
// Handle other docs paths
61+
if (path.startsWith('/docs/')) {
62+
const normalized = path
63+
.replace(/^\/docs\/(jp|ja|ru|zh|zh-CN)\//, '/docs/') // Remove /docs/locale/
64+
.replace(/^\/docs\/(jp|ja|ru|zh|zh-CN)$/, '/docs/'); // Remove /docs/locale
65+
return normalized;
66+
}
67+
68+
// Handle non-docs paths
69+
const normalized = path
70+
.replace(/^\/(jp|ja|ru|zh|zh-CN)\//, '/') // Remove /locale/
71+
.replace(/^\/(jp|ja|ru|zh|zh-CN)$/, '/'); // Remove /locale
72+
return normalized;
73+
};
74+
75+
const normalizedCurrent = normalizePathForComparison(currentPath);
76+
const normalizedPrevious = normalizePathForComparison(previousLocationRef.current);
77+
78+
// It's a language change if:
79+
// 1. The language change flag is set, OR
80+
// 2. The normalized paths are the same (same content, different locale)
81+
return isLanguageChangeRef.current || normalizedCurrent === normalizedPrevious;
82+
};
83+
84+
// If the path changed, decide whether to close the menu
85+
if (currentPath !== previousLocationRef.current) {
86+
const langChange = isLanguageChange();
87+
88+
if (!langChange) {
89+
setMenuState(false);
90+
} else {
91+
// Reset the language change flag after navigation is complete
92+
if (languageChangeTimeoutRef.current) {
93+
clearTimeout(languageChangeTimeoutRef.current);
94+
}
95+
languageChangeTimeoutRef.current = setTimeout(() => {
96+
isLanguageChangeRef.current = false;
97+
}, 300);
98+
}
99+
}
100+
101+
// Update the previous path reference and store it
102+
previousLocationRef.current = currentPath;
103+
sessionStorage.setItem('mobilemenu_previous_path', currentPath);
104+
}, [location.pathname]);
105+
12106
// Prevent body scroll when menu is open
13107
useEffect(() => {
14108
if (currentMenuState) {
@@ -52,13 +146,38 @@ const MobileSideBarMenu = ({sidebar, menu}) => {
52146
};
53147
}, [currentMenuState, LAPTOP_BREAKPOINT]);
54148

149+
// Handle language change
150+
const handleLanguageChange = (locale, href) => {
151+
// Set the flag BEFORE navigation
152+
isLanguageChangeRef.current = true;
153+
154+
// Clear any existing timeout
155+
if (languageChangeTimeoutRef.current) {
156+
clearTimeout(languageChangeTimeoutRef.current);
157+
}
158+
159+
// Set a backup timeout to reset the flag (in case something goes wrong)
160+
languageChangeTimeoutRef.current = setTimeout(() => {
161+
isLanguageChangeRef.current = false;
162+
}, 1000);
163+
};
164+
165+
// Cleanup timeout on unmount
166+
useEffect(() => {
167+
return () => {
168+
if (languageChangeTimeoutRef.current) {
169+
clearTimeout(languageChangeTimeoutRef.current);
170+
}
171+
};
172+
}, []);
173+
55174
// Function to handle menu close
56175
const handleMenuClose = () => {
57176
setMenuState(false);
58177
};
59178

60179
const handleItemClick = (item) => {
61-
// Safely check if item exists and is not collapsible
180+
// Close menu for non-collapsible items
62181
if (item && !item.collapsible) {
63182
handleMenuClose();
64183
}
@@ -77,6 +196,7 @@ const MobileSideBarMenu = ({sidebar, menu}) => {
77196
}
78197
}}
79198
onClose={handleMenuClose}
199+
onLanguageChange={handleLanguageChange}
80200
sidebar={sidebar} // Left sidebar items
81201
menu={menu} // Top level menu items
82202
isVisible={currentMenuState} // Pass menu visibility state
@@ -89,4 +209,4 @@ const MobileSideBarMenu = ({sidebar, menu}) => {
89209

90210
}
91211

92-
export default MobileSideBarMenu;
212+
export default MobileSideBarMenu;

0 commit comments

Comments
 (0)