66 * Copyright Oxide Computer Company
77 */
88import {
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'
1715import cn from 'classnames'
1816import 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}
0 commit comments