Skip to content

Commit 702a357

Browse files
authored
feat: add Autocomplete Primitive (#2708)
* feat: making search controlled and adding autosuggest props * feat: adding autocomplete support for SearchField * chore: refactor suggestion menu * style: adding styling tokens * docs: add autocomplete usage * Create giant-bugs-develop.md * Update giant-bugs-develop.md * test: update snapshot * style: fix unmatched token name * feat: add Autocomplete primitive * chore: update types and bug fixes * docs: update Autocomplete and SearchField docs * test: add unit tests * docs: update docs * chore: refine interfaces and naming * chore: address comments * chore: address comments * chore: remove unused variable * chore: address comments * chore: address comments
1 parent ba94613 commit 702a357

File tree

68 files changed

+4147
-53
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+4147
-53
lines changed

.changeset/giant-bugs-develop.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
---
2+
"@aws-amplify/ui-react": minor
3+
"@aws-amplify/ui": minor
4+
---
5+
6+
feat: add Autocomplete primitive
7+
8+
**Example**
9+
10+
```jsx
11+
// Uncontrolled component
12+
import { Autocomplete } from '@aws-amplify/ui-react';
13+
import * as React from 'react';
14+
15+
const options = [
16+
{ id: 'apple', label: 'apple' },
17+
{ id: 'banana', label: 'banana' },
18+
{ id: 'cherry', label: 'cherry' },
19+
{ id: 'grape', label: 'grape' },
20+
{ id: 'kiwis', label: 'kiwis' },
21+
{ id: 'lemon', label: 'lemon' },
22+
{ id: 'mango', label: 'mango' },
23+
{ id: 'orange', label: 'orange' },
24+
{ id: 'strawberry', label: 'strawberry' },
25+
];
26+
27+
export const AutocompleteUncontrolledExample = () => {
28+
return (
29+
<Autocomplete
30+
label="Uncontrolled autocomplete"
31+
options={options}
32+
/>
33+
);
34+
};
35+
```
36+
37+
```jsx
38+
// Controlled component
39+
import { Autocomplete } from '@aws-amplify/ui-react';
40+
import * as React from 'react';
41+
42+
const options = [
43+
{ id: 'apple', label: 'apple' },
44+
{ id: 'banana', label: 'banana' },
45+
{ id: 'cherry', label: 'cherry' },
46+
{ id: 'grape', label: 'grape' },
47+
{ id: 'kiwis', label: 'kiwis' },
48+
{ id: 'lemon', label: 'lemon' },
49+
{ id: 'mango', label: 'mango' },
50+
{ id: 'orange', label: 'orange' },
51+
{ id: 'strawberry', label: 'strawberry' },
52+
];
53+
54+
export const AutocompleteControlledExample = () => {
55+
const [value, setValue] = React.useState('');
56+
57+
const onChange = (event) => {
58+
setValue(event.target.value);
59+
};
60+
61+
// Set up onSelect
62+
const onSelect = (option) => {
63+
const { label } = option;
64+
setValue(label);
65+
};
66+
67+
// Set up onClear
68+
const onClear = () => {
69+
setValue('');
70+
};
71+
72+
return (
73+
<Autocomplete
74+
label="Controlled autocomplete"
75+
options={options}
76+
value={value}
77+
onChange={onChange}
78+
onClear={onClear}
79+
onSelect={onSelect}
80+
/>
81+
);
82+
};
83+
```

docs/src/components/Demo.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ interface DemoProps {
88
propControls?: React.ReactNode;
99
themeControls?: React.ReactNode;
1010
code: string;
11+
childrenOverflow?: React.CSSProperties['overflow'];
1112
}
1213

1314
export const Demo = ({
1415
children,
16+
childrenOverflow = 'auto',
1517
propControls,
1618
themeControls,
1719
code,
@@ -39,7 +41,7 @@ export const Demo = ({
3941
alignItems="stretch"
4042
>
4143
<Flex direction="column" flex="1">
42-
<View overflow="auto" padding="5px">
44+
<View overflow={childrenOverflow} padding="5px">
4345
{children}
4446
</View>
4547
<Divider

docs/src/data/links.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
MdTune,
3737
MdSystemUpdateAlt,
3838
MdCheckCircle,
39+
MdHighlight,
3940
} from 'react-icons/md';
4041
import { IS_REACT_NATIVE_ENABLED } from '@/utils/featureFlags';
4142

@@ -178,6 +179,13 @@ export const feedbackComponents: ComponentNavItem[] = [
178179
].sort(sortByLabel);
179180

180181
export const inputComponents: ComponentNavItem[] = [
182+
{
183+
href: '/components/autocomplete',
184+
label: 'Autocomplete',
185+
body: `Autocomplete is a SearchField enhanced by a list of suggested options.`,
186+
platforms: ['react'],
187+
icon: MdSearch,
188+
},
181189
{
182190
href: '/components/textareafield',
183191
label: 'TextArea Field',
@@ -331,6 +339,13 @@ export const utilityComponents: ComponentNavItem[] = [
331339
platforms: ['react'],
332340
icon: MdDisabledVisible,
333341
},
342+
{
343+
href: '/components/highlightmatch',
344+
label: 'Highlight Match',
345+
body: `HighlightMatch is used to highlight a substring of a text.`,
346+
platforms: ['react'],
347+
icon: MdHighlight,
348+
},
334349
].sort(sortByLabel);
335350

336351
export const navigationComponents: ComponentNavItem[] = [
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import * as React from 'react';
2+
import {
3+
AutocompleteProps,
4+
Flex,
5+
SelectField,
6+
SwitchField,
7+
TextField,
8+
} from '@aws-amplify/ui-react';
9+
10+
export interface AutocompletePropControlsProps extends AutocompleteProps {
11+
setLabel: (value: React.SetStateAction<AutocompleteProps['label']>) => void;
12+
setPlaceholder: (
13+
value: React.SetStateAction<AutocompleteProps['placeholder']>
14+
) => void;
15+
setSize: (value: React.SetStateAction<AutocompleteProps['size']>) => void;
16+
setVariation: (
17+
value: React.SetStateAction<AutocompleteProps['variation']>
18+
) => void;
19+
setLabelHidden: (
20+
value: React.SetStateAction<AutocompleteProps['labelHidden']>
21+
) => void;
22+
setIsDisabled: (
23+
value: React.SetStateAction<AutocompleteProps['isDisabled']>
24+
) => void;
25+
setIsLoading: (
26+
value: React.SetStateAction<AutocompleteProps['isLoading']>
27+
) => void;
28+
}
29+
30+
interface AutocompletePropControlsInterface {
31+
(props: AutocompletePropControlsProps): JSX.Element;
32+
}
33+
34+
export const AutocompletePropControls: AutocompletePropControlsInterface = ({
35+
label,
36+
setLabel,
37+
placeholder,
38+
setPlaceholder,
39+
size,
40+
setSize,
41+
variation,
42+
setVariation,
43+
labelHidden,
44+
setLabelHidden,
45+
isDisabled,
46+
setIsDisabled,
47+
isLoading,
48+
setIsLoading,
49+
}) => {
50+
return (
51+
<Flex direction="column">
52+
<TextField
53+
label="label"
54+
name="label"
55+
value={label as string}
56+
onChange={(event) => {
57+
setLabel(event.target.value as AutocompleteProps['label']);
58+
}}
59+
/>
60+
<TextField
61+
label="placeholder"
62+
value={placeholder}
63+
onChange={(event) => {
64+
setPlaceholder(
65+
event.target.value as AutocompleteProps['placeholder']
66+
);
67+
}}
68+
/>
69+
<SelectField
70+
label="size"
71+
name="size"
72+
id="size"
73+
value={size}
74+
onChange={(event) =>
75+
setSize(event.target.value as AutocompleteProps['size'])
76+
}
77+
>
78+
<option value="">default</option>
79+
<option value="small">small</option>
80+
<option value="large">large</option>
81+
</SelectField>
82+
<SelectField
83+
label="variation"
84+
name="variation"
85+
id="variation"
86+
value={variation}
87+
onChange={(event) =>
88+
setVariation(
89+
event.target.value as unknown as AutocompleteProps['variation']
90+
)
91+
}
92+
>
93+
<option value="">default</option>
94+
<option value="quiet">quiet</option>
95+
</SelectField>
96+
<SwitchField
97+
label="isLoading"
98+
name="isLoading"
99+
isChecked={isLoading}
100+
onChange={(event) => {
101+
setIsLoading(
102+
Boolean(event.target.checked) as AutocompleteProps['isLoading']
103+
);
104+
}}
105+
/>
106+
<SwitchField
107+
label="labelHidden"
108+
name="labelHidden"
109+
isChecked={labelHidden}
110+
onChange={(event) => {
111+
setLabelHidden(
112+
Boolean(event.target.checked) as AutocompleteProps['labelHidden']
113+
);
114+
}}
115+
/>
116+
<SwitchField
117+
label="isDisabled"
118+
name="isDisabled"
119+
isChecked={isDisabled}
120+
onChange={(event) => {
121+
setIsDisabled(
122+
Boolean(event.target.checked) as AutocompleteProps['isDisabled']
123+
);
124+
}}
125+
/>
126+
</Flex>
127+
);
128+
};
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import * as React from 'react';
2+
import { Autocomplete, AutocompleteProps } from '@aws-amplify/ui-react';
3+
4+
import { useAutocompleteProps } from './useAutocompleteProps';
5+
import { AutocompletePropControls } from './AutocompletePropControls';
6+
import { Demo } from '@/components/Demo';
7+
import { demoState } from '@/utils/demoState';
8+
9+
const propsToCode = (autocompleteProps: AutocompleteProps) => {
10+
return (
11+
`<Autocomplete` +
12+
`\n label=${JSON.stringify(autocompleteProps.label)}` +
13+
(autocompleteProps.options
14+
? `\n options={${JSON.stringify(autocompleteProps.options)}}`
15+
: '') +
16+
(autocompleteProps.placeholder
17+
? `\n placeholder=${JSON.stringify(autocompleteProps.placeholder)}`
18+
: '') +
19+
(autocompleteProps.size
20+
? `\n size=${JSON.stringify(autocompleteProps.size)}`
21+
: '') +
22+
(autocompleteProps.variation
23+
? `\n variation=${JSON.stringify(autocompleteProps.variation)}`
24+
: '') +
25+
(!autocompleteProps.labelHidden
26+
? `\n labelHidden={${JSON.stringify(autocompleteProps.labelHidden)}}`
27+
: '') +
28+
(autocompleteProps.isDisabled
29+
? `\n isDisabled={${JSON.stringify(autocompleteProps.isDisabled)}}`
30+
: '') +
31+
(autocompleteProps.isLoading
32+
? `\n isLoading={${JSON.stringify(autocompleteProps.isLoading)}}`
33+
: '') +
34+
`\n/>`
35+
);
36+
};
37+
38+
const options = [
39+
{ id: 'apple', label: 'apple' },
40+
{ id: 'banana', label: 'banana' },
41+
{ id: 'cherry', label: 'cherry' },
42+
{ id: 'grape', label: 'grape' },
43+
{ id: 'kiwis', label: 'kiwis' },
44+
{ id: 'lemon', label: 'lemon' },
45+
{ id: 'mango', label: 'mango' },
46+
{ id: 'orange', label: 'orange' },
47+
{ id: 'strawberry', label: 'strawberry' },
48+
];
49+
50+
const defaultAutocompletedProps = {
51+
isDisabled: false,
52+
isLoading: false,
53+
label: 'Autocomplete',
54+
labelHidden: true,
55+
options,
56+
placeholder: 'Search here...',
57+
size: null,
58+
variation: null,
59+
};
60+
61+
export const AutocompleteDemo = () => {
62+
const autocompleteProps = useAutocompleteProps(
63+
(demoState.get(Autocomplete.displayName) as AutocompleteProps) ||
64+
defaultAutocompletedProps
65+
);
66+
67+
const onSubmit = React.useCallback(
68+
(value) => alert(`you searched for ${value}`),
69+
[]
70+
);
71+
72+
return (
73+
<Demo
74+
childrenOverflow="initial"
75+
code={propsToCode(autocompleteProps)}
76+
propControls={<AutocompletePropControls {...autocompleteProps} />}
77+
>
78+
<Autocomplete
79+
label={autocompleteProps.label}
80+
options={autocompleteProps.options}
81+
placeholder={autocompleteProps.placeholder}
82+
size={autocompleteProps.size}
83+
variation={autocompleteProps.variation}
84+
labelHidden={autocompleteProps.labelHidden}
85+
isDisabled={autocompleteProps.isDisabled}
86+
isLoading={autocompleteProps.isLoading}
87+
onSubmit={onSubmit}
88+
/>
89+
</Demo>
90+
);
91+
};

0 commit comments

Comments
 (0)