Skip to content

Commit

Permalink
fix: layout shift caused by Dropdownmenu and make it to a separate co…
Browse files Browse the repository at this point in the history
…mponent
  • Loading branch information
Rajesh Gollapudi committed Jan 17, 2025
1 parent 83f5a69 commit 8468b01
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 74 deletions.
74 changes: 74 additions & 0 deletions app/components/user-dropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { useRef } from 'react'
import { useSubmit, Link, Form } from 'react-router'
import { getUserImgSrc } from '#app/utils/misc.tsx'
import { useUser } from '#app/utils/user.ts'
import { Button } from './ui/button'
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuPortal,
DropdownMenuContent,
DropdownMenuItem,
} from './ui/dropdown-menu'
import { Icon } from './ui/icon'

export function UserDropdown() {
const user = useUser()
const submit = useSubmit()
const formRef = useRef<HTMLFormElement>(null)
return (
<DropdownMenu modal={false}>
<DropdownMenuTrigger asChild>
<Button asChild variant="secondary">
<Link
to={`/users/${user.username}`}
// this is for progressive enhancement
onClick={(e) => e.preventDefault()}
className="flex items-center gap-2"
>
<img
className="h-8 w-8 rounded-full object-cover"
alt={user.name ?? user.username}
src={getUserImgSrc(user.image?.id)}
/>
<span className="text-body-sm font-bold">
{user.name ?? user.username}
</span>
</Link>
</Button>
</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent sideOffset={8} align="end">
<DropdownMenuItem asChild>
<Link prefetch="intent" to={`/users/${user.username}`}>
<Icon className="text-body-md" name="avatar">
Profile
</Icon>
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link prefetch="intent" to={`/users/${user.username}/notes`}>
<Icon className="text-body-md" name="pencil-2">
Notes
</Icon>
</Link>
</DropdownMenuItem>
<DropdownMenuItem
asChild
// this prevents the menu from closing before the form submission is completed
onSelect={async (event) => {
event.preventDefault()
await submit(formRef.current)
}}
>
<Form action="/logout" method="POST" ref={formRef}>
<Icon className="text-body-md" name="exit">
<button type="submit">Logout</button>
</Icon>
</Form>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenuPortal>
</DropdownMenu>
)
}
78 changes: 4 additions & 74 deletions app/root.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { useRef } from 'react'
import {
data,
Form,
Link,
Links,
Meta,
Expand All @@ -10,7 +8,6 @@ import {
ScrollRestoration,
useLoaderData,
useMatches,
useSubmit,
} from 'react-router'
import { HoneypotProvider } from 'remix-utils/honeypot/react'
import { type Route } from './+types/root.ts'
Expand All @@ -21,15 +18,9 @@ import { EpicProgress } from './components/progress-bar.tsx'
import { SearchBar } from './components/search-bar.tsx'
import { useToast } from './components/toaster.tsx'
import { Button } from './components/ui/button.tsx'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuPortal,
DropdownMenuTrigger,
} from './components/ui/dropdown-menu.tsx'
import { Icon, href as iconsHref } from './components/ui/icon.tsx'
import { href as iconsHref } from './components/ui/icon.tsx'
import { EpicToaster } from './components/ui/sonner.tsx'
import { UserDropdown } from './components/user-dropdown.tsx'
import {
ThemeSwitch,
useOptionalTheme,
Expand All @@ -42,12 +33,12 @@ import { prisma } from './utils/db.server.ts'
import { getEnv } from './utils/env.server.ts'
import { pipeHeaders } from './utils/headers.server.ts'
import { honeypot } from './utils/honeypot.server.ts'
import { combineHeaders, getDomainUrl, getUserImgSrc } from './utils/misc.tsx'
import { combineHeaders, getDomainUrl } from './utils/misc.tsx'
import { useNonce } from './utils/nonce-provider.ts'
import { type Theme, getTheme } from './utils/theme.server.ts'
import { makeTimings, time } from './utils/timing.server.ts'
import { getToast } from './utils/toast.server.ts'
import { useOptionalUser, useUser } from './utils/user.ts'
import { useOptionalUser } from './utils/user.ts'

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

export default AppWithProviders

function UserDropdown() {
const user = useUser()
const submit = useSubmit()
const formRef = useRef<HTMLFormElement>(null)
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button asChild variant="secondary">
<Link
to={`/users/${user.username}`}
// this is for progressive enhancement
onClick={(e) => e.preventDefault()}
className="flex items-center gap-2"
>
<img
className="h-8 w-8 rounded-full object-cover"
alt={user.name ?? user.username}
src={getUserImgSrc(user.image?.id)}
/>
<span className="text-body-sm font-bold">
{user.name ?? user.username}
</span>
</Link>
</Button>
</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent sideOffset={8} align="start">
<DropdownMenuItem asChild>
<Link prefetch="intent" to={`/users/${user.username}`}>
<Icon className="text-body-md" name="avatar">
Profile
</Icon>
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link prefetch="intent" to={`/users/${user.username}/notes`}>
<Icon className="text-body-md" name="pencil-2">
Notes
</Icon>
</Link>
</DropdownMenuItem>
<DropdownMenuItem
asChild
// this prevents the menu from closing before the form submission is completed
onSelect={async (event) => {
event.preventDefault()
await submit(formRef.current)
}}
>
<Form action="/logout" method="POST" ref={formRef}>
<Icon className="text-body-md" name="exit">
<button type="submit">Logout</button>
</Icon>
</Form>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenuPortal>
</DropdownMenu>
)
}

// this is a last resort error boundary. There's not much useful information we
// can offer at this level.
export const ErrorBoundary = GeneralErrorBoundary

0 comments on commit 8468b01

Please sign in to comment.