Skip to content

Commit f1624c1

Browse files
authored
Merge pull request #16694 from ethereum/find-wallets-optimizations
perf: optimize ProductTable filter rendering to reduce INP on mobile
2 parents f4950e7 + 8a55e90 commit f1624c1

File tree

11 files changed

+511
-209
lines changed

11 files changed

+511
-209
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"@radix-ui/react-compose-refs": "^1.1.0",
4343
"@radix-ui/react-dialog": "^1.1.1",
4444
"@radix-ui/react-dropdown-menu": "^2.1.1",
45+
"@radix-ui/react-focus-scope": "^1.1.8",
4546
"@radix-ui/react-navigation-menu": "^1.2.0",
4647
"@radix-ui/react-popover": "^1.1.1",
4748
"@radix-ui/react-portal": "^1.1.1",

pnpm-lock.yaml

Lines changed: 65 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"use client"
2+
3+
import * as React from "react"
4+
5+
import { PersistentPanel } from "@/components/ui/persistent-panel"
6+
import { Sheet, SheetTrigger } from "@/components/ui/sheet"
7+
8+
import { cn } from "@/lib/utils/cn"
9+
10+
import HamburgerButton from "./HamburgerButton"
11+
12+
import { useCloseOnNavigate } from "@/hooks/useCloseOnNavigate"
13+
14+
type MobileMenuClientProps = {
15+
className?: string
16+
side: "left" | "right"
17+
children: React.ReactNode
18+
}
19+
20+
const MobileMenuClient = ({
21+
className,
22+
side,
23+
children,
24+
}: MobileMenuClientProps) => {
25+
const [open, setOpen] = useCloseOnNavigate()
26+
const triggerRef = React.useRef<HTMLButtonElement>(null)
27+
28+
return (
29+
<Sheet open={open} onOpenChange={setOpen}>
30+
<SheetTrigger asChild>
31+
<HamburgerButton
32+
ref={triggerRef}
33+
className={cn("-me-2", className)}
34+
isMenuOpen={open}
35+
/>
36+
</SheetTrigger>
37+
38+
<PersistentPanel
39+
open={open}
40+
side={side}
41+
className="flex flex-col"
42+
onOpenChange={setOpen}
43+
triggerRef={triggerRef}
44+
>
45+
{children}
46+
</PersistentPanel>
47+
</Sheet>
48+
)
49+
}
50+
51+
export default MobileMenuClient

src/components/Nav/MobileMenu/index.tsx

Lines changed: 53 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,7 @@ import {
1212
CollapsibleContent,
1313
CollapsibleTrigger,
1414
} from "@/components/ui/collapsible"
15-
import {
16-
SheetContent,
17-
SheetFooter,
18-
SheetHeader,
19-
SheetTrigger,
20-
} from "@/components/ui/sheet"
21-
import { SheetCloseOnNavigate } from "@/components/ui/sheet-close-on-navigate"
15+
import { SheetFooter, SheetHeader } from "@/components/ui/sheet"
2216

2317
import { cn } from "@/lib/utils/cn"
2418
import { isLangRightToLeft } from "@/lib/utils/translations"
@@ -28,8 +22,8 @@ import { MOBILE_LANGUAGE_BUTTON_NAME, SECTION_LABELS } from "@/lib/constants"
2822

2923
import FooterButton from "./FooterButton"
3024
import FooterItemText from "./FooterItemText"
31-
import HamburgerButton from "./HamburgerButton"
3225
import MenuHeader from "./MenuHeader"
26+
import MobileMenuClient from "./MobileMenuClient"
3327
import ThemeToggleFooterButton from "./ThemeToggleFooterButton"
3428

