Skip to content

Commit 1ea48c1

Browse files
committed
fix: replace HeadlessUI Dialog with native sidebar for mobile
HeadlessUI Dialog had SSR hydration issues with Remix that caused the mobile sidebar to not render when clicking the "Open sidebar" button. The FocusTrap warning indicated the Dialog was mounting but content wasn't rendering properly. Changes: - Replace HeadlessUI Dialog/Transition with native div implementation - Use pointer-events to control click behavior on overlay vs panel - Update E2E test to use getByRole for better element targeting Closes: rb-b8v.11
1 parent ff944ec commit 1ea48c1

2 files changed

Lines changed: 34 additions & 58 deletions

File tree

app/routes/dashboard.tsx

Lines changed: 31 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { Fragment, useState } from 'react'
2-
import { Dialog, Transition } from '@headlessui/react'
1+
import { useState } from 'react'
32
import {
43
HomeIcon,
54
CashIcon,
@@ -84,56 +83,32 @@ export default function Dashboard() {
8483
```
8584
*/}
8685
<div>
87-
<Transition.Root show={sidebarOpen} as={Fragment}>
88-
<Dialog
89-
as="div"
90-
className="fixed inset-0 flex z-40 lg:hidden"
91-
onClose={setSidebarOpen}
86+
{/* Mobile sidebar - native implementation to avoid HeadlessUI SSR issues */}
87+
{sidebarOpen && (
88+
<div
89+
className="fixed inset-0 z-40 lg:hidden"
90+
role="dialog"
91+
aria-modal="true"
9292
>
93-
<Transition.Child
94-
as={Fragment}
95-
enter="transition-opacity ease-linear duration-300"
96-
enterFrom="opacity-0"
97-
enterTo="opacity-100"
98-
leave="transition-opacity ease-linear duration-300"
99-
leaveFrom="opacity-100"
100-
leaveTo="opacity-0"
101-
>
102-
<Dialog.Overlay className="fixed inset-0 bg-gray-600 bg-opacity-75" />
103-
</Transition.Child>
104-
<Transition.Child
105-
as={Fragment}
106-
enter="transition ease-in-out duration-300 transform"
107-
enterFrom="-translate-x-full"
108-
enterTo="translate-x-0"
109-
leave="transition ease-in-out duration-300 transform"
110-
leaveFrom="translate-x-0"
111-
leaveTo="-translate-x-full"
112-
>
113-
<div className="relative flex-1 flex flex-col max-w-xs w-full bg-white">
114-
<Transition.Child
115-
as={Fragment}
116-
enter="ease-in-out duration-300"
117-
enterFrom="opacity-0"
118-
enterTo="opacity-100"
119-
leave="ease-in-out duration-300"
120-
leaveFrom="opacity-100"
121-
leaveTo="opacity-0"
122-
>
123-
<div className="absolute top-0 right-0 -mr-12 pt-2">
124-
<button
125-
type="button"
126-
className="ml-1 flex items-center justify-center h-10 w-10 rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white"
127-
onClick={() => setSidebarOpen(false)}
128-
>
129-
<span className="sr-only">Close sidebar</span>
130-
<XIcon
131-
className="h-6 w-6 text-white"
132-
aria-hidden="true"
133-
/>
134-
</button>
135-
</div>
136-
</Transition.Child>
93+
{/* Overlay */}
94+
<div
95+
className="fixed inset-0 bg-gray-600 bg-opacity-75 z-0"
96+
onClick={() => setSidebarOpen(false)}
97+
aria-hidden="true"
98+
/>
99+
{/* Sidebar panel */}
100+
<div className="fixed inset-0 flex z-10 pointer-events-none">
101+
<div className="relative flex-1 flex flex-col max-w-xs w-full bg-white pointer-events-auto">
102+
<div className="absolute top-0 right-0 -mr-12 pt-2">
103+
<button
104+
type="button"
105+
className="ml-1 flex items-center justify-center h-10 w-10 rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white"
106+
onClick={() => setSidebarOpen(false)}
107+
>
108+
<span className="sr-only">Close sidebar</span>
109+
<XIcon className="h-6 w-6 text-white" aria-hidden="true" />
110+
</button>
111+
</div>
137112
<div className="flex-1 h-0 pt-5 pb-4 overflow-y-auto">
138113
<LogoWithText />
139114
<nav className="mt-5 px-2 space-y-1">
@@ -207,12 +182,12 @@ export default function Dashboard() {
207182
</Link>
208183
</div>
209184
</div>
210-
</Transition.Child>
211-
<div className="shrink-0 w-14">
212-
{/* Force sidebar to shrink to fit close icon */}
185+
<div className="shrink-0 w-14" aria-hidden="true">
186+
{/* Force sidebar to shrink to fit close icon */}
187+
</div>
213188
</div>
214-
</Dialog>
215-
</Transition.Root>
189+
</div>
190+
)}
216191

217192
{/* Static sidebar for desktop */}
218193
<div className="hidden lg:flex lg:w-64 lg:flex-col lg:fixed lg:inset-y-0">

e2e/logout.spec.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ test('Logout', async ({ page, isMobile, queries: { getByRole } }) => {
1616
await openSidebar.click()
1717
}
1818

19-
// Click the last item with text=Keluar and wait for the redirect to the /profile page
19+
// Click the Keluar button and wait for the redirect to the homepage
20+
const keluarButton = await getByRole('button', { name: /keluar/i })
2021
await Promise.all([
2122
page.waitForNavigation(/*{ url: 'http://localhost:3000' }*/),
22-
page.click('text=Keluar >> nth=-1'),
23+
keluarButton.click(),
2324
])
2425

2526
// Expect text=Masuk to be visible and linking to the

0 commit comments

Comments
 (0)