Skip to content

Commit

Permalink
[SelectUnstyled] Create unstyled select (+ hook) (#30113)
Browse files Browse the repository at this point in the history
Co-authored-by: Siriwat K <siriwatkunaporn@gmail.com>
  • Loading branch information
michaldudak and siriwatknp authored Jan 28, 2022
1 parent e048459 commit ec5df5f
Show file tree
Hide file tree
Showing 87 changed files with 6,748 additions and 22 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ module.exports = {
// This most often reports data that is defined after the component definition.
// This is safe to do and helps readability of the demo code since the data is mostly irrelevant.
'@typescript-eslint/no-use-before-define': 'off',
'react/prop-types': 'off',
},
},
{
Expand Down
23 changes: 23 additions & 0 deletions docs/pages/api-docs/multi-select-unstyled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as React from 'react';
import ApiPage from 'docs/src/modules/components/ApiPage';
import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations';
import jsonPageContent from './multi-select-unstyled.json';

export default function Page(props) {
const { descriptions, pageContent } = props;
return <ApiPage descriptions={descriptions} pageContent={pageContent} />;
}

Page.getInitialProps = () => {
const req = require.context(
'docs/translations/api-docs/multi-select-unstyled',
false,
/multi-select-unstyled.*.json$/,
);
const descriptions = mapApiPageTranslations(req);

return {
descriptions,
pageContent: jsonPageContent,
};
};
35 changes: 35 additions & 0 deletions docs/pages/api-docs/multi-select-unstyled.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"props": {
"autoFocus": { "type": { "name": "bool" } },
"components": {
"type": {
"name": "shape",
"description": "{ Listbox?: elementType, Popper?: elementType, Root?: elementType }"
},
"default": "{}"
},
"componentsProps": {
"type": {
"name": "shape",
"description": "{ listbox?: object, popper?: object, root?: object }"
},
"default": "{}"
},
"defaultListboxOpen": { "type": { "name": "bool" } },
"defaultValue": { "type": { "name": "array" }, "default": "[]" },
"disabled": { "type": { "name": "bool" } },
"listboxOpen": { "type": { "name": "bool" }, "default": "undefined" },
"onChange": { "type": { "name": "func" } },
"onListboxOpenChange": { "type": { "name": "func" } },
"renderValue": { "type": { "name": "func" } },
"value": { "type": { "name": "array" } }
},
"name": "MultiSelectUnstyled",
"styles": { "classes": [], "globalClasses": {}, "name": null },
"spread": true,
"forwardsRefTo": "HTMLButtonElement",
"filename": "/packages/mui-base/src/MultiSelectUnstyled/MultiSelectUnstyled.tsx",
"inheritance": null,
"demos": "<ul><li><a href=\"/components/selects/\">Selects</a></li></ul>",
"cssComponent": false
}
23 changes: 23 additions & 0 deletions docs/pages/api-docs/option-group-unstyled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as React from 'react';
import ApiPage from 'docs/src/modules/components/ApiPage';
import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations';
import jsonPageContent from './option-group-unstyled.json';

export default function Page(props) {
const { descriptions, pageContent } = props;
return <ApiPage descriptions={descriptions} pageContent={pageContent} />;
}

Page.getInitialProps = () => {
const req = require.context(
'docs/translations/api-docs/option-group-unstyled',
false,
/option-group-unstyled.*.json$/,
);
const descriptions = mapApiPageTranslations(req);

return {
descriptions,
pageContent: jsonPageContent,
};
};
29 changes: 29 additions & 0 deletions docs/pages/api-docs/option-group-unstyled.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"props": {
"component": { "type": { "name": "elementType" } },
"components": {
"type": {
"name": "shape",
"description": "{ Label?: elementType, List?: elementType, Root?: elementType }"
},
"default": "{}"
},
"componentsProps": {
"type": {
"name": "shape",
"description": "{ label?: object, list?: object, root?: object }"
},
"default": "{}"
},
"disabled": { "type": { "name": "bool" } },
"label": { "type": { "name": "node" } }
},
"name": "OptionGroupUnstyled",
"styles": { "classes": [], "globalClasses": {}, "name": null },
"spread": true,
"forwardsRefTo": "HTMLLIElement",
"filename": "/packages/mui-base/src/OptionGroupUnstyled/OptionGroupUnstyled.tsx",
"inheritance": null,
"demos": "<ul><li><a href=\"/components/selects/\">Selects</a></li></ul>",
"cssComponent": false
}
23 changes: 23 additions & 0 deletions docs/pages/api-docs/option-unstyled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as React from 'react';
import ApiPage from 'docs/src/modules/components/ApiPage';
import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations';
import jsonPageContent from './option-unstyled.json';

export default function Page(props) {
const { descriptions, pageContent } = props;
return <ApiPage descriptions={descriptions} pageContent={pageContent} />;
}

