Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[joy-ui][Select] Support selection of multiple options #39454

Merged
merged 29 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions docs/data/joy/components/select/SelectMultiple.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as React from 'react';
import Select from '@mui/joy/Select';
import Option from '@mui/joy/Option';

export default function SelectMultiple() {
const handleChange = (event, newValue) => {
console.log(`You have choosen "${newValue}"`);
};
return (
<Select
defaultValue={['dog']}
multiple
onChange={handleChange}
sx={{
minWidth: '13rem',
}}
slotProps={{
listbox: {
sx: {
width: '100%',
},
},
}}
>
<Option value="dog">Dog</Option>
<Option value="cat">Cat</Option>
<Option value="fish">Fish</Option>
<Option value="bird">Bird</Option>
</Select>
);
}
34 changes: 34 additions & 0 deletions docs/data/joy/components/select/SelectMultiple.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as React from 'react';
import Select from '@mui/joy/Select';
import Option from '@mui/joy/Option';

export default function SelectMultiple() {
const handleChange = (
event: React.SyntheticEvent | null,
newValue: Array<string> | null,
) => {
console.log(`You have choosen "${newValue}"`);
};
return (
<Select
defaultValue={['dog']}
multiple
onChange={handleChange}
sx={{
minWidth: '13rem',
}}
slotProps={{
listbox: {
sx: {
width: '100%',
},
},
}}
>
<Option value="dog">Dog</Option>
<Option value="cat">Cat</Option>
<Option value="fish">Fish</Option>
<Option value="bird">Bird</Option>
</Select>
);
}
37 changes: 37 additions & 0 deletions docs/data/joy/components/select/SelectMultipleAppearance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import * as React from 'react';
import Select from '@mui/joy/Select';
import Option from '@mui/joy/Option';
import { Box, Chip } from '@mui/joy';

export default function SelectMultipleAppearance() {
return (
<Select
multiple
defaultValue={['dog', 'cat']}
renderValue={(selected) => (
<Box sx={{ display: 'flex', gap: '0.25rem' }}>
{selected.map((selectedOption) => (
<Chip variant="soft" color="primary">
{selectedOption.label}
</Chip>
))}
</Box>
)}
sx={{
minWidth: '15rem',
}}
slotProps={{
listbox: {
sx: {
width: '100%',
},
},
}}
>
<Option value="dog">Dog</Option>
<Option value="cat">Cat</Option>
<Option value="fish">Fish</Option>
<Option value="bird">Bird</Option>
</Select>
);
}
37 changes: 37 additions & 0 deletions docs/data/joy/components/select/SelectMultipleAppearance.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import * as React from 'react';
import Select from '@mui/joy/Select';
import Option from '@mui/joy/Option';
import { Box, Chip } from '@mui/joy';

export default function SelectMultipleAppearance() {
return (
<Select
multiple
defaultValue={['dog', 'cat']}
renderValue={(selected) => (
<Box sx={{ display: 'flex', gap: '0.25rem' }}>
{selected.map((selectedOption) => (
<Chip variant="soft" color="primary">
{selectedOption.label}
</Chip>
))}
</Box>
)}
sx={{
minWidth: '15rem',
}}
slotProps={{
listbox: {
sx: {
width: '100%',
},
},
}}
>
<Option value="dog">Dog</Option>
<Option value="cat">Cat</Option>
<Option value="fish">Fish</Option>
<Option value="bird">Bird</Option>
</Select>
);
}
36 changes: 36 additions & 0 deletions docs/data/joy/components/select/SelectMultipleFormSubmission.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as React from 'react';
import Button from '@mui/joy/Button';
import Select from '@mui/joy/Select';
import Option from '@mui/joy/Option';
import Stack from '@mui/joy/Stack';

