Skip to content

Commit 5b35b39

Browse files
authored
Merge pull request #13141 from ethereum/performance/lazy-mobile-menu
Performance: lazy load menus
2 parents 61bfe8e + e1abde2 commit 5b35b39

File tree

2 files changed

+141
-120
lines changed

2 files changed

+141
-120
lines changed

src/components/Nav/Desktop/index.tsx

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { useRef } from "react"
2+
import { useRouter } from "next/router"
3+
import { useTranslation } from "next-i18next"
4+
import { BsTranslate } from "react-icons/bs"
5+
import { MdBrightness2, MdWbSunny } from "react-icons/md"
6+
import {
7+
Button,
8+
HStack,
9+
Icon,
10+
MenuButton,
11+
Text,
12+
useColorModeValue,
13+
useDisclosure,
14+
useEventListener,
15+
} from "@chakra-ui/react"
16+
17+
import { IconButton } from "@/components/Buttons"
18+
import LanguagePicker from "@/components/LanguagePicker"
19+
20+
import { DESKTOP_LANGUAGE_BUTTON_NAME, NAV_PY } from "@/lib/constants"
21+
22+
type DesktopNavMenuProps = {
23+
toggleColorMode: () => void
24+
}
25+
26+
const DesktopNavMenu = ({ toggleColorMode }: DesktopNavMenuProps) => {
27+
const { t } = useTranslation("common")
28+
const { locale } = useRouter()
29+
const languagePickerState = useDisclosure()
30+
const languagePickerRef = useRef<HTMLButtonElement>(null)
31+
32+
const ThemeIcon = useColorModeValue(<MdBrightness2 />, <MdWbSunny />)
33+
const themeIconAriaLabel = useColorModeValue(
34+
"Switch to Dark Theme",
35+
"Switch to Light Theme"
36+
)
37+
38+
/**
39+
* Adds a keydown event listener to toggle color mode (ctrl|cmd + \)
40+
* or open the language picker (\).
41+
* @param {string} event - The keydown event.
42+
*/
43+
useEventListener("keydown", (e) => {
44+
if (e.key !== "\\") return
45+
e.preventDefault()
46+
if (e.metaKey || e.ctrlKey) {
47+
toggleColorMode()
48+
} else {
49+
if (languagePickerState.isOpen) return
50+
languagePickerRef.current?.click()
51+
}
52+
})
53+
54+
return (
55+
<HStack hideBelow="md" gap="0">
56+
<IconButton
57+
transition="transform 0.5s, color 0.2s"
58+
icon={ThemeIcon}
59+
aria-label={themeIconAriaLabel}
60+
variant="ghost"
61+
isSecondary
62+
px={{ base: "2", xl: "3" }}
63+
_hover={{
64+
transform: "rotate(10deg)",
65+
color: "primary.hover",
66+
}}
67+
onClick={toggleColorMode}
68+
/>
69+
70+
{/* Locale-picker menu */}
71+
<LanguagePicker
72+
placement="bottom-end"
73+
minH="unset"
74+
maxH="75vh"
75+
w="xs"
76+
inset="unset"
77+
top="unset"
78+
menuState={languagePickerState}
79+
>
80+
<MenuButton
81+
as={Button}
82+
name={DESKTOP_LANGUAGE_BUTTON_NAME}
83+
ref={languagePickerRef}
84+
variant="ghost"
85+
color="body.base"
86+
transition="color 0.2s"
87+
px={{ base: "2", xl: "3" }}
88+
_hover={{
89+
color: "primary.hover",
90+
"& svg": {
91+
transform: "rotate(10deg)",
92+
transition: "transform 0.5s",
93+
},
94+
}}
95+
_active={{
96+
color: "primary.hover",
97+
bg: "primary.lowContrast",
98+
}}
99+
sx={{
100+
"& svg": {
101+
transform: "rotate(0deg)",
102+
transition: "transform 0.5s",
103+
},
104+
}}
105+
>
106+
<Icon as={BsTranslate} fontSize="2xl" verticalAlign="middle" me={2} />
107+
<Text hideBelow="lg" as="span">
108+
{t("common:languages")}&nbsp;
109+
</Text>
110+
{locale!.toUpperCase()}
111+
</MenuButton>
112+
</LanguagePicker>
113+
</HStack>
114+
)
115+
}
116+
117+
export default DesktopNavMenu

src/components/Nav/index.tsx

Lines changed: 24 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,28 @@
1-
import { useRef } from "react"
2-
import { useRouter } from "next/router"
1+
import { lazy, Suspense, useRef } from "react"
32
import { useTranslation } from "next-i18next"
4-
import { BsTranslate } from "react-icons/bs"
5-
import { MdBrightness2, MdWbSunny } from "react-icons/md"
6-
import {
7-
Box,
8-
Button,
9-
Flex,
10-
HStack,
11-
Icon,
12-
MenuButton,
13-
Text,
14-
useColorModeValue,
15-
useDisclosure,
16-
useEventListener,
17-
} from "@chakra-ui/react"
3+
import { Box, Flex, Hide, Show, useDisclosure } from "@chakra-ui/react"
184

19-
import { IconButton } from "@/components/Buttons"
205
import { EthHomeIcon } from "@/components/icons"
21-
import LanguagePicker from "@/components/LanguagePicker"
226
import { BaseLink } from "@/components/Link"
237
import Search from "@/components/Search"
248

