Skip to content

Commit eaa6d60

Browse files
rajeshgRajesh Gollapudikody
authored
fix: layout shift caused by Dropdownmenu and make it to a separate component (#906)
Co-authored-by: Rajesh Gollapudi <[email protected]> Co-authored-by: kody <[email protected]>
1 parent 83f5a69 commit eaa6d60

File tree

3 files changed

+80
-76
lines changed

3 files changed

+80
-76
lines changed

app/components/user-dropdown.tsx

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { useRef } from 'react'
2+
import { useSubmit, Link, Form } from 'react-router'
3+
import { getUserImgSrc } from '#app/utils/misc.tsx'
4+
import { useUser } from '#app/utils/user.ts'
5+
import { Button } from './ui/button'
6+
import {
7+
DropdownMenu,
8+
DropdownMenuTrigger,
9+
DropdownMenuPortal,
10+
DropdownMenuContent,
11+
DropdownMenuItem,
12+
} from './ui/dropdown-menu'
13+
import { Icon } from './ui/icon'
14+
15+
export function UserDropdown() {
16+
const user = useUser()
17+
const submit = useSubmit()
18+
const formRef = useRef<HTMLFormElement>(null)
19+
return (
20+
<DropdownMenu modal={false}>
21+
<DropdownMenuTrigger asChild>
22+
<Button asChild variant="secondary">
23+
<Link
24+
to={`/users/${user.username}`}
25+
// this is for progressive enhancement
26+
onClick={(e) => e.preventDefault()}
27+
className="flex items-center gap-2"
28+
>
29+
<img
30+
className="h-8 w-8 rounded-full object-cover"
31+
alt={user.name ?? user.username}
32+
src={getUserImgSrc(user.image?.id)}
33+
/>
34+
<span className="text-body-sm font-bold">
35+
{user.name ?? user.username}
36+
</span>
37+
</Link>
38+
</Button>
39+
</DropdownMenuTrigger>
40+
<DropdownMenuPortal>
41+
<DropdownMenuContent sideOffset={8} align="end">
42+
<DropdownMenuItem asChild>
43+
<Link prefetch="intent" to={`/users/${user.username}`}>
44+
<Icon className="text-body-md" name="avatar">
45+
Profile
46+
</Icon>
47+
</Link>
48+
</DropdownMenuItem>
49+
<DropdownMenuItem asChild>
50+
<Link prefetch="intent" to={`/users/${user.username}/notes`}>
51+
<Icon className="text-body-md" name="pencil-2">
52+
Notes
53+
</Icon>
54+
</Link>
55+
</DropdownMenuItem>
56+
<DropdownMenuItem
57+
asChild
58+
// this prevents the menu from closing before the form submission is completed
59+
onSelect={async (event) => {
60+
event.preventDefault()
61+
await submit(formRef.current)
62+
}}
63+
>
64+
<Form action="/logout" method="POST" ref={formRef}>
65+
<Icon className="text-body-md" name="exit">
66+
<button type="submit">Logout</button>
67+
</Icon>
68+
</Form>
69+
</DropdownMenuItem>
70+
</DropdownMenuContent>
71+
</DropdownMenuPortal>
72+
</DropdownMenu>
73+
)
74+
}

app/root.tsx

Lines changed: 4 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import { useRef } from 'react'
21
import {
32
data,
4-
Form,
53
Link,
64
Links,
75
Meta,
@@ -10,7 +8,6 @@ import {
108
ScrollRestoration,
119
useLoaderData,
1210
useMatches,
13-
useSubmit,
1411
} from 'react-router'
1512
import { HoneypotProvider } from 'remix-utils/honeypot/react'
1613
import { type Route } from './+types/root.ts'
@@ -21,15 +18,9 @@ import { EpicProgress } from './components/progress-bar.tsx'
2118
import { SearchBar } from './components/search-bar.tsx'
2219
import { useToast } from './components/toaster.tsx'
2320
import { Button } from './components/ui/button.tsx'
24-
import {
25-
DropdownMenu,
26-
DropdownMenuContent,
27-
DropdownMenuItem,
28-
DropdownMenuPortal,
29-
DropdownMenuTrigger,
30-
} from './components/ui/dropdown-menu.tsx'
31-
import { Icon, href as iconsHref } from './components/ui/icon.tsx'
21+
import { href as iconsHref } from './components/ui/icon.tsx'
3222
import { EpicToaster } from './components/ui/sonner.tsx'
23+
import { UserDropdown } from './components/user-dropdown.tsx'
3324
import {
3425
ThemeSwitch,
3526
useOptionalTheme,
@@ -42,12 +33,12 @@ import { prisma } from './utils/db.server.ts'
4233
import { getEnv } from './utils/env.server.ts'
4334
import { pipeHeaders } from './utils/headers.server.ts'
4435
import { honeypot } from './utils/honeypot.server.ts'
45-
import { combineHeaders, getDomainUrl, getUserImgSrc } from './utils/misc.tsx'
36+
import { combineHeaders, getDomainUrl } from './utils/misc.tsx'
4637
import { useNonce } from './utils/nonce-provider.ts'
4738
import { type Theme, getTheme } from './utils/theme.server.ts'
4839
import { makeTimings, time } from './utils/timing.server.ts'
4940
import { getToast } from './utils/toast.server.ts'
50-
import { useOptionalUser, useUser } from './utils/user.ts'
41+
import { useOptionalUser } from './utils/user.ts'
5142

5243
export const links: Route.LinksFunction = () => {
5344
return [
@@ -264,67 +255,6 @@ function AppWithProviders() {
264255

265256
export default AppWithProviders
266257

267-
function UserDropdown() {
268-
const user = useUser()
269-
const submit = useSubmit()
270-
const formRef = useRef<HTMLFormElement>(null)
271-
return (
272-
<DropdownMenu>
273-
<DropdownMenuTrigger asChild>
274-
<Button asChild variant="secondary">
275-
<Link
276-
to={`/users/${user.username}`}
277-
// this is for progressive enhancement
278-
onClick={(e) => e.preventDefault()}
279-
className="flex items-center gap-2"
280-
>
281-
<img
282-
className="h-8 w-8 rounded-full object-cover"
283-
alt={user.name ?? user.username}
284-
src={getUserImgSrc(user.image?.id)}
285-
/>
286-
<span className="text-body-sm font-bold">
287-
{user.name ?? user.username}
288-
</span>
289-
</Link>
290-
</Button>
291-
</DropdownMenuTrigger>
292-
<DropdownMenuPortal>
293-
<DropdownMenuContent sideOffset={8} align="start">
294-
<DropdownMenuItem asChild>
295-
<Link prefetch="intent" to={`/users/${user.username}`}>
296-
<Icon className="text-body-md" name="avatar">
297-
Profile
298-
</Icon>
299-
</Link>
300-
</DropdownMenuItem>
301-
<DropdownMenuItem asChild>
302-
<Link prefetch="intent" to={`/users/${user.username}/notes`}>
303-
<Icon className="text-body-md" name="pencil-2">
304-
Notes
305-
</Icon>
306-
</Link>
307-
</DropdownMenuItem>
308-
<DropdownMenuItem
309-
asChild
310-
// this prevents the menu from closing before the form submission is completed
311-
onSelect={async (event) => {
312-
event.preventDefault()
313-
await submit(formRef.current)
314-
}}
315-
>
316-
<Form action="/logout" method="POST" ref={formRef}>
317-
<Icon className="text-body-md" name="exit">
318-
<button type="submit">Logout</button>
319-
</Icon>
320-
</Form>
321-
</DropdownMenuItem>
322-
</DropdownMenuContent>
323-
</DropdownMenuPortal>
324-
</DropdownMenu>
325-
)
326-
}
327-
328258
// this is a last resort error boundary. There's not much useful information we
329259
// can offer at this level.
330260
export const ErrorBoundary = GeneralErrorBoundary

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
"sideEffects": false,
55
"license": "MIT",
66
"epic-stack": {
7-
"head": "5c5328b38e442d78933d5be8fae21fe317296c29",
8-
"date": "2025-01-17T19:16:26Z"
7+
"head": "8468b0101fbee9d09f7ea426eaefec74583641b4",
8+
"date": "2025-01-17T22:03:13Z"
99
},
1010
"author": "Kent C. Dodds <[email protected]> (https://kentcdodds.com/)",
1111
"type": "module",

0 commit comments

Comments
 (0)