export default function SelectMultipleFormSubmission() {
return (
<form
onSubmit={(event) => {
event.preventDefault();
const formData = new FormData(event.currentTarget);
const formJson = Object.fromEntries(formData.entries());
Copy link
Member

Choose a reason for hiding this comment

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

It is strange that formJson.pets is a string, not an array. Is this expected?

I got "[\"dog\"\" instead of ["dog"].

Copy link
Contributor Author

@sai6855 sai6855 Oct 23, 2023

Choose a reason for hiding this comment

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

When you console.log formJson.pets it indeed prints array, so issue you described maybe have to do something with alert On logging typeof formJson.pets i'm getting type as string. Ideally It should be object, let me check

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is this expected?

Yes, this is expected because if Select has multiple prop then value of hidden input is stringified. so users need to do JSON.parse to convert it to array.

if (Array.isArray(selectedOption)) {
if (selectedOption.length === 0) {
return '';
}
return JSON.stringify(selectedOption.map((o) => o.value));
}

So I've modified demo and parsed string to array. Now demo is looking good

Copy link
Member

Choose a reason for hiding this comment

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

Got it.

const selectedPets = JSON.parse(formJson.pets);
alert(JSON.stringify(selectedPets));
}}
>
<Stack spacing={2} alignItems="flex-start">
<Select
placeholder="Select a pet"
name="pets"
required
multiple
defaultValue={['dog', 'cat']}
sx={{ minWidth: 200 }}
>
<Option value="dog">Dog</Option>
<Option value="cat">Cat</Option>
<Option value="fish">Fish</Option>
<Option value="bird">Bird</Option>
</Select>
<Button type="submit">Submit</Button>
</Stack>
</form>
);
}
36 changes: 36 additions & 0 deletions docs/data/joy/components/select/SelectMultipleFormSubmission.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as React from 'react';
import Button from '@mui/joy/Button';
import Select from '@mui/joy/Select';
import Option from '@mui/joy/Option';
import Stack from '@mui/joy/Stack';

export default function SelectMultipleFormSubmission() {
return (
<form
onSubmit={(event) => {
event.preventDefault();
const formData = new FormData(event.currentTarget);
const formJson = Object.fromEntries((formData as any).entries());
const selectedPets = JSON.parse(formJson.pets);
alert(JSON.stringify(selectedPets));
}}
>
<Stack spacing={2} alignItems="flex-start">
<Select
placeholder="Select a pet"
name="pets"
required
multiple
defaultValue={['dog', 'cat']}
sx={{ minWidth: 200 }}
>
<Option value="dog">Dog</Option>
<Option value="cat">Cat</Option>
<Option value="fish">Fish</Option>
<Option value="bird">Bird</Option>
</Select>
<Button type="submit">Submit</Button>
</Stack>
</form>
);
}
21 changes: 21 additions & 0 deletions docs/data/joy/components/select/select.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,27 @@ const App = () => (
);
```

### Multiple selections

Set the `multiple` prop to let your users select multiple options from the list.
In contrast with single-selection mode, the options popup doesn't close after an item is selected, which enables users to continue choosing more options.

Note that in multiple selection mode, the `value` prop (and `defaultValue`) is an array.

{{"demo": "SelectMultiple.js"}}

#### Selected value appearance

Use the `renderValue` prop to customize the display of the selected options.

{{"demo": "SelectMultipleAppearance.js"}}

#### Form submission

The `Select` component supports `name` and `required` props that will be used when submitting the form.

{{"demo": "SelectMultipleFormSubmission.js"}}

Copy link
Member

Choose a reason for hiding this comment

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

I suggest to add a couple of more demos:

#### Selected value appearance

Display avatars of selected users end with "x friends".

#### Form submission

Show what the value looks like after submitted a form.

### Listbox

#### Maximum height
Expand Down
2 changes: 2 additions & 0 deletions docs/pages/joy-ui/api/select.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"indicator": { "type": { "name": "node" } },
"listboxId": { "type": { "name": "string" } },
"listboxOpen": { "type": { "name": "bool" } },
"multiple": { "type": { "name": "bool" } },
"name": { "type": { "name": "string" } },
"onChange": { "type": { "name": "func" } },
"onClose": { "type": { "name": "func" } },
Expand Down Expand Up @@ -113,6 +114,7 @@
"disabled",
"expanded",
"focusVisible",
"multiple",
"popper",
"sizeLg",
"sizeMd",
Expand Down
6 changes: 6 additions & 0 deletions docs/translations/api-docs-joy/select/select.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
"description": "<code>id</code> attribute of the listbox element. Also used to derive the <code>id</code> attributes of options."
},
"listboxOpen": { "description": "Controls the open state of the select&#39;s listbox." },
"multiple": {
"description": "If <code>true</code>, selecting multiple values is allowed. This affects the type of the <code>value</code>, <code>defaultValue</code>, and <code>onChange</code> props."
},
"name": {
"description": "Name of the element. For example used by the server to identify the fields in form submits. If the name is provided, the component will render a hidden input element that can be submitted to a server."
},
Expand Down Expand Up @@ -85,6 +88,9 @@
"description": "Class name applied to {{nodeName}}.",
"nodeName": "the listbox slot"
},
"multiple": {
"description": "Class name applied to the root slot if <code>multiple=true</code>"
},
"colorPrimary": {
"description": "Class name applied to {{nodeName}} if {{conditions}}.",
"nodeName": "the root slot",
Expand Down
Loading