diff --git a/docs/data/base/components/select/UnstyledSelectIntroduction.js b/docs/data/base/components/select/UnstyledSelectIntroduction.js
index 63d77cc40e58a1..a2523d6b2d540d 100644
--- a/docs/data/base/components/select/UnstyledSelectIntroduction.js
+++ b/docs/data/base/components/select/UnstyledSelectIntroduction.js
@@ -33,7 +33,7 @@ const Button = React.forwardRef(function Button(props, ref) {
return (
{other.children}
- {ownerState.open ? : }
+
);
});
diff --git a/docs/data/base/components/select/UnstyledSelectIntroduction.tsx b/docs/data/base/components/select/UnstyledSelectIntroduction.tsx
index a6aed20058c968..ea57e3bc07f25e 100644
--- a/docs/data/base/components/select/UnstyledSelectIntroduction.tsx
+++ b/docs/data/base/components/select/UnstyledSelectIntroduction.tsx
@@ -39,7 +39,7 @@ const Button = React.forwardRef(function Button(
return (
{other.children}
- {ownerState.open ? : }
+
);
});
diff --git a/docs/data/base/components/select/UseSelect.js b/docs/data/base/components/select/UseSelect.js
index 7e4f2e46eb6309..a3709c24a474b9 100644
--- a/docs/data/base/components/select/UseSelect.js
+++ b/docs/data/base/components/select/UseSelect.js
@@ -2,6 +2,16 @@ import * as React from 'react';
import PropTypes from 'prop-types';
import { useSelect } from '@mui/base';
import { styled } from '@mui/system';
+import UnfoldMoreRoundedIcon from '@mui/icons-material/UnfoldMoreRounded';
+
+const blue = {
+ 100: '#DAECFF',
+ 200: '#99CCF3',
+ 400: '#3399FF',
+ 500: '#007FFF',
+ 600: '#0072E5',
+ 900: '#003A75',
+};
const grey = {
50: '#f6f8fa',
@@ -20,29 +30,44 @@ const Root = styled('div')`
position: relative;
`;
-const Toggle = styled('div')(
+const Toggle = 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;
+ padding: 12px;
border-radius: 12px;
text-align: left;
line-height: 1.5;
+ background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- background: var(--color, ${theme.palette.mode === 'dark' ? grey[900] : '#fff'});
- display: inline-flex;
- align-items: center;
- justify-content: center;
- cursor: default;
+ position: relative;
+
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 120ms;
- & .placeholder {
- opacity: 0.8;
+ box-shadow: 0 0 0 2px var(--color) inset;
+
+ &:hover {
+ background: ${theme.palette.mode === 'dark' ? grey[800] : grey[50]};
+ border-color: ${theme.palette.mode === 'dark' ? grey[600] : grey[300]};
+ }
+
+ &:focus-visible {
+ border-color: ${blue[400]};
+ outline: 3px solid ${theme.palette.mode === 'dark' ? grey[600] : grey[200]};
+ }
+
+ & > svg {
+ font-size: 1rem;
+ position: absolute;
+ height: 100%;
+ top: 0;
+ right: 10px;
}
`,
);
@@ -74,32 +99,62 @@ const Listbox = styled('ul')(
&.hidden {
opacity: 0;
visibility: hidden;
- transition: opacity 0.4s 0.5s ease, visibility 0.4s 0.5s step-end;
+ transition: opacity 0.4s ease, visibility 0.4s step-end;
+ }
+ `,
+);
+
+const Option = styled('li')(
+ ({ theme }) => `
+ padding: 8px;
+ border-radius: 0.45em;
+
+ &[aria-selected='true'] {
+ background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]};
+ color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]};
}
- & > li {
- padding: 8px;
- border-radius: 0.45em;
+ &.highlighted,
+ &:hover {
+ background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
+ color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
+ }
- &:hover {
- background: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
- }
+ &[aria-selected='true'].highlighted {
+ background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]};
+ color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]};
+ }
- &[aria-selected='true'] {
- background: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- }
+ &:before {
+ content: '';
+ width: 1ex;
+ height: 1ex;
+ margin-right: 1ex;
+ background-color: var(--color);
+ display: inline-block;
+ border-radius: 50%;
+ vertical-align: middle;
}
`,
);
+function renderSelectedValue(value, options) {
+ const selectedOption = options.find((option) => option.value === value);
+
+ return selectedOption ? `${selectedOption.label} (${value})` : null;
+}
+
function CustomSelect({ options, placeholder }) {
const listboxRef = React.useRef(null);
const [listboxVisible, setListboxVisible] = React.useState(false);
- const { getButtonProps, getListboxProps, getOptionProps, value } = useSelect({
- listboxRef,
- options,
- });
+ const { getButtonProps, getListboxProps, getOptionProps, getOptionState, value } =
+ useSelect({
+ listboxRef,
+ onOpenChange: setListboxVisible,
+ open: listboxVisible,
+ options,
+ });
React.useEffect(() => {
if (listboxVisible) {
@@ -108,21 +163,32 @@ function CustomSelect({ options, placeholder }) {
}, [listboxVisible]);
return (
- setListboxVisible(true)}
- onMouseOut={() => setListboxVisible(false)}
- onFocus={() => setListboxVisible(true)}
- onBlur={() => setListboxVisible(false)}
- >
+
- {value ?? {placeholder ?? ' '} }
+ {renderSelectedValue(value, options) || (
+ {placeholder ?? ' '}
+ )}
+
+
-
- {options.map((option) => (
-
- {option.label}
-
- ))}
+
+ {options.map((option) => {
+ const optionState = getOptionState(option);
+ return (
+
+ {option.label}
+
+ );
+ })}
);
diff --git a/docs/data/base/components/select/UseSelect.tsx b/docs/data/base/components/select/UseSelect.tsx
index 5ea4417d2002a2..68b2cec36fd3be 100644
--- a/docs/data/base/components/select/UseSelect.tsx
+++ b/docs/data/base/components/select/UseSelect.tsx
@@ -1,6 +1,16 @@
import * as React from 'react';
import { useSelect, SelectOption } from '@mui/base';
import { styled } from '@mui/system';
+import UnfoldMoreRoundedIcon from '@mui/icons-material/UnfoldMoreRounded';
+
+const blue = {
+ 100: '#DAECFF',
+ 200: '#99CCF3',
+ 400: '#3399FF',
+ 500: '#007FFF',
+ 600: '#0072E5',
+ 900: '#003A75',
+};
const grey = {
50: '#f6f8fa',
@@ -19,29 +29,44 @@ const Root = styled('div')`
position: relative;
`;
-const Toggle = styled('div')(
+const Toggle = 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;
+ padding: 12px;
border-radius: 12px;
text-align: left;
line-height: 1.5;
+ background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
- background: var(--color, ${theme.palette.mode === 'dark' ? grey[900] : '#fff'});
- display: inline-flex;
- align-items: center;
- justify-content: center;
- cursor: default;
+ position: relative;
+
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 120ms;
- & .placeholder {
- opacity: 0.8;
+ box-shadow: 0 0 0 2px var(--color) inset;
+
+ &:hover {
+ background: ${theme.palette.mode === 'dark' ? grey[800] : grey[50]};
+ border-color: ${theme.palette.mode === 'dark' ? grey[600] : grey[300]};
+ }
+
+ &:focus-visible {
+ border-color: ${blue[400]};
+ outline: 3px solid ${theme.palette.mode === 'dark' ? grey[600] : grey[200]};
+ }
+
+ & > svg {
+ font-size: 1rem;
+ position: absolute;
+ height: 100%;
+ top: 0;
+ right: 10px;
}
`,
);
@@ -73,20 +98,41 @@ const Listbox = styled('ul')(
&.hidden {
opacity: 0;
visibility: hidden;
- transition: opacity 0.4s 0.5s ease, visibility 0.4s 0.5s step-end;
+ transition: opacity 0.4s ease, visibility 0.4s step-end;
+ }
+ `,
+);
+
+const Option = styled('li')(
+ ({ theme }) => `
+ padding: 8px;
+ border-radius: 0.45em;
+
+ &[aria-selected='true'] {
+ background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]};
+ color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]};
}
- & > li {
- padding: 8px;
- border-radius: 0.45em;
+ &.highlighted,
+ &:hover {
+ background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
+ color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
+ }
- &:hover {
- background: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]};
- }
+ &[aria-selected='true'].highlighted {
+ background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[100]};
+ color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]};
+ }
- &[aria-selected='true'] {
- background: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
- }
+ &:before {
+ content: '';
+ width: 1ex;
+ height: 1ex;
+ margin-right: 1ex;
+ background-color: var(--color);
+ display: inline-block;
+ border-radius: 50%;
+ vertical-align: middle;
}
`,
);
@@ -96,14 +142,23 @@ interface Props {
placeholder?: string;
}
+function renderSelectedValue(value: string | null, options: SelectOption[]) {
+ const selectedOption = options.find((option) => option.value === value);
+
+ return selectedOption ? `${selectedOption.label} (${value})` : null;
+}
+
function CustomSelect({ options, placeholder }: Props) {
const listboxRef = React.useRef(null);
const [listboxVisible, setListboxVisible] = React.useState(false);
- const { getButtonProps, getListboxProps, getOptionProps, value } = useSelect({
- listboxRef,
- options,
- });
+ const { getButtonProps, getListboxProps, getOptionProps, getOptionState, value } =
+ useSelect({
+ listboxRef,
+ onOpenChange: setListboxVisible,
+ open: listboxVisible,
+ options,
+ });
React.useEffect(() => {
if (listboxVisible) {
@@ -112,21 +167,32 @@ function CustomSelect({ options, placeholder }: Props) {
}, [listboxVisible]);
return (
- setListboxVisible(true)}
- onMouseOut={() => setListboxVisible(false)}
- onFocus={() => setListboxVisible(true)}
- onBlur={() => setListboxVisible(false)}
- >
+
- {value ?? {placeholder ?? ' '} }
+ {renderSelectedValue(value, options) || (
+ {placeholder ?? ' '}
+ )}
+
+
-
- {options.map((option) => (
-
- {option.label}
-
- ))}
+
+ {options.map((option) => {
+ const optionState = getOptionState(option);
+ return (
+
+ {option.label}
+
+ );
+ })}
);
diff --git a/docs/data/base/components/select/select.md b/docs/data/base/components/select/select.md
index fe8eb4a4597b2c..049fb639b453e4 100644
--- a/docs/data/base/components/select/select.md
+++ b/docs/data/base/components/select/select.md
@@ -161,8 +161,9 @@ With hooks, you can take full control over how your component is rendered, and d
You may not need to use hooks unless you find that you're limited by the customization options of their component counterparts—for instance, if your component requires significantly different [structure](#anatomy).
:::
-The following example shows a select that opens when hovered over or focused.
-It can be controlled by a mouse/touch or a keyboard.
+The following example shows a select built with a hook.
+Note how this component does not include any built-in classes.
+The resulting HTML is much smaller compared to the unstyled component version, as the class names are not applied.
{{"demo": "UseSelect.js", "defaultCodeOpen": false}}