Skip to content

Commit 39b4491

Browse files
authored
Bump headlessui to 2.0 (#2230)
* bump headless to 2.0 and update DocsPopover * I think I fixed Listbox * fix instance create e2e * give up, use modal=false on ListboxOptions
1 parent e4e912c commit 39b4491

File tree

6 files changed

+110
-104
lines changed

6 files changed

+110
-104
lines changed

app/components/DocsPopover.tsx

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
* Copyright Oxide Computer Company
77
*/
88

9-
import { autoUpdate, offset, useFloating } from '@floating-ui/react'
10-
import { Popover } from '@headlessui/react'
9+
import { Popover, PopoverButton, PopoverPanel } from '@headlessui/react'
1110
import cn from 'classnames'
1211

1312
import { OpenLink12Icon, Question12Icon } from '@oxide/design-system/icons/react'
@@ -43,27 +42,15 @@ type DocsPopoverProps = {
4342
}
4443

4544
export const DocsPopover = ({ heading, icon, summary, links }: DocsPopoverProps) => {
46-
const { refs, floatingStyles } = useFloating({
47-
placement: 'bottom-end',
48-
middleware: [offset(12)],
49-
whileElementsMounted: autoUpdate,
50-
// Needs to be off because it breaks the enter animation
51-
// https://floating-ui.com/docs/usefloating#transform
52-
transform: false,
53-
})
5445
return (
5546
<Popover>
56-
<Popover.Button
57-
ref={refs.setReference}
58-
className={cn(buttonStyle({ size: 'sm', variant: 'ghost' }), 'w-9')}
59-
>
47+
<PopoverButton className={cn(buttonStyle({ size: 'sm', variant: 'ghost' }), 'w-9')}>
6048
<Question12Icon aria-label="Links to docs" className="shrink-0" />
61-
</Popover.Button>
62-
<Popover.Panel
49+
</PopoverButton>
50+
<PopoverPanel
6351
// DocsPopoverPanel needed for enter animation
6452
className="DocsPopoverPanel z-10 w-96 rounded-lg border bg-raise border-secondary elevation-1"
65-
ref={refs.setFloating}
66-
style={floatingStyles}
53+
anchor={{ to: 'bottom end', gap: 12 }}
6754
>
6855
<div className="px-4">
6956
<h2 className="mt-4 flex items-center gap-1 text-sans-md">
@@ -78,7 +65,7 @@ export const DocsPopover = ({ heading, icon, summary, links }: DocsPopoverProps)
7865
<DocsPopoverLink key={link.href} {...link} />
7966
))}
8067
</div>
81-
</Popover.Panel>
68+
</PopoverPanel>
8269
</Popover>
8370
)
8471
}

app/ui/lib/Listbox.tsx

Lines changed: 45 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,12 @@
66
* Copyright Oxide Computer Company
77
*/
88
import {
9-
autoUpdate,
10-
flip,
11-
FloatingPortal,
12-
offset,
13-
size,
14-
useFloating,
15-
} from '@floating-ui/react'
16-
import { Listbox as Select } from '@headlessui/react'
9+
Listbox as HListbox,
10+
Label,
11+
ListboxButton,
12+
ListboxOption,
13+
ListboxOptions,
14+
} from '@headlessui/react'
1715
import cn from 'classnames'
1816
import type { ReactNode } from 'react'
1917

