Skip to content

Commit fe32b9b

Browse files
committed
extract DropdownMenu components similar to existing one
1 parent 0ab5627 commit fe32b9b

File tree

2 files changed

+79
-35
lines changed

2 files changed

+79
-35
lines changed

app/components/TopBar.tsx

Lines changed: 18 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,14 @@
55
*
66
* Copyright Oxide Computer Company
77
*/
8-
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'
9-
import cn from 'classnames'
108
import React from 'react'
11-
import { Link } from 'react-router-dom'
129

1310
import { navToLogin, useApiMutation } from '@oxide/api'
1411
import { DirectionDownIcon, Profile16Icon } from '@oxide/design-system/icons/react'
1512

1613
import { useCurrentUser } from '~/layouts/AuthenticatedLayout'
1714
import { buttonStyle } from '~/ui/lib/Button'
15+
import * as DropdownMenu from '~/ui/lib/DropdownMenu2'
1816
import { pb } from '~/util/path-builder'
1917

2018
export function TopBar({ children }: { children: React.ReactNode }) {
@@ -42,42 +40,27 @@ export function TopBar({ children }: { children: React.ReactNode }) {
4240
<div className="mx-3 flex h-[60px] shrink-0 items-center justify-between">
4341
<div className="flex items-center">{otherPickers}</div>
4442
<div className="flex items-center gap-2">
45-
<Menu>
46-
<MenuButton
47-
className={cn(
48-
'flex items-center gap-2',
49-
buttonStyle({ size: 'sm', variant: 'secondary' })
50-
)}
43+
<DropdownMenu.Root>
44+
<DropdownMenu.Trigger
45+
className={buttonStyle({ size: 'sm', variant: 'secondary' })}
5146
aria-label="User menu"
5247
>
53-
<Profile16Icon className="text-quaternary" />
54-
<span className="normal-case text-sans-md text-secondary">
55-
{me.displayName || 'User'}
48+
<span className="flex items-center gap-2">
49+
<Profile16Icon className="text-quaternary" />
50+
<span className="normal-case text-sans-md text-secondary">
51+
{me.displayName || 'User'}
52+
</span>
53+
<DirectionDownIcon className="!w-2.5" />
5654
</span>
57-
<DirectionDownIcon className="!w-2.5" />
58-
</MenuButton>
55+
</DropdownMenu.Trigger>
5956
{/* TODO: fix hover style + should be able to click anywhere in the menu item */}
60-
<MenuItems
61-
anchor="bottom end"
62-
className="DropdownMenuContent [--anchor-gap:8px]"
63-
>
64-
{/* TODO: extract Item and LinkItem components*/}
65-
<MenuItem>
66-
<Link className="DropdownMenuItem ox-menu-item" to={pb.profile()}>
67-
Settings
68-
</Link>
69-
</MenuItem>
70-
<MenuItem>
71-
<button
72-
type="button"
73-
onClick={() => logout.mutate({})}
74-
className="DropdownMenuItem ox-menu-item"
75-
>
76-
Sign out
77-
</button>
78-
</MenuItem>
79-
</MenuItems>
80-
</Menu>
57+
<DropdownMenu.Items>
58+
<DropdownMenu.LinkItem to={pb.profile()}>Settings</DropdownMenu.LinkItem>
59+
<DropdownMenu.Item onSelect={() => logout.mutate({})}>
60+
Sign out
61+
</DropdownMenu.Item>
62+
</DropdownMenu.Items>
63+
</DropdownMenu.Root>
8164
</div>
8265
</div>
8366
</div>

app/ui/lib/DropdownMenu2.tsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
5+
*
6+
* Copyright Oxide Computer Company
7+
*/
8+
9+
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'
10+
import cn from 'classnames'
11+
import { forwardRef, type ForwardedRef, type ReactNode } from 'react'
12+
import { Link } from 'react-router-dom'
13+
14+
export const Root = Menu
15+
16+
export const Trigger = MenuButton
17+
18+
export function Items({ children }: { children: ReactNode }) {
19+
return (
20+
<MenuItems
21+
anchor="bottom end"
22+
className="DropdownMenuContent [--anchor-gap:8px]"
23+
// necessary to turn off scroll locking so the scrollbar doesn't pop in
24+
// and out as menu closes and opens
25+
modal={false}
26+
>
27+
{children}
28+
</MenuItems>
29+
)
30+
}
31+
32+
type LinkItemProps = { className?: string; to: string; children: ReactNode }
33+
34+
export function LinkItem({ className, to, children }: LinkItemProps) {
35+
return (
36+
<MenuItem>
37+
<Link className={cn('DropdownMenuItem ox-menu-item', className)} to={to}>
38+
{children}
39+
</Link>
40+
</MenuItem>
41+
)
42+
}
43+
44+
type ButtonRef = ForwardedRef<HTMLButtonElement>
45+
type ItemProps = { className?: string; onSelect: () => void; children: ReactNode }
46+
47+
// need to forward ref because of tooltips on disabled menu buttons
48+
export const Item = forwardRef(
49+
({ className, onSelect, children }: ItemProps, ref: ButtonRef) => (
50+
<MenuItem>
51+
<button
52+
type="button"
53+
className={cn('DropdownMenuItem ox-menu-item', className)}
54+
ref={ref}
55+
onClick={onSelect}
56+
>
57+
{children}
58+
</button>
59+
</MenuItem>
60+
)
61+
)

0 commit comments

Comments
 (0)