From 99246f60f35a738b1eca76a37ddcd640b4dcc85f Mon Sep 17 00:00:00 2001 From: Siriwat K Date: Tue, 8 Nov 2022 13:39:48 +0700 Subject: [PATCH] [Joy] Add `Autocomplete` component (#34315) --- .../components/autocomplete/Asynchronous.js | 120 + .../components/autocomplete/Asynchronous.tsx | 120 + .../autocomplete/AutocompleteDecorators.js | 151 ++ .../autocomplete/AutocompleteDecorators.tsx | 151 ++ .../AutocompleteDecorators.tsx.preview | 12 + .../autocomplete/AutocompleteError.js | 143 ++ .../autocomplete/AutocompleteError.tsx | 143 ++ .../AutocompleteError.tsx.preview | 5 + .../autocomplete/AutocompleteVariables.js | 219 ++ .../autocomplete/BasicAutocomplete.js | 140 ++ .../autocomplete/BasicAutocomplete.tsx | 140 ++ .../BasicAutocomplete.tsx.preview | 5 + .../autocomplete/ControllableStates.js | 50 + .../autocomplete/ControllableStates.tsx | 50 + .../components/autocomplete/CountrySelect.js | 469 ++++ .../components/autocomplete/CountrySelect.tsx | 469 ++++ .../joy/components/autocomplete/CustomTags.js | 158 ++ .../components/autocomplete/CustomTags.tsx | 158 ++ .../autocomplete/DisabledOptions.js | 28 + .../autocomplete/DisabledOptions.tsx | 28 + .../autocomplete/DisabledOptions.tsx.preview | 11 + .../joy/components/autocomplete/Filter.js | 152 ++ .../joy/components/autocomplete/Filter.tsx | 152 ++ .../autocomplete/Filter.tsx.preview | 10 + .../joy/components/autocomplete/FixedTags.js | 170 ++ .../joy/components/autocomplete/FixedTags.tsx | 170 ++ .../joy/components/autocomplete/FreeSolo.js | 158 ++ .../joy/components/autocomplete/FreeSolo.tsx | 158 ++ .../autocomplete/FreeSoloCreateOption.js | 208 ++ .../autocomplete/FreeSoloCreateOption.tsx | 213 ++ .../FreeSoloCreateOptionDialog.js | 288 +++ .../FreeSoloCreateOptionDialog.tsx | 294 +++ .../components/autocomplete/GitHubLabel.js | 311 +++ .../components/autocomplete/GitHubLabel.tsx | 317 +++ .../joy/components/autocomplete/Grouped.js | 154 ++ .../joy/components/autocomplete/Grouped.tsx | 154 ++ .../autocomplete/Grouped.tsx.preview | 9 + .../joy/components/autocomplete/Highlights.js | 176 ++ .../components/autocomplete/Highlights.tsx | 176 ++ .../autocomplete/InputAppearance.js | 161 ++ .../autocomplete/InputAppearance.tsx | 161 ++ .../autocomplete/LabelAndHelperText.js | 147 ++ .../autocomplete/LabelAndHelperText.tsx | 147 ++ .../LabelAndHelperText.tsx.preview | 9 + .../joy/components/autocomplete/LimitTags.js | 149 ++ .../joy/components/autocomplete/LimitTags.tsx | 149 ++ .../autocomplete/LimitTags.tsx.preview | 12 + .../autocomplete/OptionStructure.js | 140 ++ .../autocomplete/OptionStructure.tsx | 140 ++ .../autocomplete/OptionStructure.tsx.preview | 5 + .../joy/components/autocomplete/Playground.js | 406 +++ .../components/autocomplete/SizeWithLabel.js | 149 ++ .../components/autocomplete/SizeWithLabel.tsx | 149 ++ .../autocomplete/SizeWithLabel.tsx.preview | 11 + .../data/joy/components/autocomplete/Sizes.js | 162 ++ .../joy/components/autocomplete/Sizes.tsx | 162 ++ docs/data/joy/components/autocomplete/Tags.js | 143 ++ .../data/joy/components/autocomplete/Tags.tsx | 143 ++ .../components/autocomplete/Tags.tsx.preview | 8 + .../joy/components/autocomplete/Virtualize.js | 135 + .../components/autocomplete/Virtualize.tsx | 135 + .../autocomplete/Virtualize.tsx.preview | 16 + .../components/autocomplete/autocomplete.md | 334 +++ .../data/joy/components/chip/ChipVariables.js | 3 + docs/data/joy/pages.ts | 1 + docs/pages/joy-ui/react-autocomplete.js | 7 + .../AutocompleteUnstyled/useAutocomplete.d.ts | 17 +- .../AutocompleteUnstyled/useAutocomplete.js | 24 +- packages/mui-base/src/utils/index.ts | 1 + .../src/Autocomplete/Autocomplete.spec.tsx | 18 + .../src/Autocomplete/Autocomplete.test.tsx | 2184 +++++++++++++++++ .../mui-joy/src/Autocomplete/Autocomplete.tsx | 1078 ++++++++ .../src/Autocomplete/AutocompleteProps.ts | 367 +++ .../src/Autocomplete/autocompleteClasses.ts | 114 + packages/mui-joy/src/Autocomplete/index.ts | 5 + .../AutocompleteListbox.test.tsx | 54 + .../AutocompleteListbox.tsx | 177 ++ .../AutocompleteListboxProps.ts | 40 + .../autocompleteListboxClasses.ts | 60 + .../mui-joy/src/AutocompleteListbox/index.ts | 4 + .../AutocompleteOption.test.js | 43 + .../AutocompleteOption/AutocompleteOption.tsx | 135 + .../AutocompleteOptionProps.ts | 35 + .../autocompleteOptionClasses.ts | 57 + .../mui-joy/src/AutocompleteOption/index.ts | 4 + packages/mui-joy/src/Chip/Chip.tsx | 40 +- .../src/FormControl/FormControl.test.tsx | 45 + .../mui-joy/src/IconButton/IconButton.tsx | 108 +- packages/mui-joy/src/Input/Input.tsx | 290 +-- packages/mui-joy/src/List/List.tsx | 12 +- packages/mui-joy/src/List/ListProps.ts | 2 +- packages/mui-joy/src/List/ListProvider.tsx | 2 +- packages/mui-joy/src/ListItem/ListItem.tsx | 2 +- .../src/ListItemButton/ListItemButton.tsx | 120 +- packages/mui-joy/src/Select/Select.tsx | 6 +- packages/mui-joy/src/Stack/StackProps.ts | 4 +- packages/mui-joy/src/Textarea/Textarea.tsx | 4 +- .../src/internal/svg-icons/ArrowDropDown.tsx | 7 + packages/mui-joy/src/styles/components.d.ts | 39 + .../mui-joy/src/styles/extendTheme.spec.ts | 122 + packages/mui-joy/src/utils/useSlot.test.tsx | 227 ++ packages/mui-joy/src/utils/useSlot.ts | 161 ++ test/utils/initMatchers.ts | 4 +- 103 files changed, 14541 insertions(+), 295 deletions(-) create mode 100644 docs/data/joy/components/autocomplete/Asynchronous.js create mode 100644 docs/data/joy/components/autocomplete/Asynchronous.tsx create mode 100644 docs/data/joy/components/autocomplete/AutocompleteDecorators.js create mode 100644 docs/data/joy/components/autocomplete/AutocompleteDecorators.tsx create mode 100644 docs/data/joy/components/autocomplete/AutocompleteDecorators.tsx.preview create mode 100644 docs/data/joy/components/autocomplete/AutocompleteError.js create mode 100644 docs/data/joy/components/autocomplete/AutocompleteError.tsx create mode 100644 docs/data/joy/components/autocomplete/AutocompleteError.tsx.preview create mode 100644 docs/data/joy/components/autocomplete/AutocompleteVariables.js create mode 100644 docs/data/joy/components/autocomplete/BasicAutocomplete.js create mode 100644 docs/data/joy/components/autocomplete/BasicAutocomplete.tsx create mode 100644 docs/data/joy/components/autocomplete/BasicAutocomplete.tsx.preview create mode 100644 docs/data/joy/components/autocomplete/ControllableStates.js create mode 100644 docs/data/joy/components/autocomplete/ControllableStates.tsx create mode 100644 docs/data/joy/components/autocomplete/CountrySelect.js create mode 100644 docs/data/joy/components/autocomplete/CountrySelect.tsx create mode 100644 docs/data/joy/components/autocomplete/CustomTags.js create mode 100644 docs/data/joy/components/autocomplete/CustomTags.tsx create mode 100644 docs/data/joy/components/autocomplete/DisabledOptions.js create mode 100644 docs/data/joy/components/autocomplete/DisabledOptions.tsx create mode 100644 docs/data/joy/components/autocomplete/DisabledOptions.tsx.preview create mode 100644 docs/data/joy/components/autocomplete/Filter.js create mode 100644 docs/data/joy/components/autocomplete/Filter.tsx create mode 100644 docs/data/joy/components/autocomplete/Filter.tsx.preview create mode 100644 docs/data/joy/components/autocomplete/FixedTags.js create mode 100644 docs/data/joy/components/autocomplete/FixedTags.tsx create mode 100644 docs/data/joy/components/autocomplete/FreeSolo.js create mode 100644 docs/data/joy/components/autocomplete/FreeSolo.tsx create mode 100644 docs/data/joy/components/autocomplete/FreeSoloCreateOption.js create mode 100644 docs/data/joy/components/autocomplete/FreeSoloCreateOption.tsx create mode 100644 docs/data/joy/components/autocomplete/FreeSoloCreateOptionDialog.js create mode 100644 docs/data/joy/components/autocomplete/FreeSoloCreateOptionDialog.tsx create mode 100644 docs/data/joy/components/autocomplete/GitHubLabel.js create mode 100644 docs/data/joy/components/autocomplete/GitHubLabel.tsx create mode 100644 docs/data/joy/components/autocomplete/Grouped.js create mode 100644 docs/data/joy/components/autocomplete/Grouped.tsx create mode 100644 docs/data/joy/components/autocomplete/Grouped.tsx.preview create mode 100644 docs/data/joy/components/autocomplete/Highlights.js create mode 100644 docs/data/joy/components/autocomplete/Highlights.tsx create mode 100644 docs/data/joy/components/autocomplete/InputAppearance.js create mode 100644 docs/data/joy/components/autocomplete/InputAppearance.tsx create mode 100644 docs/data/joy/components/autocomplete/LabelAndHelperText.js create mode 100644 docs/data/joy/components/autocomplete/LabelAndHelperText.tsx create mode 100644 docs/data/joy/components/autocomplete/LabelAndHelperText.tsx.preview create mode 100644 docs/data/joy/components/autocomplete/LimitTags.js create mode 100644 docs/data/joy/components/autocomplete/LimitTags.tsx create mode 100644 docs/data/joy/components/autocomplete/LimitTags.tsx.preview create mode 100644 docs/data/joy/components/autocomplete/OptionStructure.js create mode 100644 docs/data/joy/components/autocomplete/OptionStructure.tsx create mode 100644 docs/data/joy/components/autocomplete/OptionStructure.tsx.preview create mode 100644 docs/data/joy/components/autocomplete/Playground.js create mode 100644 docs/data/joy/components/autocomplete/SizeWithLabel.js create mode 100644 docs/data/joy/components/autocomplete/SizeWithLabel.tsx create mode 100644 docs/data/joy/components/autocomplete/SizeWithLabel.tsx.preview create mode 100644 docs/data/joy/components/autocomplete/Sizes.js create mode 100644 docs/data/joy/components/autocomplete/Sizes.tsx create mode 100644 docs/data/joy/components/autocomplete/Tags.js create mode 100644 docs/data/joy/components/autocomplete/Tags.tsx create mode 100644 docs/data/joy/components/autocomplete/Tags.tsx.preview create mode 100644 docs/data/joy/components/autocomplete/Virtualize.js create mode 100644 docs/data/joy/components/autocomplete/Virtualize.tsx create mode 100644 docs/data/joy/components/autocomplete/Virtualize.tsx.preview create mode 100644 docs/data/joy/components/autocomplete/autocomplete.md create mode 100644 docs/pages/joy-ui/react-autocomplete.js create mode 100644 packages/mui-joy/src/Autocomplete/Autocomplete.spec.tsx create mode 100644 packages/mui-joy/src/Autocomplete/Autocomplete.test.tsx create mode 100644 packages/mui-joy/src/Autocomplete/Autocomplete.tsx create mode 100644 packages/mui-joy/src/Autocomplete/AutocompleteProps.ts create mode 100644 packages/mui-joy/src/Autocomplete/autocompleteClasses.ts create mode 100644 packages/mui-joy/src/Autocomplete/index.ts create mode 100644 packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.test.tsx create mode 100644 packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.tsx create mode 100644 packages/mui-joy/src/AutocompleteListbox/AutocompleteListboxProps.ts create mode 100644 packages/mui-joy/src/AutocompleteListbox/autocompleteListboxClasses.ts create mode 100644 packages/mui-joy/src/AutocompleteListbox/index.ts create mode 100644 packages/mui-joy/src/AutocompleteOption/AutocompleteOption.test.js create mode 100644 packages/mui-joy/src/AutocompleteOption/AutocompleteOption.tsx create mode 100644 packages/mui-joy/src/AutocompleteOption/AutocompleteOptionProps.ts create mode 100644 packages/mui-joy/src/AutocompleteOption/autocompleteOptionClasses.ts create mode 100644 packages/mui-joy/src/AutocompleteOption/index.ts create mode 100644 packages/mui-joy/src/internal/svg-icons/ArrowDropDown.tsx create mode 100644 packages/mui-joy/src/utils/useSlot.test.tsx create mode 100644 packages/mui-joy/src/utils/useSlot.ts diff --git a/docs/data/joy/components/autocomplete/Asynchronous.js b/docs/data/joy/components/autocomplete/Asynchronous.js new file mode 100644 index 00000000000000..a449988fc78e95 --- /dev/null +++ b/docs/data/joy/components/autocomplete/Asynchronous.js @@ -0,0 +1,120 @@ +import * as React from 'react'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import Autocomplete from '@mui/joy/Autocomplete'; +import CircularProgress from '@mui/joy/CircularProgress'; + +function sleep(delay = 0) { + return new Promise((resolve) => { + setTimeout(resolve, delay); + }); +} + +export default function Asynchronous() { + const [open, setOpen] = React.useState(false); + const [options, setOptions] = React.useState([]); + const loading = open && options.length === 0; + + React.useEffect(() => { + let active = true; + + if (!loading) { + return undefined; + } + + (async () => { + await sleep(1e3); // For demo purposes. + + if (active) { + setOptions([...topFilms]); + } + })(); + + return () => { + active = false; + }; + }, [loading]); + + React.useEffect(() => { + if (!open) { + setOptions([]); + } + }, [open]); + + return ( + + Asynchronous + { + setOpen(true); + }} + onClose={() => { + setOpen(false); + }} + isOptionEqualToValue={(option, value) => option.title === value.title} + getOptionLabel={(option) => option.title} + options={options} + loading={loading} + endDecorator={ + loading ? ( + + ) : null + } + /> + + ); +} + +// Top films as rated by IMDb users. http://www.imdb.com/chart/top +const topFilms = [ + { 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 }, +]; diff --git a/docs/data/joy/components/autocomplete/Asynchronous.tsx b/docs/data/joy/components/autocomplete/Asynchronous.tsx new file mode 100644 index 00000000000000..e1379748d3c5bc --- /dev/null +++ b/docs/data/joy/components/autocomplete/Asynchronous.tsx @@ -0,0 +1,120 @@ +import * as React from 'react'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import Autocomplete from '@mui/joy/Autocomplete'; +import CircularProgress from '@mui/joy/CircularProgress'; + +function sleep(delay = 0) { + return new Promise((resolve) => { + setTimeout(resolve, delay); + }); +} + +export default function Asynchronous() { + const [open, setOpen] = React.useState(false); + const [options, setOptions] = React.useState([]); + const loading = open && options.length === 0; + + React.useEffect(() => { + let active = true; + + if (!loading) { + return undefined; + } + + (async () => { + await sleep(1e3); // For demo purposes. + + if (active) { + setOptions([...topFilms]); + } + })(); + + return () => { + active = false; + }; + }, [loading]); + + React.useEffect(() => { + if (!open) { + setOptions([]); + } + }, [open]); + + return ( + + Asynchronous + { + setOpen(true); + }} + onClose={() => { + setOpen(false); + }} + isOptionEqualToValue={(option, value) => option.title === value.title} + getOptionLabel={(option) => option.title} + options={options} + loading={loading} + endDecorator={ + loading ? ( + + ) : null + } + /> + + ); +} + +// Top films as rated by IMDb users. http://www.imdb.com/chart/top +const topFilms = [ + { 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 }, +]; diff --git a/docs/data/joy/components/autocomplete/AutocompleteDecorators.js b/docs/data/joy/components/autocomplete/AutocompleteDecorators.js new file mode 100644 index 00000000000000..51ea77495a152d --- /dev/null +++ b/docs/data/joy/components/autocomplete/AutocompleteDecorators.js @@ -0,0 +1,151 @@ +import * as React from 'react'; +import Autocomplete from '@mui/joy/Autocomplete'; +import Stack from '@mui/joy/Stack'; +import LiveTv from '@mui/icons-material/LiveTv'; + +export default function ComboBox() { + return ( + + } + placeholder="Decorators" + options={top100Films} + /> + } + placeholder="Decorators" + options={top100Films} + defaultValue={[top100Films[0]]} + /> + + ); +} + +// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top +const top100Films = [ + { label: 'The Shawshank Redemption', year: 1994 }, + { label: 'The Godfather', year: 1972 }, + { label: 'The Godfather: Part II', year: 1974 }, + { label: 'The Dark Knight', year: 2008 }, + { label: '12 Angry Men', year: 1957 }, + { label: "Schindler's List", year: 1993 }, + { label: 'Pulp Fiction', year: 1994 }, + { + label: 'The Lord of the Rings: The Return of the King', + year: 2003, + }, + { label: 'The Good, the Bad and the Ugly', year: 1966 }, + { label: 'Fight Club', year: 1999 }, + { + label: 'The Lord of the Rings: The Fellowship of the Ring', + year: 2001, + }, + { + label: 'Star Wars: Episode V - The Empire Strikes Back', + year: 1980, + }, + { label: 'Forrest Gump', year: 1994 }, + { label: 'Inception', year: 2010 }, + { + label: 'The Lord of the Rings: The Two Towers', + year: 2002, + }, + { label: "One Flew Over the Cuckoo's Nest", year: 1975 }, + { label: 'Goodfellas', year: 1990 }, + { label: 'The Matrix', year: 1999 }, + { label: 'Seven Samurai', year: 1954 }, + { + label: 'Star Wars: Episode IV - A New Hope', + year: 1977, + }, + { label: 'City of God', year: 2002 }, + { label: 'Se7en', year: 1995 }, + { label: 'The Silence of the Lambs', year: 1991 }, + { label: "It's a Wonderful Life", year: 1946 }, + { label: 'Life Is Beautiful', year: 1997 }, + { label: 'The Usual Suspects', year: 1995 }, + { label: 'Léon: The Professional', year: 1994 }, + { label: 'Spirited Away', year: 2001 }, + { label: 'Saving Private Ryan', year: 1998 }, + { label: 'Once Upon a Time in the West', year: 1968 }, + { label: 'American History X', year: 1998 }, + { label: 'Interstellar', year: 2014 }, + { label: 'Casablanca', year: 1942 }, + { label: 'City Lights', year: 1931 }, + { label: 'Psycho', year: 1960 }, + { label: 'The Green Mile', year: 1999 }, + { label: 'The Intouchables', year: 2011 }, + { label: 'Modern Times', year: 1936 }, + { label: 'Raiders of the Lost Ark', year: 1981 }, + { label: 'Rear Window', year: 1954 }, + { label: 'The Pianist', year: 2002 }, + { label: 'The Departed', year: 2006 }, + { label: 'Terminator 2: Judgment Day', year: 1991 }, + { label: 'Back to the Future', year: 1985 }, + { label: 'Whiplash', year: 2014 }, + { label: 'Gladiator', year: 2000 }, + { label: 'Memento', year: 2000 }, + { label: 'The Prestige', year: 2006 }, + { label: 'The Lion King', year: 1994 }, + { label: 'Apocalypse Now', year: 1979 }, + { label: 'Alien', year: 1979 }, + { label: 'Sunset Boulevard', year: 1950 }, + { + label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb', + year: 1964, + }, + { label: 'The Great Dictator', year: 1940 }, + { label: 'Cinema Paradiso', year: 1988 }, + { label: 'The Lives of Others', year: 2006 }, + { label: 'Grave of the Fireflies', year: 1988 }, + { label: 'Paths of Glory', year: 1957 }, + { label: 'Django Unchained', year: 2012 }, + { label: 'The Shining', year: 1980 }, + { label: 'WALL·E', year: 2008 }, + { label: 'American Beauty', year: 1999 }, + { label: 'The Dark Knight Rises', year: 2012 }, + { label: 'Princess Mononoke', year: 1997 }, + { label: 'Aliens', year: 1986 }, + { label: 'Oldboy', year: 2003 }, + { label: 'Once Upon a Time in America', year: 1984 }, + { label: 'Witness for the Prosecution', year: 1957 }, + { label: 'Das Boot', year: 1981 }, + { label: 'Citizen Kane', year: 1941 }, + { label: 'North by Northwest', year: 1959 }, + { label: 'Vertigo', year: 1958 }, + { + label: 'Star Wars: Episode VI - Return of the Jedi', + year: 1983, + }, + { label: 'Reservoir Dogs', year: 1992 }, + { label: 'Braveheart', year: 1995 }, + { label: 'M', year: 1931 }, + { label: 'Requiem for a Dream', year: 2000 }, + { label: 'Amélie', year: 2001 }, + { label: 'A Clockwork Orange', year: 1971 }, + { label: 'Like Stars on Earth', year: 2007 }, + { label: 'Taxi Driver', year: 1976 }, + { label: 'Lawrence of Arabia', year: 1962 }, + { label: 'Double Indemnity', year: 1944 }, + { + label: 'Eternal Sunshine of the Spotless Mind', + year: 2004, + }, + { label: 'Amadeus', year: 1984 }, + { label: 'To Kill a Mockingbird', year: 1962 }, + { label: 'Toy Story 3', year: 2010 }, + { label: 'Logan', year: 2017 }, + { label: 'Full Metal Jacket', year: 1987 }, + { label: 'Dangal', year: 2016 }, + { label: 'The Sting', year: 1973 }, + { label: '2001: A Space Odyssey', year: 1968 }, + { label: "Singin' in the Rain", year: 1952 }, + { label: 'Toy Story', year: 1995 }, + { label: 'Bicycle Thieves', year: 1948 }, + { label: 'The Kid', year: 1921 }, + { label: 'Inglourious Basterds', year: 2009 }, + { label: 'Snatch', year: 2000 }, + { label: '3 Idiots', year: 2009 }, + { label: 'Monty Python and the Holy Grail', year: 1975 }, +]; diff --git a/docs/data/joy/components/autocomplete/AutocompleteDecorators.tsx b/docs/data/joy/components/autocomplete/AutocompleteDecorators.tsx new file mode 100644 index 00000000000000..51ea77495a152d --- /dev/null +++ b/docs/data/joy/components/autocomplete/AutocompleteDecorators.tsx @@ -0,0 +1,151 @@ +import * as React from 'react'; +import Autocomplete from '@mui/joy/Autocomplete'; +import Stack from '@mui/joy/Stack'; +import LiveTv from '@mui/icons-material/LiveTv'; + +export default function ComboBox() { + return ( + + } + placeholder="Decorators" + options={top100Films} + /> + } + placeholder="Decorators" + options={top100Films} + defaultValue={[top100Films[0]]} + /> + + ); +} + +// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top +const top100Films = [ + { label: 'The Shawshank Redemption', year: 1994 }, + { label: 'The Godfather', year: 1972 }, + { label: 'The Godfather: Part II', year: 1974 }, + { label: 'The Dark Knight', year: 2008 }, + { label: '12 Angry Men', year: 1957 }, + { label: "Schindler's List", year: 1993 }, + { label: 'Pulp Fiction', year: 1994 }, + { + label: 'The Lord of the Rings: The Return of the King', + year: 2003, + }, + { label: 'The Good, the Bad and the Ugly', year: 1966 }, + { label: 'Fight Club', year: 1999 }, + { + label: 'The Lord of the Rings: The Fellowship of the Ring', + year: 2001, + }, + { + label: 'Star Wars: Episode V - The Empire Strikes Back', + year: 1980, + }, + { label: 'Forrest Gump', year: 1994 }, + { label: 'Inception', year: 2010 }, + { + label: 'The Lord of the Rings: The Two Towers', + year: 2002, + }, + { label: "One Flew Over the Cuckoo's Nest", year: 1975 }, + { label: 'Goodfellas', year: 1990 }, + { label: 'The Matrix', year: 1999 }, + { label: 'Seven Samurai', year: 1954 }, + { + label: 'Star Wars: Episode IV - A New Hope', + year: 1977, + }, + { label: 'City of God', year: 2002 }, + { label: 'Se7en', year: 1995 }, + { label: 'The Silence of the Lambs', year: 1991 }, + { label: "It's a Wonderful Life", year: 1946 }, + { label: 'Life Is Beautiful', year: 1997 }, + { label: 'The Usual Suspects', year: 1995 }, + { label: 'Léon: The Professional', year: 1994 }, + { label: 'Spirited Away', year: 2001 }, + { label: 'Saving Private Ryan', year: 1998 }, + { label: 'Once Upon a Time in the West', year: 1968 }, + { label: 'American History X', year: 1998 }, + { label: 'Interstellar', year: 2014 }, + { label: 'Casablanca', year: 1942 }, + { label: 'City Lights', year: 1931 }, + { label: 'Psycho', year: 1960 }, + { label: 'The Green Mile', year: 1999 }, + { label: 'The Intouchables', year: 2011 }, + { label: 'Modern Times', year: 1936 }, + { label: 'Raiders of the Lost Ark', year: 1981 }, + { label: 'Rear Window', year: 1954 }, + { label: 'The Pianist', year: 2002 }, + { label: 'The Departed', year: 2006 }, + { label: 'Terminator 2: Judgment Day', year: 1991 }, + { label: 'Back to the Future', year: 1985 }, + { label: 'Whiplash', year: 2014 }, + { label: 'Gladiator', year: 2000 }, + { label: 'Memento', year: 2000 }, + { label: 'The Prestige', year: 2006 }, + { label: 'The Lion King', year: 1994 }, + { label: 'Apocalypse Now', year: 1979 }, + { label: 'Alien', year: 1979 }, + { label: 'Sunset Boulevard', year: 1950 }, + { + label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb', + year: 1964, + }, + { label: 'The Great Dictator', year: 1940 }, + { label: 'Cinema Paradiso', year: 1988 }, + { label: 'The Lives of Others', year: 2006 }, + { label: 'Grave of the Fireflies', year: 1988 }, + { label: 'Paths of Glory', year: 1957 }, + { label: 'Django Unchained', year: 2012 }, + { label: 'The Shining', year: 1980 }, + { label: 'WALL·E', year: 2008 }, + { label: 'American Beauty', year: 1999 }, + { label: 'The Dark Knight Rises', year: 2012 }, + { label: 'Princess Mononoke', year: 1997 }, + { label: 'Aliens', year: 1986 }, + { label: 'Oldboy', year: 2003 }, + { label: 'Once Upon a Time in America', year: 1984 }, + { label: 'Witness for the Prosecution', year: 1957 }, + { label: 'Das Boot', year: 1981 }, + { label: 'Citizen Kane', year: 1941 }, + { label: 'North by Northwest', year: 1959 }, + { label: 'Vertigo', year: 1958 }, + { + label: 'Star Wars: Episode VI - Return of the Jedi', + year: 1983, + }, + { label: 'Reservoir Dogs', year: 1992 }, + { label: 'Braveheart', year: 1995 }, + { label: 'M', year: 1931 }, + { label: 'Requiem for a Dream', year: 2000 }, + { label: 'Amélie', year: 2001 }, + { label: 'A Clockwork Orange', year: 1971 }, + { label: 'Like Stars on Earth', year: 2007 }, + { label: 'Taxi Driver', year: 1976 }, + { label: 'Lawrence of Arabia', year: 1962 }, + { label: 'Double Indemnity', year: 1944 }, + { + label: 'Eternal Sunshine of the Spotless Mind', + year: 2004, + }, + { label: 'Amadeus', year: 1984 }, + { label: 'To Kill a Mockingbird', year: 1962 }, + { label: 'Toy Story 3', year: 2010 }, + { label: 'Logan', year: 2017 }, + { label: 'Full Metal Jacket', year: 1987 }, + { label: 'Dangal', year: 2016 }, + { label: 'The Sting', year: 1973 }, + { label: '2001: A Space Odyssey', year: 1968 }, + { label: "Singin' in the Rain", year: 1952 }, + { label: 'Toy Story', year: 1995 }, + { label: 'Bicycle Thieves', year: 1948 }, + { label: 'The Kid', year: 1921 }, + { label: 'Inglourious Basterds', year: 2009 }, + { label: 'Snatch', year: 2000 }, + { label: '3 Idiots', year: 2009 }, + { label: 'Monty Python and the Holy Grail', year: 1975 }, +]; diff --git a/docs/data/joy/components/autocomplete/AutocompleteDecorators.tsx.preview b/docs/data/joy/components/autocomplete/AutocompleteDecorators.tsx.preview new file mode 100644 index 00000000000000..b0f6a39650f860 --- /dev/null +++ b/docs/data/joy/components/autocomplete/AutocompleteDecorators.tsx.preview @@ -0,0 +1,12 @@ +} + placeholder="Decorators" + options={top100Films} +/> +} + placeholder="Decorators" + options={top100Films} + defaultValue={[top100Films[0]]} +/> \ No newline at end of file diff --git a/docs/data/joy/components/autocomplete/AutocompleteError.js b/docs/data/joy/components/autocomplete/AutocompleteError.js new file mode 100644 index 00000000000000..3a292864e9c8c2 --- /dev/null +++ b/docs/data/joy/components/autocomplete/AutocompleteError.js @@ -0,0 +1,143 @@ +import * as React from 'react'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import FormHelperText from '@mui/joy/FormHelperText'; +import Autocomplete from '@mui/joy/Autocomplete'; + +export default function InputAppearance() { + return ( + + Invalid + + Opps! something went wrong. + + ); +} + +// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top +const top100Films = [ + { label: 'The Shawshank Redemption', year: 1994 }, + { label: 'The Godfather', year: 1972 }, + { label: 'The Godfather: Part II', year: 1974 }, + { label: 'The Dark Knight', year: 2008 }, + { label: '12 Angry Men', year: 1957 }, + { label: "Schindler's List", year: 1993 }, + { label: 'Pulp Fiction', year: 1994 }, + { + label: 'The Lord of the Rings: The Return of the King', + year: 2003, + }, + { label: 'The Good, the Bad and the Ugly', year: 1966 }, + { label: 'Fight Club', year: 1999 }, + { + label: 'The Lord of the Rings: The Fellowship of the Ring', + year: 2001, + }, + { + label: 'Star Wars: Episode V - The Empire Strikes Back', + year: 1980, + }, + { label: 'Forrest Gump', year: 1994 }, + { label: 'Inception', year: 2010 }, + { + label: 'The Lord of the Rings: The Two Towers', + year: 2002, + }, + { label: "One Flew Over the Cuckoo's Nest", year: 1975 }, + { label: 'Goodfellas', year: 1990 }, + { label: 'The Matrix', year: 1999 }, + { label: 'Seven Samurai', year: 1954 }, + { + label: 'Star Wars: Episode IV - A New Hope', + year: 1977, + }, + { label: 'City of God', year: 2002 }, + { label: 'Se7en', year: 1995 }, + { label: 'The Silence of the Lambs', year: 1991 }, + { label: "It's a Wonderful Life", year: 1946 }, + { label: 'Life Is Beautiful', year: 1997 }, + { label: 'The Usual Suspects', year: 1995 }, + { label: 'Léon: The Professional', year: 1994 }, + { label: 'Spirited Away', year: 2001 }, + { label: 'Saving Private Ryan', year: 1998 }, + { label: 'Once Upon a Time in the West', year: 1968 }, + { label: 'American History X', year: 1998 }, + { label: 'Interstellar', year: 2014 }, + { label: 'Casablanca', year: 1942 }, + { label: 'City Lights', year: 1931 }, + { label: 'Psycho', year: 1960 }, + { label: 'The Green Mile', year: 1999 }, + { label: 'The Intouchables', year: 2011 }, + { label: 'Modern Times', year: 1936 }, + { label: 'Raiders of the Lost Ark', year: 1981 }, + { label: 'Rear Window', year: 1954 }, + { label: 'The Pianist', year: 2002 }, + { label: 'The Departed', year: 2006 }, + { label: 'Terminator 2: Judgment Day', year: 1991 }, + { label: 'Back to the Future', year: 1985 }, + { label: 'Whiplash', year: 2014 }, + { label: 'Gladiator', year: 2000 }, + { label: 'Memento', year: 2000 }, + { label: 'The Prestige', year: 2006 }, + { label: 'The Lion King', year: 1994 }, + { label: 'Apocalypse Now', year: 1979 }, + { label: 'Alien', year: 1979 }, + { label: 'Sunset Boulevard', year: 1950 }, + { + label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb', + year: 1964, + }, + { label: 'The Great Dictator', year: 1940 }, + { label: 'Cinema Paradiso', year: 1988 }, + { label: 'The Lives of Others', year: 2006 }, + { label: 'Grave of the Fireflies', year: 1988 }, + { label: 'Paths of Glory', year: 1957 }, + { label: 'Django Unchained', year: 2012 }, + { label: 'The Shining', year: 1980 }, + { label: 'WALL·E', year: 2008 }, + { label: 'American Beauty', year: 1999 }, + { label: 'The Dark Knight Rises', year: 2012 }, + { label: 'Princess Mononoke', year: 1997 }, + { label: 'Aliens', year: 1986 }, + { label: 'Oldboy', year: 2003 }, + { label: 'Once Upon a Time in America', year: 1984 }, + { label: 'Witness for the Prosecution', year: 1957 }, + { label: 'Das Boot', year: 1981 }, + { label: 'Citizen Kane', year: 1941 }, + { label: 'North by Northwest', year: 1959 }, + { label: 'Vertigo', year: 1958 }, + { + label: 'Star Wars: Episode VI - Return of the Jedi', + year: 1983, + }, + { label: 'Reservoir Dogs', year: 1992 }, + { label: 'Braveheart', year: 1995 }, + { label: 'M', year: 1931 }, + { label: 'Requiem for a Dream', year: 2000 }, + { label: 'Amélie', year: 2001 }, + { label: 'A Clockwork Orange', year: 1971 }, + { label: 'Like Stars on Earth', year: 2007 }, + { label: 'Taxi Driver', year: 1976 }, + { label: 'Lawrence of Arabia', year: 1962 }, + { label: 'Double Indemnity', year: 1944 }, + { + label: 'Eternal Sunshine of the Spotless Mind', + year: 2004, + }, + { label: 'Amadeus', year: 1984 }, + { label: 'To Kill a Mockingbird', year: 1962 }, + { label: 'Toy Story 3', year: 2010 }, + { label: 'Logan', year: 2017 }, + { label: 'Full Metal Jacket', year: 1987 }, + { label: 'Dangal', year: 2016 }, + { label: 'The Sting', year: 1973 }, + { label: '2001: A Space Odyssey', year: 1968 }, + { label: "Singin' in the Rain", year: 1952 }, + { label: 'Toy Story', year: 1995 }, + { label: 'Bicycle Thieves', year: 1948 }, + { label: 'The Kid', year: 1921 }, + { label: 'Inglourious Basterds', year: 2009 }, + { label: 'Snatch', year: 2000 }, + { label: '3 Idiots', year: 2009 }, + { label: 'Monty Python and the Holy Grail', year: 1975 }, +]; diff --git a/docs/data/joy/components/autocomplete/AutocompleteError.tsx b/docs/data/joy/components/autocomplete/AutocompleteError.tsx new file mode 100644 index 00000000000000..3a292864e9c8c2 --- /dev/null +++ b/docs/data/joy/components/autocomplete/AutocompleteError.tsx @@ -0,0 +1,143 @@ +import * as React from 'react'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import FormHelperText from '@mui/joy/FormHelperText'; +import Autocomplete from '@mui/joy/Autocomplete'; + +export default function InputAppearance() { + return ( + + Invalid + + Opps! something went wrong. + + ); +} + +// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top +const top100Films = [ + { label: 'The Shawshank Redemption', year: 1994 }, + { label: 'The Godfather', year: 1972 }, + { label: 'The Godfather: Part II', year: 1974 }, + { label: 'The Dark Knight', year: 2008 }, + { label: '12 Angry Men', year: 1957 }, + { label: "Schindler's List", year: 1993 }, + { label: 'Pulp Fiction', year: 1994 }, + { + label: 'The Lord of the Rings: The Return of the King', + year: 2003, + }, + { label: 'The Good, the Bad and the Ugly', year: 1966 }, + { label: 'Fight Club', year: 1999 }, + { + label: 'The Lord of the Rings: The Fellowship of the Ring', + year: 2001, + }, + { + label: 'Star Wars: Episode V - The Empire Strikes Back', + year: 1980, + }, + { label: 'Forrest Gump', year: 1994 }, + { label: 'Inception', year: 2010 }, + { + label: 'The Lord of the Rings: The Two Towers', + year: 2002, + }, + { label: "One Flew Over the Cuckoo's Nest", year: 1975 }, + { label: 'Goodfellas', year: 1990 }, + { label: 'The Matrix', year: 1999 }, + { label: 'Seven Samurai', year: 1954 }, + { + label: 'Star Wars: Episode IV - A New Hope', + year: 1977, + }, + { label: 'City of God', year: 2002 }, + { label: 'Se7en', year: 1995 }, + { label: 'The Silence of the Lambs', year: 1991 }, + { label: "It's a Wonderful Life", year: 1946 }, + { label: 'Life Is Beautiful', year: 1997 }, + { label: 'The Usual Suspects', year: 1995 }, + { label: 'Léon: The Professional', year: 1994 }, + { label: 'Spirited Away', year: 2001 }, + { label: 'Saving Private Ryan', year: 1998 }, + { label: 'Once Upon a Time in the West', year: 1968 }, + { label: 'American History X', year: 1998 }, + { label: 'Interstellar', year: 2014 }, + { label: 'Casablanca', year: 1942 }, + { label: 'City Lights', year: 1931 }, + { label: 'Psycho', year: 1960 }, + { label: 'The Green Mile', year: 1999 }, + { label: 'The Intouchables', year: 2011 }, + { label: 'Modern Times', year: 1936 }, + { label: 'Raiders of the Lost Ark', year: 1981 }, + { label: 'Rear Window', year: 1954 }, + { label: 'The Pianist', year: 2002 }, + { label: 'The Departed', year: 2006 }, + { label: 'Terminator 2: Judgment Day', year: 1991 }, + { label: 'Back to the Future', year: 1985 }, + { label: 'Whiplash', year: 2014 }, + { label: 'Gladiator', year: 2000 }, + { label: 'Memento', year: 2000 }, + { label: 'The Prestige', year: 2006 }, + { label: 'The Lion King', year: 1994 }, + { label: 'Apocalypse Now', year: 1979 }, + { label: 'Alien', year: 1979 }, + { label: 'Sunset Boulevard', year: 1950 }, + { + label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb', + year: 1964, + }, + { label: 'The Great Dictator', year: 1940 }, + { label: 'Cinema Paradiso', year: 1988 }, + { label: 'The Lives of Others', year: 2006 }, + { label: 'Grave of the Fireflies', year: 1988 }, + { label: 'Paths of Glory', year: 1957 }, + { label: 'Django Unchained', year: 2012 }, + { label: 'The Shining', year: 1980 }, + { label: 'WALL·E', year: 2008 }, + { label: 'American Beauty', year: 1999 }, + { label: 'The Dark Knight Rises', year: 2012 }, + { label: 'Princess Mononoke', year: 1997 }, + { label: 'Aliens', year: 1986 }, + { label: 'Oldboy', year: 2003 }, + { label: 'Once Upon a Time in America', year: 1984 }, + { label: 'Witness for the Prosecution', year: 1957 }, + { label: 'Das Boot', year: 1981 }, + { label: 'Citizen Kane', year: 1941 }, + { label: 'North by Northwest', year: 1959 }, + { label: 'Vertigo', year: 1958 }, + { + label: 'Star Wars: Episode VI - Return of the Jedi', + year: 1983, + }, + { label: 'Reservoir Dogs', year: 1992 }, + { label: 'Braveheart', year: 1995 }, + { label: 'M', year: 1931 }, + { label: 'Requiem for a Dream', year: 2000 }, + { label: 'Amélie', year: 2001 }, + { label: 'A Clockwork Orange', year: 1971 }, + { label: 'Like Stars on Earth', year: 2007 }, + { label: 'Taxi Driver', year: 1976 }, + { label: 'Lawrence of Arabia', year: 1962 }, + { label: 'Double Indemnity', year: 1944 }, + { + label: 'Eternal Sunshine of the Spotless Mind', + year: 2004, + }, + { label: 'Amadeus', year: 1984 }, + { label: 'To Kill a Mockingbird', year: 1962 }, + { label: 'Toy Story 3', year: 2010 }, + { label: 'Logan', year: 2017 }, + { label: 'Full Metal Jacket', year: 1987 }, + { label: 'Dangal', year: 2016 }, + { label: 'The Sting', year: 1973 }, + { label: '2001: A Space Odyssey', year: 1968 }, + { label: "Singin' in the Rain", year: 1952 }, + { label: 'Toy Story', year: 1995 }, + { label: 'Bicycle Thieves', year: 1948 }, + { label: 'The Kid', year: 1921 }, + { label: 'Inglourious Basterds', year: 2009 }, + { label: 'Snatch', year: 2000 }, + { label: '3 Idiots', year: 2009 }, + { label: 'Monty Python and the Holy Grail', year: 1975 }, +]; diff --git a/docs/data/joy/components/autocomplete/AutocompleteError.tsx.preview b/docs/data/joy/components/autocomplete/AutocompleteError.tsx.preview new file mode 100644 index 00000000000000..2d7c1e08595d42 --- /dev/null +++ b/docs/data/joy/components/autocomplete/AutocompleteError.tsx.preview @@ -0,0 +1,5 @@ + + Invalid + + Opps! something went wrong. + \ No newline at end of file diff --git a/docs/data/joy/components/autocomplete/AutocompleteVariables.js b/docs/data/joy/components/autocomplete/AutocompleteVariables.js new file mode 100644 index 00000000000000..b037ece03b86c0 --- /dev/null +++ b/docs/data/joy/components/autocomplete/AutocompleteVariables.js @@ -0,0 +1,219 @@ +import * as React from 'react'; +import JoyVariablesDemo from 'docs/src/modules/components/JoyVariablesDemo'; +import Autocomplete from '@mui/joy/Autocomplete'; +import Stack from '@mui/joy/Stack'; +import LiveTv from '@mui/icons-material/LiveTv'; + +export default function ButtonVariables() { + return ( + ( + + } + placeholder="Decorators" + options={top100Films} + sx={sx} + /> + } + placeholder="Decorators" + options={top100Films} + defaultValue={[top100Films[1], top100Films[6]]} + sx={sx} + /> + + )} + /> + ); +} + +// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top +const top100Films = [ + { label: 'The Shawshank Redemption', year: 1994 }, + { label: 'The Godfather', year: 1972 }, + { label: 'The Godfather: Part II', year: 1974 }, + { label: 'The Dark Knight', year: 2008 }, + { label: '12 Angry Men', year: 1957 }, + { label: "Schindler's List", year: 1993 }, + { label: 'Pulp Fiction', year: 1994 }, + { + label: 'The Lord of the Rings: The Return of the King', + year: 2003, + }, + { label: 'The Good, the Bad and the Ugly', year: 1966 }, + { label: 'Fight Club', year: 1999 }, + { + label: 'The Lord of the Rings: The Fellowship of the Ring', + year: 2001, + }, + { + label: 'Star Wars: Episode V - The Empire Strikes Back', + year: 1980, + }, + { label: 'Forrest Gump', year: 1994 }, + { label: 'Inception', year: 2010 }, + { + label: 'The Lord of the Rings: The Two Towers', + year: 2002, + }, + { label: "One Flew Over the Cuckoo's Nest", year: 1975 }, + { label: 'Goodfellas', year: 1990 }, + { label: 'The Matrix', year: 1999 }, + { label: 'Seven Samurai', year: 1954 }, + { + label: 'Star Wars: Episode IV - A New Hope', + year: 1977, + }, + { label: 'City of God', year: 2002 }, + { label: 'Se7en', year: 1995 }, + { label: 'The Silence of the Lambs', year: 1991 }, + { label: "It's a Wonderful Life", year: 1946 }, + { label: 'Life Is Beautiful', year: 1997 }, + { label: 'The Usual Suspects', year: 1995 }, + { label: 'Léon: The Professional', year: 1994 }, + { label: 'Spirited Away', year: 2001 }, + { label: 'Saving Private Ryan', year: 1998 }, + { label: 'Once Upon a Time in the West', year: 1968 }, + { label: 'American History X', year: 1998 }, + { label: 'Interstellar', year: 2014 }, + { label: 'Casablanca', year: 1942 }, + { label: 'City Lights', year: 1931 }, + { label: 'Psycho', year: 1960 }, + { label: 'The Green Mile', year: 1999 }, + { label: 'The Intouchables', year: 2011 }, + { label: 'Modern Times', year: 1936 }, + { label: 'Raiders of the Lost Ark', year: 1981 }, + { label: 'Rear Window', year: 1954 }, + { label: 'The Pianist', year: 2002 }, + { label: 'The Departed', year: 2006 }, + { label: 'Terminator 2: Judgment Day', year: 1991 }, + { label: 'Back to the Future', year: 1985 }, + { label: 'Whiplash', year: 2014 }, + { label: 'Gladiator', year: 2000 }, + { label: 'Memento', year: 2000 }, + { label: 'The Prestige', year: 2006 }, + { label: 'The Lion King', year: 1994 }, + { label: 'Apocalypse Now', year: 1979 }, + { label: 'Alien', year: 1979 }, + { label: 'Sunset Boulevard', year: 1950 }, + { + label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb', + year: 1964, + }, + { label: 'The Great Dictator', year: 1940 }, + { label: 'Cinema Paradiso', year: 1988 }, + { label: 'The Lives of Others', year: 2006 }, + { label: 'Grave of the Fireflies', year: 1988 }, + { label: 'Paths of Glory', year: 1957 }, + { label: 'Django Unchained', year: 2012 }, + { label: 'The Shining', year: 1980 }, + { label: 'WALL·E', year: 2008 }, + { label: 'American Beauty', year: 1999 }, + { label: 'The Dark Knight Rises', year: 2012 }, + { label: 'Princess Mononoke', year: 1997 }, + { label: 'Aliens', year: 1986 }, + { label: 'Oldboy', year: 2003 }, + { label: 'Once Upon a Time in America', year: 1984 }, + { label: 'Witness for the Prosecution', year: 1957 }, + { label: 'Das Boot', year: 1981 }, + { label: 'Citizen Kane', year: 1941 }, + { label: 'North by Northwest', year: 1959 }, + { label: 'Vertigo', year: 1958 }, + { + label: 'Star Wars: Episode VI - Return of the Jedi', + year: 1983, + }, + { label: 'Reservoir Dogs', year: 1992 }, + { label: 'Braveheart', year: 1995 }, + { label: 'M', year: 1931 }, + { label: 'Requiem for a Dream', year: 2000 }, + { label: 'Amélie', year: 2001 }, + { label: 'A Clockwork Orange', year: 1971 }, + { label: 'Like Stars on Earth', year: 2007 }, + { label: 'Taxi Driver', year: 1976 }, + { label: 'Lawrence of Arabia', year: 1962 }, + { label: 'Double Indemnity', year: 1944 }, + { + label: 'Eternal Sunshine of the Spotless Mind', + year: 2004, + }, + { label: 'Amadeus', year: 1984 }, + { label: 'To Kill a Mockingbird', year: 1962 }, + { label: 'Toy Story 3', year: 2010 }, + { label: 'Logan', year: 2017 }, + { label: 'Full Metal Jacket', year: 1987 }, + { label: 'Dangal', year: 2016 }, + { label: 'The Sting', year: 1973 }, + { label: '2001: A Space Odyssey', year: 1968 }, + { label: "Singin' in the Rain", year: 1952 }, + { label: 'Toy Story', year: 1995 }, + { label: 'Bicycle Thieves', year: 1948 }, + { label: 'The Kid', year: 1921 }, + { label: 'Inglourious Basterds', year: 2009 }, + { label: 'Snatch', year: 2000 }, + { label: '3 Idiots', year: 2009 }, + { label: 'Monty Python and the Holy Grail', year: 1975 }, +]; diff --git a/docs/data/joy/components/autocomplete/BasicAutocomplete.js b/docs/data/joy/components/autocomplete/BasicAutocomplete.js new file mode 100644 index 00000000000000..25ca9c48b98eed --- /dev/null +++ b/docs/data/joy/components/autocomplete/BasicAutocomplete.js @@ -0,0 +1,140 @@ +import * as React from 'react'; +import Autocomplete from '@mui/joy/Autocomplete'; + +export default function ComboBox() { + return ( + + ); +} + +// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top +const top100Films = [ + { label: 'The Shawshank Redemption', year: 1994 }, + { label: 'The Godfather', year: 1972 }, + { label: 'The Godfather: Part II', year: 1974 }, + { label: 'The Dark Knight', year: 2008 }, + { label: '12 Angry Men', year: 1957 }, + { label: "Schindler's List", year: 1993 }, + { label: 'Pulp Fiction', year: 1994 }, + { + label: 'The Lord of the Rings: The Return of the King', + year: 2003, + }, + { label: 'The Good, the Bad and the Ugly', year: 1966 }, + { label: 'Fight Club', year: 1999 }, + { + label: 'The Lord of the Rings: The Fellowship of the Ring', + year: 2001, + }, + { + label: 'Star Wars: Episode V - The Empire Strikes Back', + year: 1980, + }, + { label: 'Forrest Gump', year: 1994 }, + { label: 'Inception', year: 2010 }, + { + label: 'The Lord of the Rings: The Two Towers', + year: 2002, + }, + { label: "One Flew Over the Cuckoo's Nest", year: 1975 }, + { label: 'Goodfellas', year: 1990 }, + { label: 'The Matrix', year: 1999 }, + { label: 'Seven Samurai', year: 1954 }, + { + label: 'Star Wars: Episode IV - A New Hope', + year: 1977, + }, + { label: 'City of God', year: 2002 }, + { label: 'Se7en', year: 1995 }, + { label: 'The Silence of the Lambs', year: 1991 }, + { label: "It's a Wonderful Life", year: 1946 }, + { label: 'Life Is Beautiful', year: 1997 }, + { label: 'The Usual Suspects', year: 1995 }, + { label: 'Léon: The Professional', year: 1994 }, + { label: 'Spirited Away', year: 2001 }, + { label: 'Saving Private Ryan', year: 1998 }, + { label: 'Once Upon a Time in the West', year: 1968 }, + { label: 'American History X', year: 1998 }, + { label: 'Interstellar', year: 2014 }, + { label: 'Casablanca', year: 1942 }, + { label: 'City Lights', year: 1931 }, + { label: 'Psycho', year: 1960 }, + { label: 'The Green Mile', year: 1999 }, + { label: 'The Intouchables', year: 2011 }, + { label: 'Modern Times', year: 1936 }, + { label: 'Raiders of the Lost Ark', year: 1981 }, + { label: 'Rear Window', year: 1954 }, + { label: 'The Pianist', year: 2002 }, + { label: 'The Departed', year: 2006 }, + { label: 'Terminator 2: Judgment Day', year: 1991 }, + { label: 'Back to the Future', year: 1985 }, + { label: 'Whiplash', year: 2014 }, + { label: 'Gladiator', year: 2000 }, + { label: 'Memento', year: 2000 }, + { label: 'The Prestige', year: 2006 }, + { label: 'The Lion King', year: 1994 }, + { label: 'Apocalypse Now', year: 1979 }, + { label: 'Alien', year: 1979 }, + { label: 'Sunset Boulevard', year: 1950 }, + { + label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb', + year: 1964, + }, + { label: 'The Great Dictator', year: 1940 }, + { label: 'Cinema Paradiso', year: 1988 }, + { label: 'The Lives of Others', year: 2006 }, + { label: 'Grave of the Fireflies', year: 1988 }, + { label: 'Paths of Glory', year: 1957 }, + { label: 'Django Unchained', year: 2012 }, + { label: 'The Shining', year: 1980 }, + { label: 'WALL·E', year: 2008 }, + { label: 'American Beauty', year: 1999 }, + { label: 'The Dark Knight Rises', year: 2012 }, + { label: 'Princess Mononoke', year: 1997 }, + { label: 'Aliens', year: 1986 }, + { label: 'Oldboy', year: 2003 }, + { label: 'Once Upon a Time in America', year: 1984 }, + { label: 'Witness for the Prosecution', year: 1957 }, + { label: 'Das Boot', year: 1981 }, + { label: 'Citizen Kane', year: 1941 }, + { label: 'North by Northwest', year: 1959 }, + { label: 'Vertigo', year: 1958 }, + { + label: 'Star Wars: Episode VI - Return of the Jedi', + year: 1983, + }, + { label: 'Reservoir Dogs', year: 1992 }, + { label: 'Braveheart', year: 1995 }, + { label: 'M', year: 1931 }, + { label: 'Requiem for a Dream', year: 2000 }, + { label: 'Amélie', year: 2001 }, + { label: 'A Clockwork Orange', year: 1971 }, + { label: 'Like Stars on Earth', year: 2007 }, + { label: 'Taxi Driver', year: 1976 }, + { label: 'Lawrence of Arabia', year: 1962 }, + { label: 'Double Indemnity', year: 1944 }, + { + label: 'Eternal Sunshine of the Spotless Mind', + year: 2004, + }, + { label: 'Amadeus', year: 1984 }, + { label: 'To Kill a Mockingbird', year: 1962 }, + { label: 'Toy Story 3', year: 2010 }, + { label: 'Logan', year: 2017 }, + { label: 'Full Metal Jacket', year: 1987 }, + { label: 'Dangal', year: 2016 }, + { label: 'The Sting', year: 1973 }, + { label: '2001: A Space Odyssey', year: 1968 }, + { label: "Singin' in the Rain", year: 1952 }, + { label: 'Toy Story', year: 1995 }, + { label: 'Bicycle Thieves', year: 1948 }, + { label: 'The Kid', year: 1921 }, + { label: 'Inglourious Basterds', year: 2009 }, + { label: 'Snatch', year: 2000 }, + { label: '3 Idiots', year: 2009 }, + { label: 'Monty Python and the Holy Grail', year: 1975 }, +]; diff --git a/docs/data/joy/components/autocomplete/BasicAutocomplete.tsx b/docs/data/joy/components/autocomplete/BasicAutocomplete.tsx new file mode 100644 index 00000000000000..25ca9c48b98eed --- /dev/null +++ b/docs/data/joy/components/autocomplete/BasicAutocomplete.tsx @@ -0,0 +1,140 @@ +import * as React from 'react'; +import Autocomplete from '@mui/joy/Autocomplete'; + +export default function ComboBox() { + return ( + + ); +} + +// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top +const top100Films = [ + { label: 'The Shawshank Redemption', year: 1994 }, + { label: 'The Godfather', year: 1972 }, + { label: 'The Godfather: Part II', year: 1974 }, + { label: 'The Dark Knight', year: 2008 }, + { label: '12 Angry Men', year: 1957 }, + { label: "Schindler's List", year: 1993 }, + { label: 'Pulp Fiction', year: 1994 }, + { + label: 'The Lord of the Rings: The Return of the King', + year: 2003, + }, + { label: 'The Good, the Bad and the Ugly', year: 1966 }, + { label: 'Fight Club', year: 1999 }, + { + label: 'The Lord of the Rings: The Fellowship of the Ring', + year: 2001, + }, + { + label: 'Star Wars: Episode V - The Empire Strikes Back', + year: 1980, + }, + { label: 'Forrest Gump', year: 1994 }, + { label: 'Inception', year: 2010 }, + { + label: 'The Lord of the Rings: The Two Towers', + year: 2002, + }, + { label: "One Flew Over the Cuckoo's Nest", year: 1975 }, + { label: 'Goodfellas', year: 1990 }, + { label: 'The Matrix', year: 1999 }, + { label: 'Seven Samurai', year: 1954 }, + { + label: 'Star Wars: Episode IV - A New Hope', + year: 1977, + }, + { label: 'City of God', year: 2002 }, + { label: 'Se7en', year: 1995 }, + { label: 'The Silence of the Lambs', year: 1991 }, + { label: "It's a Wonderful Life", year: 1946 }, + { label: 'Life Is Beautiful', year: 1997 }, + { label: 'The Usual Suspects', year: 1995 }, + { label: 'Léon: The Professional', year: 1994 }, + { label: 'Spirited Away', year: 2001 }, + { label: 'Saving Private Ryan', year: 1998 }, + { label: 'Once Upon a Time in the West', year: 1968 }, + { label: 'American History X', year: 1998 }, + { label: 'Interstellar', year: 2014 }, + { label: 'Casablanca', year: 1942 }, + { label: 'City Lights', year: 1931 }, + { label: 'Psycho', year: 1960 }, + { label: 'The Green Mile', year: 1999 }, + { label: 'The Intouchables', year: 2011 }, + { label: 'Modern Times', year: 1936 }, + { label: 'Raiders of the Lost Ark', year: 1981 }, + { label: 'Rear Window', year: 1954 }, + { label: 'The Pianist', year: 2002 }, + { label: 'The Departed', year: 2006 }, + { label: 'Terminator 2: Judgment Day', year: 1991 }, + { label: 'Back to the Future', year: 1985 }, + { label: 'Whiplash', year: 2014 }, + { label: 'Gladiator', year: 2000 }, + { label: 'Memento', year: 2000 }, + { label: 'The Prestige', year: 2006 }, + { label: 'The Lion King', year: 1994 }, + { label: 'Apocalypse Now', year: 1979 }, + { label: 'Alien', year: 1979 }, + { label: 'Sunset Boulevard', year: 1950 }, + { + label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb', + year: 1964, + }, + { label: 'The Great Dictator', year: 1940 }, + { label: 'Cinema Paradiso', year: 1988 }, + { label: 'The Lives of Others', year: 2006 }, + { label: 'Grave of the Fireflies', year: 1988 }, + { label: 'Paths of Glory', year: 1957 }, + { label: 'Django Unchained', year: 2012 }, + { label: 'The Shining', year: 1980 }, + { label: 'WALL·E', year: 2008 }, + { label: 'American Beauty', year: 1999 }, + { label: 'The Dark Knight Rises', year: 2012 }, + { label: 'Princess Mononoke', year: 1997 }, + { label: 'Aliens', year: 1986 }, + { label: 'Oldboy', year: 2003 }, + { label: 'Once Upon a Time in America', year: 1984 }, + { label: 'Witness for the Prosecution', year: 1957 }, + { label: 'Das Boot', year: 1981 }, + { label: 'Citizen Kane', year: 1941 }, + { label: 'North by Northwest', year: 1959 }, + { label: 'Vertigo', year: 1958 }, + { + label: 'Star Wars: Episode VI - Return of the Jedi', + year: 1983, + }, + { label: 'Reservoir Dogs', year: 1992 }, + { label: 'Braveheart', year: 1995 }, + { label: 'M', year: 1931 }, + { label: 'Requiem for a Dream', year: 2000 }, + { label: 'Amélie', year: 2001 }, + { label: 'A Clockwork Orange', year: 1971 }, + { label: 'Like Stars on Earth', year: 2007 }, + { label: 'Taxi Driver', year: 1976 }, + { label: 'Lawrence of Arabia', year: 1962 }, + { label: 'Double Indemnity', year: 1944 }, + { + label: 'Eternal Sunshine of the Spotless Mind', + year: 2004, + }, + { label: 'Amadeus', year: 1984 }, + { label: 'To Kill a Mockingbird', year: 1962 }, + { label: 'Toy Story 3', year: 2010 }, + { label: 'Logan', year: 2017 }, + { label: 'Full Metal Jacket', year: 1987 }, + { label: 'Dangal', year: 2016 }, + { label: 'The Sting', year: 1973 }, + { label: '2001: A Space Odyssey', year: 1968 }, + { label: "Singin' in the Rain", year: 1952 }, + { label: 'Toy Story', year: 1995 }, + { label: 'Bicycle Thieves', year: 1948 }, + { label: 'The Kid', year: 1921 }, + { label: 'Inglourious Basterds', year: 2009 }, + { label: 'Snatch', year: 2000 }, + { label: '3 Idiots', year: 2009 }, + { label: 'Monty Python and the Holy Grail', year: 1975 }, +]; diff --git a/docs/data/joy/components/autocomplete/BasicAutocomplete.tsx.preview b/docs/data/joy/components/autocomplete/BasicAutocomplete.tsx.preview new file mode 100644 index 00000000000000..9b1ff43228a0d5 --- /dev/null +++ b/docs/data/joy/components/autocomplete/BasicAutocomplete.tsx.preview @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/docs/data/joy/components/autocomplete/ControllableStates.js b/docs/data/joy/components/autocomplete/ControllableStates.js new file mode 100644 index 00000000000000..1df56c1bf8a475 --- /dev/null +++ b/docs/data/joy/components/autocomplete/ControllableStates.js @@ -0,0 +1,50 @@ +import * as React from 'react'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import Autocomplete from '@mui/joy/Autocomplete'; +import Typography from '@mui/joy/Typography'; + +const options = ['Option 1', 'Option 2']; + +export default function ControllableStates() { + const [value, setValue] = React.useState(options[0]); + const [inputValue, setInputValue] = React.useState(''); + + return ( +
+ + value: + + + {`${value !== null ? `'${value}'` : 'null'}`} + + + + + inputValue: + + + {`'${inputValue}'`} + + + +
+ + Controllable + { + setValue(newValue); + }} + inputValue={inputValue} + onInputChange={(event, newInputValue) => { + setInputValue(newInputValue); + }} + options={options} + sx={{ width: 300 }} + /> + +
+ ); +} diff --git a/docs/data/joy/components/autocomplete/ControllableStates.tsx b/docs/data/joy/components/autocomplete/ControllableStates.tsx new file mode 100644 index 00000000000000..46e329a3114250 --- /dev/null +++ b/docs/data/joy/components/autocomplete/ControllableStates.tsx @@ -0,0 +1,50 @@ +import * as React from 'react'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import Autocomplete from '@mui/joy/Autocomplete'; +import Typography from '@mui/joy/Typography'; + +const options = ['Option 1', 'Option 2']; + +export default function ControllableStates() { + const [value, setValue] = React.useState(options[0]); + const [inputValue, setInputValue] = React.useState(''); + + return ( +
+ + value: + + + {`${value !== null ? `'${value}'` : 'null'}`} + + + + + inputValue: + + + {`'${inputValue}'`} + + + +
+ + Controllable + { + setValue(newValue); + }} + inputValue={inputValue} + onInputChange={(event, newInputValue) => { + setInputValue(newInputValue); + }} + options={options} + sx={{ width: 300 }} + /> + +
+ ); +} diff --git a/docs/data/joy/components/autocomplete/CountrySelect.js b/docs/data/joy/components/autocomplete/CountrySelect.js new file mode 100644 index 00000000000000..f58cc87f1a2faa --- /dev/null +++ b/docs/data/joy/components/autocomplete/CountrySelect.js @@ -0,0 +1,469 @@ +import * as React from 'react'; +import Autocomplete from '@mui/joy/Autocomplete'; +import AutocompleteOption from '@mui/joy/AutocompleteOption'; +import ListItemDecorator from '@mui/joy/ListItemDecorator'; +import ListItemContent from '@mui/joy/ListItemContent'; +import Typography from '@mui/joy/Typography'; + +export default function CountrySelect() { + return ( + option.label} + renderOption={(props, option) => ( + + + + + + {option.label} + + ({option.code}) +{option.phone} + + + + )} + /> + ); +} + +// From https://bitbucket.org/atlassian/atlaskit-mk-2/raw/4ad0e56649c3e6c973e226b7efaeb28cb240ccb0/packages/core/select/src/data/countries.js +const countries = [ + { code: 'AD', label: 'Andorra', phone: '376' }, + { + code: 'AE', + label: 'United Arab Emirates', + phone: '971', + }, + { code: 'AF', label: 'Afghanistan', phone: '93' }, + { + code: 'AG', + label: 'Antigua and Barbuda', + phone: '1-268', + }, + { code: 'AI', label: 'Anguilla', phone: '1-264' }, + { code: 'AL', label: 'Albania', phone: '355' }, + { code: 'AM', label: 'Armenia', phone: '374' }, + { code: 'AO', label: 'Angola', phone: '244' }, + { code: 'AQ', label: 'Antarctica', phone: '672' }, + { code: 'AR', label: 'Argentina', phone: '54' }, + { code: 'AS', label: 'American Samoa', phone: '1-684' }, + { code: 'AT', label: 'Austria', phone: '43' }, + { + code: 'AU', + label: 'Australia', + phone: '61', + suggested: true, + }, + { code: 'AW', label: 'Aruba', phone: '297' }, + { code: 'AX', label: 'Alland Islands', phone: '358' }, + { code: 'AZ', label: 'Azerbaijan', phone: '994' }, + { + code: 'BA', + label: 'Bosnia and Herzegovina', + phone: '387', + }, + { code: 'BB', label: 'Barbados', phone: '1-246' }, + { code: 'BD', label: 'Bangladesh', phone: '880' }, + { code: 'BE', label: 'Belgium', phone: '32' }, + { code: 'BF', label: 'Burkina Faso', phone: '226' }, + { code: 'BG', label: 'Bulgaria', phone: '359' }, + { code: 'BH', label: 'Bahrain', phone: '973' }, + { code: 'BI', label: 'Burundi', phone: '257' }, + { code: 'BJ', label: 'Benin', phone: '229' }, + { code: 'BL', label: 'Saint Barthelemy', phone: '590' }, + { code: 'BM', label: 'Bermuda', phone: '1-441' }, + { code: 'BN', label: 'Brunei Darussalam', phone: '673' }, + { code: 'BO', label: 'Bolivia', phone: '591' }, + { code: 'BR', label: 'Brazil', phone: '55' }, + { code: 'BS', label: 'Bahamas', phone: '1-242' }, + { code: 'BT', label: 'Bhutan', phone: '975' }, + { code: 'BV', label: 'Bouvet Island', phone: '47' }, + { code: 'BW', label: 'Botswana', phone: '267' }, + { code: 'BY', label: 'Belarus', phone: '375' }, + { code: 'BZ', label: 'Belize', phone: '501' }, + { + code: 'CA', + label: 'Canada', + phone: '1', + suggested: true, + }, + { + code: 'CC', + label: 'Cocos (Keeling) Islands', + phone: '61', + }, + { + code: 'CD', + label: 'Congo, Democratic Republic of the', + phone: '243', + }, + { + code: 'CF', + label: 'Central African Republic', + phone: '236', + }, + { + code: 'CG', + label: 'Congo, Republic of the', + phone: '242', + }, + { code: 'CH', label: 'Switzerland', phone: '41' }, + { code: 'CI', label: "Cote d'Ivoire", phone: '225' }, + { code: 'CK', label: 'Cook Islands', phone: '682' }, + { code: 'CL', label: 'Chile', phone: '56' }, + { code: 'CM', label: 'Cameroon', phone: '237' }, + { code: 'CN', label: 'China', phone: '86' }, + { code: 'CO', label: 'Colombia', phone: '57' }, + { code: 'CR', label: 'Costa Rica', phone: '506' }, + { code: 'CU', label: 'Cuba', phone: '53' }, + { code: 'CV', label: 'Cape Verde', phone: '238' }, + { code: 'CW', label: 'Curacao', phone: '599' }, + { code: 'CX', label: 'Christmas Island', phone: '61' }, + { code: 'CY', label: 'Cyprus', phone: '357' }, + { code: 'CZ', label: 'Czech Republic', phone: '420' }, + { + code: 'DE', + label: 'Germany', + phone: '49', + suggested: true, + }, + { code: 'DJ', label: 'Djibouti', phone: '253' }, + { code: 'DK', label: 'Denmark', phone: '45' }, + { code: 'DM', label: 'Dominica', phone: '1-767' }, + { + code: 'DO', + label: 'Dominican Republic', + phone: '1-809', + }, + { code: 'DZ', label: 'Algeria', phone: '213' }, + { code: 'EC', label: 'Ecuador', phone: '593' }, + { code: 'EE', label: 'Estonia', phone: '372' }, + { code: 'EG', label: 'Egypt', phone: '20' }, + { code: 'EH', label: 'Western Sahara', phone: '212' }, + { code: 'ER', label: 'Eritrea', phone: '291' }, + { code: 'ES', label: 'Spain', phone: '34' }, + { code: 'ET', label: 'Ethiopia', phone: '251' }, + { code: 'FI', label: 'Finland', phone: '358' }, + { code: 'FJ', label: 'Fiji', phone: '679' }, + { + code: 'FK', + label: 'Falkland Islands (Malvinas)', + phone: '500', + }, + { + code: 'FM', + label: 'Micronesia, Federated States of', + phone: '691', + }, + { code: 'FO', label: 'Faroe Islands', phone: '298' }, + { + code: 'FR', + label: 'France', + phone: '33', + suggested: true, + }, + { code: 'GA', label: 'Gabon', phone: '241' }, + { code: 'GB', label: 'United Kingdom', phone: '44' }, + { code: 'GD', label: 'Grenada', phone: '1-473' }, + { code: 'GE', label: 'Georgia', phone: '995' }, + { code: 'GF', label: 'French Guiana', phone: '594' }, + { code: 'GG', label: 'Guernsey', phone: '44' }, + { code: 'GH', label: 'Ghana', phone: '233' }, + { code: 'GI', label: 'Gibraltar', phone: '350' }, + { code: 'GL', label: 'Greenland', phone: '299' }, + { code: 'GM', label: 'Gambia', phone: '220' }, + { code: 'GN', label: 'Guinea', phone: '224' }, + { code: 'GP', label: 'Guadeloupe', phone: '590' }, + { code: 'GQ', label: 'Equatorial Guinea', phone: '240' }, + { code: 'GR', label: 'Greece', phone: '30' }, + { + code: 'GS', + label: 'South Georgia and the South Sandwich Islands', + phone: '500', + }, + { code: 'GT', label: 'Guatemala', phone: '502' }, + { code: 'GU', label: 'Guam', phone: '1-671' }, + { code: 'GW', label: 'Guinea-Bissau', phone: '245' }, + { code: 'GY', label: 'Guyana', phone: '592' }, + { code: 'HK', label: 'Hong Kong', phone: '852' }, + { + code: 'HM', + label: 'Heard Island and McDonald Islands', + phone: '672', + }, + { code: 'HN', label: 'Honduras', phone: '504' }, + { code: 'HR', label: 'Croatia', phone: '385' }, + { code: 'HT', label: 'Haiti', phone: '509' }, + { code: 'HU', label: 'Hungary', phone: '36' }, + { code: 'ID', label: 'Indonesia', phone: '62' }, + { code: 'IE', label: 'Ireland', phone: '353' }, + { code: 'IL', label: 'Israel', phone: '972' }, + { code: 'IM', label: 'Isle of Man', phone: '44' }, + { code: 'IN', label: 'India', phone: '91' }, + { + code: 'IO', + label: 'British Indian Ocean Territory', + phone: '246', + }, + { code: 'IQ', label: 'Iraq', phone: '964' }, + { + code: 'IR', + label: 'Iran, Islamic Republic of', + phone: '98', + }, + { code: 'IS', label: 'Iceland', phone: '354' }, + { code: 'IT', label: 'Italy', phone: '39' }, + { code: 'JE', label: 'Jersey', phone: '44' }, + { code: 'JM', label: 'Jamaica', phone: '1-876' }, + { code: 'JO', label: 'Jordan', phone: '962' }, + { + code: 'JP', + label: 'Japan', + phone: '81', + suggested: true, + }, + { code: 'KE', label: 'Kenya', phone: '254' }, + { code: 'KG', label: 'Kyrgyzstan', phone: '996' }, + { code: 'KH', label: 'Cambodia', phone: '855' }, + { code: 'KI', label: 'Kiribati', phone: '686' }, + { code: 'KM', label: 'Comoros', phone: '269' }, + { + code: 'KN', + label: 'Saint Kitts and Nevis', + phone: '1-869', + }, + { + code: 'KP', + label: "Korea, Democratic People's Republic of", + phone: '850', + }, + { code: 'KR', label: 'Korea, Republic of', phone: '82' }, + { code: 'KW', label: 'Kuwait', phone: '965' }, + { code: 'KY', label: 'Cayman Islands', phone: '1-345' }, + { code: 'KZ', label: 'Kazakhstan', phone: '7' }, + { + code: 'LA', + label: "Lao People's Democratic Republic", + phone: '856', + }, + { code: 'LB', label: 'Lebanon', phone: '961' }, + { code: 'LC', label: 'Saint Lucia', phone: '1-758' }, + { code: 'LI', label: 'Liechtenstein', phone: '423' }, + { code: 'LK', label: 'Sri Lanka', phone: '94' }, + { code: 'LR', label: 'Liberia', phone: '231' }, + { code: 'LS', label: 'Lesotho', phone: '266' }, + { code: 'LT', label: 'Lithuania', phone: '370' }, + { code: 'LU', label: 'Luxembourg', phone: '352' }, + { code: 'LV', label: 'Latvia', phone: '371' }, + { code: 'LY', label: 'Libya', phone: '218' }, + { code: 'MA', label: 'Morocco', phone: '212' }, + { code: 'MC', label: 'Monaco', phone: '377' }, + { + code: 'MD', + label: 'Moldova, Republic of', + phone: '373', + }, + { code: 'ME', label: 'Montenegro', phone: '382' }, + { + code: 'MF', + label: 'Saint Martin (French part)', + phone: '590', + }, + { code: 'MG', label: 'Madagascar', phone: '261' }, + { code: 'MH', label: 'Marshall Islands', phone: '692' }, + { + code: 'MK', + label: 'Macedonia, the Former Yugoslav Republic of', + phone: '389', + }, + { code: 'ML', label: 'Mali', phone: '223' }, + { code: 'MM', label: 'Myanmar', phone: '95' }, + { code: 'MN', label: 'Mongolia', phone: '976' }, + { code: 'MO', label: 'Macao', phone: '853' }, + { + code: 'MP', + label: 'Northern Mariana Islands', + phone: '1-670', + }, + { code: 'MQ', label: 'Martinique', phone: '596' }, + { code: 'MR', label: 'Mauritania', phone: '222' }, + { code: 'MS', label: 'Montserrat', phone: '1-664' }, + { code: 'MT', label: 'Malta', phone: '356' }, + { code: 'MU', label: 'Mauritius', phone: '230' }, + { code: 'MV', label: 'Maldives', phone: '960' }, + { code: 'MW', label: 'Malawi', phone: '265' }, + { code: 'MX', label: 'Mexico', phone: '52' }, + { code: 'MY', label: 'Malaysia', phone: '60' }, + { code: 'MZ', label: 'Mozambique', phone: '258' }, + { code: 'NA', label: 'Namibia', phone: '264' }, + { code: 'NC', label: 'New Caledonia', phone: '687' }, + { code: 'NE', label: 'Niger', phone: '227' }, + { code: 'NF', label: 'Norfolk Island', phone: '672' }, + { code: 'NG', label: 'Nigeria', phone: '234' }, + { code: 'NI', label: 'Nicaragua', phone: '505' }, + { code: 'NL', label: 'Netherlands', phone: '31' }, + { code: 'NO', label: 'Norway', phone: '47' }, + { code: 'NP', label: 'Nepal', phone: '977' }, + { code: 'NR', label: 'Nauru', phone: '674' }, + { code: 'NU', label: 'Niue', phone: '683' }, + { code: 'NZ', label: 'New Zealand', phone: '64' }, + { code: 'OM', label: 'Oman', phone: '968' }, + { code: 'PA', label: 'Panama', phone: '507' }, + { code: 'PE', label: 'Peru', phone: '51' }, + { code: 'PF', label: 'French Polynesia', phone: '689' }, + { code: 'PG', label: 'Papua New Guinea', phone: '675' }, + { code: 'PH', label: 'Philippines', phone: '63' }, + { code: 'PK', label: 'Pakistan', phone: '92' }, + { code: 'PL', label: 'Poland', phone: '48' }, + { + code: 'PM', + label: 'Saint Pierre and Miquelon', + phone: '508', + }, + { code: 'PN', label: 'Pitcairn', phone: '870' }, + { code: 'PR', label: 'Puerto Rico', phone: '1' }, + { + code: 'PS', + label: 'Palestine, State of', + phone: '970', + }, + { code: 'PT', label: 'Portugal', phone: '351' }, + { code: 'PW', label: 'Palau', phone: '680' }, + { code: 'PY', label: 'Paraguay', phone: '595' }, + { code: 'QA', label: 'Qatar', phone: '974' }, + { code: 'RE', label: 'Reunion', phone: '262' }, + { code: 'RO', label: 'Romania', phone: '40' }, + { code: 'RS', label: 'Serbia', phone: '381' }, + { code: 'RU', label: 'Russian Federation', phone: '7' }, + { code: 'RW', label: 'Rwanda', phone: '250' }, + { code: 'SA', label: 'Saudi Arabia', phone: '966' }, + { code: 'SB', label: 'Solomon Islands', phone: '677' }, + { code: 'SC', label: 'Seychelles', phone: '248' }, + { code: 'SD', label: 'Sudan', phone: '249' }, + { code: 'SE', label: 'Sweden', phone: '46' }, + { code: 'SG', label: 'Singapore', phone: '65' }, + { code: 'SH', label: 'Saint Helena', phone: '290' }, + { code: 'SI', label: 'Slovenia', phone: '386' }, + { + code: 'SJ', + label: 'Svalbard and Jan Mayen', + phone: '47', + }, + { code: 'SK', label: 'Slovakia', phone: '421' }, + { code: 'SL', label: 'Sierra Leone', phone: '232' }, + { code: 'SM', label: 'San Marino', phone: '378' }, + { code: 'SN', label: 'Senegal', phone: '221' }, + { code: 'SO', label: 'Somalia', phone: '252' }, + { code: 'SR', label: 'Suriname', phone: '597' }, + { code: 'SS', label: 'South Sudan', phone: '211' }, + { + code: 'ST', + label: 'Sao Tome and Principe', + phone: '239', + }, + { code: 'SV', label: 'El Salvador', phone: '503' }, + { + code: 'SX', + label: 'Sint Maarten (Dutch part)', + phone: '1-721', + }, + { + code: 'SY', + label: 'Syrian Arab Republic', + phone: '963', + }, + { code: 'SZ', label: 'Swaziland', phone: '268' }, + { + code: 'TC', + label: 'Turks and Caicos Islands', + phone: '1-649', + }, + { code: 'TD', label: 'Chad', phone: '235' }, + { + code: 'TF', + label: 'French Southern Territories', + phone: '262', + }, + { code: 'TG', label: 'Togo', phone: '228' }, + { code: 'TH', label: 'Thailand', phone: '66' }, + { code: 'TJ', label: 'Tajikistan', phone: '992' }, + { code: 'TK', label: 'Tokelau', phone: '690' }, + { code: 'TL', label: 'Timor-Leste', phone: '670' }, + { code: 'TM', label: 'Turkmenistan', phone: '993' }, + { code: 'TN', label: 'Tunisia', phone: '216' }, + { code: 'TO', label: 'Tonga', phone: '676' }, + { code: 'TR', label: 'Turkey', phone: '90' }, + { + code: 'TT', + label: 'Trinidad and Tobago', + phone: '1-868', + }, + { code: 'TV', label: 'Tuvalu', phone: '688' }, + { + code: 'TW', + label: 'Taiwan, Province of China', + phone: '886', + }, + { + code: 'TZ', + label: 'United Republic of Tanzania', + phone: '255', + }, + { code: 'UA', label: 'Ukraine', phone: '380' }, + { code: 'UG', label: 'Uganda', phone: '256' }, + { + code: 'US', + label: 'United States', + phone: '1', + suggested: true, + }, + { code: 'UY', label: 'Uruguay', phone: '598' }, + { code: 'UZ', label: 'Uzbekistan', phone: '998' }, + { + code: 'VA', + label: 'Holy See (Vatican City State)', + phone: '379', + }, + { + code: 'VC', + label: 'Saint Vincent and the Grenadines', + phone: '1-784', + }, + { code: 'VE', label: 'Venezuela', phone: '58' }, + { + code: 'VG', + label: 'British Virgin Islands', + phone: '1-284', + }, + { + code: 'VI', + label: 'US Virgin Islands', + phone: '1-340', + }, + { code: 'VN', label: 'Vietnam', phone: '84' }, + { code: 'VU', label: 'Vanuatu', phone: '678' }, + { code: 'WF', label: 'Wallis and Futuna', phone: '681' }, + { code: 'WS', label: 'Samoa', phone: '685' }, + { code: 'XK', label: 'Kosovo', phone: '383' }, + { code: 'YE', label: 'Yemen', phone: '967' }, + { code: 'YT', label: 'Mayotte', phone: '262' }, + { code: 'ZA', label: 'South Africa', phone: '27' }, + { code: 'ZM', label: 'Zambia', phone: '260' }, + { code: 'ZW', label: 'Zimbabwe', phone: '263' }, +]; diff --git a/docs/data/joy/components/autocomplete/CountrySelect.tsx b/docs/data/joy/components/autocomplete/CountrySelect.tsx new file mode 100644 index 00000000000000..f58cc87f1a2faa --- /dev/null +++ b/docs/data/joy/components/autocomplete/CountrySelect.tsx @@ -0,0 +1,469 @@ +import * as React from 'react'; +import Autocomplete from '@mui/joy/Autocomplete'; +import AutocompleteOption from '@mui/joy/AutocompleteOption'; +import ListItemDecorator from '@mui/joy/ListItemDecorator'; +import ListItemContent from '@mui/joy/ListItemContent'; +import Typography from '@mui/joy/Typography'; + +export default function CountrySelect() { + return ( + option.label} + renderOption={(props, option) => ( + + + + + + {option.label} + + ({option.code}) +{option.phone} + + + + )} + /> + ); +} + +// From https://bitbucket.org/atlassian/atlaskit-mk-2/raw/4ad0e56649c3e6c973e226b7efaeb28cb240ccb0/packages/core/select/src/data/countries.js +const countries = [ + { code: 'AD', label: 'Andorra', phone: '376' }, + { + code: 'AE', + label: 'United Arab Emirates', + phone: '971', + }, + { code: 'AF', label: 'Afghanistan', phone: '93' }, + { + code: 'AG', + label: 'Antigua and Barbuda', + phone: '1-268', + }, + { code: 'AI', label: 'Anguilla', phone: '1-264' }, + { code: 'AL', label: 'Albania', phone: '355' }, + { code: 'AM', label: 'Armenia', phone: '374' }, + { code: 'AO', label: 'Angola', phone: '244' }, + { code: 'AQ', label: 'Antarctica', phone: '672' }, + { code: 'AR', label: 'Argentina', phone: '54' }, + { code: 'AS', label: 'American Samoa', phone: '1-684' }, + { code: 'AT', label: 'Austria', phone: '43' }, + { + code: 'AU', + label: 'Australia', + phone: '61', + suggested: true, + }, + { code: 'AW', label: 'Aruba', phone: '297' }, + { code: 'AX', label: 'Alland Islands', phone: '358' }, + { code: 'AZ', label: 'Azerbaijan', phone: '994' }, + { + code: 'BA', + label: 'Bosnia and Herzegovina', + phone: '387', + }, + { code: 'BB', label: 'Barbados', phone: '1-246' }, + { code: 'BD', label: 'Bangladesh', phone: '880' }, + { code: 'BE', label: 'Belgium', phone: '32' }, + { code: 'BF', label: 'Burkina Faso', phone: '226' }, + { code: 'BG', label: 'Bulgaria', phone: '359' }, + { code: 'BH', label: 'Bahrain', phone: '973' }, + { code: 'BI', label: 'Burundi', phone: '257' }, + { code: 'BJ', label: 'Benin', phone: '229' }, + { code: 'BL', label: 'Saint Barthelemy', phone: '590' }, + { code: 'BM', label: 'Bermuda', phone: '1-441' }, + { code: 'BN', label: 'Brunei Darussalam', phone: '673' }, + { code: 'BO', label: 'Bolivia', phone: '591' }, + { code: 'BR', label: 'Brazil', phone: '55' }, + { code: 'BS', label: 'Bahamas', phone: '1-242' }, + { code: 'BT', label: 'Bhutan', phone: '975' }, + { code: 'BV', label: 'Bouvet Island', phone: '47' }, + { code: 'BW', label: 'Botswana', phone: '267' }, + { code: 'BY', label: 'Belarus', phone: '375' }, + { code: 'BZ', label: 'Belize', phone: '501' }, + { + code: 'CA', + label: 'Canada', + phone: '1', + suggested: true, + }, + { + code: 'CC', + label: 'Cocos (Keeling) Islands', + phone: '61', + }, + { + code: 'CD', + label: 'Congo, Democratic Republic of the', + phone: '243', + }, + { + code: 'CF', + label: 'Central African Republic', + phone: '236', + }, + { + code: 'CG', + label: 'Congo, Republic of the', + phone: '242', + }, + { code: 'CH', label: 'Switzerland', phone: '41' }, + { code: 'CI', label: "Cote d'Ivoire", phone: '225' }, + { code: 'CK', label: 'Cook Islands', phone: '682' }, + { code: 'CL', label: 'Chile', phone: '56' }, + { code: 'CM', label: 'Cameroon', phone: '237' }, + { code: 'CN', label: 'China', phone: '86' }, + { code: 'CO', label: 'Colombia', phone: '57' }, + { code: 'CR', label: 'Costa Rica', phone: '506' }, + { code: 'CU', label: 'Cuba', phone: '53' }, + { code: 'CV', label: 'Cape Verde', phone: '238' }, + { code: 'CW', label: 'Curacao', phone: '599' }, + { code: 'CX', label: 'Christmas Island', phone: '61' }, + { code: 'CY', label: 'Cyprus', phone: '357' }, + { code: 'CZ', label: 'Czech Republic', phone: '420' }, + { + code: 'DE', + label: 'Germany', + phone: '49', + suggested: true, + }, + { code: 'DJ', label: 'Djibouti', phone: '253' }, + { code: 'DK', label: 'Denmark', phone: '45' }, + { code: 'DM', label: 'Dominica', phone: '1-767' }, + { + code: 'DO', + label: 'Dominican Republic', + phone: '1-809', + }, + { code: 'DZ', label: 'Algeria', phone: '213' }, + { code: 'EC', label: 'Ecuador', phone: '593' }, + { code: 'EE', label: 'Estonia', phone: '372' }, + { code: 'EG', label: 'Egypt', phone: '20' }, + { code: 'EH', label: 'Western Sahara', phone: '212' }, + { code: 'ER', label: 'Eritrea', phone: '291' }, + { code: 'ES', label: 'Spain', phone: '34' }, + { code: 'ET', label: 'Ethiopia', phone: '251' }, + { code: 'FI', label: 'Finland', phone: '358' }, + { code: 'FJ', label: 'Fiji', phone: '679' }, + { + code: 'FK', + label: 'Falkland Islands (Malvinas)', + phone: '500', + }, + { + code: 'FM', + label: 'Micronesia, Federated States of', + phone: '691', + }, + { code: 'FO', label: 'Faroe Islands', phone: '298' }, + { + code: 'FR', + label: 'France', + phone: '33', + suggested: true, + }, + { code: 'GA', label: 'Gabon', phone: '241' }, + { code: 'GB', label: 'United Kingdom', phone: '44' }, + { code: 'GD', label: 'Grenada', phone: '1-473' }, + { code: 'GE', label: 'Georgia', phone: '995' }, + { code: 'GF', label: 'French Guiana', phone: '594' }, + { code: 'GG', label: 'Guernsey', phone: '44' }, + { code: 'GH', label: 'Ghana', phone: '233' }, + { code: 'GI', label: 'Gibraltar', phone: '350' }, + { code: 'GL', label: 'Greenland', phone: '299' }, + { code: 'GM', label: 'Gambia', phone: '220' }, + { code: 'GN', label: 'Guinea', phone: '224' }, + { code: 'GP', label: 'Guadeloupe', phone: '590' }, + { code: 'GQ', label: 'Equatorial Guinea', phone: '240' }, + { code: 'GR', label: 'Greece', phone: '30' }, + { + code: 'GS', + label: 'South Georgia and the South Sandwich Islands', + phone: '500', + }, + { code: 'GT', label: 'Guatemala', phone: '502' }, + { code: 'GU', label: 'Guam', phone: '1-671' }, + { code: 'GW', label: 'Guinea-Bissau', phone: '245' }, + { code: 'GY', label: 'Guyana', phone: '592' }, + { code: 'HK', label: 'Hong Kong', phone: '852' }, + { + code: 'HM', + label: 'Heard Island and McDonald Islands', + phone: '672', + }, + { code: 'HN', label: 'Honduras', phone: '504' }, + { code: 'HR', label: 'Croatia', phone: '385' }, + { code: 'HT', label: 'Haiti', phone: '509' }, + { code: 'HU', label: 'Hungary', phone: '36' }, + { code: 'ID', label: 'Indonesia', phone: '62' }, + { code: 'IE', label: 'Ireland', phone: '353' }, + { code: 'IL', label: 'Israel', phone: '972' }, + { code: 'IM', label: 'Isle of Man', phone: '44' }, + { code: 'IN', label: 'India', phone: '91' }, + { + code: 'IO', + label: 'British Indian Ocean Territory', + phone: '246', + }, + { code: 'IQ', label: 'Iraq', phone: '964' }, + { + code: 'IR', + label: 'Iran, Islamic Republic of', + phone: '98', + }, + { code: 'IS', label: 'Iceland', phone: '354' }, + { code: 'IT', label: 'Italy', phone: '39' }, + { code: 'JE', label: 'Jersey', phone: '44' }, + { code: 'JM', label: 'Jamaica', phone: '1-876' }, + { code: 'JO', label: 'Jordan', phone: '962' }, + { + code: 'JP', + label: 'Japan', + phone: '81', + suggested: true, + }, + { code: 'KE', label: 'Kenya', phone: '254' }, + { code: 'KG', label: 'Kyrgyzstan', phone: '996' }, + { code: 'KH', label: 'Cambodia', phone: '855' }, + { code: 'KI', label: 'Kiribati', phone: '686' }, + { code: 'KM', label: 'Comoros', phone: '269' }, + { + code: 'KN', + label: 'Saint Kitts and Nevis', + phone: '1-869', + }, + { + code: 'KP', + label: "Korea, Democratic People's Republic of", + phone: '850', + }, + { code: 'KR', label: 'Korea, Republic of', phone: '82' }, + { code: 'KW', label: 'Kuwait', phone: '965' }, + { code: 'KY', label: 'Cayman Islands', phone: '1-345' }, + { code: 'KZ', label: 'Kazakhstan', phone: '7' }, + { + code: 'LA', + label: "Lao People's Democratic Republic", + phone: '856', + }, + { code: 'LB', label: 'Lebanon', phone: '961' }, + { code: 'LC', label: 'Saint Lucia', phone: '1-758' }, + { code: 'LI', label: 'Liechtenstein', phone: '423' }, + { code: 'LK', label: 'Sri Lanka', phone: '94' }, + { code: 'LR', label: 'Liberia', phone: '231' }, + { code: 'LS', label: 'Lesotho', phone: '266' }, + { code: 'LT', label: 'Lithuania', phone: '370' }, + { code: 'LU', label: 'Luxembourg', phone: '352' }, + { code: 'LV', label: 'Latvia', phone: '371' }, + { code: 'LY', label: 'Libya', phone: '218' }, + { code: 'MA', label: 'Morocco', phone: '212' }, + { code: 'MC', label: 'Monaco', phone: '377' }, + { + code: 'MD', + label: 'Moldova, Republic of', + phone: '373', + }, + { code: 'ME', label: 'Montenegro', phone: '382' }, + { + code: 'MF', + label: 'Saint Martin (French part)', + phone: '590', + }, + { code: 'MG', label: 'Madagascar', phone: '261' }, + { code: 'MH', label: 'Marshall Islands', phone: '692' }, + { + code: 'MK', + label: 'Macedonia, the Former Yugoslav Republic of', + phone: '389', + }, + { code: 'ML', label: 'Mali', phone: '223' }, + { code: 'MM', label: 'Myanmar', phone: '95' }, + { code: 'MN', label: 'Mongolia', phone: '976' }, + { code: 'MO', label: 'Macao', phone: '853' }, + { + code: 'MP', + label: 'Northern Mariana Islands', + phone: '1-670', + }, + { code: 'MQ', label: 'Martinique', phone: '596' }, + { code: 'MR', label: 'Mauritania', phone: '222' }, + { code: 'MS', label: 'Montserrat', phone: '1-664' }, + { code: 'MT', label: 'Malta', phone: '356' }, + { code: 'MU', label: 'Mauritius', phone: '230' }, + { code: 'MV', label: 'Maldives', phone: '960' }, + { code: 'MW', label: 'Malawi', phone: '265' }, + { code: 'MX', label: 'Mexico', phone: '52' }, + { code: 'MY', label: 'Malaysia', phone: '60' }, + { code: 'MZ', label: 'Mozambique', phone: '258' }, + { code: 'NA', label: 'Namibia', phone: '264' }, + { code: 'NC', label: 'New Caledonia', phone: '687' }, + { code: 'NE', label: 'Niger', phone: '227' }, + { code: 'NF', label: 'Norfolk Island', phone: '672' }, + { code: 'NG', label: 'Nigeria', phone: '234' }, + { code: 'NI', label: 'Nicaragua', phone: '505' }, + { code: 'NL', label: 'Netherlands', phone: '31' }, + { code: 'NO', label: 'Norway', phone: '47' }, + { code: 'NP', label: 'Nepal', phone: '977' }, + { code: 'NR', label: 'Nauru', phone: '674' }, + { code: 'NU', label: 'Niue', phone: '683' }, + { code: 'NZ', label: 'New Zealand', phone: '64' }, + { code: 'OM', label: 'Oman', phone: '968' }, + { code: 'PA', label: 'Panama', phone: '507' }, + { code: 'PE', label: 'Peru', phone: '51' }, + { code: 'PF', label: 'French Polynesia', phone: '689' }, + { code: 'PG', label: 'Papua New Guinea', phone: '675' }, + { code: 'PH', label: 'Philippines', phone: '63' }, + { code: 'PK', label: 'Pakistan', phone: '92' }, + { code: 'PL', label: 'Poland', phone: '48' }, + { + code: 'PM', + label: 'Saint Pierre and Miquelon', + phone: '508', + }, + { code: 'PN', label: 'Pitcairn', phone: '870' }, + { code: 'PR', label: 'Puerto Rico', phone: '1' }, + { + code: 'PS', + label: 'Palestine, State of', + phone: '970', + }, + { code: 'PT', label: 'Portugal', phone: '351' }, + { code: 'PW', label: 'Palau', phone: '680' }, + { code: 'PY', label: 'Paraguay', phone: '595' }, + { code: 'QA', label: 'Qatar', phone: '974' }, + { code: 'RE', label: 'Reunion', phone: '262' }, + { code: 'RO', label: 'Romania', phone: '40' }, + { code: 'RS', label: 'Serbia', phone: '381' }, + { code: 'RU', label: 'Russian Federation', phone: '7' }, + { code: 'RW', label: 'Rwanda', phone: '250' }, + { code: 'SA', label: 'Saudi Arabia', phone: '966' }, + { code: 'SB', label: 'Solomon Islands', phone: '677' }, + { code: 'SC', label: 'Seychelles', phone: '248' }, + { code: 'SD', label: 'Sudan', phone: '249' }, + { code: 'SE', label: 'Sweden', phone: '46' }, + { code: 'SG', label: 'Singapore', phone: '65' }, + { code: 'SH', label: 'Saint Helena', phone: '290' }, + { code: 'SI', label: 'Slovenia', phone: '386' }, + { + code: 'SJ', + label: 'Svalbard and Jan Mayen', + phone: '47', + }, + { code: 'SK', label: 'Slovakia', phone: '421' }, + { code: 'SL', label: 'Sierra Leone', phone: '232' }, + { code: 'SM', label: 'San Marino', phone: '378' }, + { code: 'SN', label: 'Senegal', phone: '221' }, + { code: 'SO', label: 'Somalia', phone: '252' }, + { code: 'SR', label: 'Suriname', phone: '597' }, + { code: 'SS', label: 'South Sudan', phone: '211' }, + { + code: 'ST', + label: 'Sao Tome and Principe', + phone: '239', + }, + { code: 'SV', label: 'El Salvador', phone: '503' }, + { + code: 'SX', + label: 'Sint Maarten (Dutch part)', + phone: '1-721', + }, + { + code: 'SY', + label: 'Syrian Arab Republic', + phone: '963', + }, + { code: 'SZ', label: 'Swaziland', phone: '268' }, + { + code: 'TC', + label: 'Turks and Caicos Islands', + phone: '1-649', + }, + { code: 'TD', label: 'Chad', phone: '235' }, + { + code: 'TF', + label: 'French Southern Territories', + phone: '262', + }, + { code: 'TG', label: 'Togo', phone: '228' }, + { code: 'TH', label: 'Thailand', phone: '66' }, + { code: 'TJ', label: 'Tajikistan', phone: '992' }, + { code: 'TK', label: 'Tokelau', phone: '690' }, + { code: 'TL', label: 'Timor-Leste', phone: '670' }, + { code: 'TM', label: 'Turkmenistan', phone: '993' }, + { code: 'TN', label: 'Tunisia', phone: '216' }, + { code: 'TO', label: 'Tonga', phone: '676' }, + { code: 'TR', label: 'Turkey', phone: '90' }, + { + code: 'TT', + label: 'Trinidad and Tobago', + phone: '1-868', + }, + { code: 'TV', label: 'Tuvalu', phone: '688' }, + { + code: 'TW', + label: 'Taiwan, Province of China', + phone: '886', + }, + { + code: 'TZ', + label: 'United Republic of Tanzania', + phone: '255', + }, + { code: 'UA', label: 'Ukraine', phone: '380' }, + { code: 'UG', label: 'Uganda', phone: '256' }, + { + code: 'US', + label: 'United States', + phone: '1', + suggested: true, + }, + { code: 'UY', label: 'Uruguay', phone: '598' }, + { code: 'UZ', label: 'Uzbekistan', phone: '998' }, + { + code: 'VA', + label: 'Holy See (Vatican City State)', + phone: '379', + }, + { + code: 'VC', + label: 'Saint Vincent and the Grenadines', + phone: '1-784', + }, + { code: 'VE', label: 'Venezuela', phone: '58' }, + { + code: 'VG', + label: 'British Virgin Islands', + phone: '1-284', + }, + { + code: 'VI', + label: 'US Virgin Islands', + phone: '1-340', + }, + { code: 'VN', label: 'Vietnam', phone: '84' }, + { code: 'VU', label: 'Vanuatu', phone: '678' }, + { code: 'WF', label: 'Wallis and Futuna', phone: '681' }, + { code: 'WS', label: 'Samoa', phone: '685' }, + { code: 'XK', label: 'Kosovo', phone: '383' }, + { code: 'YE', label: 'Yemen', phone: '967' }, + { code: 'YT', label: 'Mayotte', phone: '262' }, + { code: 'ZA', label: 'South Africa', phone: '27' }, + { code: 'ZM', label: 'Zambia', phone: '260' }, + { code: 'ZW', label: 'Zimbabwe', phone: '263' }, +]; diff --git a/docs/data/joy/components/autocomplete/CustomTags.js b/docs/data/joy/components/autocomplete/CustomTags.js new file mode 100644 index 00000000000000..7e808ccc86ed50 --- /dev/null +++ b/docs/data/joy/components/autocomplete/CustomTags.js @@ -0,0 +1,158 @@ +import * as React from 'react'; +import Autocomplete from '@mui/joy/Autocomplete'; +import Chip from '@mui/joy/Chip'; +import Close from '@mui/icons-material/Close'; + +export default function CustomTags() { + return ( + option.title} + defaultValue={[top100Films[13]]} + renderTags={(tags, getTagProps) => + tags.map((item, index) => ( + } + {...getTagProps({ index })} + > + {item.title} + + )) + } + /> + ); +} + +// 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/data/joy/components/autocomplete/CustomTags.tsx b/docs/data/joy/components/autocomplete/CustomTags.tsx new file mode 100644 index 00000000000000..7e808ccc86ed50 --- /dev/null +++ b/docs/data/joy/components/autocomplete/CustomTags.tsx @@ -0,0 +1,158 @@ +import * as React from 'react'; +import Autocomplete from '@mui/joy/Autocomplete'; +import Chip from '@mui/joy/Chip'; +import Close from '@mui/icons-material/Close'; + +export default function CustomTags() { + return ( + option.title} + defaultValue={[top100Films[13]]} + renderTags={(tags, getTagProps) => + tags.map((item, index) => ( + } + {...getTagProps({ index })} + > + {item.title} + + )) + } + /> + ); +} + +// 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/data/joy/components/autocomplete/DisabledOptions.js b/docs/data/joy/components/autocomplete/DisabledOptions.js new file mode 100644 index 00000000000000..e7fbffaeb2a983 --- /dev/null +++ b/docs/data/joy/components/autocomplete/DisabledOptions.js @@ -0,0 +1,28 @@ +import * as React from 'react'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import Autocomplete from '@mui/joy/Autocomplete'; + +export default function DisabledOptions() { + return ( + + Disabled options + + option === timeSlots[0] || option === timeSlots[2] + } + sx={{ width: 300 }} + /> + + ); +} + +// One time slot every 30 minutes. +const timeSlots = Array.from(new Array(24 * 2)).map( + (_, index) => + `${index < 20 ? '0' : ''}${Math.floor(index / 2)}:${ + index % 2 === 0 ? '00' : '30' + }`, +); diff --git a/docs/data/joy/components/autocomplete/DisabledOptions.tsx b/docs/data/joy/components/autocomplete/DisabledOptions.tsx new file mode 100644 index 00000000000000..e7fbffaeb2a983 --- /dev/null +++ b/docs/data/joy/components/autocomplete/DisabledOptions.tsx @@ -0,0 +1,28 @@ +import * as React from 'react'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import Autocomplete from '@mui/joy/Autocomplete'; + +export default function DisabledOptions() { + return ( + + Disabled options + + option === timeSlots[0] || option === timeSlots[2] + } + sx={{ width: 300 }} + /> + + ); +} + +// One time slot every 30 minutes. +const timeSlots = Array.from(new Array(24 * 2)).map( + (_, index) => + `${index < 20 ? '0' : ''}${Math.floor(index / 2)}:${ + index % 2 === 0 ? '00' : '30' + }`, +); diff --git a/docs/data/joy/components/autocomplete/DisabledOptions.tsx.preview b/docs/data/joy/components/autocomplete/DisabledOptions.tsx.preview new file mode 100644 index 00000000000000..13a1a7e0a85367 --- /dev/null +++ b/docs/data/joy/components/autocomplete/DisabledOptions.tsx.preview @@ -0,0 +1,11 @@ + + Disabled options + + option === timeSlots[0] || option === timeSlots[2] + } + sx={{ width: 300 }} + /> + \ No newline at end of file diff --git a/docs/data/joy/components/autocomplete/Filter.js b/docs/data/joy/components/autocomplete/Filter.js new file mode 100644 index 00000000000000..e2d4ab3a057609 --- /dev/null +++ b/docs/data/joy/components/autocomplete/Filter.js @@ -0,0 +1,152 @@ +import * as React from 'react'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import Autocomplete, { createFilterOptions } from '@mui/joy/Autocomplete'; + +const filterOptions = createFilterOptions({ + matchFrom: 'start', + stringify: (option) => option.title, +}); + +export default function Filter() { + return ( + + Custom filter + option.title} + filterOptions={filterOptions} + sx={{ width: 300 }} + /> + + ); +} + +// 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/data/joy/components/autocomplete/Filter.tsx b/docs/data/joy/components/autocomplete/Filter.tsx new file mode 100644 index 00000000000000..9487ef9beb5621 --- /dev/null +++ b/docs/data/joy/components/autocomplete/Filter.tsx @@ -0,0 +1,152 @@ +import * as React from 'react'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import Autocomplete, { createFilterOptions } from '@mui/joy/Autocomplete'; + +const filterOptions = createFilterOptions({ + matchFrom: 'start', + stringify: (option: { title: string }) => option.title, +}); + +export default function Filter() { + return ( + + Custom filter + option.title} + filterOptions={filterOptions} + sx={{ width: 300 }} + /> + + ); +} + +// 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/data/joy/components/autocomplete/Filter.tsx.preview b/docs/data/joy/components/autocomplete/Filter.tsx.preview new file mode 100644 index 00000000000000..06bf87e4485741 --- /dev/null +++ b/docs/data/joy/components/autocomplete/Filter.tsx.preview @@ -0,0 +1,10 @@ + + Custom filter + option.title} + filterOptions={filterOptions} + sx={{ width: 300 }} + /> + \ No newline at end of file diff --git a/docs/data/joy/components/autocomplete/FixedTags.js b/docs/data/joy/components/autocomplete/FixedTags.js new file mode 100644 index 00000000000000..17e80d30b5ddf8 --- /dev/null +++ b/docs/data/joy/components/autocomplete/FixedTags.js @@ -0,0 +1,170 @@ +import * as React from 'react'; +import Chip from '@mui/joy/Chip'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import Autocomplete from '@mui/joy/Autocomplete'; + +export default function FixedTags() { + const fixedOptions = [top100Films[6]]; + const [value, setValue] = React.useState([...fixedOptions, top100Films[13]]); + + return ( + + Fixed tags + { + setValue([ + ...fixedOptions, + ...newValue.filter((option) => fixedOptions.indexOf(option) === -1), + ]); + }} + options={top100Films} + getOptionLabel={(option) => option.title} + renderTags={(tagValue, getTagProps) => + tagValue.map((option, index) => ( + + {option.title} + + )) + } + sx={{ width: '500px' }} + /> + + ); +} + +// 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/data/joy/components/autocomplete/FixedTags.tsx b/docs/data/joy/components/autocomplete/FixedTags.tsx new file mode 100644 index 00000000000000..17e80d30b5ddf8 --- /dev/null +++ b/docs/data/joy/components/autocomplete/FixedTags.tsx @@ -0,0 +1,170 @@ +import * as React from 'react'; +import Chip from '@mui/joy/Chip'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import Autocomplete from '@mui/joy/Autocomplete'; + +export default function FixedTags() { + const fixedOptions = [top100Films[6]]; + const [value, setValue] = React.useState([...fixedOptions, top100Films[13]]); + + return ( + + Fixed tags + { + setValue([ + ...fixedOptions, + ...newValue.filter((option) => fixedOptions.indexOf(option) === -1), + ]); + }} + options={top100Films} + getOptionLabel={(option) => option.title} + renderTags={(tagValue, getTagProps) => + tagValue.map((option, index) => ( + + {option.title} + + )) + } + sx={{ width: '500px' }} + /> + + ); +} + +// 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/data/joy/components/autocomplete/FreeSolo.js b/docs/data/joy/components/autocomplete/FreeSolo.js new file mode 100644 index 00000000000000..347052500e8cc3 --- /dev/null +++ b/docs/data/joy/components/autocomplete/FreeSolo.js @@ -0,0 +1,158 @@ +import * as React from 'react'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import Stack from '@mui/joy/Stack'; +import Autocomplete from '@mui/joy/Autocomplete'; + +export default function FreeSolo() { + return ( + + + freeSolo + option.title)} + /> + + + Search input + option.title)} + /> + + + ); +} + +// 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/data/joy/components/autocomplete/FreeSolo.tsx b/docs/data/joy/components/autocomplete/FreeSolo.tsx new file mode 100644 index 00000000000000..347052500e8cc3 --- /dev/null +++ b/docs/data/joy/components/autocomplete/FreeSolo.tsx @@ -0,0 +1,158 @@ +import * as React from 'react'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import Stack from '@mui/joy/Stack'; +import Autocomplete from '@mui/joy/Autocomplete'; + +export default function FreeSolo() { + return ( + + + freeSolo + option.title)} + /> + + + Search input + option.title)} + /> + + + ); +} + +// 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/data/joy/components/autocomplete/FreeSoloCreateOption.js b/docs/data/joy/components/autocomplete/FreeSoloCreateOption.js new file mode 100644 index 00000000000000..f748ee0cbc2bfc --- /dev/null +++ b/docs/data/joy/components/autocomplete/FreeSoloCreateOption.js @@ -0,0 +1,208 @@ +import * as React from 'react'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import Autocomplete, { createFilterOptions } from '@mui/joy/Autocomplete'; +import AutocompleteOption from '@mui/joy/AutocompleteOption'; +import ListItemDecorator from '@mui/joy/ListItemDecorator'; +import Add from '@mui/icons-material/Add'; + +const filter = createFilterOptions(); + +export default function FreeSoloCreateOption() { + const [value, setValue] = React.useState(null); + + return ( + + Free solo with text demo + { + if (typeof newValue === 'string') { + setValue({ + title: newValue, + }); + } else if (newValue && newValue.inputValue) { + // Create a new value from the user input + setValue({ + title: newValue.inputValue, + }); + } else { + setValue(newValue); + } + }} + filterOptions={(options, params) => { + const filtered = filter(options, params); + + const { inputValue } = params; + // Suggest the creation of a new value + const isExisting = options.some((option) => inputValue === option.title); + if (inputValue !== '' && !isExisting) { + filtered.push({ + inputValue, + title: `Add "${inputValue}"`, + }); + } + + return filtered; + }} + selectOnFocus + clearOnBlur + handleHomeEndKeys + freeSolo + options={top100Films} + getOptionLabel={(option) => { + // 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={(props, option) => ( + + {option.title?.startsWith('Add "') && ( + + + + )} + + {option.title} + + )} + sx={{ width: 300 }} + /> + + ); +} + +// 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/data/joy/components/autocomplete/FreeSoloCreateOption.tsx b/docs/data/joy/components/autocomplete/FreeSoloCreateOption.tsx new file mode 100644 index 00000000000000..9caf3ff6a2b496 --- /dev/null +++ b/docs/data/joy/components/autocomplete/FreeSoloCreateOption.tsx @@ -0,0 +1,213 @@ +import * as React from 'react'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import Autocomplete, { createFilterOptions } from '@mui/joy/Autocomplete'; +import AutocompleteOption from '@mui/joy/AutocompleteOption'; +import ListItemDecorator from '@mui/joy/ListItemDecorator'; +import Add from '@mui/icons-material/Add'; + +const filter = createFilterOptions(); + +export default function FreeSoloCreateOption() { + const [value, setValue] = React.useState(null); + + return ( + + Free solo with text demo + { + if (typeof newValue === 'string') { + setValue({ + title: newValue, + }); + } else if (newValue && newValue.inputValue) { + // Create a new value from the user input + setValue({ + title: newValue.inputValue, + }); + } else { + setValue(newValue); + } + }} + filterOptions={(options, params) => { + const filtered = filter(options, params); + + const { inputValue } = params; + // Suggest the creation of a new value + const isExisting = options.some((option) => inputValue === option.title); + if (inputValue !== '' && !isExisting) { + filtered.push({ + inputValue, + title: `Add "${inputValue}"`, + }); + } + + return filtered; + }} + selectOnFocus + clearOnBlur + handleHomeEndKeys + freeSolo + options={top100Films} + getOptionLabel={(option) => { + // 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={(props, option) => ( + + {option.title?.startsWith('Add "') && ( + + + + )} + {option.title} + + )} + sx={{ width: 300 }} + /> + + ); +} + +interface FilmOptionType { + inputValue?: string; + title: string; + year?: number; +} + +// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top +const top100Films: readonly FilmOptionType[] = [ + { 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/data/joy/components/autocomplete/FreeSoloCreateOptionDialog.js b/docs/data/joy/components/autocomplete/FreeSoloCreateOptionDialog.js new file mode 100644 index 00000000000000..af6a55b36c1d88 --- /dev/null +++ b/docs/data/joy/components/autocomplete/FreeSoloCreateOptionDialog.js @@ -0,0 +1,288 @@ +import * as React from 'react'; +import Autocomplete, { createFilterOptions } from '@mui/joy/Autocomplete'; +import AutocompleteOption from '@mui/joy/AutocompleteOption'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import Modal from '@mui/joy/Modal'; +import ModalDialog from '@mui/joy/ModalDialog'; +import Button from '@mui/joy/Button'; +import TextField from '@mui/joy/TextField'; +import Typography from '@mui/joy/Typography'; +import Stack from '@mui/joy/Stack'; + +const filter = createFilterOptions(); + +export default function FreeSoloCreateOptionDialog() { + const [value, setValue] = React.useState(null); + const [open, toggleOpen] = React.useState(false); + + const handleClose = () => { + setDialogValue({ + title: '', + year: '', + }); + + toggleOpen(false); + }; + + const [dialogValue, setDialogValue] = React.useState({ + title: '', + year: '', + }); + + const handleSubmit = (event) => { + event.preventDefault(); + setValue({ + title: dialogValue.title, + year: parseInt(dialogValue.year, 10), + }); + + handleClose(); + }; + + return ( + + + Free solo dialog + { + if (typeof newValue === 'string') { + // timeout to avoid instant validation of the dialog's form. + setTimeout(() => { + toggleOpen(true); + setDialogValue({ + title: newValue, + year: '', + }); + }); + } else if (newValue && newValue.inputValue) { + toggleOpen(true); + setDialogValue({ + title: newValue.inputValue, + year: '', + }); + } else { + setValue(newValue); + } + }} + filterOptions={(options, params) => { + const filtered = filter(options, params); + + if (params.inputValue !== '') { + filtered.push({ + inputValue: params.inputValue, + title: `Add "${params.inputValue}"`, + }); + } + + return filtered; + }} + options={top100Films} + getOptionLabel={(option) => { + // e.g value selected with enter, right from the input + if (typeof option === 'string') { + return option; + } + if (option.inputValue) { + return option.inputValue; + } + return option.title; + }} + freeSolo + selectOnFocus + clearOnBlur + handleHomeEndKeys + renderOption={(props, option) => ( + {option.title} + )} + sx={{ width: 300 }} + /> + + + +
+ + Add a new film + + + Did you miss any film in our list? Please, add it! + + + + setDialogValue({ + ...dialogValue, + title: event.target.value, + }) + } + label="Title" + type="text" + /> + + setDialogValue({ + ...dialogValue, + year: event.target.value, + }) + } + label="year" + type="number" + /> + + + + + +
+
+
+
+ ); +} + +// 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/data/joy/components/autocomplete/FreeSoloCreateOptionDialog.tsx b/docs/data/joy/components/autocomplete/FreeSoloCreateOptionDialog.tsx new file mode 100644 index 00000000000000..f82c9c83f8c902 --- /dev/null +++ b/docs/data/joy/components/autocomplete/FreeSoloCreateOptionDialog.tsx @@ -0,0 +1,294 @@ +import * as React from 'react'; +import Autocomplete, { createFilterOptions } from '@mui/joy/Autocomplete'; +import AutocompleteOption from '@mui/joy/AutocompleteOption'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import Modal from '@mui/joy/Modal'; +import ModalDialog from '@mui/joy/ModalDialog'; +import Button from '@mui/joy/Button'; +import TextField from '@mui/joy/TextField'; +import Typography from '@mui/joy/Typography'; +import Stack from '@mui/joy/Stack'; + +const filter = createFilterOptions(); + +export default function FreeSoloCreateOptionDialog() { + const [value, setValue] = React.useState(null); + const [open, toggleOpen] = React.useState(false); + + const handleClose = () => { + setDialogValue({ + title: '', + year: '', + }); + + toggleOpen(false); + }; + + const [dialogValue, setDialogValue] = React.useState({ + title: '', + year: '', + }); + + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + setValue({ + title: dialogValue.title, + year: parseInt(dialogValue.year, 10), + }); + + handleClose(); + }; + + return ( + + + Free solo dialog + { + if (typeof newValue === 'string') { + // timeout to avoid instant validation of the dialog's form. + setTimeout(() => { + toggleOpen(true); + setDialogValue({ + title: newValue, + year: '', + }); + }); + } else if (newValue && newValue.inputValue) { + toggleOpen(true); + setDialogValue({ + title: newValue.inputValue, + year: '', + }); + } else { + setValue(newValue); + } + }} + filterOptions={(options, params) => { + const filtered = filter(options, params); + + if (params.inputValue !== '') { + filtered.push({ + inputValue: params.inputValue, + title: `Add "${params.inputValue}"`, + }); + } + + return filtered; + }} + options={top100Films} + getOptionLabel={(option) => { + // e.g value selected with enter, right from the input + if (typeof option === 'string') { + return option; + } + if (option.inputValue) { + return option.inputValue; + } + return option.title; + }} + freeSolo + selectOnFocus + clearOnBlur + handleHomeEndKeys + renderOption={(props, option) => ( + {option.title} + )} + sx={{ width: 300 }} + /> + + + +
+ + Add a new film + + + Did you miss any film in our list? Please, add it! + + + + setDialogValue({ + ...dialogValue, + title: event.target.value, + }) + } + label="Title" + type="text" + /> + + setDialogValue({ + ...dialogValue, + year: event.target.value, + }) + } + label="year" + type="number" + /> + + + + + +
+
+
+
+ ); +} + +interface FilmOptionType { + inputValue?: string; + title: string; + year?: number; +} + +// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top +const top100Films: readonly FilmOptionType[] = [ + { 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/data/joy/components/autocomplete/GitHubLabel.js b/docs/data/joy/components/autocomplete/GitHubLabel.js new file mode 100644 index 00000000000000..27b2cbb6003029 --- /dev/null +++ b/docs/data/joy/components/autocomplete/GitHubLabel.js @@ -0,0 +1,311 @@ +import * as React from 'react'; +import PopperUnstyled from '@mui/base/PopperUnstyled'; +import ClickAwayListener from '@mui/base/ClickAwayListener'; +import Autocomplete from '@mui/joy/Autocomplete'; +import AutocompleteListbox from '@mui/joy/AutocompleteListbox'; +import AutocompleteOption from '@mui/joy/AutocompleteOption'; +import Box from '@mui/joy/Box'; +import Link from '@mui/joy/Link'; +import List from '@mui/joy/List'; +import ListItem from '@mui/joy/ListItem'; +import Typography from '@mui/joy/Typography'; +import Sheet from '@mui/joy/Sheet'; +import SettingsIcon from '@mui/icons-material/Settings'; +import CloseIcon from '@mui/icons-material/Close'; +import DoneIcon from '@mui/icons-material/Done'; + +const Listbox = React.forwardRef((props, ref) => ( + +)); + +export default function GitHubLabel() { + const [anchorEl, setAnchorEl] = React.useState(null); + const [value, setValue] = React.useState([labels[1], labels[11]]); + const [pendingValue, setPendingValue] = React.useState([]); + + const handleClick = (event) => { + setPendingValue(value); + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setValue(pendingValue); + if (anchorEl) { + anchorEl.focus(); + } + setAnchorEl(null); + }; + + const open = Boolean(anchorEl); + const id = open ? 'github-label' : undefined; + + return ( + + + {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} + + Labels + + + + {value.map((label) => ( + + {label.name} + + ))} + + + + + + + Apply labels to this pull request + + { + if (reason === 'escape') { + handleClose(); + } + }} + value={pendingValue} + onChange={(event, newValue, reason) => { + if ( + event.type === 'keydown' && + event.key === 'Backspace' && + reason === 'removeOption' + ) { + return; + } + setPendingValue(newValue); + }} + disableClearable + disableCloseOnSelect + forcePopupIcon={false} + renderTags={() => null} + noOptionsText="No labels" + renderOption={(props, option, { selected }) => ( + + + + + {option.name} + + {option.description} + + + + + )} + options={[...labels].sort((a, b) => { + // Display the selected labels first. + let ai = value.indexOf(a); + ai = ai === -1 ? value.length + labels.indexOf(a) : ai; + let bi = value.indexOf(b); + bi = bi === -1 ? value.length + labels.indexOf(b) : bi; + return ai - bi; + })} + getOptionLabel={(option) => option.name} + sx={{ + p: '10px', + borderTop: '1px solid', + borderBottom: '1px solid', + borderColor: 'divider', + '--Input-radius': '4px', + }} + /> + + + + + ); +} + +// From https://github.com/abdonrd/github-labels +const labels = [ + { + name: 'good first issue', + color: '#7057ff', + description: 'Good for newcomers', + }, + { + name: 'help wanted', + color: '#008672', + description: 'Extra attention is needed', + }, + { + name: 'priority: critical', + color: '#b60205', + description: '', + }, + { + name: 'priority: high', + color: '#d93f0b', + description: '', + }, + { + name: 'priority: low', + color: '#0e8a16', + description: '', + }, + { + name: 'priority: medium', + color: '#fbca04', + description: '', + }, + { + name: "status: can't reproduce", + color: '#fec1c1', + description: '', + }, + { + name: 'status: confirmed', + color: '#215cea', + description: '', + }, + { + name: 'status: duplicate', + color: '#cfd3d7', + description: 'This issue or pull request already exists', + }, + { + name: 'status: needs information', + color: '#fef2c0', + description: '', + }, + { + name: 'status: wont do/fix', + color: '#eeeeee', + description: 'This will not be worked on', + }, + { + name: 'type: bug', + color: '#d73a4a', + description: "Something isn't working", + }, + { + name: 'type: discussion', + color: '#d4c5f9', + description: '', + }, + { + name: 'type: documentation', + color: '#006b75', + description: '', + }, + { + name: 'type: enhancement', + color: '#84b6eb', + description: '', + }, + { + name: 'type: epic', + color: '#3e4b9e', + description: 'A theme of work that contain sub-tasks', + }, + { + name: 'type: feature request', + color: '#fbca04', + description: 'New feature or request', + }, + { + name: 'type: question', + color: '#d876e3', + description: 'Further information is requested', + }, +]; diff --git a/docs/data/joy/components/autocomplete/GitHubLabel.tsx b/docs/data/joy/components/autocomplete/GitHubLabel.tsx new file mode 100644 index 00000000000000..6f6c9fc657bcc8 --- /dev/null +++ b/docs/data/joy/components/autocomplete/GitHubLabel.tsx @@ -0,0 +1,317 @@ +import * as React from 'react'; +import PopperUnstyled from '@mui/base/PopperUnstyled'; +import ClickAwayListener from '@mui/base/ClickAwayListener'; +import Autocomplete from '@mui/joy/Autocomplete'; +import AutocompleteListbox from '@mui/joy/AutocompleteListbox'; +import AutocompleteOption from '@mui/joy/AutocompleteOption'; +import Box from '@mui/joy/Box'; +import Link from '@mui/joy/Link'; +import List from '@mui/joy/List'; +import ListItem from '@mui/joy/ListItem'; +import Typography from '@mui/joy/Typography'; +import Sheet from '@mui/joy/Sheet'; +import SettingsIcon from '@mui/icons-material/Settings'; +import CloseIcon from '@mui/icons-material/Close'; +import DoneIcon from '@mui/icons-material/Done'; + +const Listbox = React.forwardRef((props, ref) => ( + +)); + +export default function GitHubLabel() { + const [anchorEl, setAnchorEl] = React.useState(null); + const [value, setValue] = React.useState([labels[1], labels[11]]); + const [pendingValue, setPendingValue] = React.useState([]); + + const handleClick = (event: React.MouseEvent) => { + setPendingValue(value); + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setValue(pendingValue); + if (anchorEl) { + anchorEl.focus(); + } + setAnchorEl(null); + }; + + const open = Boolean(anchorEl); + const id = open ? 'github-label' : undefined; + + return ( + + + {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} + + Labels + + + + {value.map((label) => ( + + {label.name} + + ))} + + + + + + + Apply labels to this pull request + + { + if (reason === 'escape') { + handleClose(); + } + }} + value={pendingValue} + onChange={(event, newValue, reason) => { + if ( + event.type === 'keydown' && + (event as React.KeyboardEvent).key === 'Backspace' && + reason === 'removeOption' + ) { + return; + } + setPendingValue(newValue); + }} + disableClearable + disableCloseOnSelect + forcePopupIcon={false} + renderTags={() => null} + noOptionsText="No labels" + renderOption={(props, option, { selected }) => ( + + + + + {option.name} + + {option.description} + + + + + )} + options={[...labels].sort((a, b) => { + // Display the selected labels first. + let ai = value.indexOf(a); + ai = ai === -1 ? value.length + labels.indexOf(a) : ai; + let bi = value.indexOf(b); + bi = bi === -1 ? value.length + labels.indexOf(b) : bi; + return ai - bi; + })} + getOptionLabel={(option) => option.name} + sx={{ + p: '10px', + borderTop: '1px solid', + borderBottom: '1px solid', + borderColor: 'divider', + '--Input-radius': '4px', + }} + /> + + + + + ); +} + +interface LabelType { + name: string; + color: string; + description?: string; +} + +// From https://github.com/abdonrd/github-labels +const labels = [ + { + name: 'good first issue', + color: '#7057ff', + description: 'Good for newcomers', + }, + { + name: 'help wanted', + color: '#008672', + description: 'Extra attention is needed', + }, + { + name: 'priority: critical', + color: '#b60205', + description: '', + }, + { + name: 'priority: high', + color: '#d93f0b', + description: '', + }, + { + name: 'priority: low', + color: '#0e8a16', + description: '', + }, + { + name: 'priority: medium', + color: '#fbca04', + description: '', + }, + { + name: "status: can't reproduce", + color: '#fec1c1', + description: '', + }, + { + name: 'status: confirmed', + color: '#215cea', + description: '', + }, + { + name: 'status: duplicate', + color: '#cfd3d7', + description: 'This issue or pull request already exists', + }, + { + name: 'status: needs information', + color: '#fef2c0', + description: '', + }, + { + name: 'status: wont do/fix', + color: '#eeeeee', + description: 'This will not be worked on', + }, + { + name: 'type: bug', + color: '#d73a4a', + description: "Something isn't working", + }, + { + name: 'type: discussion', + color: '#d4c5f9', + description: '', + }, + { + name: 'type: documentation', + color: '#006b75', + description: '', + }, + { + name: 'type: enhancement', + color: '#84b6eb', + description: '', + }, + { + name: 'type: epic', + color: '#3e4b9e', + description: 'A theme of work that contain sub-tasks', + }, + { + name: 'type: feature request', + color: '#fbca04', + description: 'New feature or request', + }, + { + name: 'type: question', + color: '#d876e3', + description: 'Further information is requested', + }, +]; diff --git a/docs/data/joy/components/autocomplete/Grouped.js b/docs/data/joy/components/autocomplete/Grouped.js new file mode 100644 index 00000000000000..fde6c6da92b4e0 --- /dev/null +++ b/docs/data/joy/components/autocomplete/Grouped.js @@ -0,0 +1,154 @@ +import * as React from 'react'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import Autocomplete from '@mui/joy/Autocomplete'; + +export default function Grouped() { + const options = top100Films.map((option) => { + const firstLetter = option.title[0].toUpperCase(); + return { + firstLetter: /[0-9]/.test(firstLetter) ? '0-9' : firstLetter, + ...option, + }; + }); + + return ( + + With categories + -b.firstLetter.localeCompare(a.firstLetter))} + groupBy={(option) => option.firstLetter} + getOptionLabel={(option) => option.title} + sx={{ width: 300 }} + /> + + ); +} + +// 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/data/joy/components/autocomplete/Grouped.tsx b/docs/data/joy/components/autocomplete/Grouped.tsx new file mode 100644 index 00000000000000..fde6c6da92b4e0 --- /dev/null +++ b/docs/data/joy/components/autocomplete/Grouped.tsx @@ -0,0 +1,154 @@ +import * as React from 'react'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import Autocomplete from '@mui/joy/Autocomplete'; + +export default function Grouped() { + const options = top100Films.map((option) => { + const firstLetter = option.title[0].toUpperCase(); + return { + firstLetter: /[0-9]/.test(firstLetter) ? '0-9' : firstLetter, + ...option, + }; + }); + + return ( + + With categories + -b.firstLetter.localeCompare(a.firstLetter))} + groupBy={(option) => option.firstLetter} + getOptionLabel={(option) => option.title} + sx={{ width: 300 }} + /> + + ); +} + +// 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/data/joy/components/autocomplete/Grouped.tsx.preview b/docs/data/joy/components/autocomplete/Grouped.tsx.preview new file mode 100644 index 00000000000000..1b50cfdb67a473 --- /dev/null +++ b/docs/data/joy/components/autocomplete/Grouped.tsx.preview @@ -0,0 +1,9 @@ + + With categories + -b.firstLetter.localeCompare(a.firstLetter))} + groupBy={(option) => option.firstLetter} + getOptionLabel={(option) => option.title} + sx={{ width: 300 }} + /> + \ No newline at end of file diff --git a/docs/data/joy/components/autocomplete/Highlights.js b/docs/data/joy/components/autocomplete/Highlights.js new file mode 100644 index 00000000000000..97b43b929a90f6 --- /dev/null +++ b/docs/data/joy/components/autocomplete/Highlights.js @@ -0,0 +1,176 @@ +import * as React from 'react'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import Autocomplete from '@mui/joy/Autocomplete'; +import AutocompleteOption from '@mui/joy/AutocompleteOption'; +import Typography from '@mui/joy/Typography'; +import parse from 'autosuggest-highlight/parse'; +import match from 'autosuggest-highlight/match'; + +export default function Highlights() { + return ( + + Highlights + option.title} + renderOption={(props, option, { inputValue }) => { + const matches = match(option.title, inputValue); + const parts = parse(option.title, matches); + + return ( + + + {option.title === inputValue + ? option.title + : parts.map((part, index) => ( + + {part.text} + + ))} + + + ); + }} + /> + + ); +} + +// 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/data/joy/components/autocomplete/Highlights.tsx b/docs/data/joy/components/autocomplete/Highlights.tsx new file mode 100644 index 00000000000000..97b43b929a90f6 --- /dev/null +++ b/docs/data/joy/components/autocomplete/Highlights.tsx @@ -0,0 +1,176 @@ +import * as React from 'react'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import Autocomplete from '@mui/joy/Autocomplete'; +import AutocompleteOption from '@mui/joy/AutocompleteOption'; +import Typography from '@mui/joy/Typography'; +import parse from 'autosuggest-highlight/parse'; +import match from 'autosuggest-highlight/match'; + +export default function Highlights() { + return ( + + Highlights + option.title} + renderOption={(props, option, { inputValue }) => { + const matches = match(option.title, inputValue); + const parts = parse(option.title, matches); + + return ( + + + {option.title === inputValue + ? option.title + : parts.map((part, index) => ( + + {part.text} + + ))} + + + ); + }} + /> + + ); +} + +// 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/data/joy/components/autocomplete/InputAppearance.js b/docs/data/joy/components/autocomplete/InputAppearance.js new file mode 100644 index 00000000000000..0ee989fb515cfc --- /dev/null +++ b/docs/data/joy/components/autocomplete/InputAppearance.js @@ -0,0 +1,161 @@ +import * as React from 'react'; +import Autocomplete from '@mui/joy/Autocomplete'; +import Stack from '@mui/joy/Stack'; + +export default function InputAppearance() { + return ( + + + + + + + ); +} + +// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top +const top100Films = [ + { label: 'The Shawshank Redemption', year: 1994 }, + { label: 'The Godfather', year: 1972 }, + { label: 'The Godfather: Part II', year: 1974 }, + { label: 'The Dark Knight', year: 2008 }, + { label: '12 Angry Men', year: 1957 }, + { label: "Schindler's List", year: 1993 }, + { label: 'Pulp Fiction', year: 1994 }, + { + label: 'The Lord of the Rings: The Return of the King', + year: 2003, + }, + { label: 'The Good, the Bad and the Ugly', year: 1966 }, + { label: 'Fight Club', year: 1999 }, + { + label: 'The Lord of the Rings: The Fellowship of the Ring', + year: 2001, + }, + { + label: 'Star Wars: Episode V - The Empire Strikes Back', + year: 1980, + }, + { label: 'Forrest Gump', year: 1994 }, + { label: 'Inception', year: 2010 }, + { + label: 'The Lord of the Rings: The Two Towers', + year: 2002, + }, + { label: "One Flew Over the Cuckoo's Nest", year: 1975 }, + { label: 'Goodfellas', year: 1990 }, + { label: 'The Matrix', year: 1999 }, + { label: 'Seven Samurai', year: 1954 }, + { + label: 'Star Wars: Episode IV - A New Hope', + year: 1977, + }, + { label: 'City of God', year: 2002 }, + { label: 'Se7en', year: 1995 }, + { label: 'The Silence of the Lambs', year: 1991 }, + { label: "It's a Wonderful Life", year: 1946 }, + { label: 'Life Is Beautiful', year: 1997 }, + { label: 'The Usual Suspects', year: 1995 }, + { label: 'Léon: The Professional', year: 1994 }, + { label: 'Spirited Away', year: 2001 }, + { label: 'Saving Private Ryan', year: 1998 }, + { label: 'Once Upon a Time in the West', year: 1968 }, + { label: 'American History X', year: 1998 }, + { label: 'Interstellar', year: 2014 }, + { label: 'Casablanca', year: 1942 }, + { label: 'City Lights', year: 1931 }, + { label: 'Psycho', year: 1960 }, + { label: 'The Green Mile', year: 1999 }, + { label: 'The Intouchables', year: 2011 }, + { label: 'Modern Times', year: 1936 }, + { label: 'Raiders of the Lost Ark', year: 1981 }, + { label: 'Rear Window', year: 1954 }, + { label: 'The Pianist', year: 2002 }, + { label: 'The Departed', year: 2006 }, + { label: 'Terminator 2: Judgment Day', year: 1991 }, + { label: 'Back to the Future', year: 1985 }, + { label: 'Whiplash', year: 2014 }, + { label: 'Gladiator', year: 2000 }, + { label: 'Memento', year: 2000 }, + { label: 'The Prestige', year: 2006 }, + { label: 'The Lion King', year: 1994 }, + { label: 'Apocalypse Now', year: 1979 }, + { label: 'Alien', year: 1979 }, + { label: 'Sunset Boulevard', year: 1950 }, + { + label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb', + year: 1964, + }, + { label: 'The Great Dictator', year: 1940 }, + { label: 'Cinema Paradiso', year: 1988 }, + { label: 'The Lives of Others', year: 2006 }, + { label: 'Grave of the Fireflies', year: 1988 }, + { label: 'Paths of Glory', year: 1957 }, + { label: 'Django Unchained', year: 2012 }, + { label: 'The Shining', year: 1980 }, + { label: 'WALL·E', year: 2008 }, + { label: 'American Beauty', year: 1999 }, + { label: 'The Dark Knight Rises', year: 2012 }, + { label: 'Princess Mononoke', year: 1997 }, + { label: 'Aliens', year: 1986 }, + { label: 'Oldboy', year: 2003 }, + { label: 'Once Upon a Time in America', year: 1984 }, + { label: 'Witness for the Prosecution', year: 1957 }, + { label: 'Das Boot', year: 1981 }, + { label: 'Citizen Kane', year: 1941 }, + { label: 'North by Northwest', year: 1959 }, + { label: 'Vertigo', year: 1958 }, + { + label: 'Star Wars: Episode VI - Return of the Jedi', + year: 1983, + }, + { label: 'Reservoir Dogs', year: 1992 }, + { label: 'Braveheart', year: 1995 }, + { label: 'M', year: 1931 }, + { label: 'Requiem for a Dream', year: 2000 }, + { label: 'Amélie', year: 2001 }, + { label: 'A Clockwork Orange', year: 1971 }, + { label: 'Like Stars on Earth', year: 2007 }, + { label: 'Taxi Driver', year: 1976 }, + { label: 'Lawrence of Arabia', year: 1962 }, + { label: 'Double Indemnity', year: 1944 }, + { + label: 'Eternal Sunshine of the Spotless Mind', + year: 2004, + }, + { label: 'Amadeus', year: 1984 }, + { label: 'To Kill a Mockingbird', year: 1962 }, + { label: 'Toy Story 3', year: 2010 }, + { label: 'Logan', year: 2017 }, + { label: 'Full Metal Jacket', year: 1987 }, + { label: 'Dangal', year: 2016 }, + { label: 'The Sting', year: 1973 }, + { label: '2001: A Space Odyssey', year: 1968 }, + { label: "Singin' in the Rain", year: 1952 }, + { label: 'Toy Story', year: 1995 }, + { label: 'Bicycle Thieves', year: 1948 }, + { label: 'The Kid', year: 1921 }, + { label: 'Inglourious Basterds', year: 2009 }, + { label: 'Snatch', year: 2000 }, + { label: '3 Idiots', year: 2009 }, + { label: 'Monty Python and the Holy Grail', year: 1975 }, +]; diff --git a/docs/data/joy/components/autocomplete/InputAppearance.tsx b/docs/data/joy/components/autocomplete/InputAppearance.tsx new file mode 100644 index 00000000000000..0ee989fb515cfc --- /dev/null +++ b/docs/data/joy/components/autocomplete/InputAppearance.tsx @@ -0,0 +1,161 @@ +import * as React from 'react'; +import Autocomplete from '@mui/joy/Autocomplete'; +import Stack from '@mui/joy/Stack'; + +export default function InputAppearance() { + return ( + + + + + + + ); +} + +// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top +const top100Films = [ + { label: 'The Shawshank Redemption', year: 1994 }, + { label: 'The Godfather', year: 1972 }, + { label: 'The Godfather: Part II', year: 1974 }, + { label: 'The Dark Knight', year: 2008 }, + { label: '12 Angry Men', year: 1957 }, + { label: "Schindler's List", year: 1993 }, + { label: 'Pulp Fiction', year: 1994 }, + { + label: 'The Lord of the Rings: The Return of the King', + year: 2003, + }, + { label: 'The Good, the Bad and the Ugly', year: 1966 }, + { label: 'Fight Club', year: 1999 }, + { + label: 'The Lord of the Rings: The Fellowship of the Ring', + year: 2001, + }, + { + label: 'Star Wars: Episode V - The Empire Strikes Back', + year: 1980, + }, + { label: 'Forrest Gump', year: 1994 }, + { label: 'Inception', year: 2010 }, + { + label: 'The Lord of the Rings: The Two Towers', + year: 2002, + }, + { label: "One Flew Over the Cuckoo's Nest", year: 1975 }, + { label: 'Goodfellas', year: 1990 }, + { label: 'The Matrix', year: 1999 }, + { label: 'Seven Samurai', year: 1954 }, + { + label: 'Star Wars: Episode IV - A New Hope', + year: 1977, + }, + { label: 'City of God', year: 2002 }, + { label: 'Se7en', year: 1995 }, + { label: 'The Silence of the Lambs', year: 1991 }, + { label: "It's a Wonderful Life", year: 1946 }, + { label: 'Life Is Beautiful', year: 1997 }, + { label: 'The Usual Suspects', year: 1995 }, + { label: 'Léon: The Professional', year: 1994 }, + { label: 'Spirited Away', year: 2001 }, + { label: 'Saving Private Ryan', year: 1998 }, + { label: 'Once Upon a Time in the West', year: 1968 }, + { label: 'American History X', year: 1998 }, + { label: 'Interstellar', year: 2014 }, + { label: 'Casablanca', year: 1942 }, + { label: 'City Lights', year: 1931 }, + { label: 'Psycho', year: 1960 }, + { label: 'The Green Mile', year: 1999 }, + { label: 'The Intouchables', year: 2011 }, + { label: 'Modern Times', year: 1936 }, + { label: 'Raiders of the Lost Ark', year: 1981 }, + { label: 'Rear Window', year: 1954 }, + { label: 'The Pianist', year: 2002 }, + { label: 'The Departed', year: 2006 }, + { label: 'Terminator 2: Judgment Day', year: 1991 }, + { label: 'Back to the Future', year: 1985 }, + { label: 'Whiplash', year: 2014 }, + { label: 'Gladiator', year: 2000 }, + { label: 'Memento', year: 2000 }, + { label: 'The Prestige', year: 2006 }, + { label: 'The Lion King', year: 1994 }, + { label: 'Apocalypse Now', year: 1979 }, + { label: 'Alien', year: 1979 }, + { label: 'Sunset Boulevard', year: 1950 }, + { + label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb', + year: 1964, + }, + { label: 'The Great Dictator', year: 1940 }, + { label: 'Cinema Paradiso', year: 1988 }, + { label: 'The Lives of Others', year: 2006 }, + { label: 'Grave of the Fireflies', year: 1988 }, + { label: 'Paths of Glory', year: 1957 }, + { label: 'Django Unchained', year: 2012 }, + { label: 'The Shining', year: 1980 }, + { label: 'WALL·E', year: 2008 }, + { label: 'American Beauty', year: 1999 }, + { label: 'The Dark Knight Rises', year: 2012 }, + { label: 'Princess Mononoke', year: 1997 }, + { label: 'Aliens', year: 1986 }, + { label: 'Oldboy', year: 2003 }, + { label: 'Once Upon a Time in America', year: 1984 }, + { label: 'Witness for the Prosecution', year: 1957 }, + { label: 'Das Boot', year: 1981 }, + { label: 'Citizen Kane', year: 1941 }, + { label: 'North by Northwest', year: 1959 }, + { label: 'Vertigo', year: 1958 }, + { + label: 'Star Wars: Episode VI - Return of the Jedi', + year: 1983, + }, + { label: 'Reservoir Dogs', year: 1992 }, + { label: 'Braveheart', year: 1995 }, + { label: 'M', year: 1931 }, + { label: 'Requiem for a Dream', year: 2000 }, + { label: 'Amélie', year: 2001 }, + { label: 'A Clockwork Orange', year: 1971 }, + { label: 'Like Stars on Earth', year: 2007 }, + { label: 'Taxi Driver', year: 1976 }, + { label: 'Lawrence of Arabia', year: 1962 }, + { label: 'Double Indemnity', year: 1944 }, + { + label: 'Eternal Sunshine of the Spotless Mind', + year: 2004, + }, + { label: 'Amadeus', year: 1984 }, + { label: 'To Kill a Mockingbird', year: 1962 }, + { label: 'Toy Story 3', year: 2010 }, + { label: 'Logan', year: 2017 }, + { label: 'Full Metal Jacket', year: 1987 }, + { label: 'Dangal', year: 2016 }, + { label: 'The Sting', year: 1973 }, + { label: '2001: A Space Odyssey', year: 1968 }, + { label: "Singin' in the Rain", year: 1952 }, + { label: 'Toy Story', year: 1995 }, + { label: 'Bicycle Thieves', year: 1948 }, + { label: 'The Kid', year: 1921 }, + { label: 'Inglourious Basterds', year: 2009 }, + { label: 'Snatch', year: 2000 }, + { label: '3 Idiots', year: 2009 }, + { label: 'Monty Python and the Holy Grail', year: 1975 }, +]; diff --git a/docs/data/joy/components/autocomplete/LabelAndHelperText.js b/docs/data/joy/components/autocomplete/LabelAndHelperText.js new file mode 100644 index 00000000000000..f7a87207f1aa4c --- /dev/null +++ b/docs/data/joy/components/autocomplete/LabelAndHelperText.js @@ -0,0 +1,147 @@ +import * as React from 'react'; +import Autocomplete from '@mui/joy/Autocomplete'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import FormHelperText from '@mui/joy/FormHelperText'; + +export default function LabelAndHelperText() { + return ( + + Label + + A description for the combo box. + + ); +} + +// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top +const top100Films = [ + { label: 'The Shawshank Redemption', year: 1994 }, + { label: 'The Godfather', year: 1972 }, + { label: 'The Godfather: Part II', year: 1974 }, + { label: 'The Dark Knight', year: 2008 }, + { label: '12 Angry Men', year: 1957 }, + { label: "Schindler's List", year: 1993 }, + { label: 'Pulp Fiction', year: 1994 }, + { + label: 'The Lord of the Rings: The Return of the King', + year: 2003, + }, + { label: 'The Good, the Bad and the Ugly', year: 1966 }, + { label: 'Fight Club', year: 1999 }, + { + label: 'The Lord of the Rings: The Fellowship of the Ring', + year: 2001, + }, + { + label: 'Star Wars: Episode V - The Empire Strikes Back', + year: 1980, + }, + { label: 'Forrest Gump', year: 1994 }, + { label: 'Inception', year: 2010 }, + { + label: 'The Lord of the Rings: The Two Towers', + year: 2002, + }, + { label: "One Flew Over the Cuckoo's Nest", year: 1975 }, + { label: 'Goodfellas', year: 1990 }, + { label: 'The Matrix', year: 1999 }, + { label: 'Seven Samurai', year: 1954 }, + { + label: 'Star Wars: Episode IV - A New Hope', + year: 1977, + }, + { label: 'City of God', year: 2002 }, + { label: 'Se7en', year: 1995 }, + { label: 'The Silence of the Lambs', year: 1991 }, + { label: "It's a Wonderful Life", year: 1946 }, + { label: 'Life Is Beautiful', year: 1997 }, + { label: 'The Usual Suspects', year: 1995 }, + { label: 'Léon: The Professional', year: 1994 }, + { label: 'Spirited Away', year: 2001 }, + { label: 'Saving Private Ryan', year: 1998 }, + { label: 'Once Upon a Time in the West', year: 1968 }, + { label: 'American History X', year: 1998 }, + { label: 'Interstellar', year: 2014 }, + { label: 'Casablanca', year: 1942 }, + { label: 'City Lights', year: 1931 }, + { label: 'Psycho', year: 1960 }, + { label: 'The Green Mile', year: 1999 }, + { label: 'The Intouchables', year: 2011 }, + { label: 'Modern Times', year: 1936 }, + { label: 'Raiders of the Lost Ark', year: 1981 }, + { label: 'Rear Window', year: 1954 }, + { label: 'The Pianist', year: 2002 }, + { label: 'The Departed', year: 2006 }, + { label: 'Terminator 2: Judgment Day', year: 1991 }, + { label: 'Back to the Future', year: 1985 }, + { label: 'Whiplash', year: 2014 }, + { label: 'Gladiator', year: 2000 }, + { label: 'Memento', year: 2000 }, + { label: 'The Prestige', year: 2006 }, + { label: 'The Lion King', year: 1994 }, + { label: 'Apocalypse Now', year: 1979 }, + { label: 'Alien', year: 1979 }, + { label: 'Sunset Boulevard', year: 1950 }, + { + label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb', + year: 1964, + }, + { label: 'The Great Dictator', year: 1940 }, + { label: 'Cinema Paradiso', year: 1988 }, + { label: 'The Lives of Others', year: 2006 }, + { label: 'Grave of the Fireflies', year: 1988 }, + { label: 'Paths of Glory', year: 1957 }, + { label: 'Django Unchained', year: 2012 }, + { label: 'The Shining', year: 1980 }, + { label: 'WALL·E', year: 2008 }, + { label: 'American Beauty', year: 1999 }, + { label: 'The Dark Knight Rises', year: 2012 }, + { label: 'Princess Mononoke', year: 1997 }, + { label: 'Aliens', year: 1986 }, + { label: 'Oldboy', year: 2003 }, + { label: 'Once Upon a Time in America', year: 1984 }, + { label: 'Witness for the Prosecution', year: 1957 }, + { label: 'Das Boot', year: 1981 }, + { label: 'Citizen Kane', year: 1941 }, + { label: 'North by Northwest', year: 1959 }, + { label: 'Vertigo', year: 1958 }, + { + label: 'Star Wars: Episode VI - Return of the Jedi', + year: 1983, + }, + { label: 'Reservoir Dogs', year: 1992 }, + { label: 'Braveheart', year: 1995 }, + { label: 'M', year: 1931 }, + { label: 'Requiem for a Dream', year: 2000 }, + { label: 'Amélie', year: 2001 }, + { label: 'A Clockwork Orange', year: 1971 }, + { label: 'Like Stars on Earth', year: 2007 }, + { label: 'Taxi Driver', year: 1976 }, + { label: 'Lawrence of Arabia', year: 1962 }, + { label: 'Double Indemnity', year: 1944 }, + { + label: 'Eternal Sunshine of the Spotless Mind', + year: 2004, + }, + { label: 'Amadeus', year: 1984 }, + { label: 'To Kill a Mockingbird', year: 1962 }, + { label: 'Toy Story 3', year: 2010 }, + { label: 'Logan', year: 2017 }, + { label: 'Full Metal Jacket', year: 1987 }, + { label: 'Dangal', year: 2016 }, + { label: 'The Sting', year: 1973 }, + { label: '2001: A Space Odyssey', year: 1968 }, + { label: "Singin' in the Rain", year: 1952 }, + { label: 'Toy Story', year: 1995 }, + { label: 'Bicycle Thieves', year: 1948 }, + { label: 'The Kid', year: 1921 }, + { label: 'Inglourious Basterds', year: 2009 }, + { label: 'Snatch', year: 2000 }, + { label: '3 Idiots', year: 2009 }, + { label: 'Monty Python and the Holy Grail', year: 1975 }, +]; diff --git a/docs/data/joy/components/autocomplete/LabelAndHelperText.tsx b/docs/data/joy/components/autocomplete/LabelAndHelperText.tsx new file mode 100644 index 00000000000000..f7a87207f1aa4c --- /dev/null +++ b/docs/data/joy/components/autocomplete/LabelAndHelperText.tsx @@ -0,0 +1,147 @@ +import * as React from 'react'; +import Autocomplete from '@mui/joy/Autocomplete'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import FormHelperText from '@mui/joy/FormHelperText'; + +export default function LabelAndHelperText() { + return ( + + Label + + A description for the combo box. + + ); +} + +// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top +const top100Films = [ + { label: 'The Shawshank Redemption', year: 1994 }, + { label: 'The Godfather', year: 1972 }, + { label: 'The Godfather: Part II', year: 1974 }, + { label: 'The Dark Knight', year: 2008 }, + { label: '12 Angry Men', year: 1957 }, + { label: "Schindler's List", year: 1993 }, + { label: 'Pulp Fiction', year: 1994 }, + { + label: 'The Lord of the Rings: The Return of the King', + year: 2003, + }, + { label: 'The Good, the Bad and the Ugly', year: 1966 }, + { label: 'Fight Club', year: 1999 }, + { + label: 'The Lord of the Rings: The Fellowship of the Ring', + year: 2001, + }, + { + label: 'Star Wars: Episode V - The Empire Strikes Back', + year: 1980, + }, + { label: 'Forrest Gump', year: 1994 }, + { label: 'Inception', year: 2010 }, + { + label: 'The Lord of the Rings: The Two Towers', + year: 2002, + }, + { label: "One Flew Over the Cuckoo's Nest", year: 1975 }, + { label: 'Goodfellas', year: 1990 }, + { label: 'The Matrix', year: 1999 }, + { label: 'Seven Samurai', year: 1954 }, + { + label: 'Star Wars: Episode IV - A New Hope', + year: 1977, + }, + { label: 'City of God', year: 2002 }, + { label: 'Se7en', year: 1995 }, + { label: 'The Silence of the Lambs', year: 1991 }, + { label: "It's a Wonderful Life", year: 1946 }, + { label: 'Life Is Beautiful', year: 1997 }, + { label: 'The Usual Suspects', year: 1995 }, + { label: 'Léon: The Professional', year: 1994 }, + { label: 'Spirited Away', year: 2001 }, + { label: 'Saving Private Ryan', year: 1998 }, + { label: 'Once Upon a Time in the West', year: 1968 }, + { label: 'American History X', year: 1998 }, + { label: 'Interstellar', year: 2014 }, + { label: 'Casablanca', year: 1942 }, + { label: 'City Lights', year: 1931 }, + { label: 'Psycho', year: 1960 }, + { label: 'The Green Mile', year: 1999 }, + { label: 'The Intouchables', year: 2011 }, + { label: 'Modern Times', year: 1936 }, + { label: 'Raiders of the Lost Ark', year: 1981 }, + { label: 'Rear Window', year: 1954 }, + { label: 'The Pianist', year: 2002 }, + { label: 'The Departed', year: 2006 }, + { label: 'Terminator 2: Judgment Day', year: 1991 }, + { label: 'Back to the Future', year: 1985 }, + { label: 'Whiplash', year: 2014 }, + { label: 'Gladiator', year: 2000 }, + { label: 'Memento', year: 2000 }, + { label: 'The Prestige', year: 2006 }, + { label: 'The Lion King', year: 1994 }, + { label: 'Apocalypse Now', year: 1979 }, + { label: 'Alien', year: 1979 }, + { label: 'Sunset Boulevard', year: 1950 }, + { + label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb', + year: 1964, + }, + { label: 'The Great Dictator', year: 1940 }, + { label: 'Cinema Paradiso', year: 1988 }, + { label: 'The Lives of Others', year: 2006 }, + { label: 'Grave of the Fireflies', year: 1988 }, + { label: 'Paths of Glory', year: 1957 }, + { label: 'Django Unchained', year: 2012 }, + { label: 'The Shining', year: 1980 }, + { label: 'WALL·E', year: 2008 }, + { label: 'American Beauty', year: 1999 }, + { label: 'The Dark Knight Rises', year: 2012 }, + { label: 'Princess Mononoke', year: 1997 }, + { label: 'Aliens', year: 1986 }, + { label: 'Oldboy', year: 2003 }, + { label: 'Once Upon a Time in America', year: 1984 }, + { label: 'Witness for the Prosecution', year: 1957 }, + { label: 'Das Boot', year: 1981 }, + { label: 'Citizen Kane', year: 1941 }, + { label: 'North by Northwest', year: 1959 }, + { label: 'Vertigo', year: 1958 }, + { + label: 'Star Wars: Episode VI - Return of the Jedi', + year: 1983, + }, + { label: 'Reservoir Dogs', year: 1992 }, + { label: 'Braveheart', year: 1995 }, + { label: 'M', year: 1931 }, + { label: 'Requiem for a Dream', year: 2000 }, + { label: 'Amélie', year: 2001 }, + { label: 'A Clockwork Orange', year: 1971 }, + { label: 'Like Stars on Earth', year: 2007 }, + { label: 'Taxi Driver', year: 1976 }, + { label: 'Lawrence of Arabia', year: 1962 }, + { label: 'Double Indemnity', year: 1944 }, + { + label: 'Eternal Sunshine of the Spotless Mind', + year: 2004, + }, + { label: 'Amadeus', year: 1984 }, + { label: 'To Kill a Mockingbird', year: 1962 }, + { label: 'Toy Story 3', year: 2010 }, + { label: 'Logan', year: 2017 }, + { label: 'Full Metal Jacket', year: 1987 }, + { label: 'Dangal', year: 2016 }, + { label: 'The Sting', year: 1973 }, + { label: '2001: A Space Odyssey', year: 1968 }, + { label: "Singin' in the Rain", year: 1952 }, + { label: 'Toy Story', year: 1995 }, + { label: 'Bicycle Thieves', year: 1948 }, + { label: 'The Kid', year: 1921 }, + { label: 'Inglourious Basterds', year: 2009 }, + { label: 'Snatch', year: 2000 }, + { label: '3 Idiots', year: 2009 }, + { label: 'Monty Python and the Holy Grail', year: 1975 }, +]; diff --git a/docs/data/joy/components/autocomplete/LabelAndHelperText.tsx.preview b/docs/data/joy/components/autocomplete/LabelAndHelperText.tsx.preview new file mode 100644 index 00000000000000..a3b127002c2e3e --- /dev/null +++ b/docs/data/joy/components/autocomplete/LabelAndHelperText.tsx.preview @@ -0,0 +1,9 @@ + + Label + + A description for the combo box. + \ No newline at end of file diff --git a/docs/data/joy/components/autocomplete/LimitTags.js b/docs/data/joy/components/autocomplete/LimitTags.js new file mode 100644 index 00000000000000..cc1bcbb0c256ed --- /dev/null +++ b/docs/data/joy/components/autocomplete/LimitTags.js @@ -0,0 +1,149 @@ +import * as React from 'react'; +import Autocomplete from '@mui/joy/Autocomplete'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; + +export default function LimitTags() { + return ( + + limitTags + option.title} + defaultValue={[top100Films[13], top100Films[12], top100Films[11]]} + sx={{ width: '500px' }} + /> + + ); +} + +// 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/data/joy/components/autocomplete/LimitTags.tsx b/docs/data/joy/components/autocomplete/LimitTags.tsx new file mode 100644 index 00000000000000..cc1bcbb0c256ed --- /dev/null +++ b/docs/data/joy/components/autocomplete/LimitTags.tsx @@ -0,0 +1,149 @@ +import * as React from 'react'; +import Autocomplete from '@mui/joy/Autocomplete'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; + +export default function LimitTags() { + return ( + + limitTags + option.title} + defaultValue={[top100Films[13], top100Films[12], top100Films[11]]} + sx={{ width: '500px' }} + /> + + ); +} + +// 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/data/joy/components/autocomplete/LimitTags.tsx.preview b/docs/data/joy/components/autocomplete/LimitTags.tsx.preview new file mode 100644 index 00000000000000..4e1c5a56890c7c --- /dev/null +++ b/docs/data/joy/components/autocomplete/LimitTags.tsx.preview @@ -0,0 +1,12 @@ + + limitTags + option.title} + defaultValue={[top100Films[13], top100Films[12], top100Films[11]]} + sx={{ width: '500px' }} + /> + \ No newline at end of file diff --git a/docs/data/joy/components/autocomplete/OptionStructure.js b/docs/data/joy/components/autocomplete/OptionStructure.js new file mode 100644 index 00000000000000..eab045b59a018a --- /dev/null +++ b/docs/data/joy/components/autocomplete/OptionStructure.js @@ -0,0 +1,140 @@ +import * as React from 'react'; +import Autocomplete from '@mui/joy/Autocomplete'; + +export default function OptionStructure() { + return ( + option.title} + sx={{ width: 300 }} + /> + ); +} + +// 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/data/joy/components/autocomplete/OptionStructure.tsx b/docs/data/joy/components/autocomplete/OptionStructure.tsx new file mode 100644 index 00000000000000..eab045b59a018a --- /dev/null +++ b/docs/data/joy/components/autocomplete/OptionStructure.tsx @@ -0,0 +1,140 @@ +import * as React from 'react'; +import Autocomplete from '@mui/joy/Autocomplete'; + +export default function OptionStructure() { + return ( + option.title} + sx={{ width: 300 }} + /> + ); +} + +// 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/data/joy/components/autocomplete/OptionStructure.tsx.preview b/docs/data/joy/components/autocomplete/OptionStructure.tsx.preview new file mode 100644 index 00000000000000..c0322ab3152716 --- /dev/null +++ b/docs/data/joy/components/autocomplete/OptionStructure.tsx.preview @@ -0,0 +1,5 @@ + option.title} + sx={{ width: 300 }} +/> \ No newline at end of file diff --git a/docs/data/joy/components/autocomplete/Playground.js b/docs/data/joy/components/autocomplete/Playground.js new file mode 100644 index 00000000000000..0420a8dbc8dd9f --- /dev/null +++ b/docs/data/joy/components/autocomplete/Playground.js @@ -0,0 +1,406 @@ +import * as React from 'react'; +import Autocomplete from '@mui/joy/Autocomplete'; +import Box from '@mui/joy/Box'; +import Checkbox from '@mui/joy/Checkbox'; +import Divider from '@mui/joy/Divider'; +import Link from '@mui/joy/Link'; +import FormControl, { formControlClasses } from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import FormHelperText from '@mui/joy/FormHelperText'; +import List from '@mui/joy/List'; +import ListItem from '@mui/joy/ListItem'; +import Typography from '@mui/joy/Typography'; + +export default function Playground() { + const [flags, setFlags] = React.useState([]); + + const getCheckboxProps = (flag) => ({ + checked: flags.includes(flag), + onChange: (event) => { + setFlags( + event.target.checked ? [...flags, flag] : flags.filter((f) => f !== flag), + ); + }, + }); + + return ( + + + + Playground + ({ ...prev, [current]: true }), {})} + /> + + + + + Flags {flags.length ? `(${flags.length})` : ''} + + {flags.length > 0 && ( + // eslint-disable-next-line jsx-a11y/anchor-is-valid + setFlags([])} + sx={{ ml: 'auto' }} + > + Clear all + + )} + + + + li:first-child': { + mt: 'var(--List-gap)', + }, + [`& .${formControlClasses.root}`]: { position: 'initial', gap: 1.5 }, + '& > li': { + alignItems: 'flex-start', + }, + }} + > + + + +
+ autoComplete + + The completion string, appears inline after the input cursor in the + textbox. The inline completion string is visually highlighted and + has a selected state. + +
+
+
+ + + +
+ autoHighlight + + The first option is automatically highlighted. + +
+
+
+ + + +
+ autoSelect + + The selected option becomes the value of the input when the + Autocomplete loses focus unless the user chooses a different option + or changes the character string in the input. + +
+
+
+ + + +
+ blurOnSelect + The input is always blurred. +
+
+
+ + + +
+ clearOnBlur + + The input's text is cleared on blur if no value is selected. + +
+
+
+ + + +
+ clearOnEscape + + Clear all values when the user presses escape and the popup is + closed. + +
+
+
+ + + +
+ disabled + The component is disabled. +
+
+
+ + + +
+ disableClearable + The input can't be cleared +
+
+
+ + + +
+ disableCloseOnSelect + + The popup won't close when a value is selected. + +
+
+
+ + + +
+ disableListWrap + + The list box in the popup will not wrap focus. + +
+
+
+ + + +
+ filterSelectedOptions + + Hide the selected option from the list box. + +
+
+
+ + + +
+ freeSolo + + The user input is not bound to the provided options. + +
+
+
+ + + +
+ includeInputInList + The highlight can move to the input. +
+
+
+ + + +
+ openOnFocus + The popup will open on input focus. +
+
+
+ + + +
+ disablePortal + + The popup will be under the DOM hierarchy of the parent component. + +
+
+
+ + + +
+ readOnly + + The component becomes read-only. It is also supported in multiple + tags where the tag cannot be deleted. + +
+
+
+ + + +
+ selectOnFocus + + The input's text is selected on focus. It helps the user clear + the selected value. + +
+
+
+
+
+
+ ); +} + +// Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top +const top100Films = [ + { label: 'The Shawshank Redemption', year: 1994 }, + { label: 'The Godfather', year: 1972 }, + { label: 'The Godfather: Part II', year: 1974 }, + { label: 'The Dark Knight', year: 2008 }, + { label: '12 Angry Men', year: 1957 }, + { label: "Schindler's List", year: 1993 }, + { label: 'Pulp Fiction', year: 1994 }, + { + label: 'The Lord of the Rings: The Return of the King', + year: 2003, + }, + { label: 'The Good, the Bad and the Ugly', year: 1966 }, + { label: 'Fight Club', year: 1999 }, + { + label: 'The Lord of the Rings: The Fellowship of the Ring', + year: 2001, + }, + { + label: 'Star Wars: Episode V - The Empire Strikes Back', + year: 1980, + }, + { label: 'Forrest Gump', year: 1994 }, + { label: 'Inception', year: 2010 }, + { + label: 'The Lord of the Rings: The Two Towers', + year: 2002, + }, + { label: "One Flew Over the Cuckoo's Nest", year: 1975 }, + { label: 'Goodfellas', year: 1990 }, + { label: 'The Matrix', year: 1999 }, + { label: 'Seven Samurai', year: 1954 }, + { + label: 'Star Wars: Episode IV - A New Hope', + year: 1977, + }, + { label: 'City of God', year: 2002 }, + { label: 'Se7en', year: 1995 }, + { label: 'The Silence of the Lambs', year: 1991 }, + { label: "It's a Wonderful Life", year: 1946 }, + { label: 'Life Is Beautiful', year: 1997 }, + { label: 'The Usual Suspects', year: 1995 }, + { label: 'Léon: The Professional', year: 1994 }, + { label: 'Spirited Away', year: 2001 }, + { label: 'Saving Private Ryan', year: 1998 }, + { label: 'Once Upon a Time in the West', year: 1968 }, + { label: 'American History X', year: 1998 }, + { label: 'Interstellar', year: 2014 }, + { label: 'Casablanca', year: 1942 }, + { label: 'City Lights', year: 1931 }, + { label: 'Psycho', year: 1960 }, + { label: 'The Green Mile', year: 1999 }, + { label: 'The Intouchables', year: 2011 }, + { label: 'Modern Times', year: 1936 }, + { label: 'Raiders of the Lost Ark', year: 1981 }, + { label: 'Rear Window', year: 1954 }, + { label: 'The Pianist', year: 2002 }, + { label: 'The Departed', year: 2006 }, + { label: 'Terminator 2: Judgment Day', year: 1991 }, + { label: 'Back to the Future', year: 1985 }, + { label: 'Whiplash', year: 2014 }, + { label: 'Gladiator', year: 2000 }, + { label: 'Memento', year: 2000 }, + { label: 'The Prestige', year: 2006 }, + { label: 'The Lion King', year: 1994 }, + { label: 'Apocalypse Now', year: 1979 }, + { label: 'Alien', year: 1979 }, + { label: 'Sunset Boulevard', year: 1950 }, + { + label: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb', + year: 1964, + }, + { label: 'The Great Dictator', year: 1940 }, + { label: 'Cinema Paradiso', year: 1988 }, + { label: 'The Lives of Others', year: 2006 }, + { label: 'Grave of the Fireflies', year: 1988 }, + { label: 'Paths of Glory', year: 1957 }, + { label: 'Django Unchained', year: 2012 }, + { label: 'The Shining', year: 1980 }, + { label: 'WALL·E', year: 2008 }, + { label: 'American Beauty', year: 1999 }, + { label: 'The Dark Knight Rises', year: 2012 }, + { label: 'Princess Mononoke', year: 1997 }, + { label: 'Aliens', year: 1986 }, + { label: 'Oldboy', year: 2003 }, + { label: 'Once Upon a Time in America', year: 1984 }, + { label: 'Witness for the Prosecution', year: 1957 }, + { label: 'Das Boot', year: 1981 }, + { label: 'Citizen Kane', year: 1941 }, + { label: 'North by Northwest', year: 1959 }, + { label: 'Vertigo', year: 1958 }, + { + label: 'Star Wars: Episode VI - Return of the Jedi', + year: 1983, + }, + { label: 'Reservoir Dogs', year: 1992 }, + { label: 'Braveheart', year: 1995 }, + { label: 'M', year: 1931 }, + { label: 'Requiem for a Dream', year: 2000 }, + { label: 'Amélie', year: 2001 }, + { label: 'A Clockwork Orange', year: 1971 }, + { label: 'Like Stars on Earth', year: 2007 }, + { label: 'Taxi Driver', year: 1976 }, + { label: 'Lawrence of Arabia', year: 1962 }, + { label: 'Double Indemnity', year: 1944 }, + { + label: 'Eternal Sunshine of the Spotless Mind', + year: 2004, + }, + { label: 'Amadeus', year: 1984 }, + { label: 'To Kill a Mockingbird', year: 1962 }, + { label: 'Toy Story 3', year: 2010 }, + { label: 'Logan', year: 2017 }, + { label: 'Full Metal Jacket', year: 1987 }, + { label: 'Dangal', year: 2016 }, + { label: 'The Sting', year: 1973 }, + { label: '2001: A Space Odyssey', year: 1968 }, + { label: "Singin' in the Rain", year: 1952 }, + { label: 'Toy Story', year: 1995 }, + { label: 'Bicycle Thieves', year: 1948 }, + { label: 'The Kid', year: 1921 }, + { label: 'Inglourious Basterds', year: 2009 }, + { label: 'Snatch', year: 2000 }, + { label: '3 Idiots', year: 2009 }, + { label: 'Monty Python and the Holy Grail', year: 1975 }, +]; diff --git a/docs/data/joy/components/autocomplete/SizeWithLabel.js b/docs/data/joy/components/autocomplete/SizeWithLabel.js new file mode 100644 index 00000000000000..3c21108f261c3d --- /dev/null +++ b/docs/data/joy/components/autocomplete/SizeWithLabel.js @@ -0,0 +1,149 @@ +import * as React from 'react'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import FormHelperText from '@mui/joy/FormHelperText'; +import Autocomplete from '@mui/joy/Autocomplete'; + +export default function SizeWithLabel() { + return ( + + Small field + option.title} + defaultValue={[top100Films[13]]} + /> + This is a small description. + + ); +} + +// 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/data/joy/components/autocomplete/SizeWithLabel.tsx b/docs/data/joy/components/autocomplete/SizeWithLabel.tsx new file mode 100644 index 00000000000000..3c21108f261c3d --- /dev/null +++ b/docs/data/joy/components/autocomplete/SizeWithLabel.tsx @@ -0,0 +1,149 @@ +import * as React from 'react'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import FormHelperText from '@mui/joy/FormHelperText'; +import Autocomplete from '@mui/joy/Autocomplete'; + +export default function SizeWithLabel() { + return ( + + Small field + option.title} + defaultValue={[top100Films[13]]} + /> + This is a small description. + + ); +} + +// 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/data/joy/components/autocomplete/SizeWithLabel.tsx.preview b/docs/data/joy/components/autocomplete/SizeWithLabel.tsx.preview new file mode 100644 index 00000000000000..8f24d23dce327f --- /dev/null +++ b/docs/data/joy/components/autocomplete/SizeWithLabel.tsx.preview @@ -0,0 +1,11 @@ + + Small field + option.title} + defaultValue={[top100Films[13]]} + /> + This is a small description. + \ No newline at end of file diff --git a/docs/data/joy/components/autocomplete/Sizes.js b/docs/data/joy/components/autocomplete/Sizes.js new file mode 100644 index 00000000000000..827a1d5a37f470 --- /dev/null +++ b/docs/data/joy/components/autocomplete/Sizes.js @@ -0,0 +1,162 @@ +import * as React from 'react'; +import Stack from '@mui/joy/Stack'; +import Autocomplete from '@mui/joy/Autocomplete'; + +export default function Sizes() { + return ( + + option.title} + defaultValue={[top100Films[13]]} + /> + option.title} + defaultValue={[top100Films[13]]} + /> + option.title} + defaultValue={[top100Films[13]]} + /> + + ); +} + +// 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/data/joy/components/autocomplete/Sizes.tsx b/docs/data/joy/components/autocomplete/Sizes.tsx new file mode 100644 index 00000000000000..827a1d5a37f470 --- /dev/null +++ b/docs/data/joy/components/autocomplete/Sizes.tsx @@ -0,0 +1,162 @@ +import * as React from 'react'; +import Stack from '@mui/joy/Stack'; +import Autocomplete from '@mui/joy/Autocomplete'; + +export default function Sizes() { + return ( + + option.title} + defaultValue={[top100Films[13]]} + /> + option.title} + defaultValue={[top100Films[13]]} + /> + option.title} + defaultValue={[top100Films[13]]} + /> + + ); +} + +// 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/data/joy/components/autocomplete/Tags.js b/docs/data/joy/components/autocomplete/Tags.js new file mode 100644 index 00000000000000..db07ec390eb411 --- /dev/null +++ b/docs/data/joy/components/autocomplete/Tags.js @@ -0,0 +1,143 @@ +import * as React from 'react'; +import Autocomplete from '@mui/joy/Autocomplete'; + +export default function Tags() { + return ( + option.title} + defaultValue={[top100Films[13]]} + /> + ); +} + +// 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/data/joy/components/autocomplete/Tags.tsx b/docs/data/joy/components/autocomplete/Tags.tsx new file mode 100644 index 00000000000000..db07ec390eb411 --- /dev/null +++ b/docs/data/joy/components/autocomplete/Tags.tsx @@ -0,0 +1,143 @@ +import * as React from 'react'; +import Autocomplete from '@mui/joy/Autocomplete'; + +export default function Tags() { + return ( + option.title} + defaultValue={[top100Films[13]]} + /> + ); +} + +// 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/data/joy/components/autocomplete/Tags.tsx.preview b/docs/data/joy/components/autocomplete/Tags.tsx.preview new file mode 100644 index 00000000000000..49a4da6a37a5a3 --- /dev/null +++ b/docs/data/joy/components/autocomplete/Tags.tsx.preview @@ -0,0 +1,8 @@ + option.title} + defaultValue={[top100Films[13]]} +/> \ No newline at end of file diff --git a/docs/data/joy/components/autocomplete/Virtualize.js b/docs/data/joy/components/autocomplete/Virtualize.js new file mode 100644 index 00000000000000..1c20f09c0078ad --- /dev/null +++ b/docs/data/joy/components/autocomplete/Virtualize.js @@ -0,0 +1,135 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { FixedSizeList } from 'react-window'; +import PopperUnstyled from '@mui/base/PopperUnstyled'; +import Autocomplete from '@mui/joy/Autocomplete'; +import AutocompleteListbox from '@mui/joy/AutocompleteListbox'; +import AutocompleteOption from '@mui/joy/AutocompleteOption'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import ListSubheader from '@mui/joy/ListSubheader'; + +const LISTBOX_PADDING = 6; // px + +function renderRow(props) { + const { data, index, style } = props; + const dataSet = data[index]; + const inlineStyle = { + ...style, + top: style.top + LISTBOX_PADDING, + }; + + if (dataSet.hasOwnProperty('group')) { + return ( + + {dataSet.group} + + ); + } + + return ( + + {dataSet[1]} + + ); +} + +const OuterElementContext = React.createContext({}); + +const OuterElementType = React.forwardRef((props, ref) => { + const outerProps = React.useContext(OuterElementContext); + return ( + + ); +}); + +// Adapter for react-window +const ListboxComponent = React.forwardRef(function ListboxComponent(props, ref) { + const { children, anchorEl, open, modifiers, ...other } = props; + const itemData = []; + + children[0].forEach((item) => { + if (item) { + itemData.push(item); + itemData.push(...(item.children || [])); + } + }); + + const itemCount = itemData.length; + const itemSize = 40; + + return ( + + + + {renderRow} + + + + ); +}); + +ListboxComponent.propTypes = { + anchorEl: PropTypes.any.isRequired, + children: PropTypes.node, + modifiers: PropTypes.array.isRequired, + open: PropTypes.bool.isRequired, +}; + +function random(length) { + const characters = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + let result = ''; + + for (let i = 0; i < length; i += 1) { + result += characters.charAt(Math.floor(Math.random() * characters.length)); + } + + return result; +} + +const OPTIONS = Array.from(new Array(10000)) + .map(() => random(10 + Math.ceil(Math.random() * 20))) + .sort((a, b) => a.toUpperCase().localeCompare(b.toUpperCase())); + +export default function Virtualize() { + return ( + + 10,000 options + option[0].toUpperCase()} + renderOption={(props, option) => [props, option]} + // TODO: Post React 18 update - validate this conversion, look like a hidden bug + renderGroup={(params) => params} + /> + + ); +} diff --git a/docs/data/joy/components/autocomplete/Virtualize.tsx b/docs/data/joy/components/autocomplete/Virtualize.tsx new file mode 100644 index 00000000000000..e3d708d9f9fe76 --- /dev/null +++ b/docs/data/joy/components/autocomplete/Virtualize.tsx @@ -0,0 +1,135 @@ +import * as React from 'react'; +import { FixedSizeList, ListChildComponentProps } from 'react-window'; +import PopperUnstyled from '@mui/base/PopperUnstyled'; +import Autocomplete from '@mui/joy/Autocomplete'; +import AutocompleteListbox from '@mui/joy/AutocompleteListbox'; +import AutocompleteOption from '@mui/joy/AutocompleteOption'; +import FormControl from '@mui/joy/FormControl'; +import FormLabel from '@mui/joy/FormLabel'; +import ListSubheader from '@mui/joy/ListSubheader'; + +const LISTBOX_PADDING = 6; // px + +function renderRow(props: ListChildComponentProps) { + const { data, index, style } = props; + const dataSet = data[index]; + const inlineStyle = { + ...style, + top: (style.top as number) + LISTBOX_PADDING, + }; + + if (dataSet.hasOwnProperty('group')) { + return ( + + {dataSet.group} + + ); + } + + return ( + + {dataSet[1]} + + ); +} + +const OuterElementContext = React.createContext({}); + +const OuterElementType = React.forwardRef((props, ref) => { + const outerProps = React.useContext(OuterElementContext); + return ( + + ); +}); + +// Adapter for react-window +const ListboxComponent = React.forwardRef< + HTMLDivElement, + { + anchorEl: any; + open: boolean; + modifiers: any[]; + } & React.HTMLAttributes +>(function ListboxComponent(props, ref) { + const { children, anchorEl, open, modifiers, ...other } = props; + const itemData: Array = []; + ( + children as [Array<{ children: Array | undefined }>] + )[0].forEach((item) => { + if (item) { + itemData.push(item); + itemData.push(...(item.children || [])); + } + }); + + const itemCount = itemData.length; + const itemSize = 40; + + return ( + + + + {renderRow} + + + + ); +}); + +function random(length: number) { + const characters = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + let result = ''; + + for (let i = 0; i < length; i += 1) { + result += characters.charAt(Math.floor(Math.random() * characters.length)); + } + + return result; +} + +const OPTIONS = Array.from(new Array(10000)) + .map(() => random(10 + Math.ceil(Math.random() * 20))) + .sort((a, b) => a.toUpperCase().localeCompare(b.toUpperCase())); + +export default function Virtualize() { + return ( + + 10,000 options + option[0].toUpperCase()} + renderOption={(props, option) => [props, option] as React.ReactNode} + // TODO: Post React 18 update - validate this conversion, look like a hidden bug + renderGroup={(params) => params as unknown as React.ReactNode} + /> + + ); +} diff --git a/docs/data/joy/components/autocomplete/Virtualize.tsx.preview b/docs/data/joy/components/autocomplete/Virtualize.tsx.preview new file mode 100644 index 00000000000000..04ca6bb06d2ad0 --- /dev/null +++ b/docs/data/joy/components/autocomplete/Virtualize.tsx.preview @@ -0,0 +1,16 @@ + + 10,000 options + option[0].toUpperCase()} + renderOption={(props, option) => [props, option] as React.ReactNode} + // TODO: Post React 18 update - validate this conversion, look like a hidden bug + renderGroup={(params) => params as unknown as React.ReactNode} + /> + \ No newline at end of file diff --git a/docs/data/joy/components/autocomplete/autocomplete.md b/docs/data/joy/components/autocomplete/autocomplete.md new file mode 100644 index 00000000000000..b93886027ff96a --- /dev/null +++ b/docs/data/joy/components/autocomplete/autocomplete.md @@ -0,0 +1,334 @@ +--- +product: joy-ui +title: React Autocomplete component +githubLabel: 'component: autocomplete' +waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/combobox/ +--- + +# Autocomplete + +

The autocomplete is a text input enhanced by a panel of suggested options when users start typing.

+ +## Introduction + +`Autocomplete` is an enhanced version of text input that shows suggested options as the users type and also let them select an option from the list. + +{{"demo": "Playground.js", "hideToolbar": true}} + +{{"component": "modules/components/ComponentLinkHeader.js", "design": false}} + +## Usage + +After [installation](/joy-ui/getting-started/installation/), you can start building with this component using the following basic elements: + +```jsx +import Autocomplete from '@mui/joy/Autocomplete'; +import Input from '@mui/joy/Input'; + +export default function App() { + return ; +} +``` + +## Basics + +The Autocomplete component requires a list of `options` to be displayed after the textbox is focused. The value must be chosen from a predefined set of allowed values. + +{{"demo": "BasicAutocomplete.js"}} + +## Customization + +### Options structure + +By default, the `options` accepts an array of `string` or `{ label: string }`: + +```js +const options = [ + { label: 'The Godfather', id: 1 }, + { label: 'Pulp Fiction', id: 2 }, +]; +// or +const options = ['The Godfather', 'Pulp Fiction']; +``` + +However, you can use different structures by providing a `getOptionLabel` prop: + +```js +const options = [ + { title: 'Pulp Fiction', id: 2 }, + // ... +]; + + option.title}> +``` + +### Option appearance + +To customize the appearance of the options, use `renderOption` prop in combination with the `AutocompleteOption` component as an option container. + +{{"demo": "CountrySelect.js"}} + +### Variants + +The autocomplete component supports the four global variants: `outlined` (default), `soft`, `solid`, and `plain`. + +{{"demo": "InputAppearance.js"}} + +:::success +To learn how to add more variants to the component, check out [Themed components—Extend variants](/joy-ui/customization/themed-components/#extend-variants). +::: + +### Label + +Put an `Autocomplete`, a `FormLabel` and a `FormHelperText` (optional) under a `FormControl` component to create an accessible autocomplete. + +{{"demo": "LabelAndHelperText.js"}} + +### Decorators + +Use `startDecorator` or `endDecorator` to insert decorators to the autocomplete. + +{{"demo": "AutocompleteDecorators.js"}} + +### Controlled states + +The component has two states that can be controlled: + +1. the "value" state with the `value`/`onChange` props combination. This state represents the value selected by the user, for instance when pressing Enter. +2. the "input value" state with the `inputValue`/`onInputChange` props combination. This state represents the value displayed in the textbox. + +:::warning +⚠️ These two states are isolated, they should be controlled independently. +::: + +{{"demo": "ControllableStates.js"}} + +### Disabled options + +Use `getOptionDisabled` prop to read the options and returns `true` to disable them. + +{{"demo": "DisabledOptions.js"}} + +### Grouped options + +You can group the options with the `groupBy` prop. +If you do so, make sure that the options are also sorted with the same dimension that they are grouped by, +otherwise, you will notice duplicate headers. + +{{"demo": "Grouped.js"}} + +### Loading + +It displays a progress state as long as the network request is pending. + +{{"demo": "Asynchronous.js"}} + +### Search input + +Use `freeSolo` to create a **search input** with suggestions experience, e.g. Google search or [react-autowhatever](https://github.com/moroshko/react-autowhatever). + +{{"demo": "FreeSolo.js"}} + +:::warning +⚠️ Be careful when using the free solo mode with non-string options, as it may cause type mismatch. + +The value created by typing into the textbox is always a string, regardless of the type of the options. +::: + +### User's created option + +If you intend to use `freeSolo` mode for a [combo box](#combo-box) like experience (an enhanced version of a select element) we recommend setting: + +- `selectOnFocus` to help the user clear the selected value. +- `clearOnBlur` to help the user enter a new value. +- `handleHomeEndKeys` to move focus inside the popup with the Home and End keys. +- A last option, for instance: `Add "YOUR SEARCH"`. + +{{"demo": "FreeSoloCreateOption.js"}} + +You could also display a dialog when the user wants to add a new value. + +{{"demo": "FreeSoloCreateOptionDialog.js"}} + +:::info +The `AutocompleteOption` uses the same styles and variables as [`ListItemButton`](/joy-ui/react-list/#actionable), so that you get the same customization experience. +::: + +### Validation + +To display invalid state, set the `error` prop on the `FormControl`. + +{{"demo": "AutocompleteError.js"}} + +### Multiple selection + +By default, the autocomplete uses [`Chip`](/joy-ui/react-chip/) component to render the user's selected options. + +When the autocomplete is focused, the user can press the backspace to remove the latest selected option from the list. + +{{"demo": "Tags.js"}} + +### Selected options appearance + +Use the `renderTag` prop to customize the appearance. + +{{"demo": "CustomTags.js"}} + +### Limit the selected options to be displayed + +You can use the `limitTags` prop to limit the number of displayed options when not focused. + +{{"demo": "LimitTags.js"}} + +### Sizes + +The autocomplete component comes with three sizes out of the box: `sm`, `md` (the default), and `lg`. The size is propagated to internal components, including `Input`, `Chip`, and `List`. + +{{"demo": "Sizes.js"}} + +:::success +To learn how to add more sizes to the component, check out [Themed components—Extend sizes](/joy-ui/customization/themed-components/#extend-sizes). +::: + +The `size` can also be controlled at the `FormControl`. + +{{"demo": "SizeWithLabel.js"}} + +### Custom filter + +The component exposes a factory to create a filter method that can be provided to the `filterOptions` prop. +You can use it to change the default option filter behavior. + +```js +import { createFilterOptions } from '@mui/material/Autocomplete'; +``` + +#### Arguments + +1. `config` (_object_ [optional]): + +- `config.ignoreAccents` (_bool_ [optional]): Defaults to `true`. Remove diacritics. +- `config.ignoreCase` (_bool_ [optional]): Defaults to `true`. Lowercase everything. +- `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. +- `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` (_bool_ [optional]): Defaults to `false`. Remove trailing spaces. + +#### Returns + +`filterOptions`: the returned filter method can be provided directly to the `filterOptions` prop of the `Autocomplete` component, or the parameter of the same name for the hook. + +In the following demo, the options need to start with the query prefix: + +```jsx +const filterOptions = createFilterOptions({ + matchFrom: 'start', + stringify: (option) => option.title, +}); + +; +``` + +{{"demo": "Filter.js", "defaultCodeOpen": false}} + +### Advanced filter + +For richer filtering mechanisms, like fuzzy matching, it's recommended to look at [match-sorter](https://github.com/kentcdodds/match-sorter). For instance: + +```jsx +import { matchSorter } from 'match-sorter'; + +const filterOptions = (options, { inputValue }) => matchSorter(options, inputValue); + +; +``` + +## Common examples + +### Highlighting options + +The following demo relies on [autosuggest-highlight](https://github.com/moroshko/autosuggest-highlight), a small (1 kB) utility for highlighting text in autosuggest and autocomplete components. + +{{"demo": "Highlights.js"}} + +### GitHub's picker + +To reproduce GitHub's label picker, the `Autocomplete` is rendered inside a [`PopperUnstyled`](/base/react-popper/). To remove the popup behavior from the autocomplete, replace the listbox slot with the `AutocompleteListbox` component. + +{{"demo": "GitHubLabel.js"}} + +### Virtualization + +Search within 10,000 randomly generated options. The list is virtualized thanks to [react-window](https://github.com/bvaughn/react-window). + +{{"demo": "Virtualize.js"}} + +## Events + +If you would like to prevent the default key handler behavior, you can set the event's `defaultMuiPrevented` property to `true`: + +```jsx + { + if (event.key === 'Enter') { + // Prevent's default 'Enter' behavior. + event.defaultMuiPrevented = true; + // your handler code + } + }} +/> +``` + +## CSS Variables + +The Autocomplete component reuses CSS variables from the Input component to give you the consistent customization experience. + +{{"demo": "AutocompleteVariables.js", "hideToolbar": true}} + +## Limitations + +### autocomplete/autofill + +By default, the component disables the input **autocomplete** feature (remembering what the user has typed for a given field in a previous session) with the `autoComplete="off"` attribute. +Google Chrome does not currently support this attribute setting ([Issue 587466](https://bugs.chromium.org/p/chromium/issues/detail?id=587466)). +A possible workaround is to remove the `id` to have the component generate a random one. + +In addition to remembering past entered values, the browser might also propose **autofill** suggestions (saved login, address, or payment details). +In the event you want the avoid autofill, you can try the following: + +- Name the input without leaking any information the browser can use. e.g. `id="field1"` instead of `id="country"`. If you leave the id empty, the component uses a random id. +- Set `autoComplete="new-password"` (some browsers will suggest a strong password for inputs with this attribute setting): + + ```jsx + + ``` + +Read [the guide on MDN](https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion) for more details. + +### iOS VoiceOver + +VoiceOver on iOS Safari doesn't support the `aria-owns` attribute very well. +You can work around the issue with the `disablePortal` prop. + +```jsx + +``` + +## Accessibility + +(WAI-ARIA: https://www.w3.org/WAI/ARIA/apg/patterns/combobox/) + +We encourage the usage of a label for the textbox. +The component implements the WAI-ARIA authoring practices. diff --git a/docs/data/joy/components/chip/ChipVariables.js b/docs/data/joy/components/chip/ChipVariables.js index e3266238384cdd..c76b4c22cd7428 100644 --- a/docs/data/joy/components/chip/ChipVariables.js +++ b/docs/data/joy/components/chip/ChipVariables.js @@ -43,6 +43,9 @@ export default function ChipVariables() { gap: 1, }} > + + Some text + } endDecorator={} diff --git a/docs/data/joy/pages.ts b/docs/data/joy/pages.ts index efaa4e46dd6b9d..1833e0c8b49bc6 100644 --- a/docs/data/joy/pages.ts +++ b/docs/data/joy/pages.ts @@ -29,6 +29,7 @@ const pages = [ pathname: '/joy-ui/components/inputs', subheader: 'inputs', children: [ + { pathname: '/joy-ui/react-autocomplete' }, { pathname: '/joy-ui/react-button' }, { pathname: '/joy-ui/react-text-field', title: 'Text Field' }, { pathname: '/joy-ui/react-textarea' }, diff --git a/docs/pages/joy-ui/react-autocomplete.js b/docs/pages/joy-ui/react-autocomplete.js new file mode 100644 index 00000000000000..a9488b6e929e5d --- /dev/null +++ b/docs/pages/joy-ui/react-autocomplete.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; +import * as pageProps from 'docs/data/joy/components/autocomplete/autocomplete.md?@mui/markdown'; + +export default function Page() { + return ; +} diff --git a/packages/mui-base/src/AutocompleteUnstyled/useAutocomplete.d.ts b/packages/mui-base/src/AutocompleteUnstyled/useAutocomplete.d.ts index b00651b4c13e33..57cc393c3710a6 100644 --- a/packages/mui-base/src/AutocompleteUnstyled/useAutocomplete.d.ts +++ b/packages/mui-base/src/AutocompleteUnstyled/useAutocomplete.d.ts @@ -41,6 +41,17 @@ export interface UseAutocompleteProps< DisableClearable extends boolean | undefined, FreeSolo extends boolean | undefined, > { + /** + * @internal The prefix of the state class name, temporary for Joy UI + * @default 'Mui' + */ + unstable_classNamePrefix?: string; + /** + * @internal + * Temporary for Joy UI because the parent listbox is the document object + * TODO v6: Normalize the logic and remove this param. + */ + unstable_isActiveElementInListbox?: (listbox: React.RefObject) => boolean; /** * If `true`, the portion of the selected suggestion that has not been typed by the user, * known as the completion string, appears inline after the input cursor in the textbox. @@ -318,12 +329,12 @@ export default function useAutocomplete< >( props: UseAutocompleteProps, ): { - getRootProps: () => React.HTMLAttributes; + getRootProps: (externalProps?: any) => React.HTMLAttributes; getInputProps: () => React.InputHTMLAttributes; // We pass `getInputLabelProps()` to `@mui/material/InputLabel` which does not implement HTMLLabelElement#color. getInputLabelProps: () => Omit, 'color'>; - getClearProps: () => React.HTMLAttributes; - getPopupIndicatorProps: () => React.HTMLAttributes; + getClearProps: () => React.HTMLAttributes; + getPopupIndicatorProps: () => React.HTMLAttributes; getTagProps: AutocompleteGetTagProps; getListboxProps: () => React.HTMLAttributes; getOptionProps: ({ diff --git a/packages/mui-base/src/AutocompleteUnstyled/useAutocomplete.js b/packages/mui-base/src/AutocompleteUnstyled/useAutocomplete.js index 6aa0551427cb02..e80bcd91913a09 100644 --- a/packages/mui-base/src/AutocompleteUnstyled/useAutocomplete.js +++ b/packages/mui-base/src/AutocompleteUnstyled/useAutocomplete.js @@ -70,8 +70,15 @@ const defaultFilterOptions = createFilterOptions(); // Number of options to jump in list box when pageup and pagedown keys are used. const pageSize = 5; +const defaultIsActiveElementInListbox = (listboxRef) => + listboxRef.current !== null && listboxRef.current.parentElement?.contains(document.activeElement); + export default function useAutocomplete(props) { const { + // eslint-disable-next-line @typescript-eslint/naming-convention + unstable_isActiveElementInListbox = defaultIsActiveElementInListbox, + // eslint-disable-next-line @typescript-eslint/naming-convention + unstable_classNamePrefix = 'Mui', autoComplete = false, autoHighlight = false, autoSelect = false, @@ -330,10 +337,12 @@ export default function useAutocomplete(props) { return; } - const prev = listboxRef.current.querySelector('[role="option"].Mui-focused'); + const prev = listboxRef.current.querySelector( + `[role="option"].${unstable_classNamePrefix}-focused`, + ); if (prev) { - prev.classList.remove('Mui-focused'); - prev.classList.remove('Mui-focusVisible'); + prev.classList.remove(`${unstable_classNamePrefix}-focused`); + prev.classList.remove(`${unstable_classNamePrefix}-focusVisible`); } const listboxNode = listboxRef.current.parentElement.querySelector('[role="listbox"]'); @@ -354,9 +363,9 @@ export default function useAutocomplete(props) { return; } - option.classList.add('Mui-focused'); + option.classList.add(`${unstable_classNamePrefix}-focused`); if (reason === 'keyboard') { - option.classList.add('Mui-focusVisible'); + option.classList.add(`${unstable_classNamePrefix}-focusVisible`); } // Scroll active descendant into view. @@ -865,10 +874,7 @@ export default function useAutocomplete(props) { const handleBlur = (event) => { // Ignore the event when using the scrollbar with IE11 - if ( - listboxRef.current !== null && - listboxRef.current.parentElement.contains(document.activeElement) - ) { + if (unstable_isActiveElementInListbox(listboxRef)) { inputRef.current.focus(); return; } diff --git a/packages/mui-base/src/utils/index.ts b/packages/mui-base/src/utils/index.ts index 0823837f41e0de..41442723bca707 100644 --- a/packages/mui-base/src/utils/index.ts +++ b/packages/mui-base/src/utils/index.ts @@ -4,4 +4,5 @@ export { default as extractEventHandlers } from './extractEventHandlers'; export { default as isHostComponent } from './isHostComponent'; export { default as resolveComponentProps } from './resolveComponentProps'; export { default as useSlotProps } from './useSlotProps'; +export { default as mergeSlotProps } from './mergeSlotProps'; export * from './types'; diff --git a/packages/mui-joy/src/Autocomplete/Autocomplete.spec.tsx b/packages/mui-joy/src/Autocomplete/Autocomplete.spec.tsx new file mode 100644 index 00000000000000..23c91f3989d647 --- /dev/null +++ b/packages/mui-joy/src/Autocomplete/Autocomplete.spec.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import Autocomplete from '@mui/joy/Autocomplete'; + +const top100Films = [{ title: 'The Shawshank Redemption', year: 1994 }]; + +; + +; + + option.title} + defaultValue={[top100Films[13], top100Films[12], top100Films[11]]} + multiple + sx={{ width: '500px' }} +/>; diff --git a/packages/mui-joy/src/Autocomplete/Autocomplete.test.tsx b/packages/mui-joy/src/Autocomplete/Autocomplete.test.tsx new file mode 100644 index 00000000000000..44dfe3673f1d49 --- /dev/null +++ b/packages/mui-joy/src/Autocomplete/Autocomplete.test.tsx @@ -0,0 +1,2184 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { expect } from 'chai'; +import { spy } from 'sinon'; +import { + describeConformance, + createRenderer, + screen, + act, + fireEvent, + strictModeDoubleLoggingSupressed, +} from 'test/utils'; +import Autocomplete, { + autocompleteClasses as classes, + createFilterOptions, +} from '@mui/joy/Autocomplete'; +import Chip, { chipClasses } from '@mui/joy/Chip'; +import ChipDelete from '@mui/joy/ChipDelete'; +import { ThemeProvider } from '@mui/joy/styles'; + +function checkHighlightIs(listbox: HTMLElement, expected: string | null) { + const focused = listbox.querySelector(`.${classes.focused}`); + + if (expected) { + if (focused) { + expect(focused).to.have.text(expected); + } else { + // No options selected + expect(null).to.equal(expected); + } + } else { + expect(focused).to.equal(null); + } +} + +describe('Joy ', () => { + const { render } = createRenderer(); + + describeConformance(, () => ({ + classes, + inheritComponent: 'div', + render, + ThemeProvider, + refInstanceof: window.HTMLDivElement, + muiName: 'JoyAutocomplete', + testDeepOverrides: { slotName: 'popupIndicator', slotClassName: classes.popupIndicator }, + testVariantProps: { size: 'lg' }, + skip: ['componentsProp', 'classesRoot'], + })); + + it('should be customizable in the theme', () => { + render( + + + , + ); + expect(screen.getByRole('listbox')).to.toHaveComputedStyle({ + mixBlendMode: 'darken', + }); + }); + + describe('combobox', () => { + it('should clear the input when blur', () => { + const { getByRole } = render(); + const input = getByRole('combobox') as HTMLInputElement; + + act(() => { + input.focus(); + fireEvent.change(document.activeElement!, { target: { value: 'a' } }); + }); + + expect(input.value).to.equal('a'); + + act(() => { + (document.activeElement as HTMLElement).blur(); + }); + expect(input.value).to.equal(''); + }); + + it('should apply the icon classes', () => { + const { container } = render(); + expect(container.querySelector(`.${classes.root}`)).to.have.class(classes.hasClearIcon); + expect(container.querySelector(`.${classes.root}`)).to.have.class(classes.hasPopupIcon); + }); + }); + + describe('prop: loading', () => { + it('should show a loading message when open', () => { + render(); + fireEvent.keyDown(screen.getByRole('combobox'), { key: 'ArrowDown' }); + expect(document.querySelector(`.${classes.loading}`)?.textContent).to.equal('Loading…'); + }); + }); + + describe('prop: autoHighlight', () => { + it('should set the focus on the first item', () => { + const options = ['one', 'two']; + const { getByRole } = render( + , + ); + + checkHighlightIs(getByRole('listbox'), 'one'); + fireEvent.change(document.activeElement!, { target: { value: 'oo' } }); + fireEvent.change(document.activeElement!, { target: { value: 'o' } }); + checkHighlightIs(getByRole('listbox'), 'one'); + }); + + it('should keep the highlight on the first item', () => { + const options = ['one', 'two']; + const { getByRole } = render( + , + ); + + checkHighlightIs(getByRole('listbox'), 'one'); + fireEvent.change(document.activeElement!, { target: { value: 'two' } }); + checkHighlightIs(getByRole('listbox'), 'two'); + }); + + it('should set the focus on the first item when possible', () => { + const options = ['one', 'two']; + const { getByRole, setProps } = render( + , + ); + const textbox = getByRole('combobox'); + expect(textbox).not.to.have.attribute('aria-activedescendant'); + + setProps({ options, loading: false }); + expect(textbox).to.have.attribute( + 'aria-activedescendant', + screen.getAllByRole('option')[0].getAttribute('id')!, + ); + }); + + it('should set the highlight on selected item when dropdown is expanded', () => { + const { getByRole, setProps } = render( + , + ); + + checkHighlightIs(getByRole('listbox'), 'one'); + setProps({ value: 'two' }); + checkHighlightIs(getByRole('listbox'), 'two'); + }); + + it('should keep the current highlight if possible', () => { + const { getByRole } = render( + , + ); + const textbox = getByRole('combobox'); + + checkHighlightIs(getByRole('listbox'), 'one'); + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + checkHighlightIs(getByRole('listbox'), 'two'); + fireEvent.keyDown(textbox, { key: 'Enter' }); + checkHighlightIs(getByRole('listbox'), 'two'); + }); + + it('should work with filterSelectedOptions too', () => { + const options = ['Foo', 'Bar', 'Baz']; + const { getByRole } = render( + , + ); + const textbox = getByRole('combobox'); + + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + checkHighlightIs(getByRole('listbox'), 'Bar'); + fireEvent.change(textbox, { target: { value: 'a' } }); + checkHighlightIs(getByRole('listbox'), 'Bar'); + fireEvent.change(textbox, { target: { value: 'aa' } }); + fireEvent.change(textbox, { target: { value: 'a' } }); + checkHighlightIs(getByRole('listbox'), 'Bar'); + }); + }); + + describe('highlight synchronisation', () => { + it('should not update the highlight when multiple open and value change', () => { + const { setProps, getByRole } = render( + , + ); + + checkHighlightIs(getByRole('listbox'), 'two'); + setProps({ + value: [], + }); + checkHighlightIs(getByRole('listbox'), 'two'); + }); + }); + + describe('prop: limitTags', () => { + it('show all items on focus', () => { + const { container, getAllByRole, getByRole } = render( + , + ); + + expect(container.textContent).to.equal('onetwo+1'); + // include hidden clear button because JSDOM thinks it's visible + expect(getAllByRole('button', { hidden: true })).to.have.lengthOf(4); + + act(() => { + getByRole('combobox').focus(); + }); + expect(container.textContent).to.equal('onetwothree'); + // Depending on the subset of components used in this test run the computed `visibility` changes in JSDOM. + if (!/jsdom/.test(window.navigator.userAgent)) { + expect(getAllByRole('button', { hidden: false })).to.have.lengthOf(5); + } + }); + + it('show 0 item on close when set 0 to limitTags', () => { + const { container, getAllByRole, getByRole } = render( + , + ); + + expect(container.textContent).to.equal('+3'); + // include hidden clear button because JSDOM thinks it's visible + expect(getAllByRole('button', { hidden: true })).to.have.lengthOf(2); + + act(() => { + getByRole('combobox').focus(); + }); + expect(container.textContent).to.equal('onetwothree'); + // Depending on the subset of components used in this test run the computed `visibility` changes in JSDOM. + if (!/jsdom/.test(window.navigator.userAgent)) { + expect(getAllByRole('button', { hidden: false })).to.have.lengthOf(5); + } + }); + }); + + describe('prop: filterSelectedOptions', () => { + it('when the last item is selected, highlights the new last item', () => { + const { getByRole } = render( + , + ); + const textbox = getByRole('combobox'); + + fireEvent.keyDown(textbox, { key: 'ArrowUp' }); + checkHighlightIs(getByRole('listbox'), 'three'); + fireEvent.keyDown(textbox, { key: 'Enter' }); // selects the last option (three) + const input = getByRole('combobox'); + act(() => { + input.blur(); + input.focus(); // opens the listbox again + }); + checkHighlightIs(getByRole('listbox'), null); + }); + }); + + describe('prop: autoSelect', () => { + it('should not clear on blur when value does not match any option', () => { + const handleChange = spy(); + const options = ['one', 'two']; + render( + , + ); + const textbox = screen.getByRole('combobox'); + + fireEvent.change(textbox, { target: { value: 'o' } }); + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + fireEvent.change(textbox, { target: { value: 'oo' } }); + act(() => { + textbox.blur(); + }); + + expect(handleChange.callCount).to.equal(1); + expect(handleChange.args[0][1]).to.deep.equal('oo'); + }); + + it('should add new value when autoSelect & multiple on blur', () => { + const handleChange = spy(); + const options = ['one', 'two']; + render( + , + ); + const textbox = screen.getByRole('combobox'); + + act(() => { + fireEvent.change(textbox, { target: { value: 't' } }); + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + textbox.blur(); + }); + + expect(handleChange.callCount).to.equal(1); + expect(handleChange.args[0][1]).to.deep.equal(options); + }); + + it('should add new value when autoSelect & multiple & freeSolo on blur', () => { + const handleChange = spy(); + render( + , + ); + + fireEvent.change(document.activeElement!, { target: { value: 'a' } }); + act(() => { + (document.activeElement as HTMLElement).blur(); + }); + + expect(handleChange.callCount).to.equal(1); + expect(handleChange.args[0][1]).to.deep.equal(['a']); + }); + }); + + describe('prop: multiple', () => { + it('should not crash', () => { + const { getByRole } = render(); + const input = getByRole('combobox'); + + act(() => { + input.focus(); + (document.activeElement as HTMLElement).blur(); + input.focus(); + }); + }); + + it('should remove the last option', () => { + const handleChange = spy(); + const options = ['one', 'two']; + const { getAllByTestId } = render( + , + ); + fireEvent.click(getAllByTestId('CancelIcon')[1]); + expect(handleChange.callCount).to.equal(1); + expect(handleChange.args[0][1]).to.deep.equal([options[0]]); + }); + + it('navigates between different tags', () => { + const handleChange = spy(); + const options = ['one', 'two']; + render( + , + ); + const textbox = screen.getByRole('combobox'); + const [firstSelectedValue, secondSelectedValue] = screen.getAllByRole('button'); + + fireEvent.keyDown(textbox, { key: 'ArrowLeft' }); + expect(secondSelectedValue).toHaveFocus(); + + fireEvent.keyDown(secondSelectedValue, { key: 'ArrowLeft' }); + expect(firstSelectedValue).toHaveFocus(); + + fireEvent.keyDown(firstSelectedValue, { key: 'Backspace' }); + expect(handleChange.callCount).to.equal(1); + expect(handleChange.args[0][1]).to.deep.equal([options[1]]); + expect(textbox).toHaveFocus(); + }); + + it('should keep listbox open on pressing left or right keys when inputValue is not empty', () => { + const handleClose = spy(); + const options = ['one', 'two', 'three']; + const { getByRole } = render( + , + ); + + const textbox = getByRole('combobox'); + + fireEvent.mouseDown(textbox); + fireEvent.keyDown(textbox, { key: 'ArrowLeft' }); + + expect(handleClose.callCount).to.equal(0); + expect(textbox).to.have.attribute('aria-expanded', 'true'); + }); + + it('should close listbox on pressing left or right keys when inputValue is empty', () => { + const handleClose = spy(); + const options = ['one', 'two', 'three']; + const { getByRole } = render( + , + ); + + const textbox = getByRole('combobox'); + + fireEvent.mouseDown(textbox); + fireEvent.keyDown(textbox, { key: 'ArrowLeft' }); + + expect(handleClose.callCount).to.equal(1); + expect(textbox).to.have.attribute('aria-expanded', 'false'); + }); + + it('should not crash if a tag is missing', () => { + const handleChange = spy(); + const options = ['one', 'two']; + render( + + value + .filter((x, index) => index === 1) + .map((option, index) => ( + }> + {option.title} + + )) + } + onChange={handleChange} + autoFocus + multiple + />, + ); + const textbox = screen.getByRole('combobox'); + const [firstSelectedValue] = screen.getAllByRole('button'); + + fireEvent.keyDown(textbox, { key: 'ArrowLeft' }); + // skip value "two" + expect(firstSelectedValue).toHaveFocus(); + + fireEvent.keyDown(firstSelectedValue, { key: 'ArrowRight' }); + expect(textbox).toHaveFocus(); + }); + + it('should not call onChange function for duplicate values', () => { + const handleChange = spy(); + const options = ['one', 'two']; + render( + , + ); + const textbox = screen.getByRole('combobox'); + + fireEvent.change(textbox, { target: { value: 'two' } }); + fireEvent.keyDown(textbox, { key: 'Enter' }); + expect(handleChange.callCount).to.equal(0); + + fireEvent.change(textbox, { target: { value: 'three' } }); + fireEvent.keyDown(textbox, { key: 'Enter' }); + expect(handleChange.callCount).to.equal(1); + }); + + it('has no textbox value', () => { + render(); + + expect(screen.getByRole('combobox')).to.have.property('value', ''); + }); + + it('should fail validation if a required field has no value', function test() { + if (/jsdom/.test(window.navigator.userAgent)) { + // Enable once https://github.com/jsdom/jsdom/issues/2898 is resolved + this.skip(); + } + + const handleSubmit = spy((event) => event.preventDefault()); + render( +
+ + + , + ); + + screen.getByRole('button', { name: 'Submit' }).click(); + + expect(handleSubmit.callCount).to.equal(0); + }); + + it('should fail validation if a required field has a value', function test() { + // Unclear how native Constraint validation can be enabled for `multiple` + if (/jsdom/.test(window.navigator.userAgent)) { + // Enable once https://github.com/jsdom/jsdom/issues/2898 is resolved + // The test is passing in JSDOM but form validation is buggy in JSDOM so we rather skip than have false confidence + this.skip(); + } + + const handleSubmit = spy((event) => event.preventDefault()); + render( +
+ + + , + ); + + screen.getByRole('button', { name: 'Submit' }).click(); + + expect(handleSubmit.callCount).to.equal(0); + }); + }); + + it('should trigger a form expectedly', () => { + const handleSubmit = spy(); + const Test = (props: any) => ( +
{ + if (!event.defaultPrevented && event.key === 'Enter') { + handleSubmit(); + } + }} + > + +
+ ); + const { setProps } = render(); + let textbox = screen.getByRole('combobox'); + + fireEvent.keyDown(textbox, { key: 'Enter' }); + expect(handleSubmit.callCount).to.equal(1); + + fireEvent.change(textbox, { target: { value: 'o' } }); + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + fireEvent.keyDown(textbox, { key: 'Enter' }); + expect(handleSubmit.callCount).to.equal(1); + + fireEvent.keyDown(textbox, { key: 'Enter' }); + expect(handleSubmit.callCount).to.equal(2); + + setProps({ key: 'test-2', multiple: true, freeSolo: true }); + textbox = screen.getByRole('combobox'); + + fireEvent.change(textbox, { target: { value: 'o' } }); + fireEvent.keyDown(textbox, { key: 'Enter' }); + expect(handleSubmit.callCount).to.equal(2); + + fireEvent.keyDown(textbox, { key: 'Enter' }); + expect(handleSubmit.callCount).to.equal(3); + + setProps({ key: 'test-3', freeSolo: true }); + textbox = screen.getByRole('combobox'); + + fireEvent.change(textbox, { target: { value: 'o' } }); + fireEvent.keyDown(textbox, { key: 'Enter' }); + expect(handleSubmit.callCount).to.equal(4); + }); + + describe('prop: getOptionDisabled', () => { + it('should prevent the disabled option to trigger actions but allow focus with disabledItemsFocusable', () => { + const handleSubmit = spy(); + const handleChange = spy(); + const { getAllByRole } = render( +
{ + if (!event.defaultPrevented && event.key === 'Enter') { + handleSubmit(); + } + }} + > + option === 'two'} + onChange={handleChange} + openOnFocus + options={['one', 'two', 'three']} + /> +
, + ); + + let options; + const textbox = screen.getByRole('combobox'); + + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + options = getAllByRole('option'); + expect(textbox).to.have.attribute('aria-activedescendant', options[1].getAttribute('id')!); + + fireEvent.keyDown(textbox, { key: 'Enter' }); + expect(handleSubmit.callCount).to.equal(0); + expect(handleChange.callCount).to.equal(0); + + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + options = getAllByRole('option'); + expect(textbox).to.have.attribute('aria-activedescendant', options[0].getAttribute('id')!); + + fireEvent.keyDown(textbox, { key: 'Enter' }); + expect(handleSubmit.callCount).to.equal(0); + expect(handleChange.callCount).to.equal(1); + }); + }); + + describe('WAI-ARIA conforming markup', () => { + specify('when closed', () => { + const { getAllByRole, getByRole, queryByRole } = render(); + + const combobox = getByRole('combobox'); + expect(combobox).to.have.attribute('aria-expanded', 'false'); + // reflected aria-haspopup is `listbox` + // this assertion can fail if the value is `listbox` + expect(combobox).not.to.have.attribute('aria-haspopup'); + + const textbox = getByRole('combobox'); + expect(combobox).to.contain(textbox); + // reflected aria-multiline has to be false i.e. not present or false + expect(textbox).not.to.have.attribute('aria-multiline'); + expect(textbox).to.have.attribute('aria-autocomplete', 'list'); + expect(textbox, 'no option is focused when openened').not.to.have.attribute( + 'aria-activedescendant', + ); + + // listbox is not only inaccessible but not in the DOM + const listbox = queryByRole('listbox', { hidden: true }); + expect(listbox).to.equal(null); + + const buttons = getAllByRole('button', { hidden: true }); + + expect(buttons[0]).toHaveAccessibleName('Open'); + expect(buttons[0]).to.have.attribute('title', 'Open'); + expect(buttons).to.have.length(1); + expect(buttons[0], 'button is not in tab order').to.have.property('tabIndex', -1); + }); + + specify('when open', () => { + const { getAllByRole, getByRole } = render(); + + const combobox = getByRole('combobox'); + expect(combobox).to.have.attribute('aria-expanded', 'true'); + + const textbox = getByRole('combobox'); + + const listbox = getByRole('listbox'); + expect(textbox).to.have.attribute('aria-controls', listbox.getAttribute('id')!); + expect(textbox, 'no option is focused when openened').not.to.have.attribute( + 'aria-activedescendant', + ); + + const options = getAllByRole('option'); + expect(options).to.have.length(2); + options.forEach((option) => { + expect(listbox).to.contain(option); + }); + + const buttons = getAllByRole('button', { hidden: true }); + expect(buttons[0]).toHaveAccessibleName('Close'); + expect(buttons[0]).to.have.attribute('title', 'Close'); + expect(buttons).to.have.length(1); + expect(buttons[0], 'button is not in tab order').to.have.property('tabIndex', -1); + }); + + it('should add and remove aria-activedescendant', () => { + const { getAllByRole, getByRole, setProps } = render( + , + ); + const textbox = getByRole('combobox'); + expect(textbox, 'no option is focused when openened').not.to.have.attribute( + 'aria-activedescendant', + ); + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + + const options = getAllByRole('option'); + expect(textbox).to.have.attribute('aria-activedescendant', options[0].getAttribute('id')!); + setProps({ open: false }); + expect(textbox, 'no option is focused when openened').not.to.have.attribute( + 'aria-activedescendant', + ); + }); + }); + + describe('when popup closed', () => { + it('opens when the textbox is focused when `openOnFocus`', () => { + const handleOpen = spy(); + render(); + + expect(handleOpen.callCount).to.equal(1); + }); + + it('does not open on clear', () => { + const handleOpen = spy(); + const handleChange = spy(); + const { container } = render( + , + ); + + const clear = container.querySelector('button'); + fireEvent.click(clear!); + + expect(handleOpen.callCount).to.equal(0); + expect(handleChange.callCount).to.equal(1); + }); + + ['ArrowDown', 'ArrowUp'].forEach((key) => { + it(`opens on ${key} when focus is on the textbox and \`openOnFocus\` without moving focus`, () => { + const handleOpen = spy(); + render( + , + ); + const textbox = screen.getByRole('combobox'); + + fireEvent.keyDown(textbox, { key }); + + // first from focus + expect(handleOpen.callCount).to.equal(2); + expect(textbox).not.to.have.attribute('aria-activedescendant'); + }); + }); + + it('does not clear the textbox on Escape', () => { + const handleChange = spy(); + render( + , + ); + + fireEvent.keyDown(screen.getByRole('combobox'), { key: 'Escape' }); + + expect(handleChange.callCount).to.equal(0); + }); + }); + + describe('prop: clearOnEscape', () => { + it('should clear on escape', () => { + const handleChange = spy(); + render( + , + ); + + fireEvent.keyDown(screen.getByRole('combobox'), { key: 'Escape' }); + expect(handleChange.callCount).to.equal(1); + expect(handleChange.args[0][1]).to.deep.equal([]); + }); + }); + + describe('prop: clearOnBlur', () => { + it('should clear on blur', () => { + render(); + const textbox = screen.getByRole('combobox') as HTMLInputElement; + fireEvent.change(textbox, { target: { value: 'test' } }); + expect((document.activeElement as HTMLInputElement).value).to.equal('test'); + act(() => { + textbox.blur(); + }); + expect(textbox.value).to.equal(''); + }); + + it('should not clear on blur', () => { + render(); + const textbox = screen.getByRole('combobox') as HTMLInputElement; + fireEvent.change(textbox, { target: { value: 'test' } }); + expect((document.activeElement as HTMLInputElement).value).to.equal('test'); + act(() => { + textbox.blur(); + }); + expect(textbox.value).to.equal('test'); + }); + + it('should not clear on blur with `multiple` enabled', () => { + render( + , + ); + const textbox = screen.getByRole('combobox') as HTMLInputElement; + fireEvent.change(textbox, { target: { value: 'test' } }); + expect((document.activeElement as HTMLInputElement).value).to.equal('test'); + act(() => { + textbox.blur(); + }); + expect(textbox.value).to.equal('test'); + }); + }); + + describe('when popup open', () => { + it('closes the popup if Escape is pressed ', () => { + const handleClose = spy(); + render(); + + fireEvent.keyDown(screen.getByRole('combobox'), { key: 'Escape' }); + expect(handleClose.callCount).to.equal(1); + }); + + it('does not close the popup when option selected if Control is pressed', () => { + const handleClose = spy(); + const { getAllByRole } = render( + , + ); + + const options = getAllByRole('option'); + fireEvent.click(options[0], { ctrlKey: true }); + expect(handleClose.callCount).to.equal(0); + }); + + it('does not close the popup when option selected if Meta is pressed', () => { + const handleClose = spy(); + const { getAllByRole } = render( + , + ); + + const options = getAllByRole('option'); + fireEvent.click(options[0], { metaKey: true }); + expect(handleClose.callCount).to.equal(0); + }); + + it('moves focus to the first option on ArrowDown', () => { + const { getAllByRole, getByRole } = render( + , + ); + + fireEvent.keyDown(screen.getByRole('combobox'), { key: 'ArrowDown' }); + expect(getByRole('combobox')).to.have.attribute( + 'aria-activedescendant', + getAllByRole('option')[0].getAttribute('id')!, + ); + }); + + it('moves focus to the last option on ArrowUp', () => { + const { getAllByRole, getByRole } = render( + , + ); + + fireEvent.keyDown(screen.getByRole('combobox'), { key: 'ArrowUp' }); + const options = getAllByRole('option'); + expect(getByRole('combobox')).to.have.attribute( + 'aria-activedescendant', + options[options.length - 1].getAttribute('id')!, + ); + }); + + it('should ignore keydown event until the IME is confirmed', function test() { + // TODO: Often times out in Firefox 78. + // Is this slow because of testing-library or because of the implementation? + this.timeout(4000); + + const { getByRole } = render(); + const textbox = getByRole('combobox'); + const listbox = getByRole('listbox'); + // Actual Behavior when "가" (Korean) is entered and press the arrow down key once on macOS/Chrome + fireEvent.change(textbox, { target: { value: '가' } }); + fireEvent.keyDown(textbox, { key: 'ArrowDown', keyCode: 229 }); + fireEvent.keyDown(textbox, { key: 'ArrowDown', keyCode: 40 }); + + checkHighlightIs(listbox, '가1'); + }); + }); + + describe('prop: openOnFocus', () => { + it('enables open on input focus', () => { + const { getByRole } = render( + , + ); + const textbox = getByRole('combobox'); + const combobox = getByRole('combobox'); + + expect(combobox).to.have.attribute('aria-expanded', 'true'); + expect(textbox).toHaveFocus(); + + fireEvent.mouseDown(textbox); + fireEvent.click(textbox); + expect(combobox).to.have.attribute('aria-expanded', 'false'); + + act(() => { + (document.activeElement as HTMLElement).blur(); + }); + + expect(combobox).to.have.attribute('aria-expanded', 'false'); + expect(textbox).not.toHaveFocus(); + + fireEvent.mouseDown(textbox); + fireEvent.click(textbox); + expect(combobox).to.have.attribute('aria-expanded', 'true'); + expect(textbox).toHaveFocus(); + + fireEvent.mouseDown(textbox); + fireEvent.click(textbox); + expect(combobox).to.have.attribute('aria-expanded', 'false'); + }); + }); + + describe('listbox wrapping behavior', () => { + it('wraps around when navigating the list by default', () => { + const { getAllByRole } = render(); + const textbox = screen.getByRole('combobox'); + + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + fireEvent.keyDown(textbox, { key: 'ArrowUp' }); + + const options = getAllByRole('option'); + expect(textbox).toHaveFocus(); + expect(textbox).to.have.attribute( + 'aria-activedescendant', + options[options.length - 1].getAttribute('id')!, + ); + }); + + it('selects the first item if on the last item and pressing up by default', () => { + render(); + const textbox = screen.getByRole('combobox'); + + fireEvent.keyDown(textbox, { key: 'ArrowUp' }); + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + + const options = screen.getAllByRole('option'); + expect(textbox).toHaveFocus(); + expect(textbox).to.have.attribute('aria-activedescendant', options[0].getAttribute('id')!); + }); + + describe('prop: includeInputInList', () => { + it('considers the textbox the predessor of the first option when pressing Up', () => { + render( + , + ); + const textbox = screen.getByRole('combobox'); + + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + fireEvent.keyDown(textbox, { key: 'ArrowUp' }); + + expect(textbox).toHaveFocus(); + expect(textbox).not.to.have.attribute('aria-activedescendant'); + }); + + it('considers the textbox the successor of the last option when pressing Down', () => { + render( + , + ); + const textbox = screen.getByRole('combobox'); + + fireEvent.keyDown(textbox, { key: 'ArrowUp' }); + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + + expect(textbox).toHaveFocus(); + expect(textbox).not.to.have.attribute('aria-activedescendant'); + }); + }); + + describe('prop: disableListWrap', () => { + it('keeps focus on the first item if focus is on the first item and pressing Up', () => { + render(); + const textbox = screen.getByRole('combobox'); + + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + fireEvent.keyDown(textbox, { key: 'ArrowUp' }); + + expect(textbox).toHaveFocus(); + expect(textbox).to.have.attribute( + 'aria-activedescendant', + screen.getAllByRole('option')[0].getAttribute('id')!, + ); + }); + + it('focuses the last item when pressing Up when no option is active', () => { + render(); + const textbox = screen.getByRole('combobox'); + + fireEvent.keyDown(textbox, { key: 'ArrowUp' }); + + const options = screen.getAllByRole('option'); + expect(textbox).toHaveFocus(); + expect(textbox).to.have.attribute( + 'aria-activedescendant', + options[options.length - 1].getAttribute('id')!, + ); + }); + + it('keeps focus on the last item if focus is on the last item and pressing Down', () => { + render(); + const textbox = screen.getByRole('combobox'); + + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + + const options = screen.getAllByRole('option'); + expect(textbox).toHaveFocus(); + expect(textbox).to.have.attribute( + 'aria-activedescendant', + options[options.length - 1].getAttribute('id')!, + ); + }); + }); + }); + + describe('prop: disabled', () => { + it('should disable the input', () => { + const { getByRole } = render(); + const input = getByRole('combobox'); + expect(input).to.have.property('disabled', true); + }); + + it('should disable the popup button', () => { + const { queryByTitle } = render(); + expect((queryByTitle('Open') as HTMLButtonElement).disabled).to.equal(true); + }); + + it('should not render the clear button', () => { + const { queryByTitle } = render(); + expect(queryByTitle('Clear')).to.equal(null); + }); + + it('should not apply the hasClearIcon class', () => { + const { container } = render(); + expect(container.querySelector(`.${classes.root}`)).not.to.have.class(classes.hasClearIcon); + expect(container.querySelector(`.${classes.root}`)).to.have.class(classes.hasPopupIcon); + }); + + it('should close the popup when disabled is true', () => { + const { setProps } = render(); + const textbox = screen.getByRole('combobox'); + act(() => { + textbox.focus(); + }); + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + expect(screen.queryByRole('listbox')).not.to.equal(null); + setProps({ disabled: true }); + expect(screen.queryByRole('listbox')).to.equal(null); + }); + + it('should not crash when autoSelect & freeSolo are set, text is focused & disabled gets truthy', () => { + const { setProps } = render( + , + ); + const textbox = screen.getByRole('combobox'); + act(() => { + textbox.focus(); + }); + setProps({ disabled: true }); + expect(textbox).toBeVisible(); + }); + }); + + describe('prop: disableClearable', () => { + it('should not render the clear button', () => { + const { queryByTitle, container } = render( + , + ); + expect(queryByTitle('Clear')).to.equal(null); + expect(container.querySelector(`.${classes.root}`)).to.have.class(classes.hasPopupIcon); + expect(container.querySelector(`.${classes.root}`)).not.to.have.class(classes.hasClearIcon); + }); + }); + + describe('warnings', () => { + beforeEach(() => { + PropTypes.resetWarningCache(); + }); + + it('warn if getOptionLabel do not return a string', () => { + const handleChange = spy(); + render( + (option as { name: string }).name} + autoFocus + />, + ); + const textbox = screen.getByRole('combobox'); + + expect(() => { + fireEvent.change(textbox, { target: { value: 'a' } }); + fireEvent.keyDown(textbox, { key: 'Enter' }); + }).toErrorDev([ + 'MUI: The `getOptionLabel` method of Autocomplete returned undefined instead of a string', + !strictModeDoubleLoggingSupressed && + 'MUI: The `getOptionLabel` method of Autocomplete returned undefined instead of a string', + !strictModeDoubleLoggingSupressed && + 'MUI: The `getOptionLabel` method of Autocomplete returned undefined instead of a string', + 'MUI: The `getOptionLabel` method of Autocomplete returned undefined instead of a string', + 'MUI: The `getOptionLabel` method of Autocomplete returned undefined instead of a string', + 'MUI: The `getOptionLabel` method of Autocomplete returned undefined instead of a string', + ]); + expect(handleChange.callCount).to.equal(1); + expect(handleChange.args[0][1]).to.equal('a'); + }); + + it('warn if isOptionEqualToValue match multiple values for a given option', () => { + const value = [ + { id: '10', text: 'One' }, + { id: '20', text: 'Two' }, + ]; + const options = [ + { id: '10', text: 'One' }, + { id: '20', text: 'Two' }, + { id: '30', text: 'Three' }, + ]; + + render( + option.text} + isOptionEqualToValue={(option) => !!value.find((v) => v.id === option.id)} + autoFocus + />, + ); + const textbox = screen.getByRole('combobox'); + + expect(() => { + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + fireEvent.keyDown(textbox, { key: 'Enter' }); + }).toErrorDev( + 'The component expects a single value to match a given option but found 2 matches.', + ); + }); + + it('warn if value does not exist in options list', () => { + const value = 'not a good value'; + const options = ['first option', 'second option']; + + expect(() => { + render(); + }).toWarnDev([ + 'None of the options match with `"not a good value"`', + !strictModeDoubleLoggingSupressed && 'None of the options match with `"not a good value"`', + 'None of the options match with `"not a good value"`', + !strictModeDoubleLoggingSupressed && 'None of the options match with `"not a good value"`', + // React 18 Strict Effects run mount effects twice which lead to a cascading update + React.version.startsWith('18') && 'None of the options match with `"not a good value"`', + React.version.startsWith('18') && + !strictModeDoubleLoggingSupressed && + 'None of the options match with `"not a good value"`', + ]); + }); + + it('warn if groups options are not sorted', () => { + const data = [ + { group: 1, value: 'A' }, + { group: 2, value: 'D' }, + { group: 2, value: 'E' }, + { group: 1, value: 'B' }, + { group: 3, value: 'G' }, + { group: 2, value: 'F' }, + { group: 1, value: 'C' }, + ]; + expect(() => { + render( + option.value} + autoFocus + groupBy={(option) => String(option.group)} + />, + ); + }).toWarnDev([ + 'returns duplicated headers', + !strictModeDoubleLoggingSupressed && 'returns duplicated headers', + ]); + const options = screen.getAllByRole('option').map((el) => el.textContent); + expect(options).to.have.length(7); + expect(options).to.deep.equal(['A', 'D', 'E', 'B', 'G', 'F', 'C']); + }); + + it('warn if the type of the value is wrong', () => { + expect(() => { + PropTypes.checkPropTypes( + Autocomplete.propTypes, + { multiple: true, value: null, options: [], renderInput: () => null }, + 'prop', + 'Autocomplete', + ); + }).toErrorDev( + 'The Autocomplete expects the `value` prop to be an array when `multiple={true}` or undefined.', + ); + }); + + it('warn if the type of the defaultValue is wrong', () => { + expect(() => { + PropTypes.checkPropTypes( + Autocomplete.propTypes, + { multiple: true, defaultValue: 'wrong-string', options: [], renderInput: () => null }, + 'prop', + 'Autocomplete', + ); + }).toErrorDev( + 'The Autocomplete expects the `defaultValue` prop to be an array when `multiple={true}` or undefined.', + ); + }); + }); + + describe('prop: options', () => { + it('should keep focus on selected option and not reset to top option when options updated', () => { + const { setProps } = render(); + const textbox = screen.getByRole('combobox'); + const listbox = screen.getByRole('listbox'); + + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); // goes to 'one' + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); // goes to 'two' + + checkHighlightIs(listbox, 'two'); + + // three option is added and autocomplete re-renders, reset the highlight + setProps({ options: ['one', 'two', 'three'] }); + checkHighlightIs(listbox, null); + }); + + it('should not select undefined', () => { + const handleChange = spy(); + const { getByRole } = render( + , + ); + const input = getByRole('combobox'); + fireEvent.click(input); + + const listbox = getByRole('listbox'); + const firstOption = listbox.querySelector('li'); + fireEvent.click(firstOption!); + + expect(handleChange.args[0][1]).to.equal('one'); + }); + + it('should work if options are the default data structure', () => { + const options = [ + { + label: 'one', + }, + ]; + const handleChange = spy(); + const { getByRole } = render( + , + ); + + const input = getByRole('combobox'); + fireEvent.click(input); + + const listbox = getByRole('listbox'); + const htmlOptions = listbox.querySelectorAll('li'); + + expect(htmlOptions[0].innerHTML).to.equal('one'); + }); + + it("should display a 'no options' message if no options are available", () => { + const { getByRole } = render(); + + const combobox = getByRole('combobox'); + const textbox = getByRole('combobox'); + expect(combobox).to.have.attribute('aria-expanded', 'false'); + expect(combobox).not.to.have.attribute('aria-owns'); + expect(textbox).not.to.have.attribute('aria-controls'); + expect(document.querySelector(`.${classes.noOptions}`)).to.have.text('No options'); + }); + }); + + describe('enter', () => { + it('select a single value when enter is pressed', () => { + const handleChange = spy(); + render( + , + ); + const textbox = screen.getByRole('combobox'); + + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + fireEvent.keyDown(textbox, { key: 'Enter' }); + expect(handleChange.callCount).to.equal(1); + expect(handleChange.args[0][1]).to.equal('one'); + fireEvent.keyDown(textbox, { key: 'Enter' }); + expect(handleChange.callCount).to.equal(1); + }); + + it('select multiple value when enter is pressed', () => { + const handleChange = spy(); + const options = [{ name: 'one' }, { name: 'two ' }]; + render( + option.name} + autoFocus + />, + ); + const textbox = screen.getByRole('combobox'); + + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + fireEvent.keyDown(textbox, { key: 'Enter' }); + + expect(handleChange.callCount).to.equal(1); + expect(handleChange.args[0][1]).to.deep.equal([options[0]]); + fireEvent.keyDown(textbox, { key: 'Enter' }); + expect(handleChange.callCount).to.equal(1); + }); + }); + + describe('prop: autoComplete', () => { + it('add a completion string', () => { + render(); + const textbox = screen.getByRole('combobox'); + + fireEvent.change(document.activeElement as HTMLInputElement, { target: { value: 'O' } }); + + expect((document.activeElement as HTMLInputElement).value).to.equal('O'); + + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + + expect((document.activeElement as HTMLInputElement).value).to.equal('one'); + expect((document.activeElement as HTMLInputElement).selectionStart).to.equal(1); + expect((document.activeElement as HTMLInputElement).selectionEnd).to.equal(3); + + fireEvent.keyDown(textbox, { key: 'Enter' }); + + expect((document.activeElement as HTMLInputElement).value).to.equal('one'); + expect((document.activeElement as HTMLInputElement).selectionStart).to.equal(3); + expect((document.activeElement as HTMLInputElement).selectionEnd).to.equal(3); + }); + }); + + describe('click input', () => { + it('when `openOnFocus` toggles if empty', () => { + const { getByRole } = render(); + const textbox = getByRole('combobox'); + const combobox = getByRole('combobox'); + expect(combobox).to.have.attribute('aria-expanded', 'false'); + fireEvent.mouseDown(textbox); + expect(combobox).to.have.attribute('aria-expanded', 'true'); + fireEvent.mouseDown(textbox); + expect(combobox).to.have.attribute('aria-expanded', 'false'); + }); + + it('selects all the first time', () => { + const { getByRole } = render(); + const textbox = getByRole('combobox') as HTMLInputElement; + fireEvent.click(textbox); + expect(textbox.selectionStart).to.equal(0); + expect(textbox.selectionEnd).to.equal(3); + }); + + it('should focus the input when clicking on the open action', () => { + const { getByRole, queryByTitle } = render( + , + ); + + const textbox = getByRole('combobox'); + fireEvent.click(textbox); + expect(textbox).toHaveFocus(); + + act(() => { + textbox.blur(); + }); + fireEvent.click(queryByTitle('Open')!); + + expect(textbox).toHaveFocus(); + }); + + it('should maintain list box open clicking on input when it is not empty', () => { + const { getByRole, getAllByRole } = render(); + const combobox = getByRole('combobox'); + const textbox = getByRole('combobox'); + + expect(combobox).to.have.attribute('aria-expanded', 'false'); + fireEvent.mouseDown(textbox); // Open listbox + expect(combobox).to.have.attribute('aria-expanded', 'true'); + const options = getAllByRole('option'); + fireEvent.click(options[0]); + expect(combobox).to.have.attribute('aria-expanded', 'false'); + fireEvent.mouseDown(textbox); // Open listbox + expect(combobox).to.have.attribute('aria-expanded', 'true'); + fireEvent.mouseDown(textbox); // Remain open listbox + expect(combobox).to.have.attribute('aria-expanded', 'true'); + }); + + it('should not toggle list box', () => { + const { getByRole } = render(); + const combobox = getByRole('combobox'); + const textbox = getByRole('combobox'); + + expect(combobox).to.have.attribute('aria-expanded', 'false'); + fireEvent.mouseDown(textbox); + expect(combobox).to.have.attribute('aria-expanded', 'true'); + fireEvent.mouseDown(textbox); + expect(combobox).to.have.attribute('aria-expanded', 'true'); + }); + }); + + describe('controlled', () => { + it('controls the input value', () => { + const handleChange = spy(); + function MyComponent() { + const [, setInputValue] = React.useState(''); + const handleInputChange = (event: any, value: string) => { + handleChange(value); + setInputValue(value); + }; + return ( + + ); + } + + render(); + + expect(handleChange.callCount).to.equal(0); + fireEvent.change(document.activeElement as HTMLInputElement, { target: { value: 'a' } }); + expect(handleChange.callCount).to.equal(1); + expect(handleChange.args[0][0]).to.equal('a'); + expect((document.activeElement as HTMLInputElement).value).to.equal(''); + }); + + it('should fire the input change event before the change event', () => { + const handleChange = spy(); + const handleInputChange = spy(); + render( + , + ); + const textbox = screen.getByRole('combobox'); + + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + fireEvent.keyDown(textbox, { key: 'Enter' }); + + expect(handleInputChange.calledBefore(handleChange)).to.equal(true); + }); + }); + + describe('prop: filterOptions', () => { + it('should ignore object keys by default', () => { + const { queryAllByRole } = render( + option.label} + autoFocus + />, + ); + let options; + options = queryAllByRole('option'); + expect(options.length).to.equal(2); + + fireEvent.change(document.activeElement as HTMLInputElement, { target: { value: 'value' } }); + options = queryAllByRole('option'); + expect(options.length).to.equal(0); + + fireEvent.change(document.activeElement as HTMLInputElement, { target: { value: 'one' } }); + options = queryAllByRole('option'); + expect(options.length).to.equal(1); + }); + + it('limits the amount of rendered options when `limit` is set in `createFilterOptions`', () => { + const filterOptions = createFilterOptions({ limit: 2 }); + const { queryAllByRole } = render( + , + ); + expect(queryAllByRole('option').length).to.equal(2); + }); + + it('does not limit the amount of rendered options when `limit` is not set in `createFilterOptions`', () => { + const filterOptions = createFilterOptions({}); + const { queryAllByRole } = render( + , + ); + expect(queryAllByRole('option').length).to.equal(3); + }); + }); + + describe('prop: freeSolo', () => { + it('pressing twice enter should not call onChange listener twice', () => { + const handleChange = spy(); + const options = [{ name: 'foo' }]; + render( + (option as { name: string }).name} + autoFocus + />, + ); + const textbox = screen.getByRole('combobox'); + + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + fireEvent.keyDown(textbox, { key: 'Enter' }); + + expect(handleChange.callCount).to.equal(1); + expect(handleChange.args[0][1]).to.deep.equal(options[0]); + + fireEvent.keyDown(textbox, { key: 'Enter' }); + + expect(handleChange.callCount).to.equal(1); + }); + + it('should not delete exiting tag when try to add it twice', () => { + const handleChange = spy(); + const options = ['one', 'two']; + const { container } = render( + , + ); + const textbox = screen.getByRole('combobox'); + + fireEvent.change(textbox, { target: { value: 'three' } }); + fireEvent.keyDown(textbox, { key: 'Enter' }); + + expect(container.querySelectorAll(`[class*="${chipClasses.root}"]`)).to.have.length(3); + + fireEvent.change(textbox, { target: { value: 'three' } }); + fireEvent.keyDown(textbox, { key: 'Enter' }); + + expect(container.querySelectorAll(`[class*="${chipClasses.root}"]`)).to.have.length(3); + }); + + it('should not fire change event until the IME is confirmed', () => { + const handleChange = spy(); + render(); + const textbox = screen.getByRole('combobox'); + + // Actual behavior when "あ" (Japanese) is entered on macOS/Safari with IME + fireEvent.change(textbox, { target: { value: 'あ' } }); + fireEvent.keyDown(textbox, { key: 'Enter', keyCode: 229 }); + + expect(handleChange.callCount).to.equal(0); + + fireEvent.keyDown(textbox, { key: 'Enter', keyCode: 13 }); + + expect(handleChange.callCount).to.equal(1); + expect(handleChange.args[0][1]).to.equal('あ'); + }); + + it('should render endAdornment only when clear icon or popup icon is available', () => { + const { container } = render(); + + expect(container.querySelector(`.${classes.endDecorator}`)).to.equal(null); + }); + }); + + describe('prop: onChange', () => { + it('provides a reason and details on option creation', () => { + const handleChange = spy(); + const options = ['one', 'two', 'three']; + render(); + const textbox = screen.getByRole('combobox'); + + fireEvent.change(textbox, { target: { value: options[2] } }); + fireEvent.keyDown(textbox, { key: 'Enter' }); + + expect(handleChange.callCount).to.equal(1); + expect(handleChange.args[0][1]).to.equal(options[2]); + expect(handleChange.args[0][2]).to.equal('createOption'); + expect(handleChange.args[0][3]).to.deep.equal({ option: options[2] }); + }); + + it('provides a reason and details on option selection', () => { + const handleChange = spy(); + const options = ['one', 'two', 'three']; + render(); + const textbox = screen.getByRole('combobox'); + + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + fireEvent.keyDown(textbox, { key: 'Enter' }); + + expect(handleChange.callCount).to.equal(1); + expect(handleChange.args[0][1]).to.equal(options[0]); + expect(handleChange.args[0][2]).to.equal('selectOption'); + expect(handleChange.args[0][3]).to.deep.equal({ option: options[0] }); + }); + + it('provides a reason and details on option removing', () => { + const handleChange = spy(); + const options = ['one', 'two', 'three']; + render( + , + ); + const textbox = screen.getByRole('combobox'); + + fireEvent.keyDown(textbox, { key: 'Backspace' }); + + expect(handleChange.callCount).to.equal(1); + expect(handleChange.args[0][1]).to.deep.equal(options.slice(0, 2)); + expect(handleChange.args[0][2]).to.equal('removeOption'); + expect(handleChange.args[0][3]).to.deep.equal({ option: options[2] }); + }); + + it('provides a reason and details on blur', () => { + const handleChange = spy(); + const options = ['one', 'two', 'three']; + render(); + const textbox = screen.getByRole('combobox'); + + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + act(() => { + textbox.blur(); + }); + + expect(handleChange.callCount).to.equal(1); + expect(handleChange.args[0][1]).to.equal(options[0]); + expect(handleChange.args[0][2]).to.equal('blur'); + expect(handleChange.args[0][3]).to.deep.equal({ option: options[0] }); + }); + + it('provides a reason and details on clear', () => { + const handleChange = spy(); + const options = ['one', 'two', 'three']; + const { container } = render( + , + ); + + const button = container.querySelector(`[class*="${classes.clearIndicator}"]`); + fireEvent.click(button!); + expect(handleChange.callCount).to.equal(1); + expect(handleChange.args[0][1]).to.deep.equal([]); + expect(handleChange.args[0][2]).to.equal('clear'); + expect(handleChange.args[0][3]).to.equal(undefined); + }); + }); + + describe('prop: onInputChange', () => { + it('provides a reason on input change', () => { + const handleInputChange = spy(); + const options = [{ name: 'foo' }]; + render( + option.name} + autoFocus + />, + ); + fireEvent.change(document.activeElement as HTMLInputElement, { target: { value: 'a' } }); + expect(handleInputChange.callCount).to.equal(1); + expect(handleInputChange.args[0][1]).to.equal('a'); + expect(handleInputChange.args[0][2]).to.equal('input'); + }); + + it('provides a reason on select reset', () => { + const handleInputChange = spy(); + const options = [{ name: 'foo' }]; + render( + option.name} + autoFocus + />, + ); + const textbox = screen.getByRole('combobox'); + + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + fireEvent.keyDown(textbox, { key: 'Enter' }); + + expect(handleInputChange.callCount).to.equal(1); + expect(handleInputChange.args[0][1]).to.equal(options[0].name); + expect(handleInputChange.args[0][2]).to.equal('reset'); + }); + }); + + describe('prop: blurOnSelect', () => { + it('[blurOnSelect=true] should blur the input when clicking or touching options', () => { + const options = [{ name: 'foo' }]; + const { getByRole, queryByTitle } = render( + option.name} + autoFocus + blurOnSelect + />, + ); + const textbox = getByRole('combobox'); + let firstOption = getByRole('option'); + expect(textbox).toHaveFocus(); + fireEvent.click(firstOption); + expect(textbox).not.toHaveFocus(); + + fireEvent.click(queryByTitle('Open')!); + expect(textbox).toHaveFocus(); + firstOption = getByRole('option'); + fireEvent.touchStart(firstOption); + fireEvent.click(firstOption); + expect(textbox).not.toHaveFocus(); + }); + + it('[blurOnSelect="touch"] should only blur the input when an option is touched', () => { + const options = [{ name: 'foo' }]; + const { getByRole, queryByTitle } = render( + option.name} + autoFocus + blurOnSelect="touch" + />, + ); + const textbox = getByRole('combobox'); + let firstOption = getByRole('option'); + fireEvent.click(firstOption); + expect(textbox).toHaveFocus(); + + fireEvent.click(queryByTitle('Open')!); + firstOption = getByRole('option'); + fireEvent.touchStart(firstOption); + fireEvent.click(firstOption); + expect(textbox).not.toHaveFocus(); + }); + + it('[blurOnSelect="mouse"] should only blur the input when an option is clicked', () => { + const options = [{ name: 'foo' }]; + const { getByRole, queryByTitle } = render( + option.name} + autoFocus + blurOnSelect="mouse" + />, + ); + const textbox = getByRole('combobox'); + let firstOption = getByRole('option'); + fireEvent.touchStart(firstOption); + fireEvent.click(firstOption); + expect(textbox).toHaveFocus(); + + fireEvent.click(queryByTitle('Open')!); + firstOption = getByRole('option'); + fireEvent.click(firstOption); + expect(textbox).not.toHaveFocus(); + }); + }); + + describe('prop: getOptionLabel', () => { + it('is considered for falsy values when filtering the list of options', () => { + const { getAllByRole } = render( + (option === 0 ? 'Any' : option.toString())} + value={0} + />, + ); + + const options = getAllByRole('option'); + expect(options).to.have.length(3); + }); + + it('is not considered for nullish values when filtering the list of options', () => { + const { getAllByRole } = render( + (option === null ? 'Any' : option.toString())} + value={null} + />, + ); + + const options = getAllByRole('option'); + expect(options).to.have.length(3); + }); + + it('should update the input value when getOptionLabel changes', () => { + const { setProps } = render( + option} + />, + ); + const textbox = screen.getByRole('combobox'); + expect(textbox).to.have.property('value', 'one'); + setProps({ + getOptionLabel: (option: string) => option.toUpperCase(), + }); + expect(textbox).to.have.property('value', 'ONE'); + }); + + it('should not update the input value when users is focusing', () => { + const { setProps } = render( + option} + autoFocus + />, + ); + const textbox = screen.getByRole('combobox'); + expect(textbox).to.have.property('value', 'one'); + fireEvent.change(textbox, { target: { value: 'a' } }); + setProps({ + getOptionLabel: (option: string) => option.toUpperCase(), + }); + expect(textbox).to.have.property('value', 'a'); + }); + }); + + describe('prop: onHighlightChange', () => { + it('should trigger event when default value is passed', () => { + const handleHighlightChange = spy(); + const options = ['one', 'two', 'three']; + render( + , + ); + expect(handleHighlightChange.callCount).to.equal( + // FIXME: highlighted index implementation should be implemented using React not the DOM. + React.version.startsWith('18') ? 2 : 1, + ); + expect(handleHighlightChange.args[0]).to.deep.equal([undefined, options[0], 'auto']); + if (React.version.startsWith('18')) { + expect(handleHighlightChange.args[1]).to.deep.equal([undefined, options[0], 'auto']); + } + }); + + it('should support keyboard event', () => { + const handleHighlightChange = spy(); + const options = ['one', 'two', 'three']; + render( + , + ); + const textbox = screen.getByRole('combobox'); + + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + + expect(handleHighlightChange.callCount).to.equal( + // FIXME: highlighted index implementation should be implemented using React not the DOM. + React.version.startsWith('18') ? 4 : 3, + ); + if (React.version.startsWith('18')) { + expect(handleHighlightChange.args[2][0]).to.equal(undefined); + expect(handleHighlightChange.args[2][1]).to.equal(null); + expect(handleHighlightChange.args[2][2]).to.equal('auto'); + } + expect(handleHighlightChange.lastCall.args[0]).not.to.equal(undefined); + expect(handleHighlightChange.lastCall.args[1]).to.equal(options[0]); + expect(handleHighlightChange.lastCall.args[2]).to.equal('keyboard'); + + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + expect(handleHighlightChange.callCount).to.equal( + // FIXME: highlighted index implementation should be implemented using React not the DOM. + React.version.startsWith('18') ? 5 : 4, + ); + expect(handleHighlightChange.lastCall.args[0]).not.to.equal(undefined); + expect(handleHighlightChange.lastCall.args[1]).to.equal(options[1]); + expect(handleHighlightChange.lastCall.args[2]).to.equal('keyboard'); + }); + + it('should support mouse event', () => { + const handleHighlightChange = spy(); + const options = ['one', 'two', 'three']; + const { getAllByRole } = render( + , + ); + const firstOption = getAllByRole('option')[0]; + fireEvent.mouseOver(firstOption); + expect(handleHighlightChange.callCount).to.equal( + // FIXME: highlighted index implementation should be implemented using React not the DOM. + React.version.startsWith('18') ? 4 : 3, + ); + if (React.version.startsWith('18')) { + expect(handleHighlightChange.args[2][0]).to.equal(undefined); + expect(handleHighlightChange.args[2][1]).to.equal(null); + expect(handleHighlightChange.args[2][2]).to.equal('auto'); + } + expect(handleHighlightChange.lastCall.args[0]).not.to.equal(undefined); + expect(handleHighlightChange.lastCall.args[1]).to.equal(options[0]); + expect(handleHighlightChange.lastCall.args[2]).to.equal('mouse'); + }); + + it('should pass to onHighlightChange the correct value after filtering', () => { + const handleHighlightChange = spy(); + const options = ['one', 'three', 'onetwo']; + render( + , + ); + const textbox = screen.getByRole('combobox'); + + fireEvent.change(document.activeElement as HTMLInputElement, { target: { value: 'one' } }); + expect(screen.getAllByRole('option').length).to.equal(2); + + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + expect(handleHighlightChange.args[handleHighlightChange.args.length - 1][1]).to.equal( + options[2], + ); + }); + + it('should reset the highlight when the options change', () => { + const handleHighlightChange: (string | null)[] = []; + const { getByRole, setProps } = render( + { + handleHighlightChange.push(option); + }} + openOnFocus + autoHighlight + options={['one', 'two', 'three']} + autoFocus + />, + ); + + checkHighlightIs(getByRole('listbox'), 'one'); + setProps({ options: ['four', 'five'] }); + checkHighlightIs(getByRole('listbox'), 'four'); + expect(handleHighlightChange).to.deep.equal([null, 'one', 'four']); + }); + }); + + it('should filter options when new input value matches option', () => { + const handleChange = spy(); + const { getAllByRole, getByRole } = render( + , + ); + const textbox = getByRole('combobox'); + const combobox = getByRole('combobox'); + + fireEvent.change(textbox, { target: { value: 'one' } }); + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + fireEvent.keyDown(textbox, { key: 'Enter' }); + expect(handleChange.callCount).to.equal(1); + expect(handleChange.args[0][1]).to.deep.equal('one'); + expect(combobox).to.have.attribute('aria-expanded', 'false'); + + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + expect(combobox).to.have.attribute('aria-expanded', 'true'); + + expect(getAllByRole('option')).to.have.length(2); + + fireEvent.change(textbox, { target: { value: 'on' } }); + fireEvent.change(textbox, { target: { value: 'one' } }); + + expect(getAllByRole('option')).to.have.length(1); + }); + + it('should prevent the default event handlers', () => { + const handleChange = spy(); + const handleSubmit = spy(); + const Test = () => ( +
{ + if (!event.defaultPrevented && event.key === 'Enter') { + handleSubmit(); + } + }} + > + { + if (event.key === 'Enter') { + event.defaultMuiPrevented = true; + } + }} + /> +
+ ); + render(); + const textbox = screen.getByRole('combobox'); + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + fireEvent.keyDown(textbox, { key: 'ArrowDown' }); + fireEvent.keyDown(textbox, { key: 'Enter' }); + expect(handleChange.callCount).to.equal(0); + expect(handleSubmit.callCount).to.equal(1); + }); + + describe('prop: componentsProps', () => { + it('should apply the props on the AutocompleteClearIndicator component', () => { + render( + , + ); + + const clearIndicator = screen.getByTestId('clearIndicator'); + expect(clearIndicator).to.have.class('my-class'); + }); + + it('should apply the props on the AutocompletePopupIndicator component', () => { + render( + , + ); + + const popupIndicator = screen.getByTestId('popupIndicator'); + expect(popupIndicator).to.have.class('my-class'); + }); + + it('should keep AutocompletePopper mounted if keepMounted is true in popper props', () => { + // Autocomplete is not opened + render( + , + ); + + const popperRoot = screen.getByTestId('popperRoot'); + expect(popperRoot.style.display).to.equal('none'); + }); + }); + + describe('prop: readOnly', () => { + it('should make the input readonly', () => { + render(); + const input = screen.getByRole('combobox'); + expect(input).to.have.attribute('readonly'); + }); + + it('should not render the clear button', () => { + render(); + expect(screen.queryByTitle('Clear')).to.equal(null); + }); + + it('should not apply the hasClearIcon class', () => { + const { container } = render( + , + ); + expect(container.querySelector(`.${classes.root}`)).not.to.have.class(classes.hasClearIcon); + expect(container.querySelector(`.${classes.root}`)).to.have.class(classes.hasPopupIcon); + }); + + it('should focus on input when clicked', () => { + render(); + + const textbox = screen.getByRole('combobox'); + fireEvent.click(textbox); + expect(textbox).toHaveFocus(); + + act(() => { + textbox.blur(); + }); + fireEvent.click(screen.queryByTitle('Open')!); + + expect(textbox).toHaveFocus(); + }); + + it('should not open the popup', () => { + render(); + const textbox = screen.getByRole('combobox'); + fireEvent.mouseDown(textbox); + expect(screen.queryByRole('listbox')).to.equal(null); + }); + + it('should not be able to delete the tag when multiple=true', () => { + const { container } = render( + , + ); + + const textbox = screen.getByRole('combobox'); + act(() => { + textbox.focus(); + }); + expect(container.querySelectorAll(`.${chipClasses.root}`)).to.have.length(2); + fireEvent.keyDown(textbox, { key: 'Backspace' }); + expect(container.querySelectorAll(`.${chipClasses.root}`)).to.have.length(2); + }); + }); +}); diff --git a/packages/mui-joy/src/Autocomplete/Autocomplete.tsx b/packages/mui-joy/src/Autocomplete/Autocomplete.tsx new file mode 100644 index 00000000000000..8b99c069b621e6 --- /dev/null +++ b/packages/mui-joy/src/Autocomplete/Autocomplete.tsx @@ -0,0 +1,1078 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { OverridableComponent } from '@mui/types'; +import { + chainPropTypes, + integerPropType, + unstable_useForkRef as useForkRef, + unstable_capitalize as capitalize, +} from '@mui/utils'; +import composeClasses from '@mui/base/composeClasses'; +import { + useAutocomplete, + AutocompleteGroupedOption, + UseAutocompleteProps, +} from '@mui/base/AutocompleteUnstyled'; +import PopperUnstyled, { PopperUnstyledTypeMap } from '@mui/base/PopperUnstyled'; +import { useThemeProps } from '../styles'; +import ClearIcon from '../internal/svg-icons/Close'; +import ArrowDropDownIcon from '../internal/svg-icons/ArrowDropDown'; +import styled from '../styles/styled'; +// slot components +import { StyledIconButton } from '../IconButton/IconButton'; +// default render components +import Chip, { chipClasses } from '../Chip'; +import ChipDelete from '../ChipDelete'; +import { IconButtonOwnerState } from '../IconButton'; +import { + StyledInputRoot, + StyledInputHtml, + StyledInputStartDecorator, + StyledInputEndDecorator, +} from '../Input/Input'; +import List from '../List'; +import ListProvider from '../List/ListProvider'; +import ListSubheader from '../ListSubheader'; +import ListItem from '../ListItem'; +import autocompleteClasses, { getAutocompleteUtilityClass } from './autocompleteClasses'; +import { + AutocompleteProps, + AutocompleteRenderGroupParams, + AutocompleteRenderGetTagProps, + AutocompleteOwnerState, +} from './AutocompleteProps'; +import FormControlContext from '../FormControl/FormControlContext'; +import { StyledAutocompleteListbox } from '../AutocompleteListbox/AutocompleteListbox'; +import { StyledAutocompleteOption } from '../AutocompleteOption/AutocompleteOption'; +import useSlot from '../utils/useSlot'; + +type OwnerState = Omit, 'onChange' | 'defaultValue'>; + +const defaultIsActiveElementInListbox = (listboxRef: React.RefObject) => + listboxRef.current !== null && listboxRef.current.contains(document.activeElement); +const defaultGetOptionLabel = (option: T) => + (option as { label: string }).label ?? option; +const defaultLimitTagsText = (more: string | number) => `+${more}`; +const defaultRenderGroup = (params: AutocompleteRenderGroupParams) => ( + + {params.group} + {params.children} + +); + +const defaultModifiers = [ + { + name: 'offset', + options: { + offset: [0, 4], + }, + }, +]; + +const defaultVariantMapping = { + plain: 'plain', + outlined: 'plain', + soft: 'soft', + solid: 'solid', +}; + +const useUtilityClasses = (ownerState: OwnerState) => { + const { focused, hasClearIcon, hasPopupIcon, popupOpen, variant, color, size, multiple } = + ownerState; + + const slots = { + root: [ + 'root', + focused && 'focused', + hasClearIcon && 'hasClearIcon', + hasPopupIcon && 'hasPopupIcon', + variant && `variant${capitalize(variant)}`, + color && `color${capitalize(color)}`, + size && `size${capitalize(size)}`, + ], + wrapper: ['wrapper', multiple && 'multiple'], + input: ['input'], + startDecorator: ['startDecorator'], + endDecorator: ['endDecorator'], + clearIndicator: ['clearIndicator'], + popupIndicator: ['popupIndicator', popupOpen && 'popupIndicatorOpen'], + listbox: ['listbox'], + option: ['option'], + loading: ['loading'], + noOptions: ['noOptions'], + limitTag: ['limitTag'], + }; + + return composeClasses(slots, getAutocompleteUtilityClass, {}); +}; + +const AutocompleteRoot = styled(StyledInputRoot, { + name: 'JoyAutocomplete', + slot: 'Root', + overridesResolver: (props, styles) => styles.root, +})<{ ownerState: OwnerState }>(({ ownerState }) => ({ + '--Autocomplete-wrapper-gap': '3px', + ...(ownerState.size === 'lg' && { + '--Autocomplete-wrapper-gap': '4px', + }), + /* Avoid double tap issue on iOS */ + '@media (pointer: fine)': { + [`&:hover .${autocompleteClasses.clearIndicator}`]: { + visibility: 'visible', + }, + }, + ...(ownerState.multiple && + !ownerState.startDecorator && { + paddingInlineStart: 0, + }), +})); + +/** + * Wrapper groups the chips (multi selection) and the input + * so that start/end decorators can stay in the normal flow. + */ +const AutocompleteWrapper = styled('div', { + name: 'JoyAutocomplete', + slot: 'Wrapper', + overridesResolver: (props, styles) => styles.wrapper, +})<{ ownerState: OwnerState }>(({ ownerState }) => ({ + flex: 1, // stretch to fill the root slot + minWidth: 0, // won't push end decorator out of the autocomplete + display: 'flex', + alignItems: 'center', + flexWrap: 'wrap', + [`&.${autocompleteClasses.multiple}`]: { + paddingInlineStart: 0, + paddingBlockStart: + 'calc(var(--_Input-paddingBlock) - max(var(--Autocomplete-wrapper-gap), 0px))', + paddingBlockEnd: 'var(--_Input-paddingBlock)', + // TODO: use [CSS :has](https://caniuse.com/?search=%3Ahas) later + ...(ownerState.startDecorator && + Array.isArray(ownerState.value) && + (ownerState.value as Array).length > 0 && { + marginBlockStart: 'min(var(--_Input-paddingBlock) - var(--Autocomplete-wrapper-gap), 0px)', + marginInlineStart: + 'calc(-1 * min(var(--Autocomplete-wrapper-gap), var(--_Input-paddingBlock)))', + [`& .${autocompleteClasses.input}`]: { + marginInlineStart: 'var(--Input-gap)', + }, + }), + }, + [`& .${chipClasses.root}`]: { + // TODO: use flexbox `gap` later. + minWidth: 0, + marginInlineStart: 'var(--Autocomplete-wrapper-gap)', + marginBlockStart: 'var(--Autocomplete-wrapper-gap)', + }, +})); + +const AutocompleteInput = styled(StyledInputHtml, { + name: 'JoyAutocomplete', + slot: 'Input', + overridesResolver: (props, styles) => styles.input, +})<{ ownerState: OwnerState }>(({ ownerState }) => ({ + minWidth: 30, + minHeight: 'var(--Chip-minHeight)', + ...(ownerState.multiple && { + marginBlockStart: 'var(--Autocomplete-wrapper-gap)', + ...(!ownerState.startDecorator && { + marginInlineStart: 'var(--Input-paddingInline)', + }), + }), +})); + +const AutocompleteStartDecorator = styled(StyledInputStartDecorator, { + name: 'JoyAutocomplete', + slot: 'StartDecorator', + overridesResolver: (props, styles) => styles.startDecorator, +})<{ ownerState: OwnerState }>({}); + +const AutocompleteEndDecorator = styled(StyledInputEndDecorator, { + name: 'JoyAutocomplete', + slot: 'EndDecorator', + overridesResolver: (props, styles) => styles.endDecorator, +})<{ ownerState: OwnerState }>(({ ownerState }) => ({ + // don't adjust if end decorator is not the last of the autocomplete + ...((ownerState.hasClearIcon || ownerState.hasPopupIcon) && { + '--Button-margin': '0px', + '--IconButton-margin': '0px', + '--Icon-margin': '0px', + }), +})); + +const AutocompleteClearIndicator = styled(StyledIconButton, { + name: 'JoyAutocomplete', + slot: 'ClearIndicator', + overridesResolver: (props, styles) => styles.clearIndicator, +})<{ ownerState: OwnerState & IconButtonOwnerState }>(({ ownerState }) => ({ + ...(!ownerState.hasPopupIcon && { + marginInlineEnd: 'calc(var(--Input-decorator-childOffset) * -1)', + }), + marginInlineStart: 'calc(var(--_Input-paddingBlock) / 2)', + visibility: ownerState.focused ? 'visible' : 'hidden', +})); + +const AutocompletePopupIndicator = styled(StyledIconButton, { + name: 'JoyAutocomplete', + slot: 'PopupIndicator', + overridesResolver: (props, styles) => styles.popupIndicator, +})<{ ownerState: OwnerState & IconButtonOwnerState }>(({ ownerState }) => ({ + marginInlineStart: 'calc(var(--_Input-paddingBlock) / 2)', + marginInlineEnd: 'calc(var(--Input-decorator-childOffset) * -1)', + ...(ownerState.popupOpen && { + transform: 'rotate(180deg)', + }), +})); + +const AutocompleteListbox = styled(StyledAutocompleteListbox, { + name: 'JoyAutocomplete', + slot: 'Listbox', + overridesResolver: (props, styles) => styles.listbox, +})<{ ownerState: OwnerState }>({}); + +const AutocompleteOption = styled(StyledAutocompleteOption, { + name: 'JoyAutocomplete', + slot: 'Option', + overridesResolver: (props, styles) => styles.option, +})<{ ownerState: OwnerState }>({}); + +const AutocompleteLoading = styled(ListItem, { + name: 'JoyAutocomplete', + slot: 'Loading', + overridesResolver: (props, styles) => styles.loading, +})<{ ownerState: OwnerState }>(({ theme }) => ({ + color: (theme.vars || theme).palette.text.secondary, +})); + +const AutocompleteNoOptions = styled(ListItem, { + name: 'JoyAutocomplete', + slot: 'NoOptions', + overridesResolver: (props, styles) => styles.noOptions, +})<{ ownerState: OwnerState }>(({ theme }) => ({ + color: (theme.vars || theme).palette.text.secondary, +})); + +const AutocompleteLimitTag = styled('span', { + name: 'JoyAutocomplete', + slot: 'NoOptions', + overridesResolver: (props, styles) => styles.noOptions, +})<{ ownerState: OwnerState }>({ + marginInlineStart: 'calc(var(--Input-paddingInline) / 2)', + marginBlockStart: 'var(--_Input-paddingBlock)', +}); + +const excludeUseAutocompleteParams = < + T extends Partial>, +>({ + autoComplete, + autoHighlight, + autoSelect, + blurOnSelect, + clearOnBlur, + clearOnEscape, + defaultValue, + disableCloseOnSelect, + disabledItemsFocusable, + disableListWrap, + filterSelectedOptions, + handleHomeEndKeys, + includeInputInList, + openOnFocus, + selectOnFocus, + ...other +}: T) => other as T; + +const Autocomplete = React.forwardRef(function Autocomplete( + inProps, + ref: React.ForwardedRef, +) { + const props = useThemeProps({ + props: inProps, + name: 'JoyAutocomplete', + }); + + const { + 'aria-describedby': ariaDescribedby, + 'aria-label': ariaLabel, + 'aria-labelledby': ariaLabelledby, + autoFocus, + clearIcon = , + clearText = 'Clear', + closeText = 'Close', + disableClearable = false, + disabled: disabledProp, + endDecorator, + error: errorProp = false, + filterOptions, + forcePopupIcon = 'auto', + freeSolo = false, + getLimitTagsText = defaultLimitTagsText, + getOptionDisabled, + getOptionLabel = defaultGetOptionLabel, + isOptionEqualToValue, + groupBy, + id, + inputValue: inputValueProp, + limitTags = -1, + loading = false, + loadingText = 'Loading…', + multiple = false, + name, + noOptionsText = 'No options', + onChange, + onClose, + onHighlightChange, + onInputChange, + onOpen, + open, + openText = 'Open', + options, + placeholder, + popupIcon = , + readOnly = false, + renderGroup = defaultRenderGroup, + renderOption: renderOptionProp, + renderTags, + required, + type, + startDecorator, + size: sizeProp = 'md', + color: colorProp = 'neutral', + variant = 'outlined', + value: valueProp, + ...otherProps + } = props; + const other = excludeUseAutocompleteParams(otherProps); + + const formControl = React.useContext(FormControlContext); + const error = inProps.error ?? formControl?.error ?? errorProp; + const size = inProps.size ?? formControl?.size ?? sizeProp; + const color = error ? 'danger' : inProps.color ?? formControl?.color ?? colorProp; + const disabled = disabledProp ?? formControl?.disabled ?? false; + + const { + getRootProps, + getInputProps, + getPopupIndicatorProps, + getClearProps, + getTagProps, + getListboxProps, + getOptionProps, + value, + dirty, + popupOpen, + focused, + focusedTag, + anchorEl, + setAnchorEl, + inputValue, + groupedOptions, + } = useAutocomplete({ + ...props, + id: id ?? formControl?.htmlFor, + componentName: 'Autocomplete', + unstable_classNamePrefix: 'Joy', + unstable_isActiveElementInListbox: defaultIsActiveElementInListbox, + }); + + const hasClearIcon = !disableClearable && !disabled && dirty && !readOnly; + const hasPopupIcon = (!freeSolo || forcePopupIcon === true) && forcePopupIcon !== false; + + // If you modify this, make sure to keep the `AutocompleteOwnerState` type in sync. + const ownerState = { + ...props, + value, + focused, + hasOptions: !!groupedOptions.length, + hasClearIcon, + hasPopupIcon, + inputFocused: focusedTag === -1, + popupOpen, + size, + color, + variant, + } as OwnerState; + + const classes = useUtilityClasses(ownerState); + + let selectedOptions; + + if (multiple && (value as Array).length > 0) { + const getCustomizedTagProps: AutocompleteRenderGetTagProps = (params) => { + const { onDelete, ...tagProps } = getTagProps(params); + return { + disabled, + size, + onClick: onDelete, + ...tagProps, + }; + }; + + if (renderTags) { + selectedOptions = renderTags(value as Array, getCustomizedTagProps, ownerState); + } else { + selectedOptions = (value as Array).map((option, index) => { + return ( + } + > + {getOptionLabel(option)} + + ); + }); + } + } + + const rootRef = useForkRef(ref, setAnchorEl); + + const rootStateClasses = { + [autocompleteClasses.disabled]: disabled, + [autocompleteClasses.error]: error, + [autocompleteClasses.focused]: focused, + [autocompleteClasses.formControl]: Boolean(formControl), + }; + + const [SlotRoot, rootProps] = useSlot('root', { + ref: rootRef, + className: [classes.root, rootStateClasses], + elementType: AutocompleteRoot, + externalForwardedProps: other, + ownerState, + getSlotProps: getRootProps, + }); + + const [SlotWrapper, wrapperProps] = useSlot('wrapper', { + className: classes.wrapper, + elementType: AutocompleteWrapper, + externalForwardedProps: other, + ownerState, + }); + + const inputStateClasses = { + [autocompleteClasses.disabled]: disabled, + }; + + const [SlotInput, inputProps] = useSlot('input', { + className: [classes.input, inputStateClasses], + elementType: AutocompleteInput, + getSlotProps: (handlers) => { + const { onBlur, onFocus, onMouseDown, ...inputSlotProps } = getInputProps(); + return { + ...inputSlotProps, + onBlur: (event: React.FocusEvent) => { + onBlur?.(event); + handlers.onBlur?.(event); + }, + onFocus: (event: React.FocusEvent) => { + onFocus?.(event); + handlers.onFocus?.(event); + }, + onMouseDown: (event: React.MouseEvent) => { + onMouseDown?.(event); + handlers.onMouseDown?.(event); + }, + }; + }, + externalForwardedProps: other, + ownerState, + additionalProps: { + autoFocus, + placeholder, + name, + readOnly, + disabled, + required, + type, + 'aria-invalid': error || undefined, + 'aria-label': ariaLabel, + 'aria-labelledby': ariaLabelledby, + 'aria-describedby': ariaDescribedby ?? formControl?.['aria-describedby'], + }, + }); + + const [SlotStartDecorator, startDecoratorProps] = useSlot('startDecorator', { + className: classes.startDecorator, + elementType: AutocompleteStartDecorator, + externalForwardedProps: other, + ownerState, + }); + + const [SlotEndDecorator, endDecoratorProps] = useSlot('endDecorator', { + className: classes.endDecorator, + elementType: AutocompleteEndDecorator, + externalForwardedProps: other, + ownerState, + }); + + const [SlotClearIndicator, clearIndicatorProps] = useSlot('clearIndicator', { + className: classes.clearIndicator, + elementType: AutocompleteClearIndicator, + getSlotProps: getClearProps, + externalForwardedProps: other, + ownerState, + getSlotOwnerState: (mergedProps) => ({ + size: mergedProps.size || size, + variant: mergedProps.variant || defaultVariantMapping[variant] || 'plain', + color: mergedProps.color || 'neutral', + }), + additionalProps: { + 'aria-label': clearText, + title: clearText, + }, + }); + + const [SlotPopupIndicator, popupIndicatorProps] = useSlot('popupIndicator', { + className: classes.popupIndicator, + elementType: AutocompletePopupIndicator, + getSlotProps: getPopupIndicatorProps, + externalForwardedProps: other, + ownerState, + getSlotOwnerState: (mergedProps) => ({ + size: mergedProps.size || size, + variant: mergedProps.variant || defaultVariantMapping[variant] || 'plain', + color: mergedProps.color || 'neutral', + }), + additionalProps: { + disabled, + 'aria-label': popupOpen ? closeText : openText, + title: popupOpen ? closeText : openText, + }, + }); + + const [SlotListbox, listboxProps] = useSlot('listbox', { + className: classes.listbox, + elementType: PopperUnstyled as OverridableComponent>, + getSlotProps: getListboxProps, + externalForwardedProps: other, + ownerState, + getSlotOwnerState: (mergedProps) => ({ + size: mergedProps.size || size, + variant: mergedProps.variant || 'outlined', + color: mergedProps.variant || 'neutral', + }), + additionalProps: { + anchorEl, + open: popupOpen, + modifiers: defaultModifiers, + style: anchorEl + ? { + width: anchorEl.clientWidth, + } + : {}, + }, + internalForwardedProps: { + component: AutocompleteListbox, + }, + }); + + const [SlotLoading, loadingProps] = useSlot('loading', { + className: classes.loading, + elementType: AutocompleteLoading, + externalForwardedProps: other, + ownerState, + }); + + const [SlotNoOptions, noOptionsProps] = useSlot('noOptions', { + className: classes.noOptions, + elementType: AutocompleteNoOptions, + externalForwardedProps: other, + ownerState, + additionalProps: { + role: 'presentation', + onMouseDown: (event: React.MouseEvent) => { + // Prevent input blur when interacting with the "no options" content + event.preventDefault(); + }, + }, + }); + + const [SlotLimitTag, limitTagProps] = useSlot('limitTag', { + className: classes.limitTag, + elementType: AutocompleteLimitTag, + externalForwardedProps: other, + ownerState, + }); + + if (limitTags > -1 && Array.isArray(selectedOptions)) { + const more = selectedOptions.length - limitTags; + if (!focused && more > 0) { + selectedOptions = selectedOptions.splice(0, limitTags); + selectedOptions.push( + + {getLimitTagsText(more)} + , + ); + } + } + + const [SlotOption, baseOptionProps] = useSlot('option', { + className: classes.option, + elementType: AutocompleteOption, + externalForwardedProps: other, + ownerState, + getSlotOwnerState: (mergedProps) => ({ + variant: mergedProps.variant || 'plain', + color: mergedProps.color || 'neutral', + }), + additionalProps: { + as: 'li', + }, + }); + + const defaultRenderOption = (optionProps: any, option: unknown) => ( + {getOptionLabel(option)} + ); + + const renderOption = renderOptionProp || defaultRenderOption; + + const renderListOption = (option: unknown, index: number) => { + const optionProps = getOptionProps({ option, index }); + + return renderOption({ ...baseOptionProps, ...optionProps }, option, { + // `aria-selected` prop will always by boolean, see useAutocomplete hook. + selected: !!optionProps['aria-selected'], + inputValue, + ownerState, + }); + }; + + return ( + + + {startDecorator && ( + {startDecorator} + )} + + + {selectedOptions} + + + {endDecorator && {endDecorator}} + {hasClearIcon ? ( + {clearIcon} + ) : null} + {hasPopupIcon ? ( + {popupIcon} + ) : null} + + {anchorEl ? ( + // `nested` is for grouped options use case. + + + {groupedOptions.map((option, index) => { + if (groupBy) { + const typedOption = option as AutocompleteGroupedOption; + return renderGroup({ + key: String(typedOption.key), + group: typedOption.group, + children: typedOption.options.map((option2, index2) => + renderListOption(option2, typedOption.index + index2), + ), + }); + } + return renderListOption(option, index); + })} + {loading && groupedOptions.length === 0 ? ( + {loadingText} + ) : null} + {groupedOptions.length === 0 && !freeSolo && !loading ? ( + {noOptionsText} + ) : null} + + + ) : null} + + ); +}) as AutocompleteComponent; + +interface AutocompleteComponent { + < + T, + Multiple extends boolean | undefined = undefined, + DisableClearable extends boolean | undefined = undefined, + FreeSolo extends boolean | undefined = undefined, + >( + props: AutocompleteProps, + ): JSX.Element; + propTypes?: any; +} + +Autocomplete.propTypes /* remove-proptypes */ = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit TypeScript types and run "yarn proptypes" | + // ---------------------------------------------------------------------- + /** + * Identifies the element (or elements) that describes the object. + * @see aria-labelledby + */ + 'aria-describedby': PropTypes.string, + /** + * Defines a string value that labels the current element. + * @see aria-labelledby. + */ + 'aria-label': PropTypes.string, + /** + * Identifies the element (or elements) that labels the current element. + * @see aria-describedby. + */ + 'aria-labelledby': PropTypes.string, + /** + * If `true`, the `input` element is focused during the first mount. + */ + autoFocus: PropTypes.bool, + /** + * The icon to display in place of the default clear icon. + * @default + */ + clearIcon: PropTypes.node, + /** + * Override the default text for the *clear* icon button. + * + * For localization purposes, you can use the provided [translations](/material-ui/guides/localization/). + * @default 'Clear' + */ + clearText: PropTypes.string, + /** + * Override the default text for the *close popup* icon button. + * + * For localization purposes, you can use the provided [translations](/material-ui/guides/localization/). + * @default 'Close' + */ + closeText: PropTypes.string, + /** + * The color of the component. It supports those theme colors that make sense for this component. + * @default 'neutral' + */ + color: PropTypes.oneOf(['danger', 'info', 'neutral', 'primary', 'success', 'warning']), + /** + * Replace the default slots. + */ + components: PropTypes.shape({ + clearIndicator: PropTypes.elementType, + endDecorator: PropTypes.elementType, + input: PropTypes.elementType, + limitTag: PropTypes.elementType, + listbox: PropTypes.elementType, + loading: PropTypes.elementType, + noOptions: PropTypes.elementType, + option: PropTypes.elementType, + popupIndicator: PropTypes.elementType, + root: PropTypes.elementType, + startDecorator: PropTypes.elementType, + wrapper: PropTypes.elementType, + }), + /** + * The props used for each slot inside. + * @default {} + */ + componentsProps: PropTypes.shape({ + clearIndicator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + endDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + input: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + limitTag: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + listbox: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + loading: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + noOptions: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + option: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + popupIndicator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + startDecorator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + wrapper: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * The default value. Use when the component is not controlled. + * @default props.multiple ? [] : null + */ + defaultValue: chainPropTypes(PropTypes.any, (props) => { + if (props.multiple && props.defaultValue !== undefined && !Array.isArray(props.defaultValue)) { + return new Error( + [ + 'MUI: The Autocomplete expects the `defaultValue` prop to be an array when `multiple={true}` or undefined.', + `However, ${props.defaultValue} was provided.`, + ].join('\n'), + ); + } + return null; + }), + /** + * If `true`, the input can't be cleared. + * @default false + */ + disableClearable: PropTypes.bool, + /** + * If `true`, the component is disabled. + * @default false + */ + disabled: PropTypes.bool, + /** + * Trailing adornment for this input. + */ + endDecorator: PropTypes.node, + /** + * If `true`, the `input` will indicate an error. + * The prop defaults to the value (`false`) inherited from the parent FormControl component. + */ + error: PropTypes.bool, + /** + * A function that determines the filtered options to be rendered on search. + * + * @param {T[]} options The options to render. + * @param {object} state The state of the component. + * @returns {T[]} + */ + filterOptions: PropTypes.func, + /** + * Force the visibility display of the popup icon. + * @default 'auto' + */ + forcePopupIcon: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.bool]), + /** + * If `true`, the Autocomplete is free solo, meaning that the user input is not bound to provided options. + * @default false + */ + freeSolo: PropTypes.bool, + /** + * The label to display when the tags are truncated (`limitTags`). + * + * @param {number} more The number of truncated tags. + * @returns {ReactNode} + * @default (more) => `+${more}` + */ + getLimitTagsText: PropTypes.func, + /** + * Used to determine the disabled state for a given option. + * + * @param {T} option The option to test. + * @returns {boolean} + */ + getOptionDisabled: PropTypes.func, + /** + * 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). + * + * If used in free solo mode, it must accept both the type of the options and a string. + * + * @param {T} option + * @returns {string} + * @default (option) => option.label ?? option + */ + getOptionLabel: PropTypes.func, + /** + * If provided, the options will be grouped under the returned string. + * The groupBy value is also used as the text for group headings when `renderGroup` is not provided. + * + * @param {T} options The options to group. + * @returns {string} + */ + groupBy: PropTypes.func, + /** + * This prop is used to help implement the accessibility logic. + * If you don't provide an id it will fall back to a randomly generated one. + */ + id: PropTypes.string, + /** + * The input value. + */ + inputValue: PropTypes.string, + /** + * Used to determine if the option represents the given value. + * Uses strict equality by default. + * ⚠️ Both arguments need to be handled, an option can only match with one value. + * + * @param {T} option The option to test. + * @param {T} value The value to test against. + * @returns {boolean} + */ + isOptionEqualToValue: PropTypes.func, + /** + * The maximum number of tags that will be visible when not focused. + * Set `-1` to disable the limit. + * @default -1 + */ + limitTags: integerPropType, + /** + * If `true`, the component is in a loading state. + * This shows the `loadingText` in place of suggestions (only if there are no suggestions to show, e.g. `options` are empty). + * @default false + */ + loading: PropTypes.bool, + /** + * Text to display when in a loading state. + * + * For localization purposes, you can use the provided [translations](/material-ui/guides/localization/). + * @default 'Loading…' + */ + loadingText: PropTypes.node, + /** + * If `true`, `value` must be an array and the menu will support multiple selections. + * @default false + */ + multiple: PropTypes.bool, + /** + * Name attribute of the `input` element. + */ + name: PropTypes.string, + /** + * Text to display when there are no options. + * + * For localization purposes, you can use the provided [translations](/material-ui/guides/localization/). + * @default 'No options' + */ + noOptionsText: PropTypes.node, + /** + * Callback fired when the value changes. + * + * @param {React.SyntheticEvent} event The event source of the callback. + * @param {T|T[]} value The new value of the component. + * @param {string} reason One of "createOption", "selectOption", "removeOption", "blur" or "clear". + * @param {string} [details] + */ + onChange: PropTypes.func, + /** + * Callback fired when the popup requests to be closed. + * Use in controlled mode (see open). + * + * @param {React.SyntheticEvent} event The event source of the callback. + * @param {string} reason Can be: `"toggleInput"`, `"escape"`, `"selectOption"`, `"removeOption"`, `"blur"`. + */ + onClose: PropTypes.func, + /** + * Callback fired when the highlight option changes. + * + * @param {React.SyntheticEvent} event The event source of the callback. + * @param {T} option The highlighted option. + * @param {string} reason Can be: `"keyboard"`, `"auto"`, `"mouse"`. + */ + onHighlightChange: PropTypes.func, + /** + * Callback fired when the input value changes. + * + * @param {React.SyntheticEvent} event The event source of the callback. + * @param {string} value The new value of the text input. + * @param {string} reason Can be: `"input"` (user input), `"reset"` (programmatic change), `"clear"`. + */ + onInputChange: PropTypes.func, + /** + * @ignore + */ + onKeyDown: PropTypes.func, + /** + * Callback fired when the popup requests to be opened. + * Use in controlled mode (see open). + * + * @param {React.SyntheticEvent} event The event source of the callback. + */ + onOpen: PropTypes.func, + /** + * If `true`, the component is shown. + */ + open: PropTypes.bool, + /** + * Override the default text for the *open popup* icon button. + * + * For localization purposes, you can use the provided [translations](/material-ui/guides/localization/). + * @default 'Open' + */ + openText: PropTypes.string, + /** + * Array of options. + */ + options: PropTypes.array.isRequired, + /** + * The input placeholder + */ + placeholder: PropTypes.string, + /** + * The icon to display in place of the default popup icon. + * @default + */ + popupIcon: PropTypes.node, + /** + * If `true`, the component becomes read-only. It is also supported in multiple tags where the tag cannot be deleted. + * @default false + */ + readOnly: PropTypes.bool, + /** + * Render the group. + * + * @param {AutocompleteRenderGroupParams} params The group to render. + * @returns {ReactNode} + */ + renderGroup: PropTypes.func, + /** + * Render the option, use `getOptionLabel` by default. + * + * @param {object} props The props to apply on the li element. + * @param {T} option The option to render. + * @param {object} state The state of the component. + * @returns {ReactNode} + */ + renderOption: PropTypes.func, + /** + * Render the selected value. + * + * @param {T[]} value The `value` provided to the component. + * @param {function} getTagProps A tag props getter. + * @param {object} ownerState The state of the Autocomplete component. + * @returns {ReactNode} + */ + renderTags: PropTypes.func, + /** + * If `true`, the `input` element is required. + * The prop defaults to the value (`false`) inherited from the parent FormControl component. + */ + required: PropTypes.bool, + /** + * The size of the component. + * @default 'md' + */ + size: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([ + PropTypes.oneOf(['sm', 'md', 'lg']), + PropTypes.string, + ]), + /** + * Leading adornment for this input. + */ + startDecorator: PropTypes.node, + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), + /** + * Type of the `input` element. It should be [a valid HTML5 input type](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Form_%3Cinput%3E_types). + */ + type: PropTypes.string, + /** + * The value of the autocomplete. + * + * The value must have reference equality with the option in order to be selected. + * You can customize the equality behavior with the `isOptionEqualToValue` prop. + */ + value: chainPropTypes(PropTypes.any, (props) => { + if (props.multiple && props.value !== undefined && !Array.isArray(props.value)) { + return new Error( + [ + 'MUI: The Autocomplete expects the `value` prop to be an array when `multiple={true}` or undefined.', + `However, ${props.value} was provided.`, + ].join('\n'), + ); + } + return null; + }), + /** + * The variant to use. + * @default 'outlined' + */ + variant: PropTypes.oneOf(['outlined', 'plain', 'soft', 'solid']), +} as any; + +export default Autocomplete; diff --git a/packages/mui-joy/src/Autocomplete/AutocompleteProps.ts b/packages/mui-joy/src/Autocomplete/AutocompleteProps.ts new file mode 100644 index 00000000000000..b8f358cd23276f --- /dev/null +++ b/packages/mui-joy/src/Autocomplete/AutocompleteProps.ts @@ -0,0 +1,367 @@ +import * as React from 'react'; +import { OverridableStringUnion } from '@mui/types'; +import { SlotComponentProps } from '@mui/base/utils'; +import { + useAutocomplete, + AutocompleteValue, + AutocompleteChangeDetails, + AutocompleteChangeReason, + AutocompleteCloseReason, + AutocompleteInputChangeReason, + UseAutocompleteProps, +} from '@mui/base/AutocompleteUnstyled'; +import { PopperUnstyledOwnProps } from '@mui/base/PopperUnstyled'; +import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types'; +import { IconButtonOwnerState } from '../IconButton/IconButtonProps'; +import { AutocompleteListboxProps } from '../AutocompleteListbox/AutocompleteListboxProps'; +import { AutocompleteOptionProps } from '../AutocompleteOption/AutocompleteOptionProps'; + +export type AutocompleteSlot = keyof ComponentsProps; + +export interface AutocompletePropsVariantOverrides {} + +export interface AutocompletePropsColorOverrides {} + +export interface AutocompletePropsSizeOverrides {} + +export type { + AutocompleteChangeDetails, + AutocompleteChangeReason, + AutocompleteCloseReason, + AutocompleteInputChangeReason, +}; + +export type AutocompleteRenderGetTagProps = ({ index }: { index: number }) => { + key: number; + disabled: boolean; + 'data-tag-index': number; + tabIndex: -1; + onClick: React.MouseEventHandler; +}; + +export interface AutocompleteRenderOptionState { + inputValue: string; + selected: boolean; + ownerState: AutocompleteOwnerState; +} + +export interface AutocompleteRenderGroupParams { + key: string; + group: string; + children?: React.ReactNode; +} + +export interface AutocompleteRenderInputParams { + placeholder?: string; + disabled: boolean; + size: OverridableStringUnion<'sm' | 'md' | 'lg', AutocompletePropsSizeOverrides>; + ref: React.Ref; + startDecorator: React.ReactNode; + endDecorator?: React.ReactNode; + componentsProps: { + input: ReturnType['getInputProps']>; + }; +} + +interface ComponentsProps { + root?: SlotComponentProps< + 'div', + { component?: React.ElementType; sx?: SxProps }, + AutocompleteOwnerState + >; + wrapper?: SlotComponentProps< + 'div', + { component?: React.ElementType; sx?: SxProps }, + AutocompleteOwnerState + >; + input?: SlotComponentProps< + 'input', + { component?: React.ElementType; sx?: SxProps }, + AutocompleteOwnerState + >; + startDecorator?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + AutocompleteOwnerState + >; + endDecorator?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + AutocompleteOwnerState + >; + clearIndicator?: SlotComponentProps< + 'button', + { component?: React.ElementType; sx?: SxProps } & Pick< + IconButtonOwnerState, + 'color' | 'variant' | 'size' + >, + AutocompleteOwnerState & IconButtonOwnerState + >; + popupIndicator?: SlotComponentProps< + 'button', + { component?: React.ElementType; sx?: SxProps } & Pick< + IconButtonOwnerState, + 'color' | 'variant' | 'size' + >, + AutocompleteOwnerState & IconButtonOwnerState + >; + listbox?: SlotComponentProps< + 'ul', + { + component?: React.ElementType; + sx?: SxProps; + } & Omit & + Pick, + AutocompleteOwnerState + >; + option?: SlotComponentProps< + 'li', + { + component?: React.ElementType; + sx?: SxProps; + } & Pick, + AutocompleteOwnerState + >; + loading?: SlotComponentProps< + 'div', + { component?: React.ElementType; sx?: SxProps }, + AutocompleteOwnerState + >; + noOptions?: SlotComponentProps< + 'li', + { component?: React.ElementType; sx?: SxProps }, + AutocompleteOwnerState + >; + limitTag?: SlotComponentProps< + 'span', + { component?: React.ElementType; sx?: SxProps }, + AutocompleteOwnerState + >; +} + +interface AutocompleteOwnProps< + T, + Multiple extends boolean | undefined, + DisableClearable extends boolean | undefined, + FreeSolo extends boolean | undefined, +> extends UseAutocompleteProps { + /** + * If `true`, the `input` element is focused during the first mount. + */ + autoFocus?: boolean; + /** + * The icon to display in place of the default clear icon. + * @default + */ + clearIcon?: React.ReactNode; + /** + * Override the default text for the *clear* icon button. + * + * For localization purposes, you can use the provided [translations](/material-ui/guides/localization/). + * @default 'Clear' + */ + clearText?: string; + /** + * Override the default text for the *close popup* icon button. + * + * For localization purposes, you can use the provided [translations](/material-ui/guides/localization/). + * @default 'Close' + */ + closeText?: string; + /** + * The color of the component. It supports those theme colors that make sense for this component. + * @default 'neutral' + */ + color?: OverridableStringUnion; + /** + * Replace the default slots. + */ + components?: { + root?: React.ElementType; + wrapper?: React.ElementType; + input?: React.ElementType; + startDecorator?: React.ElementType; + endDecorator?: React.ElementType; + clearIndicator?: React.ElementType; + popupIndicator?: React.ElementType; + listbox?: React.ElementType; + option?: React.ElementType; + loading?: React.ElementType; + noOptions?: React.ElementType; + limitTag?: React.ElementType; + }; + /** + * The props used for each slot inside. + * @default {} + */ + componentsProps?: ComponentsProps; + /** + * The default value. Use when the component is not controlled. + * @default props.multiple ? [] : null + */ + defaultValue?: AutocompleteValue; + /** + * If `true`, the component is disabled. + * @default false + */ + disabled?: boolean; + /** + * If `true`, the `input` will indicate an error. + * The prop defaults to the value (`false`) inherited from the parent FormControl component. + */ + error?: boolean; + /** + * Trailing adornment for this input. + */ + endDecorator?: React.ReactNode; + /** + * Force the visibility display of the popup icon. + * @default 'auto' + */ + forcePopupIcon?: true | false | 'auto'; + /** + * The label to display when the tags are truncated (`limitTags`). + * + * @param {number} more The number of truncated tags. + * @returns {ReactNode} + * @default (more) => `+${more}` + */ + getLimitTagsText?: (more: number) => React.ReactNode; + /** + * If `true`, the component is in a loading state. + * This shows the `loadingText` in place of suggestions (only if there are no suggestions to show, e.g. `options` are empty). + * @default false + */ + loading?: boolean; + /** + * Text to display when in a loading state. + * + * For localization purposes, you can use the provided [translations](/material-ui/guides/localization/). + * @default 'Loading…' + */ + loadingText?: React.ReactNode; + /** + * The maximum number of tags that will be visible when not focused. + * Set `-1` to disable the limit. + * @default -1 + */ + limitTags?: number; + /** + * Name attribute of the `input` element. + */ + name?: string; + /** + * Text to display when there are no options. + * + * For localization purposes, you can use the provided [translations](/material-ui/guides/localization/). + * @default 'No options' + */ + noOptionsText?: React.ReactNode; + /** + * Override the default text for the *open popup* icon button. + * + * For localization purposes, you can use the provided [translations](/material-ui/guides/localization/). + * @default 'Open' + */ + openText?: string; + /** + * The input placeholder + */ + placeholder?: string; + /** + * The icon to display in place of the default popup icon. + * @default + */ + popupIcon?: React.ReactNode; + /** + * If `true`, the component becomes read-only. It is also supported in multiple tags where the tag cannot be deleted. + * @default false + */ + readOnly?: boolean; + /** + * Render the group. + * + * @param {AutocompleteRenderGroupParams} params The group to render. + * @returns {ReactNode} + */ + renderGroup?: (params: AutocompleteRenderGroupParams) => React.ReactNode; + /** + * Render the option, use `getOptionLabel` by default. + * + * @param {object} props The props to apply on the li element. + * @param {T} option The option to render. + * @param {object} state The state of the component. + * @returns {ReactNode} + */ + renderOption?: ( + props: Omit, 'color'>, + option: T, + state: AutocompleteRenderOptionState, + ) => React.ReactNode; + /** + * Render the selected value. + * + * @param {T[]} value The `value` provided to the component. + * @param {function} getTagProps A tag props getter. + * @param {object} ownerState The state of the Autocomplete component. + * @returns {ReactNode} + */ + renderTags?: ( + value: T[], + getTagProps: AutocompleteRenderGetTagProps, + ownerState: AutocompleteOwnerState, + ) => React.ReactNode; + /** + * If `true`, the `input` element is required. + * The prop defaults to the value (`false`) inherited from the parent FormControl component. + */ + required?: boolean; + /** + * The size of the component. + * @default 'md' + */ + size?: OverridableStringUnion<'sm' | 'md' | 'lg', AutocompletePropsSizeOverrides>; + /** + * Leading adornment for this input. + */ + startDecorator?: React.ReactNode; + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx?: SxProps; + /** + * The variant to use. + * @default 'outlined' + */ + variant?: OverridableStringUnion; +} + +export interface AutocompleteProps< + T, + Multiple extends boolean | undefined, + DisableClearable extends boolean | undefined, + FreeSolo extends boolean | undefined, +> extends AutocompleteOwnProps, + Omit, 'defaultValue' | 'onChange' | 'children' | 'color'> { + /** + * Type of the `input` element. It should be [a valid HTML5 input type](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Form_%3Cinput%3E_types). + */ + type?: string; + onKeyDown?: ( + event: React.KeyboardEvent & { defaultMuiPrevented?: boolean }, + ) => void; +} + +export interface AutocompleteOwnerState< + T, + Multiple extends boolean | undefined, + DisableClearable extends boolean | undefined, + FreeSolo extends boolean | undefined, +> extends AutocompleteOwnProps { + focused: boolean; + hasClearIcon: boolean; + hasPopupIcon: boolean; + hasOptions: boolean; + inputFocused: boolean; + popupOpen: boolean; +} diff --git a/packages/mui-joy/src/Autocomplete/autocompleteClasses.ts b/packages/mui-joy/src/Autocomplete/autocompleteClasses.ts new file mode 100644 index 00000000000000..e48385d4651176 --- /dev/null +++ b/packages/mui-joy/src/Autocomplete/autocompleteClasses.ts @@ -0,0 +1,114 @@ +import { generateUtilityClass, generateUtilityClasses } from '../className'; + +export interface AutocompleteClasses { + /** Styles applied to the root element. */ + root: string; + /** Styles applied to the wrapper element. */ + wrapper: string; + /** Styles applied to the input element. */ + input: string; + /** Styles applied to the startDecorator element. */ + startDecorator: string; + /** Styles applied to the endDecorator element. */ + endDecorator: string; + /** Styles applied to the root element if the component is a descendant of `FormControl`. */ + formControl: string; + /** Styles applied to the root element if the component is focused. */ + focused: string; + /** Styles applied to the root element if `disabled={true}`. */ + disabled: string; + /** State class applied to the root element if `error={true}`. */ + error: string; + /** Styles applied to the wrapper element if `multiple={true}`. */ + multiple: string; + /** Styles applied to the limitTag element. */ + limitTag: string; + /** Styles applied when the popup icon is rendered. */ + hasPopupIcon: string; + /** Styles applied when the clear icon is rendered. */ + hasClearIcon: string; + /** Styles applied to the clear indicator. */ + clearIndicator: string; + /** Styles applied to the popup indicator. */ + popupIndicator: string; + /** Styles applied to the popup indicator if the popup is open. */ + popupIndicatorOpen: string; + /** Styles applied to the listbox component. */ + listbox: string; + /** Styles applied to the option component. */ + option: string; + /** Styles applied to the loading wrapper. */ + loading: string; + /** Styles applied to the no option wrapper. */ + noOptions: string; + /** Styles applied to the root element if `color="primary"`. */ + colorPrimary: string; + /** Styles applied to the root element if `color="neutral"`. */ + colorNeutral: string; + /** Styles applied to the root element if `color="danger"`. */ + colorDanger: string; + /** Styles applied to the root element if `color="info"`. */ + colorInfo: string; + /** Styles applied to the root element if `color="success"`. */ + colorSuccess: string; + /** Styles applied to the root element if `color="warning"`. */ + colorWarning: string; + /** Styles applied to the root element if `size="sm"`. */ + sizeSm: string; + /** Styles applied to the root element if `size="md"`. */ + sizeMd: string; + /** Styles applied to the root element if `size="lg"`. */ + sizeLg: string; + /** Styles applied to the root element if `variant="plain"`. */ + variantPlain: string; + /** Styles applied to the root element if `variant="outlined"`. */ + variantOutlined: string; + /** Styles applied to the root element if `variant="soft"`. */ + variantSoft: string; + /** Styles applied to the root element if `variant="solid"`. */ + variantSolid: string; +} + +export type AutocompleteClassKey = keyof AutocompleteClasses; + +export function getAutocompleteUtilityClass(slot: string): string { + return generateUtilityClass('JoyAutocomplete', slot); +} + +const autocompleteClasses: AutocompleteClasses = generateUtilityClasses('JoyAutocomplete', [ + 'root', + 'wrapper', + 'input', + 'startDecorator', + 'endDecorator', + 'formControl', + 'focused', + 'disabled', + 'error', + 'multiple', + 'limitTag', + 'hasPopupIcon', + 'hasClearIcon', + 'clearIndicator', + 'popupIndicator', + 'popupIndicatorOpen', + 'listbox', + 'option', + 'loading', + 'noOptions', + 'colorPrimary', + 'colorNeutral', + 'colorDanger', + 'colorInfo', + 'colorSuccess', + 'colorWarning', + 'sizeSm', + 'sizeMd', + 'sizeLg', + 'variantPlain', + 'variantOutlined', + 'variantSoft', + 'variantSolid', +]); + +export default autocompleteClasses; diff --git a/packages/mui-joy/src/Autocomplete/index.ts b/packages/mui-joy/src/Autocomplete/index.ts new file mode 100644 index 00000000000000..33bf9686e822b5 --- /dev/null +++ b/packages/mui-joy/src/Autocomplete/index.ts @@ -0,0 +1,5 @@ +export { createFilterOptions } from '@mui/base/AutocompleteUnstyled'; +export { default } from './Autocomplete'; +export * from './autocompleteClasses'; +export { default as autocompleteClasses } from './autocompleteClasses'; +export * from './AutocompleteProps'; diff --git a/packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.test.tsx b/packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.test.tsx new file mode 100644 index 00000000000000..c1dce2700792d3 --- /dev/null +++ b/packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.test.tsx @@ -0,0 +1,54 @@ +import * as React from 'react'; +import { expect } from 'chai'; +import { describeConformance, createRenderer } from 'test/utils'; +import { ThemeProvider } from '@mui/joy/styles'; +import AutocompleteListbox, { + autocompleteListboxClasses as classes, +} from '@mui/joy/AutocompleteListbox'; + +describe('Joy ', () => { + const { render } = createRenderer(); + + describeConformance(, () => ({ + classes, + inheritComponent: 'ul', + render, + ThemeProvider, + muiName: 'JoyAutocompleteListbox', + refInstanceof: window.HTMLUListElement, + testVariantProps: { variant: 'solid' }, + testCustomVariant: true, + skip: ['componentsProp', 'classesRoot'], + })); + + it('should have ul tag', () => { + const { container } = render(); + expect(container.firstChild).to.have.tagName('ul'); + }); + + it('should have root className', () => { + const { container } = render(); + expect(container.firstChild).to.have.class(classes.root); + expect(container.firstChild).to.have.class(classes.sizeMd); + }); + + it('should accept className prop', () => { + const { container } = render(); + expect(container.firstChild).to.have.class('foo-bar'); + }); + + it('should have sm classes', () => { + const { container } = render(); + expect(container.firstChild).to.have.class(classes.sizeSm); + }); + + it('should render with the variant class', () => { + const { container } = render(); + expect(container.firstChild).to.have.class(classes.variantOutlined); + }); + + it('should render with primary color class', () => { + const { container } = render(); + expect(container.firstChild).to.have.class(classes.colorPrimary); + }); +}); diff --git a/packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.tsx b/packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.tsx new file mode 100644 index 00000000000000..810c38f70c6d59 --- /dev/null +++ b/packages/mui-joy/src/AutocompleteListbox/AutocompleteListbox.tsx @@ -0,0 +1,177 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { OverridableComponent } from '@mui/types'; +import { unstable_capitalize as capitalize } from '@mui/utils'; +import { useSlotProps } from '@mui/base/utils'; +import composeClasses from '@mui/base/composeClasses'; +import { StyledList } from '../List/List'; +import { styled, useThemeProps } from '../styles'; +import { getAutocompleteListboxUtilityClass } from './autocompleteListboxClasses'; +import { + AutocompleteListboxOwnerState, + AutocompleteListboxTypeMap, +} from './AutocompleteListboxProps'; +import listItemClasses from '../ListItem/listItemClasses'; +import listClasses from '../List/listClasses'; +import { scopedVariables } from '../List/ListProvider'; + +const useUtilityClasses = (ownerState: AutocompleteListboxOwnerState) => { + const { variant, color, size } = ownerState; + const slots = { + root: [ + 'root', + variant && `variant${capitalize(variant)}`, + color && `color${capitalize(color)}`, + size && `size${capitalize(size)}`, + ], + }; + + return composeClasses(slots, getAutocompleteListboxUtilityClass, {}); +}; + +const excludePopperProps = >({ + anchorEl, + direction, + disablePortal, + modifiers, + open, + placement, + popperOptions, + popperRef, + TransitionProps, + ...other +}: T) => other; + +export const StyledAutocompleteListbox = styled(StyledList)<{ + ownerState: AutocompleteListboxOwnerState; +}>(({ theme, ownerState }) => { + const variantStyle = theme.variants[ownerState.variant!]?.[ownerState.color!]; + return { + '--focus-outline-offset': `calc(${theme.vars.focus.thickness} * -1)`, // to prevent the focus outline from being cut by overflow + '--List-radius': theme.vars.radius.sm, + '--List-item-stickyBackground': + variantStyle?.backgroundColor || + variantStyle?.background || + theme.vars.palette.background.surface, + '--List-item-stickyTop': 'calc(var(--List-padding, var(--List-divider-gap)) * -1)', + ...scopedVariables, + boxShadow: theme.vars.shadow.md, + ...(!variantStyle?.backgroundColor && { + backgroundColor: theme.vars.palette.background.surface, + }), + zIndex: 1200, + overflow: 'auto', + maxHeight: '40vh', + position: 'relative', // to make sure that the listbox is positioned for grouped options to work. + '&:empty': { + visibility: 'hidden', + }, + [`& .${listItemClasses.nested}, & .${listItemClasses.nested} .${listClasses.root}`]: { + // For grouped options autocomplete: + // Force the position to make the scroll into view logic works because the `element.offsetTop` should reference to the listbox, not the grouped list. + // See the implementation of the `useAutocomplete` line:370 + // + // Resource: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetTop + position: 'initial', + }, + }; +}); + +const AutocompleteListboxRoot = styled(StyledAutocompleteListbox, { + name: 'JoyAutocompleteListbox', + slot: 'Root', + overridesResolver: (props, styles) => styles.root, +})({}); + +const AutocompleteListbox = React.forwardRef(function AutocompleteListbox(inProps, ref) { + const props = useThemeProps({ + props: inProps, + name: 'JoyAutocompleteListbox', + }); + + const { + children, + component, + color = 'neutral', + variant = 'outlined', + size = 'md', + ...otherProps + } = props; + + const ownerState = { + ...props, + size, + component, + color, + variant, + nesting: false, + row: false, + wrap: false, + }; + + const other = excludePopperProps(otherProps); + + const classes = useUtilityClasses(ownerState); + + const rootProps = useSlotProps({ + elementType: AutocompleteListbox, + externalSlotProps: {}, + externalForwardedProps: other, + ownerState, + additionalProps: { + ref, + as: component, + role: 'listbox', + }, + className: classes.root, + }); + + return {children}; +}) as OverridableComponent; + +AutocompleteListbox.propTypes /* remove-proptypes */ = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit TypeScript types and run "yarn proptypes" | + // ---------------------------------------------------------------------- + /** + * @ignore + */ + children: PropTypes.node, + /** + * The color of the component. It supports those theme colors that make sense for this component. + * @default 'neutral' + */ + color: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([ + PropTypes.oneOf(['danger', 'info', 'neutral', 'primary', 'success', 'warning']), + PropTypes.string, + ]), + /** + * The component used for the root node. + * Either a string to use a HTML element or a component. + */ + component: PropTypes.elementType, + /** + * The size of the component (affect other nested list* components). + * @default 'md' + */ + size: PropTypes.oneOf(['sm', 'md', 'lg']), + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), + /** + * The variant to use. + * @default 'outlined' + */ + variant: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([ + PropTypes.oneOf(['contained', 'light', 'outlined', 'text']), + PropTypes.string, + ]), +} as any; + +export default AutocompleteListbox; diff --git a/packages/mui-joy/src/AutocompleteListbox/AutocompleteListboxProps.ts b/packages/mui-joy/src/AutocompleteListbox/AutocompleteListboxProps.ts new file mode 100644 index 00000000000000..d48cfd219c1d0a --- /dev/null +++ b/packages/mui-joy/src/AutocompleteListbox/AutocompleteListboxProps.ts @@ -0,0 +1,40 @@ +import * as React from 'react'; +import { OverrideProps } from '@mui/types'; +import { ListProps } from '../List/ListProps'; +import { SxProps } from '../styles/types'; + +export type AutocompleteListboxSlot = 'root'; + +export interface AutocompleteListboxTypeMap

{ + props: P & { + /** + * The color of the component. It supports those theme colors that make sense for this component. + * @default 'neutral' + */ + color?: ListProps['color']; + /** + * The variant to use. + * @default 'outlined' + */ + variant?: ListProps['variant']; + /** + * The size of the component (affect other nested list* components). + * @default 'md' + */ + size?: ListProps['size']; + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx?: SxProps; + }; + defaultComponent: D; +} + +export type AutocompleteListboxProps< + D extends React.ElementType = AutocompleteListboxTypeMap['defaultComponent'], + P = { + component?: React.ElementType; + }, +> = OverrideProps, D>; + +export interface AutocompleteListboxOwnerState extends AutocompleteListboxProps {} diff --git a/packages/mui-joy/src/AutocompleteListbox/autocompleteListboxClasses.ts b/packages/mui-joy/src/AutocompleteListbox/autocompleteListboxClasses.ts new file mode 100644 index 00000000000000..d3fa27a8717fe7 --- /dev/null +++ b/packages/mui-joy/src/AutocompleteListbox/autocompleteListboxClasses.ts @@ -0,0 +1,60 @@ +import { generateUtilityClass, generateUtilityClasses } from '../className'; + +export interface AutocompleteListboxClasses { + /** Styles applied to the root element. */ + root: string; + /** Classname applied to the root element if `size="sm"`. */ + sizeSm: string; + /** Classname applied to the root element if `size="md"`. */ + sizeMd: string; + /** Classname applied to the root element if `size="lg"`. */ + sizeLg: string; + /** Classname applied to the root element if `color="primary"`. */ + colorPrimary: string; + /** Classname applied to the root element if `color="neutral"`. */ + colorNeutral: string; + /** Classname applied to the root element if `color="danger"`. */ + colorDanger: string; + /** Classname applied to the root element if `color="info"`. */ + colorInfo: string; + /** Classname applied to the root element if `color="success"`. */ + colorSuccess: string; + /** Classname applied to the root element if `color="warning"`. */ + colorWarning: string; + /** Classname applied to the root element if `variant="plain"`. */ + variantPlain: string; + /** Classname applied to the root element if `variant="outlined"`. */ + variantOutlined: string; + /** Classname applied to the root element if `variant="soft"`. */ + variantSoft: string; + /** Classname applied to the root element if `variant="solid"`. */ + variantSolid: string; +} + +export type AutocompleteListboxClassKey = keyof AutocompleteListboxClasses; + +export function getAutocompleteListboxUtilityClass(slot: string): string { + return generateUtilityClass('JoyAutocompleteListbox', slot); +} + +const autocompleteListboxClasses: AutocompleteListboxClasses = generateUtilityClasses( + 'JoyAutocompleteListbox', + [ + 'root', + 'sizeSm', + 'sizeMd', + 'sizeLg', + 'colorPrimary', + 'colorNeutral', + 'colorDanger', + 'colorInfo', + 'colorSuccess', + 'colorWarning', + 'variantPlain', + 'variantOutlined', + 'variantSoft', + 'variantSolid', + ], +); + +export default autocompleteListboxClasses; diff --git a/packages/mui-joy/src/AutocompleteListbox/index.ts b/packages/mui-joy/src/AutocompleteListbox/index.ts new file mode 100644 index 00000000000000..0fc430b15af660 --- /dev/null +++ b/packages/mui-joy/src/AutocompleteListbox/index.ts @@ -0,0 +1,4 @@ +export { default } from './AutocompleteListbox'; +export * from './autocompleteListboxClasses'; +export { default as autocompleteListboxClasses } from './autocompleteListboxClasses'; +export * from './AutocompleteListboxProps'; diff --git a/packages/mui-joy/src/AutocompleteOption/AutocompleteOption.test.js b/packages/mui-joy/src/AutocompleteOption/AutocompleteOption.test.js new file mode 100644 index 00000000000000..205980e72aa61f --- /dev/null +++ b/packages/mui-joy/src/AutocompleteOption/AutocompleteOption.test.js @@ -0,0 +1,43 @@ +import * as React from 'react'; +import { expect } from 'chai'; +import { describeConformance, createRenderer } from 'test/utils'; +import { ThemeProvider } from '@mui/joy/styles'; +import AutocompleteOption, { + autocompleteOptionClasses as classes, +} from '@mui/joy/AutocompleteOption'; + +describe('Joy ', () => { + const { render } = createRenderer(); + + describeConformance(, () => ({ + classes, + inheritComponent: 'li', + render, + ThemeProvider, + muiName: 'JoyAutocompleteOption', + refInstanceof: window.HTMLLIElement, + testVariantProps: { color: 'primary' }, + testCustomVariant: true, + skip: ['componentsProp', 'classesRoot'], + })); + + it('should have li tag', () => { + const { getByRole } = render(); + expect(getByRole('option')).to.have.tagName('li'); + }); + + it('should render with the variant class', () => { + const { getByRole } = render(); + expect(getByRole('option')).to.have.class(classes.variantOutlined); + }); + + it('should render with primary color class', () => { + const { getByRole } = render(); + expect(getByRole('option')).to.have.class(classes.colorPrimary); + }); + + it('should accept className prop', () => { + const { container } = render(); + expect(container.firstChild).to.have.class('foo-bar'); + }); +}); diff --git a/packages/mui-joy/src/AutocompleteOption/AutocompleteOption.tsx b/packages/mui-joy/src/AutocompleteOption/AutocompleteOption.tsx new file mode 100644 index 00000000000000..b54098681d529d --- /dev/null +++ b/packages/mui-joy/src/AutocompleteOption/AutocompleteOption.tsx @@ -0,0 +1,135 @@ +import * as React from 'react'; +import clsx from 'clsx'; +import PropTypes from 'prop-types'; +import { OverridableComponent } from '@mui/types'; +import { unstable_capitalize as capitalize } from '@mui/utils'; +import composeClasses from '@mui/base/composeClasses'; +import { StyledListItemButton } from '../ListItemButton/ListItemButton'; +import { styled, useThemeProps } from '../styles'; +import autocompleteOptionClasses, { + getAutocompleteOptionUtilityClass, +} from './autocompleteOptionClasses'; +import { AutocompleteOptionOwnerState, AutocompleteOptionTypeMap } from './AutocompleteOptionProps'; + +const useUtilityClasses = (ownerState: AutocompleteOptionOwnerState) => { + const { color, variant } = ownerState; + + const slots = { + root: [ + 'root', + color && `color${capitalize(color)}`, + variant && `variant${capitalize(variant)}`, + ], + }; + + return composeClasses(slots, getAutocompleteOptionUtilityClass, {}); +}; + +export const StyledAutocompleteOption = styled(StyledListItemButton as unknown as 'li')<{ + ownerState: AutocompleteOptionOwnerState; +}>(({ theme, ownerState }) => ({ + '&:not(:hover)': { + transition: 'none', // prevent flicker when using keyboard arrows to move between options + }, + '&[aria-disabled="true"]': theme.variants[`${ownerState.variant!}Disabled`]?.[ownerState.color!], + '&[aria-selected="true"]': { + color: theme.vars.palette.primary.softColor, + backgroundColor: theme.vars.palette.primary.softBg, + fontWeight: theme.vars.fontWeight.md, + }, + [`&.${autocompleteOptionClasses.focused}:not([aria-selected="true"]):not(:hover)`]: { + // create the focused style similar to the hover state + backgroundColor: + theme.variants[`${ownerState.variant!}Hover`]?.[ownerState.color!]?.backgroundColor, + }, +})); + +const AutocompleteOptionRoot = styled(StyledAutocompleteOption, { + name: 'JoyAutocompleteOption', + slot: 'Root', + overridesResolver: (props, styles) => styles.root, +})({}); + +const AutocompleteOption = React.forwardRef(function AutocompleteOption(inProps, ref) { + const props = useThemeProps({ + props: inProps, + name: 'JoyAutocompleteOption', + }); + + const { + children, + component = 'li', + color = 'neutral', + variant = 'plain', + className, + ...other + } = props; + + const ownerState = { + ...props, + component, + color, + variant, + }; + + const classes = useUtilityClasses(ownerState); + + return ( + + {children} + + ); +}) as OverridableComponent; + +AutocompleteOption.propTypes /* remove-proptypes */ = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit TypeScript types and run "yarn proptypes" | + // ---------------------------------------------------------------------- + /** + * @ignore + */ + children: PropTypes.node, + /** + * @ignore + */ + className: PropTypes.string, + /** + * The color of the component. It supports those theme colors that make sense for this component. + * @default 'neutral' + */ + color: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([ + PropTypes.oneOf(['danger', 'info', 'neutral', 'primary', 'success', 'warning']), + PropTypes.string, + ]), + /** + * The component used for the root node. + * Either a string to use a HTML element or a component. + */ + component: PropTypes.elementType, + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), + /** + * The variant to use. + * @default 'plain' + */ + variant: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([ + PropTypes.oneOf(['contained', 'light', 'outlined', 'text']), + PropTypes.string, + ]), +} as any; + +export default AutocompleteOption; diff --git a/packages/mui-joy/src/AutocompleteOption/AutocompleteOptionProps.ts b/packages/mui-joy/src/AutocompleteOption/AutocompleteOptionProps.ts new file mode 100644 index 00000000000000..4d2e672dc482b4 --- /dev/null +++ b/packages/mui-joy/src/AutocompleteOption/AutocompleteOptionProps.ts @@ -0,0 +1,35 @@ +import * as React from 'react'; +import { OverrideProps } from '@mui/types'; +import { ListItemButtonProps } from '../ListItemButton/ListItemButtonProps'; +import { SxProps } from '../styles/types'; + +export type AutocompleteOptionSlot = 'root'; + +export interface AutocompleteOptionTypeMap

{ + props: P & { + /** + * The color of the component. It supports those theme colors that make sense for this component. + * @default 'neutral' + */ + color?: ListItemButtonProps['color']; + /** + * The variant to use. + * @default 'plain' + */ + variant?: ListItemButtonProps['variant']; + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx?: SxProps; + }; + defaultComponent: D; +} + +export type AutocompleteOptionProps< + D extends React.ElementType = AutocompleteOptionTypeMap['defaultComponent'], + P = { + component?: React.ElementType; + }, +> = OverrideProps, D>; + +export interface AutocompleteOptionOwnerState extends AutocompleteOptionProps {} diff --git a/packages/mui-joy/src/AutocompleteOption/autocompleteOptionClasses.ts b/packages/mui-joy/src/AutocompleteOption/autocompleteOptionClasses.ts new file mode 100644 index 00000000000000..711ee09fc73203 --- /dev/null +++ b/packages/mui-joy/src/AutocompleteOption/autocompleteOptionClasses.ts @@ -0,0 +1,57 @@ +import { generateUtilityClass, generateUtilityClasses } from '../className'; + +export interface AutocompleteOptionClasses { + /** Styles applied to the root element. */ + root: string; + /** State class applied to the root element if focused. */ + focused: string; + /** State class applied to the `component`'s `focusVisibleClassName` prop. */ + focusVisible: string; + /** Styles applied to the root element if `color="primary"`. */ + colorPrimary: string; + /** Styles applied to the root element if `color="neutral"`. */ + colorNeutral: string; + /** Styles applied to the root element if `color="danger"`. */ + colorDanger: string; + /** Styles applied to the root element if `color="info"`. */ + colorInfo: string; + /** Styles applied to the root element if `color="success"`. */ + colorSuccess: string; + /** Styles applied to the root element if `color="warning"`. */ + colorWarning: string; + /** State class applied to the root element if `variant="plain"`. */ + variantPlain: string; + /** State class applied to the root element if `variant="soft"`. */ + variantSoft: string; + /** State class applied to the root element if `variant="outlined"`. */ + variantOutlined: string; + /** State class applied to the root element if `variant="solid"`. */ + variantSolid: string; +} + +export type AutocompleteOptionClassKey = keyof AutocompleteOptionClasses; + +export function getAutocompleteOptionUtilityClass(slot: string): string { + return generateUtilityClass('JoyAutocompleteOption', slot); +} + +const autocompleteOptionClasses: AutocompleteOptionClasses = generateUtilityClasses( + 'JoyAutocompleteOption', + [ + 'root', + 'focused', + 'focusVisible', + 'colorPrimary', + 'colorNeutral', + 'colorDanger', + 'colorInfo', + 'colorSuccess', + 'colorWarning', + 'variantPlain', + 'variantSoft', + 'variantOutlined', + 'variantSolid', + ], +); + +export default autocompleteOptionClasses; diff --git a/packages/mui-joy/src/AutocompleteOption/index.ts b/packages/mui-joy/src/AutocompleteOption/index.ts new file mode 100644 index 00000000000000..dda31b68a6b501 --- /dev/null +++ b/packages/mui-joy/src/AutocompleteOption/index.ts @@ -0,0 +1,4 @@ +export { default } from './AutocompleteOption'; +export * from './autocompleteOptionClasses'; +export { default as autocompleteOptionClasses } from './autocompleteOptionClasses'; +export * from './AutocompleteOptionProps'; diff --git a/packages/mui-joy/src/Chip/Chip.tsx b/packages/mui-joy/src/Chip/Chip.tsx index 9a58948fca42c8..93ca70b547a065 100644 --- a/packages/mui-joy/src/Chip/Chip.tsx +++ b/packages/mui-joy/src/Chip/Chip.tsx @@ -39,48 +39,48 @@ const ChipRoot = styled('div', { })<{ ownerState: ChipOwnerState }>(({ theme, ownerState }) => { return [ { - '--Chip-radius': '1.5rem', // for controlling chip delete margin offset '--Chip-decorator-childOffset': - 'min(calc(var(--Chip-paddingInline) - (var(--Chip-minHeight) - 2 * var(--variant-borderWidth) - var(--Chip-decorator-childHeight)) / 2), var(--Chip-paddingInline))', - '--internal-paddingBlock': - 'max((var(--Chip-minHeight) - 2 * var(--variant-borderWidth) - var(--Chip-decorator-childHeight)) / 2, 0px)', + 'min(calc(var(--Chip-paddingInline) - (var(--_Chip-minHeight) - 2 * var(--variant-borderWidth) - var(--Chip-decorator-childHeight)) / 2), var(--Chip-paddingInline))', '--Chip-decorator-childRadius': - 'max(var(--Chip-radius) - var(--internal-paddingBlock), min(var(--internal-paddingBlock) / 2, var(--Chip-radius) / 2))', + 'max(var(--_Chip-radius) - var(--_Chip-paddingBlock), min(var(--_Chip-paddingBlock) / 2, var(--_Chip-radius) / 2))', '--Chip-delete-radius': 'var(--Chip-decorator-childRadius)', '--Chip-delete-size': 'var(--Chip-decorator-childHeight)', '--Avatar-radius': 'var(--Chip-decorator-childRadius)', '--Avatar-size': 'var(--Chip-decorator-childHeight)', '--Icon-margin': 'initial', // reset the icon's margin. - '--internal-action-radius': 'var(--Chip-radius)', // to be used with Radio or Checkbox + '--internal-action-radius': 'var(--_Chip-radius)', // to be used with Radio or Checkbox ...(ownerState.size === 'sm' && { '--Chip-gap': '0.25rem', '--Chip-paddingInline': '0.5rem', '--Chip-decorator-childHeight': - 'calc(min(1.125rem, var(--Chip-minHeight)) - 2 * var(--variant-borderWidth))', - '--Icon-fontSize': 'calc(var(--Chip-minHeight, 1.5rem) / 1.714)', // 0.875rem by default - '--Chip-minHeight': '1.5rem', + 'calc(min(1.125rem, var(--_Chip-minHeight)) - 2 * var(--variant-borderWidth))', + '--Icon-fontSize': 'calc(var(--_Chip-minHeight) / 1.714)', // 0.875rem by default + '--_Chip-minHeight': 'var(--Chip-minHeight, 1.5rem)', fontSize: theme.vars.fontSize.xs, }), ...(ownerState.size === 'md' && { '--Chip-gap': '0.375rem', '--Chip-paddingInline': '0.75rem', - '--Chip-decorator-childHeight': 'min(1.375rem, var(--Chip-minHeight))', - '--Icon-fontSize': 'calc(var(--Chip-minHeight, 2rem) / 1.778)', // 1.125rem by default - '--Chip-minHeight': '2rem', + '--Chip-decorator-childHeight': 'min(1.375rem, var(--_Chip-minHeight))', + '--Icon-fontSize': 'calc(var(--_Chip-minHeight) / 1.778)', // 1.125rem by default + '--_Chip-minHeight': 'var(--Chip-minHeight, 2rem)', fontSize: theme.vars.fontSize.sm, }), ...(ownerState.size === 'lg' && { '--Chip-gap': '0.5rem', '--Chip-paddingInline': '1rem', - '--Chip-decorator-childHeight': 'min(1.75rem, var(--Chip-minHeight))', - '--Icon-fontSize': 'calc(var(--Chip-minHeight, 2.5rem) / 2)', // 1.25rem by default - '--Chip-minHeight': '2.5rem', + '--Chip-decorator-childHeight': 'min(1.75rem, var(--_Chip-minHeight))', + '--Icon-fontSize': 'calc(var(--_Chip-minHeight) / 2)', // 1.25rem by default + '--_Chip-minHeight': 'var(--Chip-minHeight, 2.5rem)', fontSize: theme.vars.fontSize.md, }), - minHeight: 'var(--Chip-minHeight)', + '--_Chip-radius': 'var(--Chip-radius, 1.5rem)', + '--_Chip-paddingBlock': + 'max((var(--_Chip-minHeight) - 2 * var(--variant-borderWidth) - var(--Chip-decorator-childHeight)) / 2, 0px)', + minHeight: 'var(--_Chip-minHeight)', paddingInline: 'var(--Chip-paddingInline)', - borderRadius: 'var(--Chip-radius)', + borderRadius: 'var(--_Chip-radius)', position: 'relative', fontWeight: theme.vars.fontWeight.md, fontFamily: theme.vars.fontFamily.body, @@ -119,9 +119,11 @@ const ChipLabel = styled('span', { slot: 'Label', overridesResolver: (props, styles) => styles.label, })<{ ownerState: ChipOwnerState }>(({ ownerState }) => ({ - display: 'inherit', - alignItems: 'center', + display: 'inline-block', + overflow: 'hidden', + textOverflow: 'ellipsis', order: 1, + minInlineSize: 0, flexGrow: 1, ...(ownerState.clickable && { zIndex: 1, diff --git a/packages/mui-joy/src/FormControl/FormControl.test.tsx b/packages/mui-joy/src/FormControl/FormControl.test.tsx index f01f1e41aabf5f..f89bdfe390ac62 100644 --- a/packages/mui-joy/src/FormControl/FormControl.test.tsx +++ b/packages/mui-joy/src/FormControl/FormControl.test.tsx @@ -13,6 +13,7 @@ import Textarea, { textareaClasses } from '@mui/joy/Textarea'; import RadioGroup from '@mui/joy/RadioGroup'; import Radio, { radioClasses } from '@mui/joy/Radio'; import Switch, { switchClasses } from '@mui/joy/Switch'; +import Autocomplete, { autocompleteClasses } from '@mui/joy/Autocomplete'; describe('', () => { const { render } = createRenderer(); @@ -376,4 +377,48 @@ describe('', () => { expect(getByLabelText('label')).to.have.attribute('disabled'); }); }); + + describe('Autocomplete', () => { + it('should linked the label', () => { + const { getByLabelText } = render( + + label + + , + ); + + expect(getByLabelText('label')).toBeVisible(); + }); + + it('should inherit color prop from FormControl', () => { + const { getByTestId } = render( + + + , + ); + + expect(getByTestId('input')).to.have.class(autocompleteClasses.colorSuccess); + }); + + it('should inherit error prop from FormControl', () => { + const { getByTestId } = render( + + + , + ); + + expect(getByTestId('input')).to.have.class(autocompleteClasses.colorDanger); + }); + + it('should inherit disabled from FormControl', () => { + const { getByRole } = render( + + label + + , + ); + + expect(getByRole('combobox')).to.have.attribute('disabled'); + }); + }); }); diff --git a/packages/mui-joy/src/IconButton/IconButton.tsx b/packages/mui-joy/src/IconButton/IconButton.tsx index 24d2f33d90a198..f008725acebb9a 100644 --- a/packages/mui-joy/src/IconButton/IconButton.tsx +++ b/packages/mui-joy/src/IconButton/IconButton.tsx @@ -31,61 +31,65 @@ const useUtilityClasses = (ownerState: IconButtonOwnerState) => { return composedClasses; }; -export const IconButtonRoot = styled('button', { +export const StyledIconButton = styled('button')<{ ownerState: IconButtonOwnerState }>( + ({ theme, ownerState }) => [ + { + '--Icon-margin': 'initial', // reset the icon's margin. + '--CircularProgress-size': 'var(--Icon-fontSize)', + ...(ownerState.size === 'sm' && { + '--Icon-fontSize': 'calc(var(--IconButton-size, 2rem) / 1.6)', // 1.25rem by default + minWidth: 'var(--IconButton-size, 2rem)', // use min-width instead of height to make the button resilient to its content + minHeight: 'var(--IconButton-size, 2rem)', // use min-height instead of height to make the button resilient to its content + fontSize: theme.vars.fontSize.sm, + paddingInline: '2px', // add a gap, in case the content is long, e.g. multiple icons + }), + ...(ownerState.size === 'md' && { + '--Icon-fontSize': 'calc(var(--IconButton-size, 2.5rem) / 1.667)', // 1.5rem by default + minWidth: 'var(--IconButton-size, 2.5rem)', + minHeight: 'var(--IconButton-size, 2.5rem)', + fontSize: theme.vars.fontSize.md, + paddingInline: '0.25rem', + }), + ...(ownerState.size === 'lg' && { + '--Icon-fontSize': 'calc(var(--IconButton-size, 3rem) / 1.714)', // 1.75rem by default + minWidth: 'var(--IconButton-size, 3rem)', + minHeight: 'var(--IconButton-size, 3rem)', + fontSize: theme.vars.fontSize.lg, + paddingInline: '0.375rem', + }), + WebkitTapHighlightColor: 'transparent', + paddingBlock: 0, + fontFamily: theme.vars.fontFamily.body, + fontWeight: theme.vars.fontWeight.md, + margin: `var(--IconButton-margin)`, // to be controlled by other components, eg. Input + borderRadius: `var(--IconButton-radius, ${theme.vars.radius.sm})`, // to be controlled by other components, eg. Input + border: 'none', + boxSizing: 'border-box', + backgroundColor: 'transparent', + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + position: 'relative', + // TODO: discuss the transition approach in a separate PR. This value is copied from mui-material Button. + transition: + 'background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, border-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms', + [theme.focus.selector]: theme.focus.default, + }, + theme.variants[ownerState.variant!]?.[ownerState.color!], + { '&:hover': theme.variants[`${ownerState.variant!}Hover`]?.[ownerState.color!] }, + { '&:active': theme.variants[`${ownerState.variant!}Active`]?.[ownerState.color!] }, + { + [`&.${iconButtonClasses.disabled}`]: + theme.variants[`${ownerState.variant!}Disabled`]?.[ownerState.color!], + }, + ], +); + +export const IconButtonRoot = styled(StyledIconButton, { name: 'JoyIconButton', slot: 'Root', overridesResolver: (props, styles) => styles.root, -})<{ ownerState: IconButtonOwnerState }>(({ theme, ownerState }) => [ - { - '--Icon-margin': 'initial', // reset the icon's margin. - '--CircularProgress-size': 'var(--Icon-fontSize)', - ...(ownerState.size === 'sm' && { - '--Icon-fontSize': 'calc(var(--IconButton-size, 2rem) / 1.6)', // 1.25rem by default - minWidth: 'var(--IconButton-size, 2rem)', // use min-width instead of height to make the button resilient to its content - minHeight: 'var(--IconButton-size, 2rem)', // use min-height instead of height to make the button resilient to its content - fontSize: theme.vars.fontSize.sm, - paddingInline: '2px', // add a gap, in case the content is long, e.g. multiple icons - }), - ...(ownerState.size === 'md' && { - '--Icon-fontSize': 'calc(var(--IconButton-size, 2.5rem) / 1.667)', // 1.5rem by default - minWidth: 'var(--IconButton-size, 2.5rem)', - minHeight: 'var(--IconButton-size, 2.5rem)', - fontSize: theme.vars.fontSize.md, - paddingInline: '0.25rem', - }), - ...(ownerState.size === 'lg' && { - '--Icon-fontSize': 'calc(var(--IconButton-size, 3rem) / 1.714)', // 1.75rem by default - minWidth: 'var(--IconButton-size, 3rem)', - minHeight: 'var(--IconButton-size, 3rem)', - fontSize: theme.vars.fontSize.lg, - paddingInline: '0.375rem', - }), - WebkitTapHighlightColor: 'transparent', - paddingBlock: 0, - fontFamily: theme.vars.fontFamily.body, - fontWeight: theme.vars.fontWeight.md, - margin: `var(--IconButton-margin)`, // to be controlled by other components, eg. Input - borderRadius: `var(--IconButton-radius, ${theme.vars.radius.sm})`, // to be controlled by other components, eg. Input - border: 'none', - boxSizing: 'border-box', - backgroundColor: 'transparent', - display: 'inline-flex', - alignItems: 'center', - justifyContent: 'center', - position: 'relative', - // TODO: discuss the transition approach in a separate PR. This value is copied from mui-material Button. - transition: - 'background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, border-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms', - [theme.focus.selector]: theme.focus.default, - }, - theme.variants[ownerState.variant!]?.[ownerState.color!], - { '&:hover': theme.variants[`${ownerState.variant!}Hover`]?.[ownerState.color!] }, - { '&:active': theme.variants[`${ownerState.variant!}Active`]?.[ownerState.color!] }, - { - [`&.${iconButtonClasses.disabled}`]: - theme.variants[`${ownerState.variant!}Disabled`]?.[ownerState.color!], - }, -]); +})({}); const IconButton = React.forwardRef(function IconButton(inProps, ref) { const props = useThemeProps({ diff --git a/packages/mui-joy/src/Input/Input.tsx b/packages/mui-joy/src/Input/Input.tsx index f735b35d708c17..06353aa3f932ae 100644 --- a/packages/mui-joy/src/Input/Input.tsx +++ b/packages/mui-joy/src/Input/Input.tsx @@ -29,118 +29,112 @@ const useUtilityClasses = (ownerState: InputOwnerState) => { return composeClasses(slots, getInputUtilityClass, {}); }; -const InputRoot = styled('div', { - name: 'JoyInput', - slot: 'Root', - overridesResolver: (props, styles) => styles.root, -})<{ ownerState: InputOwnerState }>(({ theme, ownerState }) => { - const variantStyle = theme.variants[`${ownerState.variant!}`]?.[ownerState.color!]; - return [ - { - '--Input-radius': theme.vars.radius.sm, - '--Input-gap': '0.5rem', - '--Input-placeholderOpacity': 0.5, - '--Input-focusedThickness': theme.vars.focus.thickness, - ...(ownerState.color === 'context' - ? { - '--Input-focusedHighlight': theme.vars.palette.focusVisible, - } - : { - '--Input-focusedHighlight': - theme.vars.palette[ - ownerState.color === 'neutral' ? 'primary' : ownerState.color! - ]?.[500], - }), - ...(ownerState.size === 'sm' && { - '--Input-minHeight': '2rem', - '--Input-paddingInline': '0.5rem', - '--Input-decorator-childHeight': 'min(1.5rem, var(--Input-minHeight))', - '--Icon-fontSize': '1.25rem', - }), - ...(ownerState.size === 'md' && { - '--Input-minHeight': '2.5rem', - '--Input-paddingInline': '0.75rem', - '--Input-decorator-childHeight': 'min(2rem, var(--Input-minHeight))', - '--Icon-fontSize': '1.5rem', - }), - ...(ownerState.size === 'lg' && { - '--Input-minHeight': '3rem', - '--Input-paddingInline': '1rem', - '--Input-gap': '0.75rem', - '--Input-decorator-childHeight': 'min(2.375rem, var(--Input-minHeight))', - '--Icon-fontSize': '1.75rem', - }), - // variables for controlling child components - '--Input-decorator-childOffset': - 'min(calc(var(--Input-paddingInline) - (var(--Input-minHeight) - 2 * var(--variant-borderWidth) - var(--Input-decorator-childHeight)) / 2), var(--Input-paddingInline))', - '--internal-paddingBlock': - 'max((var(--Input-minHeight) - 2 * var(--variant-borderWidth) - var(--Input-decorator-childHeight)) / 2, 0px)', - '--Input-decorator-childRadius': - 'max(var(--Input-radius) - var(--internal-paddingBlock), min(var(--internal-paddingBlock) / 2, var(--Input-radius) / 2))', - '--Button-minHeight': 'var(--Input-decorator-childHeight)', - '--IconButton-size': 'var(--Input-decorator-childHeight)', - '--Button-radius': 'var(--Input-decorator-childRadius)', - '--IconButton-radius': 'var(--Input-decorator-childRadius)', - boxSizing: 'border-box', - minWidth: 0, - minHeight: 'var(--Input-minHeight)', - ...(ownerState.fullWidth && { - width: '100%', - }), - cursor: 'text', - position: 'relative', - display: 'flex', - alignItems: 'center', - paddingInline: `var(--Input-paddingInline)`, - borderRadius: 'var(--Input-radius)', - fontFamily: theme.vars.fontFamily.body, - fontSize: theme.vars.fontSize.md, - ...(ownerState.size === 'sm' && { - fontSize: theme.vars.fontSize.sm, - }), - // TODO: discuss the transition approach in a separate PR. This value is copied from mui-material Button. - transition: - 'border-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms', - '&:before': { +export const StyledInputRoot = styled('div')<{ ownerState: InputOwnerState }>( + ({ theme, ownerState }) => { + const variantStyle = theme.variants[`${ownerState.variant!}`]?.[ownerState.color!]; + return [ + { + '--Input-radius': theme.vars.radius.sm, + '--Input-gap': '0.5rem', + '--Input-placeholderOpacity': 0.5, + '--Input-focusedThickness': theme.vars.focus.thickness, + ...(ownerState.color === 'context' + ? { + '--Input-focusedHighlight': theme.vars.palette.focusVisible, + } + : { + '--Input-focusedHighlight': + theme.vars.palette[ + ownerState.color === 'neutral' ? 'primary' : ownerState.color! + ]?.[500], + }), + ...(ownerState.size === 'sm' && { + '--Input-minHeight': '2rem', + '--Input-paddingInline': '0.5rem', + '--Input-decorator-childHeight': 'min(1.5rem, var(--Input-minHeight))', + '--Icon-fontSize': '1.25rem', + }), + ...(ownerState.size === 'md' && { + '--Input-minHeight': '2.5rem', + '--Input-paddingInline': '0.75rem', + '--Input-decorator-childHeight': 'min(2rem, var(--Input-minHeight))', + '--Icon-fontSize': '1.5rem', + }), + ...(ownerState.size === 'lg' && { + '--Input-minHeight': '3rem', + '--Input-paddingInline': '1rem', + '--Input-gap': '0.75rem', + '--Input-decorator-childHeight': 'min(2.375rem, var(--Input-minHeight))', + '--Icon-fontSize': '1.75rem', + }), + // variables for controlling child components + '--Input-decorator-childOffset': + 'min(calc(var(--Input-paddingInline) - (var(--Input-minHeight) - 2 * var(--variant-borderWidth) - var(--Input-decorator-childHeight)) / 2), var(--Input-paddingInline))', + '--_Input-paddingBlock': + 'max((var(--Input-minHeight) - 2 * var(--variant-borderWidth) - var(--Input-decorator-childHeight)) / 2, 0px)', + '--Input-decorator-childRadius': + 'max(var(--Input-radius) - var(--_Input-paddingBlock), min(var(--_Input-paddingBlock) / 2, var(--Input-radius) / 2))', + '--Button-minHeight': 'var(--Input-decorator-childHeight)', + '--IconButton-size': 'var(--Input-decorator-childHeight)', + '--Button-radius': 'var(--Input-decorator-childRadius)', + '--IconButton-radius': 'var(--Input-decorator-childRadius)', boxSizing: 'border-box', - content: '""', - display: 'block', - position: 'absolute', - pointerEvents: 'none', - top: 0, - left: 0, - right: 0, - bottom: 0, - zIndex: 1, - borderRadius: 'inherit', - margin: 'calc(var(--variant-borderWidth) * -1)', // for outlined variant - }, - }, - { - // variant styles - ...variantStyle, - backgroundColor: variantStyle?.backgroundColor ?? theme.vars.palette.background.surface, - [`&:hover:not(.${inputClasses.focused})`]: { - ...theme.variants[`${ownerState.variant!}Hover`]?.[ownerState.color!], - backgroundColor: null, // it is not common to change background on hover for Input + minWidth: 0, + minHeight: 'var(--Input-minHeight)', + ...(ownerState.fullWidth && { + width: '100%', + }), cursor: 'text', - }, - [`&.${inputClasses.disabled}`]: - theme.variants[`${ownerState.variant!}Disabled`]?.[ownerState.color!], - [`&.${inputClasses.focused}`]: { + position: 'relative', + display: 'flex', + alignItems: 'center', + paddingInline: `var(--Input-paddingInline)`, + borderRadius: 'var(--Input-radius)', + fontFamily: theme.vars.fontFamily.body, + fontSize: theme.vars.fontSize.md, + ...(ownerState.size === 'sm' && { + fontSize: theme.vars.fontSize.sm, + }), + // TODO: discuss the transition approach in a separate PR. This value is copied from mui-material Button. + transition: + 'border-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms', '&:before': { - boxShadow: `inset 0 0 0 var(--Input-focusedThickness) var(--Input-focusedHighlight)`, + boxSizing: 'border-box', + content: '""', + display: 'block', + position: 'absolute', + pointerEvents: 'none', + top: 0, + left: 0, + right: 0, + bottom: 0, + zIndex: 1, + borderRadius: 'inherit', + margin: 'calc(var(--variant-borderWidth) * -1)', // for outlined variant }, }, - }, - ]; -}); + { + // variant styles + ...variantStyle, + backgroundColor: variantStyle?.backgroundColor ?? theme.vars.palette.background.surface, + [`&:hover:not(.${inputClasses.focused})`]: { + ...theme.variants[`${ownerState.variant!}Hover`]?.[ownerState.color!], + backgroundColor: null, // it is not common to change background on hover for Input + cursor: 'text', + }, + [`&.${inputClasses.disabled}`]: + theme.variants[`${ownerState.variant!}Disabled`]?.[ownerState.color!], + [`&.${inputClasses.focused}`]: { + '&:before': { + boxShadow: `inset 0 0 0 var(--Input-focusedThickness) var(--Input-focusedHighlight)`, + }, + }, + }, + ]; + }, +); -const InputInput = styled('input', { - name: 'JoyInput', - slot: 'Input', - overridesResolver: (props, styles) => styles.input, -})<{ ownerState: InputOwnerState }>({ +export const StyledInputHtml = styled('input')<{ ownerState: InputOwnerState }>({ border: 'none', // remove the native input width minWidth: 0, // remove the native input width outline: 0, // remove the native input outline @@ -165,44 +159,66 @@ const InputInput = styled('input', { '&::-ms-input-placeholder': { opacity: 'var(--Input-placeholderOpacity)', color: 'inherit' }, // Edge }); -const InputStartDecorator = styled('span', { +export const StyledInputStartDecorator = styled('span')<{ ownerState: InputOwnerState }>( + ({ theme, ownerState }) => ({ + '--Button-margin': '0 0 0 calc(var(--Input-decorator-childOffset) * -1)', + '--IconButton-margin': '0 0 0 calc(var(--Input-decorator-childOffset) * -1)', + '--Icon-margin': '0 0 0 calc(var(--Input-paddingInline) / -4)', + display: 'inherit', + alignItems: 'center', + paddingBlock: 'var(--unstable_Input-paddingBlock)', // for wrapping Autocomplete's tags + flexWrap: 'wrap', // for wrapping Autocomplete's tags + marginInlineEnd: 'var(--Input-gap)', + color: theme.vars.palette.text.tertiary, + cursor: 'initial', + ...(ownerState.focused && { + color: theme.variants[ownerState.variant!]?.[ownerState.color!]?.color, + }), + ...(ownerState.disabled && { + color: theme.variants[`${ownerState.variant!}Disabled`]?.[ownerState.color!]?.color, + }), + }), +); + +export const StyledInputEndDecorator = styled('span')<{ ownerState: InputOwnerState }>( + ({ theme, ownerState }) => ({ + '--Button-margin': '0 calc(var(--Input-decorator-childOffset) * -1) 0 0', + '--IconButton-margin': '0 calc(var(--Input-decorator-childOffset) * -1) 0 0', + '--Icon-margin': '0 calc(var(--Input-paddingInline) / -4) 0 0', + display: 'inherit', + alignItems: 'center', + marginInlineStart: 'var(--Input-gap)', + color: theme.variants[ownerState.variant!]?.[ownerState.color!]?.color, + cursor: 'initial', + ...(ownerState.disabled && { + color: theme.variants[`${ownerState.variant!}Disabled`]?.[ownerState.color!]?.color, + }), + }), +); + +const InputRoot = styled(StyledInputRoot, { + name: 'JoyInput', + slot: 'Root', + overridesResolver: (props, styles) => styles.root, +})({}); + +const InputInput = styled(StyledInputHtml, { + name: 'JoyInput', + slot: 'Input', + overridesResolver: (props, styles) => styles.input, +})({}); + +const InputStartDecorator = styled(StyledInputStartDecorator, { name: 'JoyInput', slot: 'StartDecorator', overridesResolver: (props, styles) => styles.startDecorator, -})<{ ownerState: InputOwnerState }>(({ theme, ownerState }) => ({ - '--Button-margin': '0 0 0 calc(var(--Input-decorator-childOffset) * -1)', - '--IconButton-margin': '0 0 0 calc(var(--Input-decorator-childOffset) * -1)', - '--Icon-margin': '0 0 0 calc(var(--Input-paddingInline) / -4)', - display: 'inherit', - alignItems: 'center', - marginInlineEnd: 'var(--Input-gap)', - color: theme.vars.palette.text.tertiary, - cursor: 'initial', - ...(ownerState.focused && { - color: theme.variants[ownerState.variant!]?.[ownerState.color!]?.color, - }), - ...(ownerState.disabled && { - color: theme.variants[`${ownerState.variant!}Disabled`]?.[ownerState.color!]?.color, - }), -})); +})({}); -const InputEndDecorator = styled('span', { +const InputEndDecorator = styled(StyledInputEndDecorator, { name: 'JoyInput', slot: 'EndDecorator', overridesResolver: (props, styles) => styles.endDecorator, -})<{ ownerState: InputOwnerState }>(({ theme, ownerState }) => ({ - '--Button-margin': '0 calc(var(--Input-decorator-childOffset) * -1) 0 0', - '--IconButton-margin': '0 calc(var(--Input-decorator-childOffset) * -1) 0 0', - '--Icon-margin': '0 calc(var(--Input-paddingInline) / -4) 0 0', - display: 'inherit', - alignItems: 'center', - marginInlineStart: 'var(--Input-gap)', - color: theme.variants[ownerState.variant!]?.[ownerState.color!]?.color, - cursor: 'initial', - ...(ownerState.disabled && { - color: theme.variants[`${ownerState.variant!}Disabled`]?.[ownerState.color!]?.color, - }), -})); +})({}); const Input = React.forwardRef(function Input(inProps, ref) { const props = useThemeProps({ diff --git a/packages/mui-joy/src/List/List.tsx b/packages/mui-joy/src/List/List.tsx index 5dd7f40655d5ac..7d1a9dffbf7e09 100644 --- a/packages/mui-joy/src/List/List.tsx +++ b/packages/mui-joy/src/List/List.tsx @@ -31,11 +31,7 @@ const useUtilityClasses = (ownerState: ListOwnerState) => { return composeClasses(slots, getListUtilityClass, {}); }; -export const ListRoot = styled('ul', { - name: 'JoyList', - slot: 'Root', - overridesResolver: (props, styles) => styles.root, -})<{ ownerState: ListOwnerState }>(({ theme, ownerState }) => { +export const StyledList = styled('ul')<{ ownerState: ListOwnerState }>(({ theme, ownerState }) => { function applySizeVars(size: ListProps['size']) { if (size === 'sm') { return { @@ -140,6 +136,12 @@ export const ListRoot = styled('ul', { ]; }); +export const ListRoot = styled(StyledList, { + name: 'JoyList', + slot: 'Root', + overridesResolver: (props, styles) => styles.root, +})({}); + const List = React.forwardRef(function List(inProps, ref) { const nesting = React.useContext(NestedListContext); const menuContext = React.useContext(MenuUnstyledContext); diff --git a/packages/mui-joy/src/List/ListProps.ts b/packages/mui-joy/src/List/ListProps.ts index 4d3e601f22e4a1..6de57cf9b48b5b 100644 --- a/packages/mui-joy/src/List/ListProps.ts +++ b/packages/mui-joy/src/List/ListProps.ts @@ -68,5 +68,5 @@ export interface ListOwnerState extends ListProps { * @internal * If `true`, the element is rendered in a nested list item. */ - nesting: boolean | string; + nesting?: boolean | string; } diff --git a/packages/mui-joy/src/List/ListProvider.tsx b/packages/mui-joy/src/List/ListProvider.tsx index eabf0697665e14..67202fad1fe0cd 100644 --- a/packages/mui-joy/src/List/ListProvider.tsx +++ b/packages/mui-joy/src/List/ListProvider.tsx @@ -7,7 +7,7 @@ import NestedListContext from './NestedListContext'; * This variables should be used in a List to create a scope * that will not inherit variables from the upper scope. * - * Used in `Menu`, `MenuList`, `TabList`, `Select` to communicate with nested List. + * Used in `Menu`, `MenuList`, `TabList`, `Select`, and `Autocomplete` to communicate with nested List. * * e.g. menu group: *

diff --git a/packages/mui-joy/src/ListItem/ListItem.tsx b/packages/mui-joy/src/ListItem/ListItem.tsx index d1a45f2386e11a..5ede1556990d6d 100644 --- a/packages/mui-joy/src/ListItem/ListItem.tsx +++ b/packages/mui-joy/src/ListItem/ListItem.tsx @@ -73,7 +73,7 @@ const ListItemRoot = styled('li', { boxSizing: 'border-box', borderRadius: 'var(--List-item-radius)', display: 'flex', - flex: 'none', + flex: 'none', // prevent children from shrinking when the List's height is limited. position: 'relative', paddingBlockStart: ownerState.nested ? 0 : 'var(--List-item-paddingY)', paddingBlockEnd: ownerState.nested ? 0 : 'var(--List-item-paddingY)', diff --git a/packages/mui-joy/src/ListItemButton/ListItemButton.tsx b/packages/mui-joy/src/ListItemButton/ListItemButton.tsx index 79927efab9a184..169cf1da3d4fe6 100644 --- a/packages/mui-joy/src/ListItemButton/ListItemButton.tsx +++ b/packages/mui-joy/src/ListItemButton/ListItemButton.tsx @@ -37,68 +37,72 @@ const useUtilityClasses = (ownerState: ListItemButtonOwnerState) => { return composedClasses; }; +export const StyledListItemButton = styled('div')<{ ownerState: ListItemButtonOwnerState }>( + ({ theme, ownerState }) => [ + { + ...(ownerState.selected && { + '--List-decorator-color': 'initial', + }), + ...(ownerState.disabled && { + '--List-decorator-color': + theme.vars.palette[ownerState.color!]?.[`${ownerState.variant!}DisabledColor`], + }), + WebkitTapHighlightColor: 'transparent', + boxSizing: 'border-box', + position: 'relative', + display: 'flex', + flexDirection: ownerState.orientation === 'vertical' ? 'column' : 'row', + alignItems: 'center', + textAlign: 'initial', + textDecoration: 'initial', // reset native anchor tag + backgroundColor: 'initial', // reset button background + // In some cases, ListItemButton is a child of ListItem so the margin needs to be controlled by the ListItem. The value is negative to account for the ListItem's padding + marginInline: 'var(--List-itemButton-marginInline)', + marginBlock: 'var(--List-itemButton-marginBlock)', + ...(ownerState['data-first-child'] === undefined && { + marginInlineStart: ownerState.row ? 'var(--List-gap)' : undefined, + marginBlockStart: ownerState.row ? undefined : 'var(--List-gap)', + }), + // account for the border width, so that all of the ListItemButtons content aligned horizontally + paddingBlock: 'calc(var(--List-item-paddingY) - var(--variant-borderWidth))', + // account for the border width, so that all of the ListItemButtons content aligned vertically + paddingInlineStart: + 'calc(var(--List-item-paddingLeft) + var(--List-item-startActionWidth, var(--internal-startActionWidth, 0px)))', // --internal variable makes it possible to customize the actionWidth from the top List + paddingInlineEnd: + 'calc(var(--List-item-paddingRight) + var(--List-item-endActionWidth, var(--internal-endActionWidth, 0px)))', // --internal variable makes it possible to customize the actionWidth from the top List + minBlockSize: 'var(--List-item-minHeight)', + border: 'none', + borderRadius: 'var(--List-item-radius)', + flexGrow: ownerState.row ? 0 : 1, + flexBasis: ownerState.row ? 'auto' : '0%', // for long text (in vertical), displays in multiple lines. + flexShrink: 0, + minInlineSize: 0, + // TODO: discuss the transition approach in a separate PR. This value is copied from mui-material Button. + transition: + 'background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms', + fontSize: 'var(--List-item-fontSize)', + fontFamily: theme.vars.fontFamily.body, + ...(ownerState.selected && { + fontWeight: theme.vars.fontWeight.md, + }), + [theme.focus.selector]: theme.focus.default, + }, + theme.variants[ownerState.variant!]?.[ownerState.color!], + { '&:hover': theme.variants[`${ownerState.variant!}Hover`]?.[ownerState.color!] }, + { '&:active': theme.variants[`${ownerState.variant!}Active`]?.[ownerState.color!] }, + { + [`&.${listItemButtonClasses.disabled}`]: + theme.variants[`${ownerState.variant!}Disabled`]?.[ownerState.color!], + }, + ], +); + // MenuItem uses ListItemButtonRoot (not the whole ListItemButton) to leverage only styles and CSS variables. -export const ListItemButtonRoot = styled('div', { +export const ListItemButtonRoot = styled(StyledListItemButton, { name: 'JoyListItemButton', slot: 'Root', overridesResolver: (props, styles) => styles.root, -})<{ ownerState: ListItemButtonOwnerState }>(({ theme, ownerState }) => [ - { - ...(ownerState.selected && { - '--List-decorator-color': 'initial', - }), - ...(ownerState.disabled && { - '--List-decorator-color': - theme.vars.palette[ownerState.color!]?.[`${ownerState.variant!}DisabledColor`], - }), - WebkitTapHighlightColor: 'transparent', - boxSizing: 'border-box', - position: 'relative', - display: 'flex', - flexDirection: ownerState.orientation === 'vertical' ? 'column' : 'row', - alignItems: 'center', - textAlign: 'initial', - textDecoration: 'initial', // reset native anchor tag - backgroundColor: 'initial', // reset button background - // In some cases, ListItemButton is a child of ListItem so the margin needs to be controlled by the ListItem. The value is negative to account for the ListItem's padding - marginInline: 'var(--List-itemButton-marginInline)', - marginBlock: 'var(--List-itemButton-marginBlock)', - ...(ownerState['data-first-child'] === undefined && { - marginInlineStart: ownerState.row ? 'var(--List-gap)' : undefined, - marginBlockStart: ownerState.row ? undefined : 'var(--List-gap)', - }), - // account for the border width, so that all of the ListItemButtons content aligned horizontally - paddingBlock: 'calc(var(--List-item-paddingY) - var(--variant-borderWidth))', - // account for the border width, so that all of the ListItemButtons content aligned vertically - paddingInlineStart: - 'calc(var(--List-item-paddingLeft) + var(--List-item-startActionWidth, var(--internal-startActionWidth, 0px)))', // --internal variable makes it possible to customize the actionWidth from the top List - paddingInlineEnd: - 'calc(var(--List-item-paddingRight) + var(--List-item-endActionWidth, var(--internal-endActionWidth, 0px)))', // --internal variable makes it possible to customize the actionWidth from the top List - minBlockSize: 'var(--List-item-minHeight)', - border: 'none', - borderRadius: 'var(--List-item-radius)', - flexGrow: ownerState.row ? 0 : 1, - flexBasis: ownerState.row ? 'auto' : '0%', // for long text (in vertical), displays in multiple lines. - flexShrink: 0, - minInlineSize: 0, - // TODO: discuss the transition approach in a separate PR. This value is copied from mui-material Button. - transition: - 'background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms', - fontSize: 'var(--List-item-fontSize)', - fontFamily: theme.vars.fontFamily.body, - ...(ownerState.selected && { - fontWeight: theme.vars.fontWeight.md, - }), - [theme.focus.selector]: theme.focus.default, - }, - theme.variants[ownerState.variant!]?.[ownerState.color!], - { '&:hover': theme.variants[`${ownerState.variant!}Hover`]?.[ownerState.color!] }, - { '&:active': theme.variants[`${ownerState.variant!}Active`]?.[ownerState.color!] }, - { - [`&.${listItemButtonClasses.disabled}`]: - theme.variants[`${ownerState.variant!}Disabled`]?.[ownerState.color!], - }, -]); +})({}); const ListItemButton = React.forwardRef(function ListItemButton(inProps, ref) { const props = useThemeProps({ diff --git a/packages/mui-joy/src/Select/Select.tsx b/packages/mui-joy/src/Select/Select.tsx index 19eecb0b1ad1e9..8fb32af35e4371 100644 --- a/packages/mui-joy/src/Select/Select.tsx +++ b/packages/mui-joy/src/Select/Select.tsx @@ -79,7 +79,6 @@ const SelectRoot = styled('div', { const variantStyle = theme.variants[`${ownerState.variant!}`]?.[ownerState.color!]; return [ { - '--focus-outline-offset': `calc(${theme.vars.focus.thickness} * -1)`, // to prevent the focus outline from being cut by overflow '--Select-radius': theme.vars.radius.sm, '--Select-gap': '0.5rem', '--Select-placeholderOpacity': 0.5, @@ -108,10 +107,10 @@ const SelectRoot = styled('div', { // variables for controlling child components '--Select-decorator-childOffset': 'min(calc(var(--Select-paddingInline) - (var(--Select-minHeight) - 2 * var(--variant-borderWidth) - var(--Select-decorator-childHeight)) / 2), var(--Select-paddingInline))', - '--internal-paddingBlock': + '--_Select-paddingBlock': 'max((var(--Select-minHeight) - 2 * var(--variant-borderWidth) - var(--Select-decorator-childHeight)) / 2, 0px)', '--Select-decorator-childRadius': - 'max(var(--Select-radius) - var(--internal-paddingBlock), min(var(--internal-paddingBlock) / 2, var(--Select-radius) / 2))', + 'max(var(--Select-radius) - var(--_Select-paddingBlock), min(var(--_Select-paddingBlock) / 2, var(--Select-radius) / 2))', '--Button-minHeight': 'var(--Select-decorator-childHeight)', '--IconButton-size': 'var(--Select-decorator-childHeight)', '--Button-radius': 'var(--Select-decorator-childRadius)', @@ -203,6 +202,7 @@ const SelectListbox = styled(ListRoot, { })<{ ownerState: SelectOwnerState }>(({ theme, ownerState }) => { const variantStyle = theme.variants[ownerState.variant!]?.[ownerState.color!]; return { + '--focus-outline-offset': `calc(${theme.vars.focus.thickness} * -1)`, // to prevent the focus outline from being cut by overflow '--List-radius': theme.vars.radius.sm, '--List-item-stickyBackground': variantStyle?.backgroundColor || diff --git a/packages/mui-joy/src/Stack/StackProps.ts b/packages/mui-joy/src/Stack/StackProps.ts index 351f7fddfb2a89..86ab75748a2b0c 100644 --- a/packages/mui-joy/src/Stack/StackProps.ts +++ b/packages/mui-joy/src/Stack/StackProps.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import { OverrideProps } from '@mui/types'; import { ResponsiveStyleValue } from '@mui/system'; -import { SxProps } from '../styles/types'; +import { SxProps, SystemProps } from '../styles/types'; export type StackSlot = 'root'; @@ -30,7 +30,7 @@ export interface StackTypeMap

{ * The system prop, which allows defining system overrides as well as additional CSS styles. */ sx?: SxProps; - }; + } & SystemProps; defaultComponent: D; } diff --git a/packages/mui-joy/src/Textarea/Textarea.tsx b/packages/mui-joy/src/Textarea/Textarea.tsx index 2266ed7c1a8979..cabef266ae0219 100644 --- a/packages/mui-joy/src/Textarea/Textarea.tsx +++ b/packages/mui-joy/src/Textarea/Textarea.tsx @@ -74,10 +74,10 @@ const TextareaRoot = styled('div', { '--Icon-fontSize': '1.75rem', }), // variables for controlling child components - '--internal-paddingBlock': + '--_Textarea-paddingBlock': 'max((var(--Textarea-minHeight) - 2 * var(--variant-borderWidth) - var(--Textarea-decorator-childHeight)) / 2, 0px)', '--Textarea-decorator-childRadius': - 'max(var(--Textarea-radius) - var(--internal-paddingBlock), min(var(--internal-paddingBlock) / 2, var(--Textarea-radius) / 2))', + 'max(var(--Textarea-radius) - var(--_Textarea-paddingBlock), min(var(--_Textarea-paddingBlock) / 2, var(--Textarea-radius) / 2))', '--Button-minHeight': 'var(--Textarea-decorator-childHeight)', '--IconButton-size': 'var(--Textarea-decorator-childHeight)', '--Button-radius': 'var(--Textarea-decorator-childRadius)', diff --git a/packages/mui-joy/src/internal/svg-icons/ArrowDropDown.tsx b/packages/mui-joy/src/internal/svg-icons/ArrowDropDown.tsx new file mode 100644 index 00000000000000..2c6a702fc7ccf8 --- /dev/null +++ b/packages/mui-joy/src/internal/svg-icons/ArrowDropDown.tsx @@ -0,0 +1,7 @@ +import * as React from 'react'; +import createSvgIcon from '../../utils/createSvgIcon'; + +/** + * @ignore - internal component. + */ +export default createSvgIcon(, 'ArrowDropDown'); diff --git a/packages/mui-joy/src/styles/components.d.ts b/packages/mui-joy/src/styles/components.d.ts index 40d34f4436e2bc..82da1cf230c957 100644 --- a/packages/mui-joy/src/styles/components.d.ts +++ b/packages/mui-joy/src/styles/components.d.ts @@ -5,6 +5,21 @@ import { AspectRatioOwnerState, AspectRatioSlot, } from '../AspectRatio/AspectRatioProps'; +import { + AutocompleteProps, + AutocompleteOwnerState, + AutocompleteSlot, +} from '../Autocomplete/AutocompleteProps'; +import { + AutocompleteListboxProps, + AutocompleteListboxOwnerState, + AutocompleteListboxSlot, +} from '../AutocompleteListbox/AutocompleteListboxProps'; +import { + AutocompleteOptionProps, + AutocompleteOptionOwnerState, + AutocompleteOptionSlot, +} from '../AutocompleteOption/AutocompleteOptionProps'; import { AvatarProps, AvatarOwnerState, AvatarSlot } from '../Avatar/AvatarProps'; import { AvatarGroupProps, @@ -170,6 +185,30 @@ export interface Components { defaultProps?: Partial; styleOverrides?: OverridesStyleRules; }; + JoyAutocomplete?: { + defaultProps?: Partial>; + styleOverrides?: OverridesStyleRules< + AutocompleteSlot, + AutocompleteOwnerState, + Theme + >; + }; + JoyAutocompleteListbox?: { + defaultProps?: Partial; + styleOverrides?: OverridesStyleRules< + AutocompleteListboxSlot, + AutocompleteListboxOwnerState, + Theme + >; + }; + JoyAutocompleteOption?: { + defaultProps?: Partial; + styleOverrides?: OverridesStyleRules< + AutocompleteOptionSlot, + AutocompleteOptionOwnerState, + Theme + >; + }; JoyAvatar?: { defaultProps?: Partial; styleOverrides?: OverridesStyleRules; diff --git a/packages/mui-joy/src/styles/extendTheme.spec.ts b/packages/mui-joy/src/styles/extendTheme.spec.ts index 42b674029b2180..71c8b24b3c0eaf 100644 --- a/packages/mui-joy/src/styles/extendTheme.spec.ts +++ b/packages/mui-joy/src/styles/extendTheme.spec.ts @@ -1,5 +1,8 @@ import { AlertOwnerState } from '@mui/joy/Alert'; import { AspectRatioOwnerState } from '@mui/joy/AspectRatio'; +import { AutocompleteOwnerState } from '@mui/joy/Autocomplete'; +import { AutocompleteListboxOwnerState } from '@mui/joy/AutocompleteListbox'; +import { AutocompleteOptionOwnerState } from '@mui/joy/AutocompleteOption'; import { AvatarOwnerState } from '@mui/joy/Avatar'; import { AvatarGroupOwnerState } from '@mui/joy/AvatarGroup'; import { BadgeOwnerState } from '@mui/joy/Badge'; @@ -98,6 +101,125 @@ extendTheme({ }, }, }, + JoyAutocomplete: { + defaultProps: { + freeSolo: true, + }, + styleOverrides: { + root: ({ ownerState }) => { + expectType< + AutocompleteOwnerState & Record, + typeof ownerState + >(ownerState); + return {}; + }, + wrapper: ({ ownerState }) => { + expectType< + AutocompleteOwnerState & Record, + typeof ownerState + >(ownerState); + return {}; + }, + input: ({ ownerState }) => { + expectType< + AutocompleteOwnerState & Record, + typeof ownerState + >(ownerState); + return {}; + }, + startDecorator: ({ ownerState }) => { + expectType< + AutocompleteOwnerState & Record, + typeof ownerState + >(ownerState); + return {}; + }, + endDecorator: ({ ownerState }) => { + expectType< + AutocompleteOwnerState & Record, + typeof ownerState + >(ownerState); + return {}; + }, + clearIndicator: ({ ownerState }) => { + expectType< + AutocompleteOwnerState & Record, + typeof ownerState + >(ownerState); + return {}; + }, + popupIndicator: ({ ownerState }) => { + expectType< + AutocompleteOwnerState & Record, + typeof ownerState + >(ownerState); + return {}; + }, + listbox: ({ ownerState }) => { + expectType< + AutocompleteOwnerState & Record, + typeof ownerState + >(ownerState); + return {}; + }, + option: ({ ownerState }) => { + expectType< + AutocompleteOwnerState & Record, + typeof ownerState + >(ownerState); + return {}; + }, + loading: ({ ownerState }) => { + expectType< + AutocompleteOwnerState & Record, + typeof ownerState + >(ownerState); + return {}; + }, + noOptions: ({ ownerState }) => { + expectType< + AutocompleteOwnerState & Record, + typeof ownerState + >(ownerState); + return {}; + }, + limitTag: ({ ownerState }) => { + expectType< + AutocompleteOwnerState & Record, + typeof ownerState + >(ownerState); + return {}; + }, + }, + }, + JoyAutocompleteListbox: { + defaultProps: { + variant: 'solid', + color: 'primary', + }, + styleOverrides: { + root: ({ ownerState }) => { + expectType, typeof ownerState>( + ownerState, + ); + return {}; + }, + }, + }, + JoyAutocompleteOption: { + defaultProps: { + variant: 'solid', + color: 'primary', + }, + styleOverrides: { + root: ({ ownerState }) => { + expectType, typeof ownerState>( + ownerState, + ); + return {}; + }, + }, + }, JoyAvatar: { defaultProps: { variant: 'outlined', diff --git a/packages/mui-joy/src/utils/useSlot.test.tsx b/packages/mui-joy/src/utils/useSlot.test.tsx new file mode 100644 index 00000000000000..c3378938daf0f1 --- /dev/null +++ b/packages/mui-joy/src/utils/useSlot.test.tsx @@ -0,0 +1,227 @@ +import * as React from 'react'; +import { expect } from 'chai'; +import { createRenderer } from 'test/utils'; +import PopperUnstyled from '@mui/base/PopperUnstyled'; +import { SlotComponentProps } from '@mui/base/utils'; +import { styled } from '../styles'; +import useSlot from './useSlot'; + +describe('useSlot', () => { + const { render } = createRenderer(); + + describe('single slot', () => { + const ItemRoot = styled('button')({}); + const Item = React.forwardRef< + HTMLButtonElement, + { component?: React.ElementType; href?: string } + >((props, ref) => { + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: 'root', + elementType: ItemRoot, + externalForwardedProps: props, + ownerState: {}, + }); + return ; + }); + + it('should render correct tag', () => { + const { getByRole } = render(); + expect(getByRole('button')).toBeVisible(); + }); + + it('should change leaf component and spread props', () => { + const { getByRole } = render(); + expect(getByRole('link')).toBeVisible(); + }); + }); + + describe('multiple slots', () => { + const ItemRoot = styled('button')({}); + const ItemDecorator = styled('span')({}); + const Item = React.forwardRef< + HTMLButtonElement, + { + className?: string; + component?: React.ElementType; + href?: string; + components?: { root?: React.ElementType; decorator?: React.ElementType }; + componentsProps?: { + root?: SlotComponentProps<'button', Record, {}>; + decorator?: SlotComponentProps<'span', { size?: 'sm' | 'md' } & Record, {}>; + }; + } + >((props, ref) => { + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: 'root', + elementType: ItemRoot, + externalForwardedProps: props, + ownerState: {}, + }); + const [SlotDecorator, decoratorProps] = useSlot('decorator', { + className: 'decorator', + elementType: ItemDecorator, + externalForwardedProps: props, + ownerState: {}, + getSlotOwnerState: (mergedProps) => ({ + size: mergedProps.size ?? 'md', + }), + }); + return ( + + + + ); + }); + + it('should render both tags', () => { + const { getByRole } = render(); + expect(getByRole('button')).toBeVisible(); + expect(getByRole('button').firstChild).to.have.tagName('span'); + }); + + it('should have classes', () => { + const { getByRole } = render(); + expect(getByRole('button')).to.have.class('root'); + expect(getByRole('button').firstChild).to.have.class('decorator'); + }); + + it('should append classes', () => { + const { getByRole } = render( + , + ); + expect(getByRole('button')).to.have.class('root'); + expect(getByRole('button')).to.have.class('foo-bar'); + expect(getByRole('button').firstChild).to.have.class('decorator'); + expect(getByRole('button').firstChild).to.have.class('foo-bar'); + }); + + it('slot has default size `md`', () => { + const { getByRole } = render(); + expect(getByRole('button').firstChild).to.have.class('size-md'); + }); + + it('slot ownerstate should be overriable', () => { + const { getByRole } = render(); + expect(getByRole('button').firstChild).to.have.class('size-sm'); + }); + + it('componentsProps has higher priority', () => { + const { getByRole } = render( + , + ); + expect(getByRole('button')).to.have.attribute('data-item', 'bar'); + }); + + it('can change root leaf component with `component` prop', () => { + const { getByRole } = render(); + expect(getByRole('link')).toBeVisible(); + }); + + it('use componentsProps `component` over `component` prop', () => { + const { getByRole } = render( + , + ); + expect(getByRole('link')).toBeVisible(); + }); + + it('can change decorator leaf component', () => { + const { getByRole } = render(); + expect(getByRole('button').firstChild).to.have.tagName('div'); + }); + }); + + describe('multiple slots with unstyled popper', () => { + const ItemRoot = styled('div')({}); + const ItemListbox = styled('ul')({ + margin: 'initial', // prevent Popper error. + }); + const ItemOption = styled('div')({}); + + const Item = (props: { + component?: React.ElementType; + components?: { + root?: React.ElementType; + listbox?: React.ElementType; + option?: React.ElementType; + }; + componentsProps?: { + root?: SlotComponentProps<'button', Record, {}>; + listbox?: SlotComponentProps<'span', Record, {}>; + option?: SlotComponentProps<'div', Record, {}>; + }; + }) => { + const ref = React.useRef(null); + const [SlotRoot, rootProps] = useSlot('root', { + ref, + className: 'root', + elementType: ItemRoot, + externalForwardedProps: props, + ownerState: {}, + }); + const [SlotListbox, listboxProps] = useSlot('listbox', { + className: 'listbox', + elementType: PopperUnstyled, + externalForwardedProps: props, + ownerState: {}, + additionalProps: { + open: true, + role: 'menu', + anchorEl: () => document.createElement('div'), + }, + internalForwardedProps: { + component: ItemListbox, + }, + }); + const [SlotOption, optionProps] = useSlot('option', { + className: 'option', + elementType: ItemOption, + externalForwardedProps: props, + ownerState: {}, + additionalProps: { + role: 'menuitem', + }, + }); + return ( + + + + + + + ); + }; + + it('should render popper with styled-component', () => { + const { getByRole } = render(); + expect(getByRole('menu')).toBeVisible(); + expect(getByRole('menu')).to.have.tagName('ul'); + expect(getByRole('menu')).to.have.class('listbox'); + expect(getByRole('menuitem')).to.have.tagName('li'); + }); + + it('the listbox slot should be replaceable', () => { + const Listbox = ({ component }: { component?: React.ElementType }) => ( +

    + ); + const { getByRole } = render(); + expect(getByRole('list')).toBeVisible(); + expect(getByRole('list')).not.to.have.attribute('class'); + expect(getByRole('list')).not.to.have.attribute('data-component'); + }); + + it('the listbox leaf component can be changed', () => { + const { getByRole } = render(); + expect(getByRole('menu')).to.have.tagName('div'); + }); + + it('the option leaf component can be changed', () => { + const { getByRole } = render(); + expect(getByRole('menuitem')).to.have.tagName('div'); + }); + }); +}); diff --git a/packages/mui-joy/src/utils/useSlot.ts b/packages/mui-joy/src/utils/useSlot.ts new file mode 100644 index 00000000000000..407e733ea7a711 --- /dev/null +++ b/packages/mui-joy/src/utils/useSlot.ts @@ -0,0 +1,161 @@ +import * as React from 'react'; +import { ClassValue } from 'clsx'; +import { unstable_useForkRef as useForkRef } from '@mui/utils'; +import { appendOwnerState, resolveComponentProps, mergeSlotProps } from '@mui/base/utils'; + +export type WithCommonProps = T & { + className?: string; + style?: React.CSSProperties; + ref?: React.Ref; +}; + +type EventHandlers = Record>; + +type ExtractComponentProps

    = P extends infer T | ((ownerState: any) => infer T) ? T : never; + +/** + * An internal function to create a Joy UI slot. + * + * This is an advanced version of MUI Base `useSlotProps` because Joy UI allows leaf component to be customized via `component` prop + * while MUI Base does not need to support leaf component customization. + * + * @param {string} name: name of the slot + * @param {object} parameters + * @returns {[Slot, slotProps]} The slot's React component and the slot's props + * + * Note: the returned slot's props + * - will never contain `component` prop. + * - might contain `as` prop. + */ +export default function useSlot< + T extends string, + ElementType extends React.ElementType, + SlotProps, + OwnerState extends {}, + ExternalSlotProps extends { component?: React.ElementType }, + ExternalForwardedProps extends { + component?: React.ElementType; + components?: { [k in T]?: React.ElementType }; + componentsProps?: { + [k in T]?: + | WithCommonProps + | ((ownerState: OwnerState) => WithCommonProps); + }; + }, + AdditionalProps, + SlotOwnerState extends {}, +>( + /** + * The slot's name. All Joy UI components should have `root` slot. + * + * If the name is `root`, the logic behaves differently from other slots, + * e.g. the `externalForwardedProps` are spread to `root` slot but not other slots. + */ + name: T, + parameters: (T extends 'root' ? { ref: React.ForwardedRef } : {}) & { + /** + * The slot's className + */ + className: ClassValue | ClassValue[]; + /** + * The slot's default styled-component + */ + elementType: ElementType; + /** + * The component's ownerState + */ + ownerState: OwnerState; + /** + * The `other` props from the consumer. It has to contain `component`, `components`, and `componentsProps`. + * The function will use those props to calculate the final leaf component and the returned props. + * + * If the slot is not `root`, the rest of the `externalForwardedProps` are neglected. + */ + externalForwardedProps: ExternalForwardedProps; + getSlotProps?: (other: EventHandlers) => WithCommonProps; + additionalProps?: WithCommonProps; + + // Joy UI specifics + /** + * For overriding the component's ownerState for the slot. + * This is required for some components that need styling via `ownerState`. + * + * It is a function because `componentsProps.{slot}` can be a function which has to be resolved first. + */ + getSlotOwnerState?: ( + mergedProps: SlotProps & + ExternalSlotProps & + ExtractComponentProps< + Exclude[T], undefined> + >, + ) => SlotOwnerState; + /** + * props forward to `T` only if leaf component is not provided. + * e.g. Autocomplete's listbox uses PopperUnstyled + StyledComponent + */ + internalForwardedProps?: any; + }, +) { + const { + className, + elementType, + ownerState, + externalForwardedProps, + getSlotOwnerState, + internalForwardedProps, + ...useSlotPropsParams + } = parameters; + const { + component: rootComponent, + components = { [name]: undefined }, + componentsProps = { [name]: undefined }, + ...other + } = externalForwardedProps; + + // `componentsProps[name]` can be a callback that receives the component's `ownerState`. + // `resolvedComponentsProps` is always a plain object. + const resolvedComponentsProps = resolveComponentProps(componentsProps[name], ownerState); + + const { + props: { component: slotComponent, ...mergedProps }, + internalRef, + } = mergeSlotProps({ + className, + ...useSlotPropsParams, + externalForwardedProps: name === 'root' ? other : undefined, + externalSlotProps: resolvedComponentsProps, + }); + + const ref = useForkRef( + internalRef, + // @ts-ignore `ref` is required for the 'root' slot + useForkRef(resolvedComponentsProps?.ref, name === 'root' ? parameters.ref : undefined), + ) as ((instance: any | null) => void) | null; + + const finalOwnerState = getSlotOwnerState + ? { ...ownerState, ...getSlotOwnerState(mergedProps as any) } + : ownerState; + + const LeafComponent = (name === 'root' ? slotComponent || rootComponent : slotComponent) as + | React.ElementType + | undefined; + + const props = appendOwnerState( + elementType, + { + ...(name === 'root' && !rootComponent && !components[name] && internalForwardedProps), + ...(name !== 'root' && !components[name] && internalForwardedProps), + ...(mergedProps as { className: string } & SlotProps & + ExternalSlotProps & + AdditionalProps & + (T extends 'root' ? ExternalForwardedProps : {})), + ...(LeafComponent && { + as: LeafComponent, + }), + ref, + }, + finalOwnerState as OwnerState & SlotOwnerState, + ); + + return [components[name] || elementType, props] as [ElementType, typeof props]; +} diff --git a/test/utils/initMatchers.ts b/test/utils/initMatchers.ts index c7fac2bdd894fe..adfcc8babaede9 100644 --- a/test/utils/initMatchers.ts +++ b/test/utils/initMatchers.ts @@ -103,7 +103,7 @@ declare global { * @example expect(() => render()).toWarnDev('single message') * @example expect(() => render()).toWarnDev(['first warning', 'then the second']) */ - toWarnDev(messages?: string | readonly string[]): void; + toWarnDev(messages?: string | readonly (string | boolean)[]): void; /** * Matches calls to `console.error` in the asserted callback. * @@ -111,7 +111,7 @@ declare global { * @example expect(() => render()).toErrorDev('single message') * @example expect(() => render()).toErrorDev(['first warning', 'then the second']) */ - toErrorDev(messages?: string | readonly string[]): void; + toErrorDev(messages?: string | readonly (string | boolean)[]): void; /** * Asserts that the given callback throws an error matching the given message in development (process.env.NODE_ENV !== 'production'). * In production it expects a minified error.