259
import { isMobile } from "@/lib/utils/isMobile"
2610

27-
import { DESKTOP_LANGUAGE_BUTTON_NAME, NAV_PY } from "@/lib/constants"
11+
import { NAV_PY } from "@/lib/constants"
2812

13+
import DesktopNavMenu from "./Desktop"
2914
import Menu from "./Menu"
30-
import MobileNavMenu from "./Mobile"
3115
import { useNav } from "./useNav"
3216

17+
const MobileNavMenu = lazy(() => import("./Mobile"))
18+
3319
// TODO display page title on mobile
3420
const Nav = () => {
3521
const { toggleColorMode, linkSections, mobileNavProps } = useNav()
36-
const { locale } = useRouter()
3722
const { t } = useTranslation("common")
3823
const isDesktop = !isMobile()
3924
const searchModalDisclosure = useDisclosure()
4025
const navWrapperRef = useRef(null)
41-
const languagePickerState = useDisclosure()
42-
const languagePickerRef = useRef<HTMLButtonElement>(null)
43-
44-
/**
45-
* Adds a keydown event listener to toggle color mode (ctrl|cmd + \)
46-
* or open the language picker (\).
47-
* @param {string} event - The keydown event.
48-
*/
49-
useEventListener("keydown", (e) => {
50-
if (e.key !== "\\") return
51-
e.preventDefault()
52-
if (e.metaKey || e.ctrlKey) {
53-
toggleColorMode()
54-
} else {
55-
if (languagePickerState.isOpen) return
56-
languagePickerRef.current?.click()
57-
}
58-
})
59-
60-
const ThemeIcon = useColorModeValue(<MdBrightness2 />, <MdWbSunny />)
61-
const themeIconAriaLabel = useColorModeValue(
62-
"Switch to Dark Theme",
63-
"Switch to Light Theme"
64-
)
6526

6627
return (
6728
<Box position="sticky" top={0} zIndex="sticky" width="full">
@@ -102,83 +63,26 @@ const Nav = () => {
10263
{isDesktop && <Menu hideBelow="md" sections={linkSections} />}
10364

10465
<Flex alignItems="center" /* justifyContent="space-between" */>
105-
<Search {...searchModalDisclosure} />
10666
{/* Desktop */}
107-
{/* avoid rendering desktop LanguagePicker version on mobile */}
108-
{isDesktop && (
109-
<HStack hideBelow="md" gap="0">
110-
<IconButton
111-
transition="transform 0.5s, color 0.2s"
112-
icon={ThemeIcon}
113-
aria-label={themeIconAriaLabel}
114-
variant="ghost"
115-
isSecondary
116-
px={{ base: "2", xl: "3" }}
117-
_hover={{
118-
transform: "rotate(10deg)",
119-
color: "primary.hover",
120-
}}
121-
onClick={toggleColorMode}
122-
/>
123-
124-
{/* Locale-picker menu */}
125-
<LanguagePicker
126-
placement="bottom-end"
127-
minH="unset"
128-
maxH="75vh"
129-
w="xs"
130-
inset="unset"
131-
top="unset"
132-
menuState={languagePickerState}
133-
>
134-
<MenuButton
135-
as={Button}
136-
name={DESKTOP_LANGUAGE_BUTTON_NAME}
137-
ref={languagePickerRef}
138-
variant="ghost"
139-
color="body.base"
140-
transition="color 0.2s"
141-
px={{ base: "2", xl: "3" }}
142-
_hover={{
143-
color: "primary.hover",
144-
"& svg": {
145-
transform: "rotate(10deg)",
146-
transition: "transform 0.5s",
147-
},
148-
}}
149-
_active={{
150-
color: "primary.hover",
151-
bg: "primary.lowContrast",
152-
}}
153-
sx={{
154-
"& svg": {
155-
transform: "rotate(0deg)",
156-
transition: "transform 0.5s",
157-
},
158-
}}
159-
>
160-
<Icon
161-
as={BsTranslate}
162-
fontSize="2xl"
163-
verticalAlign="middle"
164-
me={2}
165-
/>
166-
<Text hideBelow="lg" as="span">
167-
{t("common:languages")}&nbsp;
168-
</Text>
169-
{locale!.toUpperCase()}
170-
</MenuButton>
171-
</LanguagePicker>
172-
</HStack>
173-
)}
67+
{/* avoid rendering desktop menu version on mobile */}
68+
<Show above="md">
69+
<Search {...searchModalDisclosure} />
70+
<DesktopNavMenu toggleColorMode={toggleColorMode} />
71+
</Show>
17472

175-
<MobileNavMenu
176-
{...mobileNavProps}
177-
linkSections={linkSections}
178-
hideFrom="md"
179-
toggleSearch={searchModalDisclosure.onOpen}
180-
drawerContainerRef={navWrapperRef}
181-
/>
73+
<Hide above="md">
74+
{/* Mobile */}
75+
{/* use Suspense to display the Search & the Menu at the same time */}
76+
<Suspense>
77+
<Search {...searchModalDisclosure} />
78+
<MobileNavMenu
79+
{...mobileNavProps}
80+
linkSections={linkSections}
81+
toggleSearch={searchModalDisclosure.onOpen}
82+
drawerContainerRef={navWrapperRef}
83+
/>
84+
</Suspense>
85+
</Hide>
18286
</Flex>
18387
</Flex>
18488
</Flex>

0 commit comments

Comments
 (0)