Skip to content

Commit

Permalink
[Autocomplete] Improve search box & create options support (#20624)
Browse files Browse the repository at this point in the history
  • Loading branch information
oliviertassinari authored Apr 19, 2020
1 parent 74509f2 commit 77f6fe0
Show file tree
Hide file tree
Showing 14 changed files with 147 additions and 11 deletions.
1 change: 1 addition & 0 deletions docs/pages/api-docs/autocomplete.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ The `MuiAutocomplete` name can be used for providing [default props](/customizat
| <span class="prop-name">blurOnSelect</span> | <span class="prop-type">'mouse'<br>&#124;&nbsp;'touch'<br>&#124;&nbsp;bool</span> | <span class="prop-default">false</span> | Control if the input should be blurred when an option is selected:<br>- `false` the input is not blurred. - `true` the input is always blurred. - `touch` the input is blurred after a touch event. - `mouse` the input is blurred after a mouse event. |
| <span class="prop-name">ChipProps</span> | <span class="prop-type">object</span> | | Props applied to the [`Chip`](/api/chip/) element. |
| <span class="prop-name">classes</span> | <span class="prop-type">object</span> | | Override or extend the styles applied to the component. See [CSS API](#css) below for more details. |
| <span class="prop-name">clearOnBlur</span> | <span class="prop-type">bool</span> | <span class="prop-default">!props.freeSolo</span> | If `true`, the input's text will be cleared on blur if no value is selected.<br>Set to `true` if you want to help the user enter a new value. Set to `false` if you want to help the user resume his search. |
| <span class="prop-name">clearOnEscape</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, clear all values when the user presses escape and the popup is closed. |
| <span class="prop-name">clearText</span> | <span class="prop-type">string</span> | <span class="prop-default">'Clear'</span> | Override the default text for the *clear* icon button.<br>For localization purposes, you can use the provided [translations](/guides/localization/). |
| <span class="prop-name">closeIcon</span> | <span class="prop-type">node</span> | <span class="prop-default">&lt;CloseIcon fontSize="small" /></span> | The icon to display in place of the default close icon. |
Expand Down
12 changes: 12 additions & 0 deletions docs/src/modules/components/MarkdownElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,18 @@ const styles = (theme) => ({
flexShrink: 0,
backgroundColor: theme.palette.divider,
},
'& kbd': {
// Style taken from GitHub
padding: '2px 5px',
font: '11px Consolas,Liberation Mono,Menlo,monospace',
lineHeight: '10px',
color: '#444d56',
verticalAlign: 'middle',
backgroundColor: '#fafbfc',
border: '1px solid #d1d5da',
borderRadius: 3,
boxShadow: 'inset 0 -1px 0 #d1d5da',
},
},
});

Expand Down
32 changes: 32 additions & 0 deletions docs/src/pages/components/autocomplete/ControllableStates.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';
import TextField from '@material-ui/core/TextField';
import Autocomplete from '@material-ui/lab/Autocomplete';

const options = ['Option 1', 'Option 2'];

export default function ControllableStates() {
const [value, setValue] = React.useState(options[0]);
const [inputValue, setInputValue] = React.useState('');

return (
<div>
<div>{`value: ${value}`}</div>
<div>{`inputValue: ${inputValue}`}</div>
<br />
<Autocomplete
value={value}
onChange={(event, newValue) => {
setValue(newValue);
}}
inputValue={inputValue}
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
}}
id="controllable-states-demo"
options={options}
style={{ width: 300 }}
renderInput={(params) => <TextField {...params} label="Controllable" variant="outlined" />}
/>
</div>
);
}
32 changes: 32 additions & 0 deletions docs/src/pages/components/autocomplete/ControllableStates.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';
import TextField from '@material-ui/core/TextField';
import Autocomplete from '@material-ui/lab/Autocomplete';

const options = ['Option 1', 'Option 2'];

