Skip to content

Commit

Permalink
🚸 Autocomplete: implement native popover (#3416)
Browse files Browse the repository at this point in the history
* 🚸 Autocomplete: implement native popover

* Autocomplete: update tests
  • Loading branch information
oddvernes authored May 3, 2024
1 parent 11bdd37 commit 5cbda66
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ const mockResizeObserver = jest.fn(() => ({

beforeAll(() => {
window.ResizeObserver = mockResizeObserver
HTMLDivElement.prototype.showPopover = jest.fn()
HTMLDivElement.prototype.hidePopover = jest.fn()

//https://github.com/TanStack/virtual/issues/641
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
Expand Down Expand Up @@ -52,7 +54,7 @@ describe('Autocomplete', () => {
expect(optionsList).toMatchSnapshot()
})
it('Has provided label', async () => {
render(<Autocomplete disablePortal label={labelText} options={items} />)
render(<Autocomplete label={labelText} options={items} />)

// The same label is used for both the input field and the list of options
const labeledNodes = await screen.findAllByLabelText(labelText)
Expand All @@ -69,13 +71,7 @@ describe('Autocomplete', () => {
})

it('Has provided ReactNode label', async () => {
render(
<Autocomplete
disablePortal
label={<div>{labelText}</div>}
options={items}
/>,
)
render(<Autocomplete label={<div>{labelText}</div>} options={items} />)

// The same label is used for both the input field and the list of options
const labeledNodes = await screen.findAllByLabelText(labelText)
Expand All @@ -95,7 +91,6 @@ describe('Autocomplete', () => {
const labler = (text: string) => `${text}+1`
render(
<Autocomplete
disablePortal
options={itemObjects}
label={labelText}
optionLabel={(item) => labler(item.label)}
Expand Down Expand Up @@ -128,7 +123,6 @@ describe('Autocomplete', () => {
}
render(
<Autocomplete
disablePortal
options={itemObjects}
label={labelText}
optionLabel={(item) => item.label}
Expand All @@ -153,9 +147,7 @@ describe('Autocomplete', () => {
})

it('Can be disabled', async () => {
render(
<Autocomplete disablePortal label={labelText} options={items} disabled />,
)
render(<Autocomplete label={labelText} options={items} disabled />)
const labeledNodes = await screen.findAllByLabelText(labelText)
const input = labeledNodes[0]

Expand Down Expand Up @@ -189,7 +181,6 @@ describe('Autocomplete', () => {
options={items}
data-testid="styled-autocomplete"
multiple={true}
disablePortal={true}
allowSelectAll={true}
onOptionsChange={onChange}
/>,
Expand Down Expand Up @@ -219,7 +210,7 @@ describe('Autocomplete', () => {
})

it('Can open the options on button click', async () => {
render(<Autocomplete disablePortal options={items} label={labelText} />)
render(<Autocomplete options={items} label={labelText} />)

const labeledNodes = await screen.findAllByLabelText(labelText)
const optionsList = labeledNodes[1]
Expand All @@ -243,7 +234,6 @@ describe('Autocomplete', () => {
return (
<Autocomplete
multiple
disablePortal
options={items}
label={labelText}
selectedOptions={selected}
Expand Down Expand Up @@ -277,7 +267,7 @@ describe('Autocomplete', () => {
})

it('Can filter results by input value', async () => {
render(<Autocomplete disablePortal options={items} label={labelText} />)
render(<Autocomplete options={items} label={labelText} />)
const labeledNodes = await screen.findAllByLabelText(labelText)
const input = labeledNodes[0]
const optionsList = labeledNodes[1]
Expand All @@ -301,7 +291,6 @@ describe('Autocomplete', () => {
it('Second option is first when first option is disabled', async () => {
render(
<Autocomplete
disablePortal
options={items}
label={labelText}
optionDisabled={(item) => item === items[0]}
Expand Down Expand Up @@ -330,7 +319,7 @@ describe('Autocomplete', () => {
})

it('Clears the input text on blur when no option is selected', async () => {
render(<Autocomplete disablePortal options={items} label={labelText} />)
render(<Autocomplete options={items} label={labelText} />)
const labeledNodes = await screen.findAllByLabelText(labelText)
const input = labeledNodes[0]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,15 @@ import {
useToken,
bordersTemplate,
useIsomorphicLayoutEffect,
useIsInDialog,
} from '@equinor/eds-utils'
import { AutocompleteOption } from './Option'
import {
offset,
flip,
shift,
size,
autoUpdate,
useFloating,
useInteractions,
FloatingPortal,
MiddlewareState,
} from '@floating-ui/react'
import { Variants } from '../types'
Expand All @@ -76,6 +73,16 @@ const StyledList = styled(List)(
`,
)

const StyledPopover = styled('div').withConfig({
shouldForwardProp: () => true, //workaround to avoid warning until popover gets added to react types
})<{ popover: string }>`
inset: unset;
border: 0;
padding: 0;
margin: 0;
overflow: visible;
`

const HelperText = styled(_HelperText)`
margin-top: 8px;
margin-left: 8px;
Expand Down Expand Up @@ -275,7 +282,9 @@ export type AutocompleteProps<T> = {
allowSelectAll?: boolean
/** Custom option template */
optionComponent?: (option: T, isSelected: boolean) => ReactNode
/** Disable use of react portal for dropdown */
/** Disable use of react portal for dropdown
* @deprecated Autocomplete now uses the native popover api to render the dropdown. This prop will be removed in a future version
*/
disablePortal?: boolean
/** Custom filter function for options */
optionsFilter?: (option: T, inputValue: string) => boolean
Expand Down Expand Up @@ -335,6 +344,12 @@ function AutocompleteInner<T>(
...other
} = props

if (disablePortal) {
console.warn(
'Autocomplete "disablePortal" prop has been deprecated. Autocomplete now uses the native popover api',
)
}

const isControlled = Boolean(selectedOptions)
const [inputOptions, setInputOptions] = useState(options)
const [_availableItems, setAvailableItems] = useState(inputOptions)
Expand Down Expand Up @@ -721,8 +736,9 @@ function AutocompleteInner<T>(
placement: 'bottom-start',
middleware: [
offset(4),
flip(),
shift({ padding: 8 }),
flip({
boundary: document?.body,
}),
size({
apply({ rects, elements }: MiddlewareState) {
const anchorWidth = `${rects.reference.width}px`
Expand All @@ -743,6 +759,14 @@ function AutocompleteInner<T>(
}
}, [refs.reference, refs.floating, update, isOpen])

useEffect(() => {
if (isOpen) {
refs.floating.current.showPopover()
} else {
refs.floating.current.hidePopover()
}
}, [isOpen, refs.floating])

const clear = () => {
resetCombobox()
resetSelection()
Expand All @@ -759,18 +783,15 @@ function AutocompleteInner<T>(
[selectedItems, getLabel],
)

//temporary fix when inside dialog. Should be replaced by popover api when it is ready
const inDialog = useIsInDialog(refs.domReference.current)

const optionsList = (
<div
<StyledPopover
popover="manual"
{...getFloatingProps({
ref: refs.setFloating,
style: {
position: strategy,
top: y || 0,
left: x || 0,
zIndex: 1500,
},
})}
>
Expand Down Expand Up @@ -881,7 +902,7 @@ function AutocompleteInner<T>(
)
})}
</StyledList>
</div>
</StyledPopover>
)

const inputProps = getInputProps(
Expand Down Expand Up @@ -951,13 +972,7 @@ function AutocompleteInner<T>(
icon={helperIcon}
/>
)}
{disablePortal || inDialog ? (
optionsList
) : (
<FloatingPortal id="eds-autocomplete-container">
{optionsList}
</FloatingPortal>
)}
{optionsList}
</Container>
</ThemeProvider>
)
Expand Down

0 comments on commit 5cbda66

Please sign in to comment.