From b1a4b91e370e44b61c6ee25509ec0d8ad461eca4 Mon Sep 17 00:00:00 2001 From: Neto Chaves Date: Sat, 21 Mar 2020 16:26:33 -0300 Subject: [PATCH 1/5] feat(Autocomplete): add filterMax prop --- docs/pages/api-docs/autocomplete.md | 1 + .../src/pages/components/autocomplete/Tags.js | 11 +++++ .../src/Autocomplete/Autocomplete.d.ts | 4 ++ .../src/Autocomplete/Autocomplete.js | 13 ++++++ .../src/Autocomplete/Autocomplete.test.js | 45 +++++++++++++++++++ 5 files changed, 74 insertions(+) diff --git a/docs/pages/api-docs/autocomplete.md b/docs/pages/api-docs/autocomplete.md index 574bd0497811ca..7fe170e8ff8821 100644 --- a/docs/pages/api-docs/autocomplete.md +++ b/docs/pages/api-docs/autocomplete.md @@ -41,6 +41,7 @@ You can learn more about the difference by [reading this guide](/guides/minimizi | disabled | bool | false | If `true`, the input will be disabled. | | disableListWrap | bool | false | If `true`, the list box in the popup will not wrap focus. | | disablePortal | bool | false | Disable the portal behavior. The children stay within it's parent DOM hierarchy. | +| filterMax | number | | The numbers of items that will be visible | | filterOptions | func | | A filter function that determines the options that are eligible.

**Signature:**
`function(options: T[], state: object) => undefined`
*options:* The options to render.
*state:* The state of the component. | | filterSelectedOptions | bool | false | If `true`, hide the selected options from the list box. | | forcePopupIcon | 'auto'
| bool
| 'auto' | Force the visibility display of the popup icon. | diff --git a/docs/src/pages/components/autocomplete/Tags.js b/docs/src/pages/components/autocomplete/Tags.js index f5c867e4d2e3f1..36b20ee67f9bf2 100644 --- a/docs/src/pages/components/autocomplete/Tags.js +++ b/docs/src/pages/components/autocomplete/Tags.js @@ -65,6 +65,17 @@ export default function Tags() { )} /> + option.title} + defaultValue={[top100Films[13], top100Films[12], top100Films[11]]} + renderInput={params => ( + + )} + /> ); } diff --git a/packages/material-ui-lab/src/Autocomplete/Autocomplete.d.ts b/packages/material-ui-lab/src/Autocomplete/Autocomplete.d.ts index ab200169a2847d..103da6efb5f27e 100644 --- a/packages/material-ui-lab/src/Autocomplete/Autocomplete.d.ts +++ b/packages/material-ui-lab/src/Autocomplete/Autocomplete.d.ts @@ -83,6 +83,10 @@ export interface AutocompleteProps * Force the visibility display of the popup icon. */ forcePopupIcon?: true | false | 'auto'; + /** + * The numbers of items that will be visible + */ + filterMax?: number; /** * The component used to render the listbox. */ diff --git a/packages/material-ui-lab/src/Autocomplete/Autocomplete.js b/packages/material-ui-lab/src/Autocomplete/Autocomplete.js index 6a1e25b8491c7e..7016a490df20cb 100644 --- a/packages/material-ui-lab/src/Autocomplete/Autocomplete.js +++ b/packages/material-ui-lab/src/Autocomplete/Autocomplete.js @@ -269,6 +269,7 @@ const Autocomplete = React.forwardRef(function Autocomplete(props, ref) { loading = false, loadingText = 'Loading…', multiple = false, + filterMax, noOptionsText = 'No options', onChange, onClose, @@ -340,6 +341,14 @@ const Autocomplete = React.forwardRef(function Autocomplete(props, ref) { } } + if (startAdornment) { + const more = startAdornment.length - filterMax; + if (filterMax && !focused && more > 0) { + startAdornment = startAdornment.splice(0, filterMax); + startAdornment.push({` + ${more} more`}); + } + } + const defaultRenderGroup = (params) => (
  • @@ -572,6 +581,10 @@ Autocomplete.propTypes = { * The children stay within it's parent DOM hierarchy. */ disablePortal: PropTypes.bool, + /** + * The numbers of items that will be visible + */ + filterMax: PropTypes.number, /** * A filter function that determines the options that are eligible. * diff --git a/packages/material-ui-lab/src/Autocomplete/Autocomplete.test.js b/packages/material-ui-lab/src/Autocomplete/Autocomplete.test.js index e60baa86d3138a..19f62f10ad27d9 100644 --- a/packages/material-ui-lab/src/Autocomplete/Autocomplete.test.js +++ b/packages/material-ui-lab/src/Autocomplete/Autocomplete.test.js @@ -97,6 +97,51 @@ describe('', () => { }); }); + describe('prop: filterMax', () => { + it('show only the max number of items', () => { + const { queryByTestId, getAllByRole } = render( + } + />, + ); + + const tags = getAllByRole('button'); + expect(queryByTestId('more')).to.be.exist; + expect(tags[0].textContent).to.be.equal('one'); + expect(tags[1].textContent).to.be.equal('two'); + expect(tags[2].textContent).to.not.be.equal('three'); + }); + + it('show all items on focus', () => { + const { getAllByRole, queryByTestId, getByRole } = render( + } + />, + ); + + const input = getByRole('textbox'); + expect(getAllByRole('button')[2].textContent).to.not.be.equal('three'); + expect(queryByTestId('more')).to.exist; + + input.focus(); + const tags = getAllByRole('button'); + expect(queryByTestId('more')).to.not.exist; + expect(tags[0].textContent).to.be.equal('one'); + expect(tags[1].textContent).to.be.equal('two'); + expect(tags[2].textContent).to.be.equal('three'); + }); + }); + describe('prop: filterSelectedOptions', () => { it('when the last item is selected, highlights the new last item', () => { const { getByRole } = render( From 0ad4f7ea27f582ae5325a4c8b6ce4a4f1fee9fa9 Mon Sep 17 00:00:00 2001 From: Neto Chaves Date: Sun, 22 Mar 2020 14:42:59 -0300 Subject: [PATCH 2/5] feat(Autocomplete): add new demo, assert array and change type description --- .../components/autocomplete/FilterMaxTags.js | 138 ++++++++++++++++++ .../components/autocomplete/FilterMaxTags.tsx | 138 ++++++++++++++++++ .../src/pages/components/autocomplete/Tags.js | 11 -- .../components/autocomplete/autocomplete.md | 28 ++-- .../src/Autocomplete/Autocomplete.d.ts | 4 +- .../src/Autocomplete/Autocomplete.js | 16 +- .../src/Autocomplete/Autocomplete.test.js | 6 +- 7 files changed, 306 insertions(+), 35 deletions(-) create mode 100644 docs/src/pages/components/autocomplete/FilterMaxTags.js create mode 100644 docs/src/pages/components/autocomplete/FilterMaxTags.tsx diff --git a/docs/src/pages/components/autocomplete/FilterMaxTags.js b/docs/src/pages/components/autocomplete/FilterMaxTags.js new file mode 100644 index 00000000000000..e8035b115e0e4b --- /dev/null +++ b/docs/src/pages/components/autocomplete/FilterMaxTags.js @@ -0,0 +1,138 @@ +/* eslint-disable no-use-before-define */ +import React from 'react'; +import Autocomplete from '@material-ui/lab/Autocomplete'; +import { makeStyles } from '@material-ui/core/styles'; +import TextField from '@material-ui/core/TextField'; + +const useStyles = makeStyles(theme => ({ + root: { + width: 500, + '& > * + *': { + marginTop: theme.spacing(3), + }, + }, +})); + +export default function Tags() { + const classes = useStyles(); + + return ( +
    + option.title} + defaultValue={[top100Films[13], top100Films[12], top100Films[11]]} + renderInput={params => ( + + )} + /> +
    + ); +} + +// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top +const top100Films = [ + { title: 'The Shawshank Redemption', year: 1994 }, + { title: 'The Godfather', year: 1972 }, + { title: 'The Godfather: Part II', year: 1974 }, + { title: 'The Dark Knight', year: 2008 }, + { title: '12 Angry Men', year: 1957 }, + { title: "Schindler's List", year: 1993 }, + { title: 'Pulp Fiction', year: 1994 }, + { title: 'The Lord of the Rings: The Return of the King', year: 2003 }, + { title: 'The Good, the Bad and the Ugly', year: 1966 }, + { title: 'Fight Club', year: 1999 }, + { title: 'The Lord of the Rings: The Fellowship of the Ring', year: 2001 }, + { title: 'Star Wars: Episode V - The Empire Strikes Back', year: 1980 }, + { title: 'Forrest Gump', year: 1994 }, + { title: 'Inception', year: 2010 }, + { title: 'The Lord of the Rings: The Two Towers', year: 2002 }, + { title: "One Flew Over the Cuckoo's Nest", year: 1975 }, + { title: 'Goodfellas', year: 1990 }, + { title: 'The Matrix', year: 1999 }, + { title: 'Seven Samurai', year: 1954 }, + { title: 'Star Wars: Episode IV - A New Hope', year: 1977 }, + { title: 'City of God', year: 2002 }, + { title: 'Se7en', year: 1995 }, + { title: 'The Silence of the Lambs', year: 1991 }, + { title: "It's a Wonderful Life", year: 1946 }, + { title: 'Life Is Beautiful', year: 1997 }, + { title: 'The Usual Suspects', year: 1995 }, + { title: 'Léon: The Professional', year: 1994 }, + { title: 'Spirited Away', year: 2001 }, + { title: 'Saving Private Ryan', year: 1998 }, + { title: 'Once Upon a Time in the West', year: 1968 }, + { title: 'American History X', year: 1998 }, + { title: 'Interstellar', year: 2014 }, + { title: 'Casablanca', year: 1942 }, + { title: 'City Lights', year: 1931 }, + { title: 'Psycho', year: 1960 }, + { title: 'The Green Mile', year: 1999 }, + { title: 'The Intouchables', year: 2011 }, + { title: 'Modern Times', year: 1936 }, + { title: 'Raiders of the Lost Ark', year: 1981 }, + { title: 'Rear Window', year: 1954 }, + { title: 'The Pianist', year: 2002 }, + { title: 'The Departed', year: 2006 }, + { title: 'Terminator 2: Judgment Day', year: 1991 }, + { title: 'Back to the Future', year: 1985 }, + { title: 'Whiplash', year: 2014 }, + { title: 'Gladiator', year: 2000 }, + { title: 'Memento', year: 2000 }, + { title: 'The Prestige', year: 2006 }, + { title: 'The Lion King', year: 1994 }, + { title: 'Apocalypse Now', year: 1979 }, + { title: 'Alien', year: 1979 }, + { title: 'Sunset Boulevard', year: 1950 }, + { title: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb', year: 1964 }, + { title: 'The Great Dictator', year: 1940 }, + { title: 'Cinema Paradiso', year: 1988 }, + { title: 'The Lives of Others', year: 2006 }, + { title: 'Grave of the Fireflies', year: 1988 }, + { title: 'Paths of Glory', year: 1957 }, + { title: 'Django Unchained', year: 2012 }, + { title: 'The Shining', year: 1980 }, + { title: 'WALL·E', year: 2008 }, + { title: 'American Beauty', year: 1999 }, + { title: 'The Dark Knight Rises', year: 2012 }, + { title: 'Princess Mononoke', year: 1997 }, + { title: 'Aliens', year: 1986 }, + { title: 'Oldboy', year: 2003 }, + { title: 'Once Upon a Time in America', year: 1984 }, + { title: 'Witness for the Prosecution', year: 1957 }, + { title: 'Das Boot', year: 1981 }, + { title: 'Citizen Kane', year: 1941 }, + { title: 'North by Northwest', year: 1959 }, + { title: 'Vertigo', year: 1958 }, + { title: 'Star Wars: Episode VI - Return of the Jedi', year: 1983 }, + { title: 'Reservoir Dogs', year: 1992 }, + { title: 'Braveheart', year: 1995 }, + { title: 'M', year: 1931 }, + { title: 'Requiem for a Dream', year: 2000 }, + { title: 'Amélie', year: 2001 }, + { title: 'A Clockwork Orange', year: 1971 }, + { title: 'Like Stars on Earth', year: 2007 }, + { title: 'Taxi Driver', year: 1976 }, + { title: 'Lawrence of Arabia', year: 1962 }, + { title: 'Double Indemnity', year: 1944 }, + { title: 'Eternal Sunshine of the Spotless Mind', year: 2004 }, + { title: 'Amadeus', year: 1984 }, + { title: 'To Kill a Mockingbird', year: 1962 }, + { title: 'Toy Story 3', year: 2010 }, + { title: 'Logan', year: 2017 }, + { title: 'Full Metal Jacket', year: 1987 }, + { title: 'Dangal', year: 2016 }, + { title: 'The Sting', year: 1973 }, + { title: '2001: A Space Odyssey', year: 1968 }, + { title: "Singin' in the Rain", year: 1952 }, + { title: 'Toy Story', year: 1995 }, + { title: 'Bicycle Thieves', year: 1948 }, + { title: 'The Kid', year: 1921 }, + { title: 'Inglourious Basterds', year: 2009 }, + { title: 'Snatch', year: 2000 }, + { title: '3 Idiots', year: 2009 }, + { title: 'Monty Python and the Holy Grail', year: 1975 }, +]; diff --git a/docs/src/pages/components/autocomplete/FilterMaxTags.tsx b/docs/src/pages/components/autocomplete/FilterMaxTags.tsx new file mode 100644 index 00000000000000..7347971eca1e64 --- /dev/null +++ b/docs/src/pages/components/autocomplete/FilterMaxTags.tsx @@ -0,0 +1,138 @@ +/* eslint-disable no-use-before-define */ +import React from 'react'; +import Autocomplete from '@material-ui/lab/Autocomplete'; +import { makeStyles } from '@material-ui/core/styles'; +import TextField from '@material-ui/core/TextField'; + +const useStyles = makeStyles((theme: Theme) => ({ + root: { + width: 500, + '& > * + *': { + marginTop: theme.spacing(3), + }, + }, +})); + +export default function FilterMaxTags() { + const classes = useStyles(); + + return ( +
    + option.title} + defaultValue={[top100Films[13], top100Films[12], top100Films[11]]} + renderInput={params => ( + + )} + /> +
    + ); +} + +// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top +const top100Films = [ + { title: 'The Shawshank Redemption', year: 1994 }, + { title: 'The Godfather', year: 1972 }, + { title: 'The Godfather: Part II', year: 1974 }, + { title: 'The Dark Knight', year: 2008 }, + { title: '12 Angry Men', year: 1957 }, + { title: "Schindler's List", year: 1993 }, + { title: 'Pulp Fiction', year: 1994 }, + { title: 'The Lord of the Rings: The Return of the King', year: 2003 }, + { title: 'The Good, the Bad and the Ugly', year: 1966 }, + { title: 'Fight Club', year: 1999 }, + { title: 'The Lord of the Rings: The Fellowship of the Ring', year: 2001 }, + { title: 'Star Wars: Episode V - The Empire Strikes Back', year: 1980 }, + { title: 'Forrest Gump', year: 1994 }, + { title: 'Inception', year: 2010 }, + { title: 'The Lord of the Rings: The Two Towers', year: 2002 }, + { title: "One Flew Over the Cuckoo's Nest", year: 1975 }, + { title: 'Goodfellas', year: 1990 }, + { title: 'The Matrix', year: 1999 }, + { title: 'Seven Samurai', year: 1954 }, + { title: 'Star Wars: Episode IV - A New Hope', year: 1977 }, + { title: 'City of God', year: 2002 }, + { title: 'Se7en', year: 1995 }, + { title: 'The Silence of the Lambs', year: 1991 }, + { title: "It's a Wonderful Life", year: 1946 }, + { title: 'Life Is Beautiful', year: 1997 }, + { title: 'The Usual Suspects', year: 1995 }, + { title: 'Léon: The Professional', year: 1994 }, + { title: 'Spirited Away', year: 2001 }, + { title: 'Saving Private Ryan', year: 1998 }, + { title: 'Once Upon a Time in the West', year: 1968 }, + { title: 'American History X', year: 1998 }, + { title: 'Interstellar', year: 2014 }, + { title: 'Casablanca', year: 1942 }, + { title: 'City Lights', year: 1931 }, + { title: 'Psycho', year: 1960 }, + { title: 'The Green Mile', year: 1999 }, + { title: 'The Intouchables', year: 2011 }, + { title: 'Modern Times', year: 1936 }, + { title: 'Raiders of the Lost Ark', year: 1981 }, + { title: 'Rear Window', year: 1954 }, + { title: 'The Pianist', year: 2002 }, + { title: 'The Departed', year: 2006 }, + { title: 'Terminator 2: Judgment Day', year: 1991 }, + { title: 'Back to the Future', year: 1985 }, + { title: 'Whiplash', year: 2014 }, + { title: 'Gladiator', year: 2000 }, + { title: 'Memento', year: 2000 }, + { title: 'The Prestige', year: 2006 }, + { title: 'The Lion King', year: 1994 }, + { title: 'Apocalypse Now', year: 1979 }, + { title: 'Alien', year: 1979 }, + { title: 'Sunset Boulevard', year: 1950 }, + { title: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb', year: 1964 }, + { title: 'The Great Dictator', year: 1940 }, + { title: 'Cinema Paradiso', year: 1988 }, + { title: 'The Lives of Others', year: 2006 }, + { title: 'Grave of the Fireflies', year: 1988 }, + { title: 'Paths of Glory', year: 1957 }, + { title: 'Django Unchained', year: 2012 }, + { title: 'The Shining', year: 1980 }, + { title: 'WALL·E', year: 2008 }, + { title: 'American Beauty', year: 1999 }, + { title: 'The Dark Knight Rises', year: 2012 }, + { title: 'Princess Mononoke', year: 1997 }, + { title: 'Aliens', year: 1986 }, + { title: 'Oldboy', year: 2003 }, + { title: 'Once Upon a Time in America', year: 1984 }, + { title: 'Witness for the Prosecution', year: 1957 }, + { title: 'Das Boot', year: 1981 }, + { title: 'Citizen Kane', year: 1941 }, + { title: 'North by Northwest', year: 1959 }, + { title: 'Vertigo', year: 1958 }, + { title: 'Star Wars: Episode VI - Return of the Jedi', year: 1983 }, + { title: 'Reservoir Dogs', year: 1992 }, + { title: 'Braveheart', year: 1995 }, + { title: 'M', year: 1931 }, + { title: 'Requiem for a Dream', year: 2000 }, + { title: 'Amélie', year: 2001 }, + { title: 'A Clockwork Orange', year: 1971 }, + { title: 'Like Stars on Earth', year: 2007 }, + { title: 'Taxi Driver', year: 1976 }, + { title: 'Lawrence of Arabia', year: 1962 }, + { title: 'Double Indemnity', year: 1944 }, + { title: 'Eternal Sunshine of the Spotless Mind', year: 2004 }, + { title: 'Amadeus', year: 1984 }, + { title: 'To Kill a Mockingbird', year: 1962 }, + { title: 'Toy Story 3', year: 2010 }, + { title: 'Logan', year: 2017 }, + { title: 'Full Metal Jacket', year: 1987 }, + { title: 'Dangal', year: 2016 }, + { title: 'The Sting', year: 1973 }, + { title: '2001: A Space Odyssey', year: 1968 }, + { title: "Singin' in the Rain", year: 1952 }, + { title: 'Toy Story', year: 1995 }, + { title: 'Bicycle Thieves', year: 1948 }, + { title: 'The Kid', year: 1921 }, + { title: 'Inglourious Basterds', year: 2009 }, + { title: 'Snatch', year: 2000 }, + { title: '3 Idiots', year: 2009 }, + { title: 'Monty Python and the Holy Grail', year: 1975 }, +]; diff --git a/docs/src/pages/components/autocomplete/Tags.js b/docs/src/pages/components/autocomplete/Tags.js index 36b20ee67f9bf2..f5c867e4d2e3f1 100644 --- a/docs/src/pages/components/autocomplete/Tags.js +++ b/docs/src/pages/components/autocomplete/Tags.js @@ -65,17 +65,6 @@ export default function Tags() { )} /> - option.title} - defaultValue={[top100Films[13], top100Films[12], top100Films[11]]} - renderInput={params => ( - - )} - /> ); } diff --git a/docs/src/pages/components/autocomplete/autocomplete.md b/docs/src/pages/components/autocomplete/autocomplete.md index 3a1a2042cb93be..dd8c560324d011 100644 --- a/docs/src/pages/components/autocomplete/autocomplete.md +++ b/docs/src/pages/components/autocomplete/autocomplete.md @@ -100,6 +100,12 @@ Also known as tags, the user is allowed to enter more than one value. {{"demo": "pages/components/autocomplete/Tags.js"}} +## Filter Max Tags + +Do you want to show only a few tags? Use the `filterMaxTags` prop. + +{{"demo": "pages/components/autocomplete/FilterMaxTags.js"}} + ### Fixed options In the event that you need to lock certain tag so that they can't be removed in the interface, you can set the chips disabled. @@ -143,13 +149,14 @@ import { createFilterOptions } from '@material-ui/lab/Autocomplete'; #### Arguments -1. `config` (*Object* [optional]): - - `config.ignoreAccents` (*Boolean* [optional]): Defaults to `true`. Remove diacritics. - - `config.ignoreCase` (*Boolean* [optional]): Defaults to `true`. Lowercase everything. - - `config.matchFrom` (*'any' | 'start'* [optional]): Defaults to `'any'`. - - `config.stringify` (*Func* [optional]): Controls how an option is converted into a string so that it can be matched against the input text fragment. - - `config.trim` (*Boolean* [optional]): Defaults to `false`. Remove trailing spaces. - - `config.limit` (*Number* [optional]): Default to null. Limit the number of suggested options to be shown. For example, if `config.limit` is `100`, only the first `100` matching options are shown. It can be useful if a lot of options match and virtualization wasn't set up. +1. `config` (_Object_ [optional]): + +- `config.ignoreAccents` (_Boolean_ [optional]): Defaults to `true`. Remove diacritics. +- `config.ignoreCase` (_Boolean_ [optional]): Defaults to `true`. Lowercase everything. +- `config.matchFrom` (_'any' | 'start'_ [optional]): Defaults to `'any'`. +- `config.stringify` (_Func_ [optional]): Controls how an option is converted into a string so that it can be matched against the input text fragment. +- `config.trim` (_Boolean_ [optional]): Defaults to `false`. Remove trailing spaces. +- `config.limit` (_Number_ [optional]): Default to null. Limit the number of suggested options to be shown. For example, if `config.limit` is `100`, only the first `100` matching options are shown. It can be useful if a lot of options match and virtualization wasn't set up. #### Returns @@ -163,7 +170,7 @@ const filterOptions = createFilterOptions({ stringify: option => option.title, }); - +; ``` {{"demo": "pages/components/autocomplete/Filter.js", "defaultCodeOpen": false}} @@ -175,10 +182,9 @@ For richer filtering mechanisms, like fuzzy matching, it's recommended to look a ```jsx import matchSorter from 'match-sorter'; -const filterOptions = (options, { inputValue }) => - matchSorter(options, inputValue); +const filterOptions = (options, { inputValue }) => matchSorter(options, inputValue); - +; ``` ## Virtualization diff --git a/packages/material-ui-lab/src/Autocomplete/Autocomplete.d.ts b/packages/material-ui-lab/src/Autocomplete/Autocomplete.d.ts index 103da6efb5f27e..d6f46b985446b9 100644 --- a/packages/material-ui-lab/src/Autocomplete/Autocomplete.d.ts +++ b/packages/material-ui-lab/src/Autocomplete/Autocomplete.d.ts @@ -84,9 +84,9 @@ export interface AutocompleteProps */ forcePopupIcon?: true | false | 'auto'; /** - * The numbers of items that will be visible + * The number of tags that will be visible. Set `-1` to display them all. */ - filterMax?: number; + filterMaxTags?: number; /** * The component used to render the listbox. */ diff --git a/packages/material-ui-lab/src/Autocomplete/Autocomplete.js b/packages/material-ui-lab/src/Autocomplete/Autocomplete.js index 7016a490df20cb..1752ae671bb095 100644 --- a/packages/material-ui-lab/src/Autocomplete/Autocomplete.js +++ b/packages/material-ui-lab/src/Autocomplete/Autocomplete.js @@ -269,7 +269,7 @@ const Autocomplete = React.forwardRef(function Autocomplete(props, ref) { loading = false, loadingText = 'Loading…', multiple = false, - filterMax, + filterMaxTags, noOptionsText = 'No options', onChange, onClose, @@ -341,11 +341,11 @@ const Autocomplete = React.forwardRef(function Autocomplete(props, ref) { } } - if (startAdornment) { - const more = startAdornment.length - filterMax; - if (filterMax && !focused && more > 0) { - startAdornment = startAdornment.splice(0, filterMax); - startAdornment.push({` + ${more} more`}); + if (filterMaxTags && Array.isArray(startAdornment)) { + const more = startAdornment.length - filterMaxTags; + if (filterMaxTags && !focused && more > 0) { + startAdornment = startAdornment.splice(0, filterMaxTags); + startAdornment.push({` + ${more} more`}); } } @@ -582,9 +582,9 @@ Autocomplete.propTypes = { */ disablePortal: PropTypes.bool, /** - * The numbers of items that will be visible + * The number of tags that will be visible. Set `-1` to display them all. */ - filterMax: PropTypes.number, + filterMaxTags: PropTypes.number, /** * A filter function that determines the options that are eligible. * diff --git a/packages/material-ui-lab/src/Autocomplete/Autocomplete.test.js b/packages/material-ui-lab/src/Autocomplete/Autocomplete.test.js index 19f62f10ad27d9..e0b009be6712f5 100644 --- a/packages/material-ui-lab/src/Autocomplete/Autocomplete.test.js +++ b/packages/material-ui-lab/src/Autocomplete/Autocomplete.test.js @@ -97,12 +97,12 @@ describe('', () => { }); }); - describe('prop: filterMax', () => { + describe('prop: filterMaxTags', () => { it('show only the max number of items', () => { const { queryByTestId, getAllByRole } = render( ', () => { const { getAllByRole, queryByTestId, getByRole } = render( Date: Sun, 22 Mar 2020 15:11:32 -0300 Subject: [PATCH 3/5] yarn docs:api --- docs/pages/api-docs/autocomplete.md | 2 +- docs/src/pages/components/autocomplete/FilterMaxTags.js | 2 +- docs/src/pages/components/autocomplete/FilterMaxTags.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/pages/api-docs/autocomplete.md b/docs/pages/api-docs/autocomplete.md index 7fe170e8ff8821..c7c5761fa951a1 100644 --- a/docs/pages/api-docs/autocomplete.md +++ b/docs/pages/api-docs/autocomplete.md @@ -41,7 +41,7 @@ You can learn more about the difference by [reading this guide](/guides/minimizi | disabled | bool | false | If `true`, the input will be disabled. | | disableListWrap | bool | false | If `true`, the list box in the popup will not wrap focus. | | disablePortal | bool | false | Disable the portal behavior. The children stay within it's parent DOM hierarchy. | -| filterMax | number | | The numbers of items that will be visible | +| filterMaxTags | number | | The number of tags that will be visible. Set `-1` to display them all. | | filterOptions | func | | A filter function that determines the options that are eligible.

    **Signature:**
    `function(options: T[], state: object) => undefined`
    *options:* The options to render.
    *state:* The state of the component. | | filterSelectedOptions | bool | false | If `true`, hide the selected options from the list box. | | forcePopupIcon | 'auto'
    | bool
    | 'auto' | Force the visibility display of the popup icon. | diff --git a/docs/src/pages/components/autocomplete/FilterMaxTags.js b/docs/src/pages/components/autocomplete/FilterMaxTags.js index e8035b115e0e4b..60684e901b28b3 100644 --- a/docs/src/pages/components/autocomplete/FilterMaxTags.js +++ b/docs/src/pages/components/autocomplete/FilterMaxTags.js @@ -13,7 +13,7 @@ const useStyles = makeStyles(theme => ({ }, })); -export default function Tags() { +export default function FilterMaxTags() { const classes = useStyles(); return ( diff --git a/docs/src/pages/components/autocomplete/FilterMaxTags.tsx b/docs/src/pages/components/autocomplete/FilterMaxTags.tsx index 7347971eca1e64..bb273f94185484 100644 --- a/docs/src/pages/components/autocomplete/FilterMaxTags.tsx +++ b/docs/src/pages/components/autocomplete/FilterMaxTags.tsx @@ -1,7 +1,7 @@ /* eslint-disable no-use-before-define */ import React from 'react'; import Autocomplete from '@material-ui/lab/Autocomplete'; -import { makeStyles } from '@material-ui/core/styles'; +import { makeStyles, Theme } from '@material-ui/core/styles'; import TextField from '@material-ui/core/TextField'; const useStyles = makeStyles((theme: Theme) => ({ From 1a000080b9f0ac04e328f4c9ae794c1b5d17f84e Mon Sep 17 00:00:00 2001 From: Neto Chaves Date: Sun, 22 Mar 2020 16:48:12 -0300 Subject: [PATCH 4/5] fix: types --- .../components/autocomplete/FilterMaxTags.js | 6 ++--- .../components/autocomplete/FilterMaxTags.tsx | 22 ++++++++++--------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/docs/src/pages/components/autocomplete/FilterMaxTags.js b/docs/src/pages/components/autocomplete/FilterMaxTags.js index 60684e901b28b3..8354edf95e82b3 100644 --- a/docs/src/pages/components/autocomplete/FilterMaxTags.js +++ b/docs/src/pages/components/autocomplete/FilterMaxTags.js @@ -4,7 +4,7 @@ import Autocomplete from '@material-ui/lab/Autocomplete'; import { makeStyles } from '@material-ui/core/styles'; import TextField from '@material-ui/core/TextField'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ root: { width: 500, '& > * + *': { @@ -23,9 +23,9 @@ export default function FilterMaxTags() { filterMaxTags={2} id="tags-standard" options={top100Films} - getOptionLabel={option => option.title} + getOptionLabel={(option) => option.title} defaultValue={[top100Films[13], top100Films[12], top100Films[11]]} - renderInput={params => ( + renderInput={(params) => ( )} /> diff --git a/docs/src/pages/components/autocomplete/FilterMaxTags.tsx b/docs/src/pages/components/autocomplete/FilterMaxTags.tsx index bb273f94185484..a64ea509b30b77 100644 --- a/docs/src/pages/components/autocomplete/FilterMaxTags.tsx +++ b/docs/src/pages/components/autocomplete/FilterMaxTags.tsx @@ -1,17 +1,19 @@ /* eslint-disable no-use-before-define */ import React from 'react'; import Autocomplete from '@material-ui/lab/Autocomplete'; -import { makeStyles, Theme } from '@material-ui/core/styles'; +import { makeStyles, createStyles, Theme } from '@material-ui/core/styles'; import TextField from '@material-ui/core/TextField'; -const useStyles = makeStyles((theme: Theme) => ({ - root: { - width: 500, - '& > * + *': { - marginTop: theme.spacing(3), +const useStyles = makeStyles((theme: Theme) => + createStyles({ + root: { + width: 500, + '& > * + *': { + marginTop: theme.spacing(3), + }, }, - }, -})); + }), +); export default function FilterMaxTags() { const classes = useStyles(); @@ -23,9 +25,9 @@ export default function FilterMaxTags() { filterMaxTags={2} id="tags-standard" options={top100Films} - getOptionLabel={option => option.title} + getOptionLabel={(option) => option.title} defaultValue={[top100Films[13], top100Films[12], top100Films[11]]} - renderInput={params => ( + renderInput={(params) => ( )} /> From fee15837b0e49cc7e1f4f0790cca7d5fd3e40eb3 Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Sun, 22 Mar 2020 21:32:44 +0100 Subject: [PATCH 5/5] polish :) --- docs/pages/api-docs/autocomplete.md | 3 +- .../{FilterMaxTags.js => LimitTags.js} | 6 +-- .../{FilterMaxTags.tsx => LimitTags.tsx} | 6 +-- .../components/autocomplete/autocomplete.md | 34 +++++++------- .../src/Autocomplete/Autocomplete.d.ts | 12 ++++- .../src/Autocomplete/Autocomplete.js | 33 +++++++++---- .../src/Autocomplete/Autocomplete.test.js | 46 ++++++------------- 7 files changed, 71 insertions(+), 69 deletions(-) rename docs/src/pages/components/autocomplete/{FilterMaxTags.js => LimitTags.js} (96%) rename docs/src/pages/components/autocomplete/{FilterMaxTags.tsx => LimitTags.tsx} (97%) diff --git a/docs/pages/api-docs/autocomplete.md b/docs/pages/api-docs/autocomplete.md index c7c5761fa951a1..f70c2f82aafb48 100644 --- a/docs/pages/api-docs/autocomplete.md +++ b/docs/pages/api-docs/autocomplete.md @@ -41,11 +41,11 @@ You can learn more about the difference by [reading this guide](/guides/minimizi | disabled | bool | false | If `true`, the input will be disabled. | | disableListWrap | bool | false | If `true`, the list box in the popup will not wrap focus. | | disablePortal | bool | false | Disable the portal behavior. The children stay within it's parent DOM hierarchy. | -| filterMaxTags | number | | The number of tags that will be visible. Set `-1` to display them all. | | filterOptions | func | | A filter function that determines the options that are eligible.

    **Signature:**
    `function(options: T[], state: object) => undefined`
    *options:* The options to render.
    *state:* The state of the component. | | filterSelectedOptions | bool | false | If `true`, hide the selected options from the list box. | | forcePopupIcon | 'auto'
    | bool
    | 'auto' | Force the visibility display of the popup icon. | | freeSolo | bool | false | If `true`, the Autocomplete is free solo, meaning that the user input is not bound to provided options. | +| getLimitTagsText | func | (more) => `+${more}` | The label to display when the tags are truncated (`limitTags`).

    **Signature:**
    `function(more: number) => ReactNode`
    *more:* The number of truncated tags. | | getOptionDisabled | func | | Used to determine the disabled state for a given option. | | getOptionLabel | func | (x) => x | Used to determine the string value for a given option. It's used to fill the input (and the list box options if `renderOption` is not provided). | | getOptionSelected | func | | Used to determine if an option is selected. Uses strict equality by default. | @@ -53,6 +53,7 @@ You can learn more about the difference by [reading this guide](/guides/minimizi | id | string | | This prop is used to help implement the accessibility logic. If you don't provide this prop. It falls back to a randomly generated id. | | includeInputInList | bool | false | If `true`, the highlight can move to the input. | | inputValue | string | | The input value. | +| limitTags | number | -1 | The maximum number of tags that will be visible when not focused. Set `-1` to disable the limit. | | ListboxComponent | elementType | 'ul' | The component used to render the listbox. | | ListboxProps | object | | Props applied to the Listbox element. | | loading | bool | false | If `true`, the component is in a loading state. | diff --git a/docs/src/pages/components/autocomplete/FilterMaxTags.js b/docs/src/pages/components/autocomplete/LimitTags.js similarity index 96% rename from docs/src/pages/components/autocomplete/FilterMaxTags.js rename to docs/src/pages/components/autocomplete/LimitTags.js index 8354edf95e82b3..8002327845d082 100644 --- a/docs/src/pages/components/autocomplete/FilterMaxTags.js +++ b/docs/src/pages/components/autocomplete/LimitTags.js @@ -13,20 +13,20 @@ const useStyles = makeStyles((theme) => ({ }, })); -export default function FilterMaxTags() { +export default function LimitTags() { const classes = useStyles(); return (
    option.title} defaultValue={[top100Films[13], top100Films[12], top100Films[11]]} renderInput={(params) => ( - + )} />
    diff --git a/docs/src/pages/components/autocomplete/FilterMaxTags.tsx b/docs/src/pages/components/autocomplete/LimitTags.tsx similarity index 97% rename from docs/src/pages/components/autocomplete/FilterMaxTags.tsx rename to docs/src/pages/components/autocomplete/LimitTags.tsx index a64ea509b30b77..8c7c8ea8a5af8a 100644 --- a/docs/src/pages/components/autocomplete/FilterMaxTags.tsx +++ b/docs/src/pages/components/autocomplete/LimitTags.tsx @@ -15,20 +15,20 @@ const useStyles = makeStyles((theme: Theme) => }), ); -export default function FilterMaxTags() { +export default function LimitTags() { const classes = useStyles(); return (
    option.title} defaultValue={[top100Films[13], top100Films[12], top100Films[11]]} renderInput={(params) => ( - + )} />
    diff --git a/docs/src/pages/components/autocomplete/autocomplete.md b/docs/src/pages/components/autocomplete/autocomplete.md index dd8c560324d011..dd174a00644a76 100644 --- a/docs/src/pages/components/autocomplete/autocomplete.md +++ b/docs/src/pages/components/autocomplete/autocomplete.md @@ -100,12 +100,6 @@ Also known as tags, the user is allowed to enter more than one value. {{"demo": "pages/components/autocomplete/Tags.js"}} -## Filter Max Tags - -Do you want to show only a few tags? Use the `filterMaxTags` prop. - -{{"demo": "pages/components/autocomplete/FilterMaxTags.js"}} - ### Fixed options In the event that you need to lock certain tag so that they can't be removed in the interface, you can set the chips disabled. @@ -116,6 +110,12 @@ In the event that you need to lock certain tag so that they can't be removed in {{"demo": "pages/components/autocomplete/CheckboxesTags.js"}} +### Limit tags + +You can use the `limitTags` prop to limit the number of displayed options when not focused. + +{{"demo": "pages/components/autocomplete/LimitTags.js"}} + ## Sizes Fancy smaller inputs? Use the `size` prop. @@ -149,14 +149,13 @@ import { createFilterOptions } from '@material-ui/lab/Autocomplete'; #### Arguments -1. `config` (_Object_ [optional]): - -- `config.ignoreAccents` (_Boolean_ [optional]): Defaults to `true`. Remove diacritics. -- `config.ignoreCase` (_Boolean_ [optional]): Defaults to `true`. Lowercase everything. -- `config.matchFrom` (_'any' | 'start'_ [optional]): Defaults to `'any'`. -- `config.stringify` (_Func_ [optional]): Controls how an option is converted into a string so that it can be matched against the input text fragment. -- `config.trim` (_Boolean_ [optional]): Defaults to `false`. Remove trailing spaces. -- `config.limit` (_Number_ [optional]): Default to null. Limit the number of suggested options to be shown. For example, if `config.limit` is `100`, only the first `100` matching options are shown. It can be useful if a lot of options match and virtualization wasn't set up. +1. `config` (*Object* [optional]): + - `config.ignoreAccents` (*Boolean* [optional]): Defaults to `true`. Remove diacritics. + - `config.ignoreCase` (*Boolean* [optional]): Defaults to `true`. Lowercase everything. + - `config.matchFrom` (*'any' | 'start'* [optional]): Defaults to `'any'`. + - `config.stringify` (*Func* [optional]): Controls how an option is converted into a string so that it can be matched against the input text fragment. + - `config.trim` (*Boolean* [optional]): Defaults to `false`. Remove trailing spaces. + - `config.limit` (*Number* [optional]): Default to null. Limit the number of suggested options to be shown. For example, if `config.limit` is `100`, only the first `100` matching options are shown. It can be useful if a lot of options match and virtualization wasn't set up. #### Returns @@ -170,7 +169,7 @@ const filterOptions = createFilterOptions({ stringify: option => option.title, }); -; + ``` {{"demo": "pages/components/autocomplete/Filter.js", "defaultCodeOpen": false}} @@ -182,9 +181,10 @@ For richer filtering mechanisms, like fuzzy matching, it's recommended to look a ```jsx import matchSorter from 'match-sorter'; -const filterOptions = (options, { inputValue }) => matchSorter(options, inputValue); +const filterOptions = (options, { inputValue }) => + matchSorter(options, inputValue); -; + ``` ## Virtualization diff --git a/packages/material-ui-lab/src/Autocomplete/Autocomplete.d.ts b/packages/material-ui-lab/src/Autocomplete/Autocomplete.d.ts index d6f46b985446b9..d60dea008cc3c7 100644 --- a/packages/material-ui-lab/src/Autocomplete/Autocomplete.d.ts +++ b/packages/material-ui-lab/src/Autocomplete/Autocomplete.d.ts @@ -84,9 +84,12 @@ export interface AutocompleteProps */ forcePopupIcon?: true | false | 'auto'; /** - * The number of tags that will be visible. Set `-1` to display them all. + * The label to display when the tags are truncated (`limitTags`). + * + * @param {number} more The number of truncated tags. + * @returns {ReactNode} */ - filterMaxTags?: number; + getLimitTagsText?: (more: number) => React.ReactNode; /** * The component used to render the listbox. */ @@ -105,6 +108,11 @@ export interface AutocompleteProps * For localization purposes, you can use the provided [translations](/guides/localization/). */ loadingText?: React.ReactNode; + /** + * The maximum number of tags that will be visible when not focused. + * Set `-1` to disable the limit. + */ + limitTags?: number; /** * Text to display when there are no options. * diff --git a/packages/material-ui-lab/src/Autocomplete/Autocomplete.js b/packages/material-ui-lab/src/Autocomplete/Autocomplete.js index 1752ae671bb095..3bb69ebe684323 100644 --- a/packages/material-ui-lab/src/Autocomplete/Autocomplete.js +++ b/packages/material-ui-lab/src/Autocomplete/Autocomplete.js @@ -257,6 +257,7 @@ const Autocomplete = React.forwardRef(function Autocomplete(props, ref) { filterSelectedOptions = false, forcePopupIcon = 'auto', freeSolo = false, + getLimitTagsText = (more) => `+${more}`, getOptionDisabled, getOptionLabel = (x) => x, getOptionSelected, @@ -264,12 +265,12 @@ const Autocomplete = React.forwardRef(function Autocomplete(props, ref) { id: idProp, includeInputInList = false, inputValue: inputValueProp, + limitTags = -1, ListboxComponent = 'ul', ListboxProps, loading = false, loadingText = 'Loading…', multiple = false, - filterMaxTags, noOptionsText = 'No options', onChange, onClose, @@ -341,11 +342,15 @@ const Autocomplete = React.forwardRef(function Autocomplete(props, ref) { } } - if (filterMaxTags && Array.isArray(startAdornment)) { - const more = startAdornment.length - filterMaxTags; - if (filterMaxTags && !focused && more > 0) { - startAdornment = startAdornment.splice(0, filterMaxTags); - startAdornment.push({` + ${more} more`}); + if (limitTags > -1 && Array.isArray(startAdornment)) { + const more = startAdornment.length - limitTags; + if (limitTags && !focused && more > 0) { + startAdornment = startAdornment.splice(0, limitTags); + startAdornment.push( + + {getLimitTagsText(more)} + , + ); } } @@ -581,10 +586,6 @@ Autocomplete.propTypes = { * The children stay within it's parent DOM hierarchy. */ disablePortal: PropTypes.bool, - /** - * The number of tags that will be visible. Set `-1` to display them all. - */ - filterMaxTags: PropTypes.number, /** * A filter function that determines the options that are eligible. * @@ -605,6 +606,13 @@ Autocomplete.propTypes = { * If `true`, the Autocomplete is free solo, meaning that the user input is not bound to provided options. */ freeSolo: PropTypes.bool, + /** + * The label to display when the tags are truncated (`limitTags`). + * + * @param {number} more The number of truncated tags. + * @returns {ReactNode} + */ + getLimitTagsText: PropTypes.func, /** * Used to determine the disabled state for a given option. */ @@ -640,6 +648,11 @@ Autocomplete.propTypes = { * The input value. */ inputValue: PropTypes.string, + /** + * The maximum number of tags that will be visible when not focused. + * Set `-1` to disable the limit. + */ + limitTags: PropTypes.number, /** * The component used to render the listbox. */ diff --git a/packages/material-ui-lab/src/Autocomplete/Autocomplete.test.js b/packages/material-ui-lab/src/Autocomplete/Autocomplete.test.js index e0b009be6712f5..2b3fe008b9e15b 100644 --- a/packages/material-ui-lab/src/Autocomplete/Autocomplete.test.js +++ b/packages/material-ui-lab/src/Autocomplete/Autocomplete.test.js @@ -97,48 +97,28 @@ describe('', () => { }); }); - describe('prop: filterMaxTags', () => { - it('show only the max number of items', () => { - const { queryByTestId, getAllByRole } = render( - } - />, - ); - - const tags = getAllByRole('button'); - expect(queryByTestId('more')).to.be.exist; - expect(tags[0].textContent).to.be.equal('one'); - expect(tags[1].textContent).to.be.equal('two'); - expect(tags[2].textContent).to.not.be.equal('three'); - }); - + describe('prop: limitTags', () => { it('show all items on focus', () => { - const { getAllByRole, queryByTestId, getByRole } = render( + const { container, getAllByRole, getByRole } = render( } + renderInput={(params) => } />, ); - const input = getByRole('textbox'); - expect(getAllByRole('button')[2].textContent).to.not.be.equal('three'); - expect(queryByTestId('more')).to.exist; + let tags; + tags = getAllByRole('button'); + expect(container.textContent).to.equal('onetwo+1'); + expect(tags.length).to.be.equal(4); - input.focus(); - const tags = getAllByRole('button'); - expect(queryByTestId('more')).to.not.exist; - expect(tags[0].textContent).to.be.equal('one'); - expect(tags[1].textContent).to.be.equal('two'); - expect(tags[2].textContent).to.be.equal('three'); + getByRole('textbox').focus(); + tags = getAllByRole('button'); + expect(container.textContent).to.equal('onetwothree'); + expect(tags.length).to.be.equal(5); }); });