export default function ControllableStates() {
const [value, setValue] = React.useState<string | null>(options[0]);
const [inputValue, setInputValue] = React.useState('');

return (
<div>
<div>{`value: ${value}`}</div>
<div>{`inputValue: ${inputValue}`}</div>
<br />
<Autocomplete
value={value}
onChange={(event: any, newValue: string | null) => {
setValue(newValue);
}}
inputValue={inputValue}
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
}}
id="controllable-states-demo"
options={options}
style={{ width: 300 }}
renderInput={(params) => <TextField {...params} label="Controllable" variant="outlined" />}
/>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default function FreeSoloCreateOption() {
<Autocomplete
value={value}
onChange={(event, newValue) => {
// Create a new value from the user input
if (newValue && newValue.inputValue) {
setValue({
title: newValue.inputValue,
Expand All @@ -25,6 +26,7 @@ export default function FreeSoloCreateOption() {
filterOptions={(options, params) => {
const filtered = filter(options, params);

// Suggest the creation of a new value
if (params.inputValue !== '') {
filtered.push({
inputValue: params.inputValue,
Expand All @@ -34,16 +36,20 @@ export default function FreeSoloCreateOption() {

return filtered;
}}
selectOnFocus
clearOnBlur
id="free-solo-with-text-demo"
options={top100Films}
getOptionLabel={(option) => {
// e.g value selected with enter, right from the input
// Value selected with enter, right from the input
if (typeof option === 'string') {
return option;
}
// Add "xxx" option created dynamically
if (option.inputValue) {
return option.inputValue;
}
// Regular option
return option.title;
}}
renderOption={(option) => option.title}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default function FreeSoloCreateOption() {
<Autocomplete
value={value}
onChange={(event: any, newValue: FilmOptionType | null) => {
// Create a new value from the user input
if (newValue && newValue.inputValue) {
setValue({
title: newValue.inputValue,
Expand All @@ -24,6 +25,7 @@ export default function FreeSoloCreateOption() {
filterOptions={(options, params) => {
const filtered = filter(options, params) as FilmOptionType[];

// Suggest the creation of a new value
if (params.inputValue !== '') {
filtered.push({
inputValue: params.inputValue,
Expand All @@ -33,16 +35,20 @@ export default function FreeSoloCreateOption() {

return filtered;
}}
selectOnFocus
clearOnBlur
id="free-solo-with-text-demo"
options={top100Films as FilmOptionType[]}
getOptionLabel={(option) => {
// e.g value selected with enter, right from the input
// Value selected with enter, right from the input
if (typeof option === 'string') {
return option;
}
// Add "xxx" option created dynamically
if (option.inputValue) {
return option.inputValue;
}
// Regular option
return option.title;
}}
renderOption={(option) => option.title}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ export default function FreeSoloCreateOptionDialog() {
}
return option.title;
}}
selectOnFocus
clearOnBlur
renderOption={(option) => option.title}
style={{ width: 300 }}
freeSolo
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ export default function FreeSoloCreateOptionDialog() {
}
return option.title;
}}
selectOnFocus
clearOnBlur
renderOption={(option) => option.title}
style={{ width: 300 }}
freeSolo
Expand Down
12 changes: 12 additions & 0 deletions docs/src/pages/components/autocomplete/Playground.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,18 @@ export default function Playground() {
blurOnSelect
renderInput={(params) => <TextField {...params} label="blurOnSelect" margin="normal" />}
/>
<Autocomplete
{...defaultProps}
id="clear-on-blur"
clearOnBlur
renderInput={(params) => <TextField {...params} label="clearOnBlur" margin="normal" />}
/>
<Autocomplete
{...defaultProps}
id="select-on-focus"
selectOnFocus
renderInput={(params) => <TextField {...params} label="selectOnFocus" margin="normal" />}
/>
</div>
);
}
Expand Down
12 changes: 12 additions & 0 deletions docs/src/pages/components/autocomplete/Playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,18 @@ export default function Playground() {
blurOnSelect
renderInput={(params) => <TextField {...params} label="blurOnSelect" margin="normal" />}
/>
<Autocomplete
{...defaultProps}
id="clear-on-blur"
clearOnBlur
renderInput={(params) => <TextField {...params} label="clearOnBlur" margin="normal" />}
/>
<Autocomplete
{...defaultProps}
id="select-on-focus"
selectOnFocus
renderInput={(params) => <TextField {...params} label="selectOnFocus" margin="normal" />}
/>
</div>
);
}
Expand Down
19 changes: 11 additions & 8 deletions docs/src/pages/components/autocomplete/autocomplete.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,26 @@ Choose one of the 248 countries.

The component has two states that can be controlled:

1. the "value" state with the `value`/`onChange` props combination.
2. the "input value" state with the `inputValue`/`onInputChange` props combination.
1. the "value" state with the `value`/`onChange` props combination. This state represents the value selected by the user, for instance when pressing <kbd>Enter</kbd>.
2. the "input value" state with the `inputValue`/`onInputChange` props combination. This state represents the value displayed in the textbox.

> ⚠️ These two state are isolated, they should be controlled independently.
## Free solo
{{"demo": "pages/components/autocomplete/ControllableStates.js"}}

Set `freeSolo` to true so the textbox can contain any arbitrary value. The prop is designed to cover the primary use case of a search box with suggestions, e.g. Google search.
## Free solo

However, if you intend to use it for a [combo box](#combo-box) like experience (an enhanced version of a select element) we recommend setting `selectOnFocus` (it helps the user clear the selected value).
Set `freeSolo` to true so the textbox can contain any arbitrary value. The prop is designed to cover the primary use case of a **search box** with suggestions, e.g. Google search or react-autowhatever.

{{"demo": "pages/components/autocomplete/FreeSolo.js"}}

### Helper message
### Creatable

If you intend to use this mode for a [combo box](#combo-box) like experience (an enhanced version of a select element) we recommend setting:

Sometimes you want to make explicit to the user that he/she can add whatever value he/she wants.
The following demo adds a last option: `Add "YOUR SEARCH"`.
- `selectOnFocus` to helps the user clear the selected value.
- `clearOnBlur` to helps the user to enter a new value.
- A last option, for instance `Add "YOUR SEARCH"`.

{{"demo": "pages/components/autocomplete/FreeSoloCreateOption.js"}}

Expand Down
8 changes: 8 additions & 0 deletions packages/material-ui-lab/src/Autocomplete/Autocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ const Autocomplete = React.forwardRef(function Autocomplete(props, ref) {
ChipProps,
classes,
className,
clearOnBlur = !props.freeSolo,
clearOnEscape = false,
clearText = 'Clear',
closeIcon = <CloseIcon fontSize="small" />,
Expand Down Expand Up @@ -541,6 +542,13 @@ Autocomplete.propTypes = {
* @ignore
*/
className: PropTypes.string,
/**
* If `true`, the input's text will be cleared on blur if no value is selected.
*
* Set to `true` if you want to help the user enter a new value.
* Set to `false` if you want to help the user resume his search.
*/
clearOnBlur: PropTypes.bool,
/**
* If `true`, clear all values when the user presses escape and the popup is closed.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ export interface UseAutocompleteCommonProps<T> {
* - `mouse` the input is blurred after a mouse event.
*/
blurOnSelect?: 'touch' | 'mouse' | true | false;
/**
* If `true`, the input's text will be cleared on blur if no value is selected.
*
* Set to `true` if you want to help the user enter a new value.
* Set to `false` if you want to help the user resume his search.
*/
clearOnBlur?: boolean;
/**
* If `true`, clear all values when the user presses escape and the popup is closed.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export default function useAutocomplete(props) {
autoHighlight = false,
autoSelect = false,
blurOnSelect = false,
clearOnBlur = !props.freeSolo,
clearOnEscape = false,
componentName = 'useAutocomplete',
debug = false,
Expand Down Expand Up @@ -762,7 +763,7 @@ export default function useAutocomplete(props) {
selectNewValue(event, filteredOptions[highlightedIndexRef.current], 'blur');
} else if (autoSelect && freeSolo && inputValue !== '') {
selectNewValue(event, inputValue, 'blur', 'freeSolo');
} else {
} else if (clearOnBlur) {
resetInputValue(event, value);
}

Expand Down

0 comments on commit 77f6fe0

Please sign in to comment.