Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 6 additions & 19 deletions app/components/DocsPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
* Copyright Oxide Computer Company
*/

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

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

export const DocsPopover = ({ heading, icon, summary, links }: DocsPopoverProps) => {
const { refs, floatingStyles } = useFloating({
placement: 'bottom-end',
middleware: [offset(12)],
whileElementsMounted: autoUpdate,
// Needs to be off because it breaks the enter animation
// https://floating-ui.com/docs/usefloating#transform
transform: false,
})
return (
<Popover>
<Popover.Button
ref={refs.setReference}
className={cn(buttonStyle({ size: 'sm', variant: 'ghost' }), 'w-9')}
>
<PopoverButton className={cn(buttonStyle({ size: 'sm', variant: 'ghost' }), 'w-9')}>
<Question12Icon aria-label="Links to docs" className="shrink-0" />
</Popover.Button>
<Popover.Panel
</PopoverButton>
<PopoverPanel
// DocsPopoverPanel needed for enter animation
className="DocsPopoverPanel z-10 w-96 rounded-lg border bg-raise border-secondary elevation-1"
ref={refs.setFloating}
style={floatingStyles}
anchor={{ to: 'bottom end', gap: 12 }}
Copy link
Collaborator Author

@david-crespo david-crespo May 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How great is this! Need to make sure autoUpdate still works. Update: it seems to work. The thing moves with the page on scroll.

>
<div className="px-4">
<h2 className="mt-4 flex items-center gap-1 text-sans-md">
Expand All @@ -78,7 +65,7 @@ export const DocsPopover = ({ heading, icon, summary, links }: DocsPopoverProps)
<DocsPopoverLink key={link.href} {...link} />
))}
</div>
</Popover.Panel>
</PopoverPanel>
</Popover>
)
}
101 changes: 45 additions & 56 deletions app/ui/lib/Listbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@
* Copyright Oxide Computer Company
*/
import {
autoUpdate,
flip,
FloatingPortal,
offset,
size,
useFloating,
} from '@floating-ui/react'
import { Listbox as Select } from '@headlessui/react'
Listbox as HListbox,
Label,
ListboxButton,
ListboxOption,
ListboxOptions,
} from '@headlessui/react'
import cn from 'classnames'
import type { ReactNode } from 'react'

Expand Down Expand Up @@ -61,29 +59,14 @@ export const Listbox = <Value extends string = string>({
isLoading = false,
...props
}: ListboxProps<Value>) => {
const { refs, floatingStyles } = useFloating({
middleware: [
offset(12),
flip(),
size({
apply({ rects, elements }) {
Object.assign(elements.floating.style, {
width: `${rects.reference.width}px`,
})
},
}),
],
whileElementsMounted: autoUpdate,
})

const selectedItem = selected && items.find((i) => i.value === selected)
const noItems = !isLoading && items.length === 0
const isDisabled = disabled || noItems
const zIndex = usePopoverZIndex()

return (
<div className={cn('relative', className)}>
<Select
<HListbox
value={selected}
// you shouldn't ever be able to select null, but we check here anyway
// to make TS happy so the calling code doesn't have to. note `val !==
Expand All @@ -96,14 +79,13 @@ export const Listbox = <Value extends string = string>({
{label && (
<div className="mb-2">
<FieldLabel id={``} as="div" tip={tooltipText} optional={!required}>
<Select.Label>{label}</Select.Label>
<Label>{label}</Label>
</FieldLabel>
{description && <TextInputHint id={``}>{description}</TextInputHint>}
</div>
)}
<Select.Button
<ListboxButton
name={name}
ref={refs.setReference}
className={cn(
`flex h-10 w-full items-center justify-between
rounded border text-sans-md`,
Expand Down Expand Up @@ -136,37 +118,44 @@ export const Listbox = <Value extends string = string>({
>
<SelectArrows6Icon title="Select" className="w-2 text-tertiary" />
</div>
</Select.Button>
<FloatingPortal>
<Select.Options
ref={refs.setFloating}
style={floatingStyles}
className={`ox-menu pointer-events-auto ${zIndex} overflow-y-auto !outline-none`}
>
{items.map((item) => (
<Select.Option
key={item.value}
value={item.value}
className="relative border-b border-secondary last:border-0"
>
{({ active, selected }) => (
<div
className={cn(
'ox-menu-item text-secondary',
selected && 'is-selected',
active && 'is-highlighted'
)}
>
{item.label}
</div>
)}
</Select.Option>
))}
</Select.Options>
</FloatingPortal>
</ListboxButton>
<ListboxOptions
anchor={{ gap: 12 }}
className={`ox-menu pointer-events-auto ${zIndex} w-[var(--button-width)] overflow-y-auto !outline-none`}
// This is to prevent the `useOthersInert` call in ListboxOptions.
// Without this, when the listbox options box scrolls under the
// top bar (for example) you can click through the top bar to the
// options because the topbar (and all other elements) has been
// marked "inert", which means it does not catch mouse events.
// This would not be necessary if useScrollLock in ListboxOptions
// worked, but we suspect it doesn't work because the page as a
// whole is not the scroll container.
modal={false}
>
{items.map((item) => (
<ListboxOption
key={item.value}
value={item.value}
className="relative border-b border-secondary last:border-0"
>
{({ active, selected }) => (
// TODO: redo active styling with `data-active` somehow
<div
className={cn(
'ox-menu-item text-secondary',
selected && 'is-selected',
active && 'is-highlighted'
)}
>
{item.label}
</div>
)}
</ListboxOption>
))}
</ListboxOptions>
</>
)}
</Select>
</HListbox>
</div>
)
}
2 changes: 1 addition & 1 deletion app/ui/styles/components/menu-list.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

.ox-menu {
@apply max-h-[17.5rem] overflow-y-auto rounded border bg-raise border-secondary elevation-2;
@apply !max-h-[17.5rem] overflow-y-auto rounded border bg-raise border-secondary elevation-2;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

! just to make this stronger than some height the component is applying. But maybe there's a better way.

}

.ox-menu-item {
Expand Down
83 changes: 56 additions & 27 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"private": true,
"dependencies": {
"@floating-ui/react": "^0.26.13",
"@headlessui/react": "^1.7.18",
"@headlessui/react": "^2.0.3",
"@oxide/design-system": "^1.4.6",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-dialog": "^1.0.5",
Expand Down
1 change: 1 addition & 0 deletions test/e2e/instance-create.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ test('can create an instance', async ({ page }) => {
await expect(assignEphemeralIpCheckbox).toBeChecked()
await assignEphemeralIpButton.click()
await expect(page.getByRole('option', { name: 'ip-pool-1' })).toBeEnabled()
await assignEphemeralIpButton.click() // click closes the listbox so we can do more stuff

// unchecking the box should disable the selector
await assignEphemeralIpCheckbox.uncheck()
Expand Down