@@ -61,29 +59,14 @@ export const Listbox = <Value extends string = string>({
6159
isLoading = false,
6260
...props
6361
}: ListboxProps<Value>) => {
64-
const { refs, floatingStyles } = useFloating({
65-
middleware: [
66-
offset(12),
67-
flip(),
68-
size({
69-
apply({ rects, elements }) {
70-
Object.assign(elements.floating.style, {
71-
width: `${rects.reference.width}px`,
72-
})
73-
},
74-
}),
75-
],
76-
whileElementsMounted: autoUpdate,
77-
})
78-
7962
const selectedItem = selected && items.find((i) => i.value === selected)
8063
const noItems = !isLoading && items.length === 0
8164
const isDisabled = disabled || noItems
8265
const zIndex = usePopoverZIndex()
8366

8467
return (
8568
<div className={cn('relative', className)}>
86-
<Select
69+
<HListbox
8770
value={selected}
8871
// you shouldn't ever be able to select null, but we check here anyway
8972
// to make TS happy so the calling code doesn't have to. note `val !==
@@ -96,14 +79,13 @@ export const Listbox = <Value extends string = string>({
9679
{label && (
9780
<div className="mb-2">
9881
<FieldLabel id={``} as="div" tip={tooltipText} optional={!required}>
99-
<Select.Label>{label}</Select.Label>
82+
<Label>{label}</Label>
10083
</FieldLabel>
10184
{description && <TextInputHint id={``}>{description}</TextInputHint>}
10285
</div>
10386
)}
104-
<Select.Button
87+
<ListboxButton
10588
name={name}
106-
ref={refs.setReference}
10789
className={cn(
10890
`flex h-10 w-full items-center justify-between
10991
rounded border text-sans-md`,
@@ -136,37 +118,44 @@ export const Listbox = <Value extends string = string>({
136118
>
137119
<SelectArrows6Icon title="Select" className="w-2 text-tertiary" />
138120
</div>
139-
</Select.Button>
140-
<FloatingPortal>
141-
<Select.Options
142-
ref={refs.setFloating}
143-
style={floatingStyles}
144-
className={`ox-menu pointer-events-auto ${zIndex} overflow-y-auto !outline-none`}
145-
>
146-
{items.map((item) => (
147-
<Select.Option
148-
key={item.value}
149-
value={item.value}
150-
className="relative border-b border-secondary last:border-0"
151-
>
152-
{({ active, selected }) => (
153-
<div
154-
className={cn(
155-
'ox-menu-item text-secondary',
156-
selected && 'is-selected',
157-
active && 'is-highlighted'
158-
)}
159-
>
160-
{item.label}
161-
</div>
162-
)}
163-
</Select.Option>
164-
))}
165-
</Select.Options>
166-
</FloatingPortal>
121+
</ListboxButton>
122+
<ListboxOptions
123+
anchor={{ gap: 12 }}
124+
className={`ox-menu pointer-events-auto ${zIndex} w-[var(--button-width)] overflow-y-auto !outline-none`}
125+
// This is to prevent the `useOthersInert` call in ListboxOptions.
126+
// Without this, when the listbox options box scrolls under the
127+
// top bar (for example) you can click through the top bar to the
128+
// options because the topbar (and all other elements) has been
129+
// marked "inert", which means it does not catch mouse events.
130+
// This would not be necessary if useScrollLock in ListboxOptions
131+
// worked, but we suspect it doesn't work because the page as a
132+
// whole is not the scroll container.
133+
modal={false}
134+
>
135+
{items.map((item) => (
136+
<ListboxOption
137+
key={item.value}
138+
value={item.value}
139+
className="relative border-b border-secondary last:border-0"
140+
>
141+
{({ active, selected }) => (
142+
// TODO: redo active styling with `data-active` somehow
143+
<div
144+
className={cn(
145+
'ox-menu-item text-secondary',
146+
selected && 'is-selected',
147+
active && 'is-highlighted'
148+
)}
149+
>
150+
{item.label}
151+
</div>
152+
)}
153+
</ListboxOption>
154+
))}
155+
</ListboxOptions>
167156
</>
168157
)}
169-
</Select>
158+
</HListbox>
170159
</div>
171160
)
172161
}

app/ui/styles/components/menu-list.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
.ox-menu {
10-
@apply max-h-[17.5rem] overflow-y-auto rounded border bg-raise border-secondary elevation-2;
10+
@apply !max-h-[17.5rem] overflow-y-auto rounded border bg-raise border-secondary elevation-2;
1111
}
1212

1313
.ox-menu-item {

package-lock.json

Lines changed: 56 additions & 27 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"private": true,
3535
"dependencies": {
3636
"@floating-ui/react": "^0.26.13",
37-
"@headlessui/react": "^1.7.18",
37+
"@headlessui/react": "^2.0.3",
3838
"@oxide/design-system": "^1.4.6",
3939
"@radix-ui/react-accordion": "^1.1.2",
4040
"@radix-ui/react-dialog": "^1.0.5",

test/e2e/instance-create.e2e.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ test('can create an instance', async ({ page }) => {
6262
await expect(assignEphemeralIpCheckbox).toBeChecked()
6363
await assignEphemeralIpButton.click()
6464
await expect(page.getByRole('option', { name: 'ip-pool-1' })).toBeEnabled()
65+
await assignEphemeralIpButton.click() // click closes the listbox so we can do more stuff
6566

6667
// unchecking the box should disable the selector
6768
await assignEphemeralIpCheckbox.uncheck()

0 commit comments

Comments
 (0)