Skip to content

Commit dd62709

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

File tree

2 files changed

+91
-38
lines changed

2 files changed

+91
-38
lines changed

app/components/TopBar.tsx

Lines changed: 17 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,13 @@
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'
17-
import { buttonStyle } from '~/ui/lib/Button'
14+
import * as DropdownMenu from '~/ui/lib/DropdownMenu2'
1815
import { pb } from '~/util/path-builder'
1916

2017
export function TopBar({ children }: { children: React.ReactNode }) {
@@ -42,42 +39,24 @@ export function TopBar({ children }: { children: React.ReactNode }) {
4239
<div className="mx-3 flex h-[60px] shrink-0 items-center justify-between">
4340
<div className="flex items-center">{otherPickers}</div>
4441
<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-
)}
51-
aria-label="User menu"
52-
>
53-
<Profile16Icon className="text-quaternary" />
54-
<span className="normal-case text-sans-md text-secondary">
55-
{me.displayName || 'User'}
42+
<DropdownMenu.Root>
43+
<DropdownMenu.Trigger aria-label="User menu">
44+
<span className="flex items-center gap-2">
45+
<Profile16Icon className="text-quaternary" />
46+
<span className="normal-case text-sans-md text-secondary">
47+
{me.displayName || 'User'}
48+
</span>
49+
<DirectionDownIcon className="!w-2.5" />
5650
</span>
57-
<DirectionDownIcon className="!w-2.5" />
58-
</MenuButton>
51+
</DropdownMenu.Trigger>
5952
{/* 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>
53+
<DropdownMenu.Items>
54+
<DropdownMenu.LinkItem to={pb.profile()}>Settings</DropdownMenu.LinkItem>
55+
<DropdownMenu.Item onSelect={() => logout.mutate({})}>
56+
Sign out
57+
</DropdownMenu.Item>
58+
</DropdownMenu.Items>
59+
</DropdownMenu.Root>
8160
</div>
8261
</div>
8362
</div>

app/ui/lib/DropdownMenu2.tsx

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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 {
10+
Menu,
11+
MenuButton,
12+
MenuItem,
13+
MenuItems,
14+
type MenuButtonProps,
15+
} from '@headlessui/react'
16+
import cn from 'classnames'
17+
import { forwardRef, type ForwardedRef, type ReactNode } from 'react'
18+
import { Link } from 'react-router-dom'
19+
20+
import { buttonStyle } from './Button'
21+
22+
export const Root = Menu
23+
24+
export const Trigger = ({ className, ...props }: MenuButtonProps) => (
25+
<MenuButton
26+
className={cn(buttonStyle({ size: 'sm', variant: 'secondary' }), className)}
27+
{...props}
28+
/>
29+
)
30+
31+
export function Items({ children }: { children: ReactNode }) {
32+
return (
33+
<MenuItems
34+
anchor="bottom end"
35+
className="DropdownMenuContent [--anchor-gap:8px]"
36+
// necessary to turn off scroll locking so the scrollbar doesn't pop in
37+
// and out as menu closes and opens
38+
modal={false}
39+
>
40+
{children}
41+
</MenuItems>
42+
)
43+
}
44+
45+
type LinkItemProps = { className?: string; to: string; children: ReactNode }
46+
47+
export function LinkItem({ className, to, children }: LinkItemProps) {
48+
return (
49+
<MenuItem>
50+
<Link className={cn('DropdownMenuItem ox-menu-item', className)} to={to}>
51+
{children}
52+
</Link>
53+
</MenuItem>
54+
)
55+
}
56+
57+
type ButtonRef = ForwardedRef<HTMLButtonElement>
58+
type ItemProps = { className?: string; onSelect: () => void; children: ReactNode }
59+
60+
// need to forward ref because of tooltips on disabled menu buttons
61+
export const Item = forwardRef(
62+
({ className, onSelect, children }: ItemProps, ref: ButtonRef) => (
63+
<MenuItem>
64+
<button
65+
type="button"
66+
className={cn('DropdownMenuItem ox-menu-item', className)}
67+
ref={ref}
68+
onClick={onSelect}
69+
>
70+
{children}
71+
</button>
72+
</MenuItem>
73+
)
74+
)

0 commit comments

Comments
 (0)