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
2 changes: 2 additions & 0 deletions .storybook/polaris-readme-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ import {
ChoiceList,
Collapsible,
ColorPicker,
ComboBox,
Connected,
ContextualSaveBar,
DataTable,
Expand Down Expand Up @@ -105,6 +106,7 @@ import {
Layout,
Link,
List,
ListBox,
Loading,
MediaCard,
Modal,
Expand Down
4 changes: 4 additions & 0 deletions UNRELEASED.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Use [the changelog guidelines](https://git.io/polaris-changelog-guidelines) to f
- Updated `react` and `react-dom` to version 16.14.0. This is now the minimum version of React required to use the `@shopify/polaris` library.
- Dropping support for node 10.x
- Made `autoComplete` prop in `TextField` a required string ([#4267](https://github.com/Shopify/polaris-react/pull/4267)). If you do not want the browser to autofill a user's information (for example an email input which is a customer's email, but not the email of the user who is entering the information), we recommend setting `autoComplete` to `"off"`.
- `Autocomplete` now requires `Autocomplete.TextField` to be used ([#3910](https://github.com/Shopify/polaris-react/pull/3910))
- Removed ComboBox as a named export on `Autocomplete` ([#3910](https://github.com/Shopify/polaris-react/pull/3910))

### Enhancements

Expand All @@ -33,4 +35,6 @@ Use [the changelog guidelines](https://git.io/polaris-changelog-guidelines) to f

### Code quality

- Rebuilt `Autocomplete` internals using new `ComboBox` and `ListBox` components built on the ARIA 1.2 spec for improved accessibility ([#3910](https://github.com/Shopify/polaris-react/pull/3910))

### Deprecations
3 changes: 2 additions & 1 deletion locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"labelWithInitials": "Avatar with initials {initials}"
},
"Autocomplete": {
"spinnerAccessibilityLabel": "Loading"
"spinnerAccessibilityLabel": "Loading",
"ellipsis": "{content}…"
Copy link
Member

Choose a reason for hiding this comment

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

We're moving the ellipsis prop to a translated string for internationalization

},
"Badge": {
"PROGRESS_LABELS": {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@
{
"name": "esm",
"path": "dist/esm/index.js",
"limit": "100 kB"
"limit": "105 kB"
},
{
"name": "esnext",
Expand Down
123 changes: 87 additions & 36 deletions src/components/Autocomplete/Autocomplete.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
import React from 'react';
import React, {useMemo, useCallback} from 'react';

import {useI18n} from '../../utilities/i18n';
import type {ActionListItemDescriptor} from '../../types';
import {Spinner} from '../Spinner';
import type {OptionDescriptor} from '../OptionList';
import type {PopoverProps} from '../Popover';
import {useI18n} from '../../utilities/i18n';
import {ComboBox} from '../ComboBox';
import {ListBox} from '../ListBox';

import {TextField, ComboBox, ComboBoxProps} from './components';
import styles from './Autocomplete.scss';
import {MappedOption, MappedAction} from './components';

export interface AutocompleteProps {
/** A unique identifier for the Autocomplete */
id?: string;
/** Collection of options to be listed */
options: ComboBoxProps['options'];
options: OptionDescriptor[];
/** The selected options */
selected: string[];
/** The text field component attached to the list of options */
textField: React.ReactElement;
/** The preferred direction to open the popover */
preferredPosition?: ComboBoxProps['preferredPosition'];
preferredPosition?: PopoverProps['preferredPosition'];
/** Title of the list of options */
listTitle?: string;
/** Allow more than one option to be selected */
Expand All @@ -42,10 +44,8 @@ export interface AutocompleteProps {
// generated *.d.ts files.

export const Autocomplete: React.FunctionComponent<AutocompleteProps> & {
ComboBox: typeof ComboBox;
TextField: typeof TextField;
TextField: typeof ComboBox.TextField;
Copy link
Member

Choose a reason for hiding this comment

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

Both text fields extend the base Polaris textfield without adding additional API's

} = function Autocomplete({
id,
options,
selected,
textField,
Expand All @@ -61,38 +61,89 @@ export const Autocomplete: React.FunctionComponent<AutocompleteProps> & {
}: AutocompleteProps) {
const i18n = useI18n();

const spinnerMarkup = loading ? (
<div className={styles.Loading}>
<Spinner
size="small"
accessibilityLabel={i18n.translate(
'Polaris.Autocomplete.spinnerAccessibilityLabel',
)}
/>
</div>
const optionsMarkup = useMemo(() => {
const conditionalOptions = loading && !willLoadMoreResults ? [] : options;
const optionList =
conditionalOptions.length > 0
? conditionalOptions.map((option) => (
<MappedOption
{...option}
key={option.id || option.value}
selected={selected.includes(option.value)}
singleSelection={!allowMultiple}
/>
))
: null;

if (listTitle) {
return (
<ListBox.Section
divider={false}
title={<ListBox.Header>{listTitle}</ListBox.Header>}
>
{optionList}
</ListBox.Section>
);
}

return optionList;
}, [
listTitle,
loading,
options,
willLoadMoreResults,
allowMultiple,
selected,
]);

const loadingMarkup = loading ? (
<ListBox.Loading
accessibilityLabel={i18n.translate(
'Polaris.Autocomplete.spinnerAccessibilityLabel',
)}
/>
) : null;

const conditionalOptions = loading && !willLoadMoreResults ? [] : options;
const conditionalAction =
actionBefore && actionBefore !== [] ? [actionBefore] : undefined;
const updateSelection = useCallback(
(newSelection: string) => {
if (allowMultiple) {
if (selected.includes(newSelection)) {
onSelect(selected.filter((option) => option !== newSelection));
} else {
onSelect([...selected, newSelection]);
}
} else {
onSelect([newSelection]);
}
},
[allowMultiple, onSelect, selected],
);

const actionMarkup = actionBefore && <MappedAction {...actionBefore} />;

const emptyStateMarkup = emptyState && options.length < 1 && !loading && (
<div role="status">{emptyState}</div>
);

return (
<ComboBox
id={id}
options={conditionalOptions}
selected={selected}
textField={textField}
preferredPosition={preferredPosition}
listTitle={listTitle}
activator={textField}
allowMultiple={allowMultiple}
contentAfter={spinnerMarkup}
actionsBefore={conditionalAction}
onSelect={onSelect}
onEndReached={onLoadMoreResults}
emptyState={emptyState}
/>
onScrolledToBottom={onLoadMoreResults}
preferredPosition={preferredPosition}
>
{actionMarkup || optionsMarkup || loadingMarkup || emptyStateMarkup ? (
<ListBox onSelect={updateSelection}>
{actionMarkup}
{optionsMarkup && (!loading || willLoadMoreResults)
? optionsMarkup
: null}
{loadingMarkup}
{emptyStateMarkup}
</ListBox>
) : null}
</ComboBox>
);
};

Autocomplete.ComboBox = ComboBox;
Autocomplete.TextField = TextField;
Autocomplete.TextField = ComboBox.TextField;
Loading