3529
import { getLanguagesDisplayInfo, getNavigation } from "@/lib/nav/links"
@@ -49,73 +43,59 @@ export default async function MobileMenu({
4943
const dir = isRtl ? "rtl" : "ltr"
5044

5145
return (
52-
<SheetCloseOnNavigate>
53-
<SheetTrigger className={className} asChild>
54-
<HamburgerButton
55-
className={cn("-me-2", className)}
56-
isMenuOpen={false}
57-
{...props}
58-
/>
59-
</SheetTrigger>
60-
<SheetContent
61-
side={side}
62-
hideOverlay
63-
className="flex flex-col"
64-
aria-describedby=""
46+
<MobileMenuClient className={className} side={side} {...props}>
47+
<SheetHeader>
48+
<MenuHeader />
49+
</SheetHeader>
50+
51+
<TabsPrimitive.Root
52+
dir={dir}
53+
defaultValue="navigation"
54+
className="flex min-h-0 flex-1 flex-col"
6555
>
66-
<SheetHeader>
67-
<MenuHeader />
68-
</SheetHeader>
69-
70-
<TabsPrimitive.Root
71-
dir={dir}
72-
defaultValue="navigation"
73-
className="flex min-h-0 flex-1 flex-col"
56+
<TabsPrimitive.Content
57+
value="navigation"
58+
className="mt-0 hidden min-h-0 flex-1 flex-col border-none p-0 data-[state=active]:flex"
7459
>
75-
<TabsPrimitive.Content
76-
value="navigation"
77-
className="mt-0 hidden min-h-0 flex-1 flex-col border-none p-0 data-[state=active]:flex"
78-
>
79-
<NavigationContent className="flex-1 overflow-y-auto" />
80-
</TabsPrimitive.Content>
81-
<TabsPrimitive.Content
82-
value="languages"
83-
className="mt-0 hidden min-h-0 flex-1 flex-col border-none p-0 data-[state=active]:flex"
84-
>
85-
<LanguageContent className="flex min-h-0 flex-1 flex-col" />
86-
</TabsPrimitive.Content>
87-
88-
<SheetFooter className="h-[108px] shrink-0 justify-center border-t border-body-light px-4 py-0">
89-
<TabsPrimitive.List className="grid h-auto w-full grid-cols-3">
90-
<div className="flex flex-col items-center gap-1 py-2">
91-
<TabsPrimitive.Trigger value="languages" asChild>
92-
<FooterButton
93-
icon={Languages}
94-
name={MOBILE_LANGUAGE_BUTTON_NAME}
95-
data-testid="mobile-menu-language-picker"
96-
>
97-
<FooterItemText>{t("languages")}</FooterItemText>
98-
</FooterButton>
99-
</TabsPrimitive.Trigger>
100-
</div>
101-
<div className="flex flex-col items-center gap-1 py-2">
102-
<ThemeToggleFooterButton />
103-
</div>
104-
<div className="flex flex-col items-center gap-1 py-2">
105-
<TabsPrimitive.Trigger value="navigation" asChild>
106-
<FooterButton
107-
icon={Menu}
108-
data-testid="mobile-menu-navigation-picker"
109-
>
110-
<FooterItemText>{t("menu")}</FooterItemText>
111-
</FooterButton>
112-
</TabsPrimitive.Trigger>
113-
</div>
114-
</TabsPrimitive.List>
115-
</SheetFooter>
116-
</TabsPrimitive.Root>
117-
</SheetContent>
118-
</SheetCloseOnNavigate>
60+
<NavigationContent className="flex-1 overflow-y-auto" />
61+
</TabsPrimitive.Content>
62+
<TabsPrimitive.Content
63+
value="languages"
64+
className="mt-0 hidden min-h-0 flex-1 flex-col border-none p-0 data-[state=active]:flex"
65+
>
66+
<LanguageContent className="flex min-h-0 flex-1 flex-col" />
67+
</TabsPrimitive.Content>
68+
69+
<SheetFooter className="h-[108px] shrink-0 justify-center border-t border-body-light px-4 py-0">
70+
<TabsPrimitive.List className="grid h-auto w-full grid-cols-3">
71+
<div className="flex flex-col items-center gap-1 py-2">
72+
<TabsPrimitive.Trigger value="languages" asChild>
73+
<FooterButton
74+
icon={Languages}
75+
name={MOBILE_LANGUAGE_BUTTON_NAME}
76+
data-testid="mobile-menu-language-picker"
77+
>
78+
<FooterItemText>{t("languages")}</FooterItemText>
79+
</FooterButton>
80+
</TabsPrimitive.Trigger>
81+
</div>
82+
<div className="flex flex-col items-center gap-1 py-2">
83+
<ThemeToggleFooterButton />
84+
</div>
85+
<div className="flex flex-col items-center gap-1 py-2">
86+
<TabsPrimitive.Trigger value="navigation" asChild>
87+
<FooterButton
88+
icon={Menu}
89+
data-testid="mobile-menu-navigation-picker"
90+
>
91+
<FooterItemText>{t("menu")}</FooterItemText>
92+
</FooterButton>
93+
</TabsPrimitive.Trigger>
94+
</div>
95+
</TabsPrimitive.List>
96+
</SheetFooter>
97+
</TabsPrimitive.Root>
98+
</MobileMenuClient>
11999
)
120100
}
121101

src/components/ProductTable/Filter.tsx

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,37 @@ interface FilterProps {
1414
onChange: (updatedFilter: FilterOption) => void
1515
}
1616

17+
const arePropsEqual = (prevProps: FilterProps, nextProps: FilterProps) => {
18+
if (prevProps.filterIndex !== nextProps.filterIndex) return false
19+
if (prevProps.filter.title !== nextProps.filter.title) return false
20+
if (prevProps.filter.showFilterOption !== nextProps.filter.showFilterOption)
21+
return false
22+
23+
const prevItems = prevProps.filter.items
24+
const nextItems = nextProps.filter.items
25+
26+
if (prevItems.length !== nextItems.length) return false
27+
28+
for (let i = 0; i < prevItems.length; i++) {
29+
const prevItem = prevItems[i]
30+
const nextItem = nextItems[i]
31+
32+
if (prevItem.filterKey !== nextItem.filterKey) return false
33+
if (prevItem.inputState !== nextItem.inputState) return false
34+
35+
if (prevItem.options.length !== nextItem.options.length) return false
36+
37+
for (let j = 0; j < prevItem.options.length; j++) {
38+
if (prevItem.options[j].filterKey !== nextItem.options[j].filterKey)
39+
return false
40+
if (prevItem.options[j].inputState !== nextItem.options[j].inputState)
41+
return false
42+
}
43+
}
44+
45+
return true
46+
}
47+
1748
const Filter = ({ filter, filterIndex, onChange }: FilterProps) => {
1849
const handleChange = (
1950
_: number,
@@ -110,4 +141,4 @@ const Filter = ({ filter, filterIndex, onChange }: FilterProps) => {
110141
)
111142
}
112143

113-
export default memo(Filter)
144+
export default memo(Filter, arePropsEqual)

0 commit comments

Comments
 (0)