Page.getInitialProps = () => {
const req = require.context(
'docs/translations/api-docs/option-unstyled',
false,
/option-unstyled.*.json$/,
);
const descriptions = mapApiPageTranslations(req);

return {
descriptions,
pageContent: jsonPageContent,
};
};
23 changes: 23 additions & 0 deletions docs/pages/api-docs/option-unstyled.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"props": {
"value": { "type": { "name": "any" }, "required": true },
"component": { "type": { "name": "elementType" } },
"components": {
"type": { "name": "shape", "description": "{ Root?: elementType }" },
"default": "{}"
},
"componentsProps": {
"type": { "name": "shape", "description": "{ root?: object }" },
"default": "{}"
},
"disabled": { "type": { "name": "bool" } }
},
"name": "OptionUnstyled",
"styles": { "classes": [], "globalClasses": {}, "name": null },
"spread": true,
"forwardsRefTo": "HTMLLIElement",
"filename": "/packages/mui-base/src/OptionUnstyled/OptionUnstyled.tsx",
"inheritance": null,
"demos": "<ul><li><a href=\"/components/selects/\">Selects</a></li></ul>",
"cssComponent": false
}
23 changes: 23 additions & 0 deletions docs/pages/api-docs/select-unstyled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as React from 'react';
import ApiPage from 'docs/src/modules/components/ApiPage';
import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations';
import jsonPageContent from './select-unstyled.json';

export default function Page(props) {
const { descriptions, pageContent } = props;
return <ApiPage descriptions={descriptions} pageContent={pageContent} />;
}

Page.getInitialProps = () => {
const req = require.context(
'docs/translations/api-docs/select-unstyled',
false,
/select-unstyled.*.json$/,
);
const descriptions = mapApiPageTranslations(req);

return {
descriptions,
pageContent: jsonPageContent,
};
};
35 changes: 35 additions & 0 deletions docs/pages/api-docs/select-unstyled.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"props": {
"autoFocus": { "type": { "name": "bool" } },
"components": {
"type": {
"name": "shape",
"description": "{ Listbox?: elementType, Popper?: elementType, Root?: elementType }"
},
"default": "{}"
},
"componentsProps": {
"type": {
"name": "shape",
"description": "{ listbox?: object, popper?: object, root?: object }"
},
"default": "{}"
},
"defaultListboxOpen": { "type": { "name": "bool" } },
"defaultValue": { "type": { "name": "any" } },
"disabled": { "type": { "name": "bool" } },
"listboxOpen": { "type": { "name": "bool" }, "default": "undefined" },
"onChange": { "type": { "name": "func" } },
"onListboxOpenChange": { "type": { "name": "func" } },
"renderValue": { "type": { "name": "func" } },
"value": { "type": { "name": "any" } }
},
"name": "SelectUnstyled",
"styles": { "classes": [], "globalClasses": {}, "name": null },
"spread": true,
"forwardsRefTo": "HTMLButtonElement",
"filename": "/packages/mui-base/src/SelectUnstyled/SelectUnstyled.tsx",
"inheritance": null,
"demos": "<ul><li><a href=\"/components/selects/\">Selects</a></li></ul>",
"cssComponent": false
}
129 changes: 129 additions & 0 deletions docs/src/pages/components/selects/UnstyledSelectControlled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
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 StyledButton = styled('button')`
font-family: IBM Plex Sans, sans-serif;
font-size: 0.875rem;
box-sizing: border-box;
min-height: calc(1.5em + 22px);
min-width: 200px;
background: #fff;
border: 1px solid #ccc;
border-radius: 0.75em;
margin: 0.5em;
padding: 10px;
text-align: left;
line-height: 1.5;
color: #000;
&.${selectUnstyledClasses.focusVisible} {
outline: 4px solid rgba(100, 100, 100, 0.3);
}
&.${selectUnstyledClasses.expanded} {
border-radius: 0.75em 0.75em 0 0;
&::after {
content: '▴';
}
}
&::after {
content: '▾';
float: right;
}
`;

const StyledListbox = styled('ul')`
font-family: IBM Plex Sans, sans-serif;
font-size: 0.875rem;
box-sizing: border-box;
padding: 0;
margin: 0;
background-color: #fff;
min-width: 200px;
border: 1px solid #ccc;
border-top: none;
color: #000;
`;

const StyledOption = styled(OptionUnstyled)`
list-style: none;
padding: 4px 10px;
margin: 0;
border-bottom: 1px solid #ddd;
cursor: default;
&:last-of-type {
border-bottom: none;
}
&.${optionUnstyledClasses.disabled} {
color: #888;
}
&.${optionUnstyledClasses.selected} {
background-color: rgba(25, 118, 210, 0.08);
}
&.${optionUnstyledClasses.highlighted} {
background-color: #16d;
color: #fff;
}
&.${optionUnstyledClasses.highlighted}.${optionUnstyledClasses.selected} {
background-color: #05e;
color: #fff;
}
&:hover:not(.${optionUnstyledClasses.disabled}) {
background-color: #39e;
}
`;

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

function CustomSelect(props) {
const components = {
Root: StyledButton,
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.elementType,
Root: PropTypes.elementType,
}),
};

export default function UnstyledSelectsMultiple() {
const [value, setValue] = React.useState(10);
return (
<div>
<CustomSelect value={value} onChange={setValue}>
<StyledOption value={10}>Ten</StyledOption>
<StyledOption value={20}>Twenty</StyledOption>
<StyledOption value={30}>Thirty</StyledOption>
</CustomSelect>

<p>Selected value: {value}</p>
</div>
);
}
Loading

0 comments on commit ec5df5f

Please sign in to comment.