Use Headless UI v2 data attributes and anchor positioning for UserMenu and ProfileTabs

This commit is contained in:
Aaron Po
2026-09-12 18:18:38 -04:00
parent 5449b284cb
commit 81717eddbf
3 changed files with 49 additions and 44 deletions

View File

@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { expect, userEvent, within } from 'storybook/test';
import { expect, screen, userEvent, within } from 'storybook/test';
import Navbar from './Navbar';
const navbarDescription = `Top-level navigation for the Biergarten website. These stories cover guest, authenticated, and mobile states so you can review branding, route visibility, account menu behavior, and responsive collapse without leaving Storybook.`;
@@ -47,9 +47,10 @@ export const Authenticated: Story = {
const userButton = canvas.getByRole('button', { name: /hans/i });
await expect(userButton).toBeInTheDocument();
await userEvent.click(userButton);
await expect(canvas.getByRole('menuitem', { name: /my profile/i })).toBeInTheDocument();
await expect(canvas.getByRole('menuitem', { name: /dashboard/i })).toBeInTheDocument();
await expect(canvas.getByRole('menuitem', { name: /logout/i })).toBeInTheDocument();
// MenuItems is anchor-positioned, which portals it outside canvasElement.
await expect(screen.getByRole('menuitem', { name: /my profile/i })).toBeInTheDocument();
await expect(screen.getByRole('menuitem', { name: /dashboard/i })).toBeInTheDocument();
await expect(screen.getByRole('menuitem', { name: /logout/i })).toBeInTheDocument();
},
};

View File

@@ -7,29 +7,36 @@ interface UserMenuProps {
}
const UserMenu = ({ username, userAccountId }: UserMenuProps) => (
<Menu as="div" className="relative">
<Menu>
<MenuButton className="btn btn-ghost btn-sm">{username}</MenuButton>
<MenuItems className="menu absolute right-0 z-60 mt-2 w-52 rounded-box border border-base-300 bg-base-100 p-2 shadow-xl focus:outline-none">
<MenuItems
anchor="bottom end"
transition
className="menu z-60 w-52 rounded-box border border-base-300 bg-base-100 p-2 shadow-xl [--anchor-gap:8px] focus:outline-none transition duration-100 ease-out data-[closed]:scale-95 data-[closed]:opacity-0"
>
<MenuItem>
{({ focus }) => (
<Link to={`/users/${userAccountId}`} className={focus ? 'active' : ''}>
My profile
</Link>
)}
<Link
to={`/users/${userAccountId}`}
className="rounded-field block px-3 py-1.5 data-[focus]:bg-base-200"
>
My profile
</Link>
</MenuItem>
<MenuItem>
{({ focus }) => (
<Link to="/dashboard" className={focus ? 'active' : ''}>
Dashboard
</Link>
)}
<Link
to="/dashboard"
className="rounded-field block px-3 py-1.5 data-[focus]:bg-base-200"
>
Dashboard
</Link>
</MenuItem>
<MenuItem>
{({ focus }) => (
<Link to="/logout" className={focus ? 'active' : ''}>
Logout
</Link>
)}
<Link
to="/logout"
className="rounded-field block px-3 py-1.5 data-[focus]:bg-base-200"
>
Logout
</Link>
</MenuItem>
</MenuItems>
</Menu>