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

[SelectUnstyled] Add ability to post the select's value when submitting a form #33697

Merged
merged 7 commits into from Aug 12, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
237 changes: 237 additions & 0 deletions docs/data/base/components/select/UnstyledSelectObjectValuesForm.js
@@ -0,0 +1,237 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import SelectUnstyled, { selectUnstyledClasses } from '@mui/base/SelectUnstyled';
import OptionUnstyled, { optionUnstyledClasses } from '@mui/base/OptionUnstyled';
import PopperUnstyled from '@mui/base/PopperUnstyled';
import { styled } from '@mui/system';

const blue = {
100: '#DAECFF',
200: '#99CCF3',
400: '#3399FF',
500: '#007FFF',
600: '#0072E5',
900: '#003A75',
};

const grey = {
50: '#F3F6F9',
100: '#E7EBF0',
200: '#E0E3E7',
300: '#CDD2D7',
400: '#B2BAC2',
500: '#A0AAB4',
600: '#6F7E8C',
700: '#3E5060',
800: '#2D3843',
900: '#1A2027',
};

const StyledButton = styled('button')(
({ theme }) => `
font-family: IBM Plex Sans, sans-serif;
font-size: 0.875rem;
box-sizing: border-box;
min-height: calc(1.5em + 22px);
min-width: 320px;
background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
border: 1px solid ${theme.palette.mode === 'dark' ? grey[800] : grey[300]};
border-radius: 0.75em;
margin: 0.5em;
padding: 10px;
text-align: left;
line-height: 1.5;
color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};

&:hover {
background: ${theme.palette.mode === 'dark' ? '' : grey[100]};
border-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[400]};
}

&.${selectUnstyledClasses.focusVisible} {
outline: 3px solid ${theme.palette.mode === 'dark' ? blue[600] : blue[100]};
}

&.${selectUnstyledClasses.expanded} {
&::after {
content: '▴';
}
}

&::after {
content: '▾';
float: right;
}
`,
);

const StyledListbox = styled('ul')(
({ theme }) => `
font-family: IBM Plex Sans, sans-serif;
font-size: 0.875rem;
box-sizing: border-box;
padding: 5px;
margin: 10px 0;
min-width: 320px;
background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
border: 1px solid ${theme.palette.mode === 'dark' ? grey[800] : grey[300]};
border-radius: 0.75em;
color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
overflow: auto;
outline: 0px;
`,
);

const StyledOption = styled(OptionUnstyled)(
({ theme }) => `
list-style: none;
padding: 8px;
border-radius: 0.45em;
cursor: default;

&:last-of-type {
border-bottom: none;
}

&.${optionUnstyledClasses.selected} {
background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]};
color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]};
}

&.${optionUnstyledClasses.highlighted} {
background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
}

&.${optionUnstyledClasses.highlighted}.${optionUnstyledClasses.selected} {
background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]};
color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]};
}

&.${optionUnstyledClasses.disabled} {
color: ${theme.palette.mode === 'dark' ? grey[700] : grey[400]};
}

&:hover:not(.${optionUnstyledClasses.disabled}) {
background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
}
`,
);

const StyledPopper = styled(PopperUnstyled)`
z-index: 1;
`;

const Header = styled('h1')(
({ theme }) => `
font-family: IBM Plex Sans, sans-serif;
font-size: 1rem;
margin: 10px 0;
font-weight: 400;
color: ${theme.palette.mode === 'dark' ? grey[400] : grey[700]};
`,
);

const Button = styled('button')`
font-family: IBM Plex Sans, sans-serif;
font-weight: bold;
font-size: 0.875rem;
background-color: ${blue[500]};
padding: 12px 24px;
border-radius: 8px;
color: white;
transition: all 150ms ease;
cursor: pointer;
border: none;
vertical-align: middle;

&:hover {
background-color: ${blue[600]};
}
`;

function CustomSelect(props) {
const components = {
Root: StyledButton,
Copy link
Member

Choose a reason for hiding this comment

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

Are we sure about the use of a <button>? In https://www.w3.org/WAI/ARIA/apg/example-index/combobox/combobox-select-only.html, it's a <div role="combobox">. Curiously, all the other libraries I can benchmark use a button like us.

Copy link
Member Author

Choose a reason for hiding this comment

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

Unless it causes any issues, I'd like to keep it this way. The link you mentioned points to an example, not reference documentation, so I assume it's not an error to use a button here.

Listbox: StyledListbox,
Popper: StyledPopper,
...props.components,
};

return <SelectUnstyled {...props} components={components} />;
}

CustomSelect.propTypes = {
/**
* The components used for each slot inside the Select.
* Either a string to use a HTML element or a component.
* @default {}
*/
components: PropTypes.shape({
Listbox: PropTypes.elementType,
Popper: PropTypes.func,
Root: PropTypes.elementType,
}),
};

const characters = [
{ name: 'Frodo', race: 'Hobbit' },
{ name: 'Sam', race: 'Hobbit' },
{ name: 'Merry', race: 'Hobbit' },
{ name: 'Gandalf', race: 'Maia' },
{ name: 'Gimli', race: 'Dwarf' },
];

export default function UnstyledSelectObjectValues() {
const [character, setCharacter] = React.useState(characters[0]);

const getSerializedValue = (option) => {
if (option?.value == null) {
return '';
}

return `${option.value.race}.${option.value.name}`;
};

const handleSubmit = (event) => {
event.preventDefault();
const formData = new FormData(event.currentTarget);
alert(`character=${formData.get('character')}`);
};

return (
<div>
<Header>Default behavior</Header>
<form onSubmit={handleSubmit}>
<CustomSelect value={character} onChange={setCharacter} name="character">
{characters.map((c) => (
<StyledOption key={c.name} value={c}>
{c.name}
</StyledOption>
))}
</CustomSelect>

<Button type="submit">Submit</Button>
</form>

<Header>Custom getSerializedValue</Header>
<form onSubmit={handleSubmit}>
<CustomSelect
value={character}
onChange={setCharacter}
getSerializedValue={getSerializedValue}
name="character"
>
{characters.map((c) => (
<StyledOption key={c.name} value={c}>
{c.name}
</StyledOption>
))}
</CustomSelect>

<Button type="submit">Submit</Button>
</form>
</div>
);
}