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
5 changes: 5 additions & 0 deletions .changeset/many-hairs-lay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/polaris': patch
---

Fixed `Combobox` not rendering `Popover` until the second firing of the `onChange` event
86 changes: 86 additions & 0 deletions polaris-react/src/components/Combobox/Combobox.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,92 @@ export function Default() {
);
}

export function WithChildrenBasedOnInput() {
const deselectedOptions = useMemo(
() => [
{value: 'rustic', label: 'Rustic'},
{value: 'antique', label: 'Antique'},
{value: 'vinyl', label: 'Vinyl'},
{value: 'vintage', label: 'Vintage'},
{value: 'refurbished', label: 'Refurbished'},
],
[],
);

const [selectedOption, setSelectedOption] = useState();
const [inputValue, setInputValue] = useState('');
const [options, setOptions] = useState(deselectedOptions);

const updateText = useCallback(
(value) => {
setInputValue(value);

if (value === '') {
setOptions(deselectedOptions);
return;
}

const filterRegex = new RegExp(value, 'i');
const resultOptions = deselectedOptions.filter((option) =>
option.label.match(filterRegex),
);
setOptions(resultOptions);
},
[deselectedOptions],
);

const updateSelection = useCallback(
(selected) => {
const matchedOption = options.find((option) => {
return option.value.match(selected);
});

setSelectedOption(selected);
setInputValue((matchedOption && matchedOption.label) || '');
},
[options],
);

const optionsMarkup =
options.length > 0
? options.map((option) => {
const {label, value} = option;

return (
<Listbox.Option
key={`${value}`}
value={value}
selected={selectedOption === value}
accessibilityLabel={label}
>
{label}
</Listbox.Option>
);
})
: null;

return (
<div style={{height: '225px'}}>
<Combobox
activator={
<Combobox.TextField
prefix={<Icon source={SearchIcon} />}
onChange={updateText}
label="Search tags"
labelHidden
value={inputValue}
placeholder="Search tags"
/>
}
>
{inputValue.length > 0 && options.length > 0 ? (
<Listbox onSelect={updateSelection}>{optionsMarkup}</Listbox>
) : null}
</Combobox>
</div>
);
}

export function WithManualSelection() {
const deselectedOptions = useMemo(
() => [
Expand Down
6 changes: 4 additions & 2 deletions polaris-react/src/components/Combobox/Combobox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ export function Combobox({
const [textFieldLabelId, setTextFieldLabelId] = useState<string>();
const [listboxId, setListboxId] = useState<string>();
const [textFieldFocused, setTextFieldFocused] = useState<boolean>(false);
const shouldOpen = Boolean(!popoverActive && Children.count(children) > 0);
const shouldOpen = !popoverActive;
const popoverActiveWithChildren =
popoverActive && Children.count(children) > 0;
const ref = useRef<PopoverPublicAPI | null>(null);

const handleClose = useCallback(() => {
Expand Down Expand Up @@ -151,7 +153,7 @@ export function Combobox({
return (
<Popover
ref={ref}
active={popoverActive}
active={popoverActiveWithChildren}
activator={
<ComboboxTextFieldContext.Provider value={textFieldContextValue}>
{activator}
Expand Down
42 changes: 41 additions & 1 deletion polaris-react/src/components/Combobox/tests/Combobox.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, {useState} from 'react';
import {mountWithApp} from 'tests/utilities';

import {TextField} from '../../TextField';
Expand Down Expand Up @@ -100,6 +100,46 @@ describe('<Combobox />', () => {
});
});

it('renders an active Popover when the text input has content and children depends on that text input', () => {
function ComboboxWithHandledChange() {
const handleChange = (value: string) => {
setInputValue(value);
};

const [inputValue, setInputValue] = useState('');

return (
<Combobox
activator={
<Combobox.TextField
onChange={handleChange}
label=""
value=""
autoComplete="off"
/>
}
>
{
// eslint-disable-next-line jest/no-if
inputValue.length > 0 ? (
<Listbox>
<Listbox.Option accessibilityLabel="Option 1" value="option1" />
</Listbox>
) : null
}
</Combobox>
);
}

const combobox = mountWithApp(<ComboboxWithHandledChange />);

combobox.find(TextField)?.trigger('onChange', 'value');

expect(combobox).toContainReactComponent(Popover, {
active: true,
});
});

it('closes the Popover when onOptionSelected is triggered and allowMultiple is false', () => {
const combobox = mountWithApp(
<Combobox activator={activator}>
Expand Down