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] Create unstyled select (+ hook) #30113

Merged
merged 61 commits into from
Jan 28, 2022
Merged
Show file tree
Hide file tree
Changes from 59 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
09e24c1
Create useListbox implementation
michaldudak Oct 8, 2021
1bef211
Add few tests
michaldudak Nov 2, 2021
b2ba738
Add tests and small fixes
michaldudak Nov 3, 2021
0ff3247
Remove temporary demos
michaldudak Nov 3, 2021
140b8b9
Add more jsdoc comments
michaldudak Nov 3, 2021
3b632a5
Cleanup
michaldudak Nov 3, 2021
b9fc66c
Improvements after CR
michaldudak Nov 8, 2021
88d69db
Merge branch 'master' into unstyled-listbox
michaldudak Nov 9, 2021
3252a28
Merge branch 'master' into unstyled-listbox
michaldudak Nov 23, 2021
3e41655
Create SelectUnstyled and useSelect
michaldudak Nov 25, 2021
1e2da48
Merge remote-tracking branch 'upstream/master' into unstyled-select
michaldudak Dec 8, 2021
99c9c57
Fix TS compilation error
michaldudak Dec 8, 2021
864b85d
Fix proptypes generation in SelectUnstyled
michaldudak Dec 8, 2021
20c010d
Fix docs generation
michaldudak Dec 10, 2021
7dc728d
Fix conformance tests
michaldudak Dec 10, 2021
93e8a04
Fix passing ownerState to slots
michaldudak Dec 10, 2021
07f57e7
Merge remote-tracking branch 'upstream/master' into unstyled-select
michaldudak Dec 10, 2021
96627c5
Docs and demos
michaldudak Dec 13, 2021
432eaee
Merge branch 'master' into unstyled-select
michaldudak Dec 21, 2021
77b906a
Fix stylelint errors
michaldudak Dec 21, 2021
7b4a133
Fix tests
michaldudak Dec 21, 2021
fe0988f
Accept external event handlers
michaldudak Dec 21, 2021
0900af3
Export SingleSelectUnstyled and MultiSelectUnstyled
michaldudak Dec 21, 2021
c023e22
Correct imports in demos
michaldudak Dec 21, 2021
5e4cd6d
Fix the tests
michaldudak Dec 21, 2021
d263a44
Merge remote-tracking branch 'upstream/master' into unstyled-select
michaldudak Dec 21, 2021
7bda657
Merge remote-tracking branch 'upstream/master' into unstyled-select
michaldudak Dec 22, 2021
cfed174
Improve useSelect types, add demo
michaldudak Dec 22, 2021
a03bbae
Improve types
michaldudak Dec 22, 2021
ac01aaa
Polish the implementation and demos
michaldudak Dec 22, 2021
a61c6aa
Fix TS errors
michaldudak Dec 22, 2021
6cb1975
Move option rendering logic to OptionUnstyled and OptionGroupUnstyled
michaldudak Dec 23, 2021
8e723d1
Remove duplicated export
michaldudak Dec 27, 2021
a1feede
Move OptionUnstyled out of Select directory
michaldudak Dec 27, 2021
a6518ee
Prop types and API docs
michaldudak Dec 27, 2021
4a64150
Fix unit tests
michaldudak Dec 27, 2021
101174c
Fix problem with API docs generation
michaldudak Dec 27, 2021
6693c8e
Clean up the demos
michaldudak Dec 29, 2021
9fe65ea
Merge remote-tracking branch 'upstream/master' into unstyled-select
michaldudak Dec 29, 2021
a5b17c7
PR fixes
michaldudak Dec 29, 2021
22ed1e9
Export SelectUnstyled and MultiSelectUntyled
michaldudak Dec 29, 2021
9852828
Add a not about forwardRef
michaldudak Dec 29, 2021
ea26853
Move MultiSelectUnstyled to its own directory
michaldudak Dec 29, 2021
81b6b71
API docs
michaldudak Dec 29, 2021
72a19c4
Fix errors
michaldudak Dec 29, 2021
04b1fcd
Merge branch 'master' into unstyled-select
michaldudak Jan 18, 2022
3e00b30
Fix runtime errors in demos
michaldudak Jan 18, 2022
d81dbb5
Fix issues reported in PR
michaldudak Jan 19, 2022
91600c8
Merge remote-tracking branch 'upstream/master' into unstyled-select
michaldudak Jan 19, 2022
26a236f
Proptypes
michaldudak Jan 19, 2022
a778b69
Add preventDefault for arrow key presses
michaldudak Jan 19, 2022
064b927
Use enter key to open the dropdown
michaldudak Jan 21, 2022
bd2c8fe
Tests
michaldudak Jan 24, 2022
65ec3f0
Proptypes
michaldudak Jan 24, 2022
c52328a
Scroll highlighted element into view
michaldudak Jan 24, 2022
31aa964
Call scrollIntoView only if available
michaldudak Jan 24, 2022
b8f373a
Allow customizing popper
michaldudak Jan 24, 2022
2ace209
Merge remote-tracking branch 'upstream/master' into unstyled-select
michaldudak Jan 24, 2022
f1796d2
Update docs/src/pages/components/selects/UnstyledSelectObjectValues.tsx
michaldudak Jan 25, 2022
07d9f85
Test opening and selection with different keys
michaldudak Jan 26, 2022
7cbfc7d
Move focus management to useSelect, update tests and demos
michaldudak Jan 27, 2022
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
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',
Copy link
Member

Choose a reason for hiding this comment

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

Why did you decided to disable this on the whole project?

Copy link
Member Author

Choose a reason for hiding this comment

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

It's just for the demos. I don't think proptypes add anything valuable.
Currently, they were required for JS files, but not for TS and since we generate one from the other, it was tricky to make things right.

},
},
{
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