diff --git a/docs/data/joy/components/select/SelectGroupedOptions.js b/docs/data/joy/components/select/SelectGroupedOptions.js index 5ad34fd4494d01..f34ff09f5ba370 100644 --- a/docs/data/joy/components/select/SelectGroupedOptions.js +++ b/docs/data/joy/components/select/SelectGroupedOptions.js @@ -41,11 +41,10 @@ export default function SelectGroupedOptions() { {index !== 0 && } - + {name} ({animals.length}) diff --git a/docs/data/joy/components/select/select.md b/docs/data/joy/components/select/select.md index 6ac7667a495953..c55b37a90514dd 100644 --- a/docs/data/joy/components/select/select.md +++ b/docs/data/joy/components/select/select.md @@ -103,7 +103,7 @@ We're also using the `ListDivider` as a visual separator. Take a look at [selected value appearance](#selected-value-appearance) to see how to customize its appearance. ::: -#### Group options +### Grouped options To create a [listbox with grouped options](https://www.w3.org/WAI/ARIA/apg/example-index/listbox/listbox-grouped.html), wrap the `Option` with `List` component and provide an associated label using `ListItem`. That way, you'll have a consistent height and will be able to leverage nested CSS variables. diff --git a/packages/mui-joy/src/List/List.tsx b/packages/mui-joy/src/List/List.tsx index ad3240910b9c21..107609bb32ce84 100644 --- a/packages/mui-joy/src/List/List.tsx +++ b/packages/mui-joy/src/List/List.tsx @@ -87,6 +87,7 @@ export const ListRoot = styled('ul', { marginInlineStart: 'var(--NestedList-marginLeft)', marginInlineEnd: 'var(--NestedList-marginRight)', marginBlockStart: 'var(--List-gap)', + marginBlockEnd: 'initial', // reset user agent stylesheet. }, !ownerState.nesting && { ...applySizeVars(ownerState.size), diff --git a/packages/mui-joy/src/ListDivider/ListDivider.tsx b/packages/mui-joy/src/ListDivider/ListDivider.tsx index 19e9feacca9bcf..3d7c5aa2746ad6 100644 --- a/packages/mui-joy/src/ListDivider/ListDivider.tsx +++ b/packages/mui-joy/src/ListDivider/ListDivider.tsx @@ -25,6 +25,7 @@ const ListDividerRoot = styled('li', { border: 'none', // reset the border for `hr` tag listStyle: 'none', backgroundColor: theme.vars.palette.divider, // use logical size + background is better than border because they work with gradient. + flexShrink: 0, ...(ownerState.row && { inlineSize: 'var(--ListDivider-thickness, 1px)', marginBlock: ownerState.inset === 'gutter' ? 'var(--List-item-paddingY)' : 0, diff --git a/packages/mui-joy/src/Select/Select.tsx b/packages/mui-joy/src/Select/Select.tsx index ffbfe650e77402..f56d22a258e332 100644 --- a/packages/mui-joy/src/Select/Select.tsx +++ b/packages/mui-joy/src/Select/Select.tsx @@ -22,6 +22,7 @@ import Unfold from '../internal/svg-icons/Unfold'; import { styled, useThemeProps } from '../styles'; import { SelectOwnProps, SelectStaticProps, SelectOwnerState, SelectTypeMap } from './SelectProps'; import selectClasses, { getSelectUtilityClass } from './selectClasses'; +import { ListOwnerState } from '../List'; function defaultRenderSingleValue(selectedOption: SelectOption | null) { return selectedOption?.label ?? ''; @@ -450,7 +451,7 @@ const Select = React.forwardRef(function Select( [resolveListboxProps?.modifiers], ); - const listboxProps = useSlotProps({ + const { component: listboxComponent, ...listboxProps } = useSlotProps({ elementType: SelectListbox, getSlotProps: getListboxProps, externalSlotProps: componentsProps.listbox, @@ -460,15 +461,14 @@ const Select = React.forwardRef(function Select( disablePortal: true, open: listboxOpen, placement: 'bottom' as const, - component: SelectListbox, modifiers: cachedModifiers, }, ownerState: { ...ownerState, - // @ts-ignore internal logic nesting: false, row: false, - }, + wrap: false, + } as SelectOwnerState & ListOwnerState, className: classes.listbox, }); @@ -517,8 +517,10 @@ const Select = React.forwardRef(function Select( {indicator && {indicator}} {anchorEl && ( - + // @ts-ignore internal logic: `listboxComponent` should not replace `SelectListbox`. + + {/* for building grouped options */} {children} diff --git a/packages/mui-joy/src/Select/SelectProps.ts b/packages/mui-joy/src/Select/SelectProps.ts index ce5f7f4d294963..1c06382716a9b8 100644 --- a/packages/mui-joy/src/Select/SelectProps.ts +++ b/packages/mui-joy/src/Select/SelectProps.ts @@ -3,7 +3,6 @@ import { OverridableStringUnion, OverrideProps } from '@mui/types'; import { SelectUnstyledCommonProps, SelectOption } from '@mui/base/SelectUnstyled'; import { PopperUnstyledOwnProps } from '@mui/base/PopperUnstyled'; import { SlotComponentProps } from '@mui/base/utils'; -import { ListProps } from '../List/ListProps'; import { ColorPaletteProp, VariantProp, SxProps } from '../styles/types'; export type SelectSlot = @@ -28,7 +27,10 @@ interface ComponentsProps { indicator?: SlotComponentProps<'span', { sx?: SxProps }, SelectOwnerState>; listbox?: SlotComponentProps< 'ul', - Omit & ListProps, + Omit & { + component?: React.ElementType; + sx?: SxProps; + }, SelectOwnerState >; } diff --git a/test/regressions/fixtures/SelectJoy/GroupedOptionSelect.js b/test/regressions/fixtures/SelectJoy/GroupedOptionSelect.js new file mode 100644 index 00000000000000..96a7fc9733ba0f --- /dev/null +++ b/test/regressions/fixtures/SelectJoy/GroupedOptionSelect.js @@ -0,0 +1,84 @@ +import * as React from 'react'; +import Box from '@mui/joy/Box'; +import Select from '@mui/joy/Select'; +import Option, { optionClasses } from '@mui/joy/Option'; +import Chip from '@mui/joy/Chip'; +import List from '@mui/joy/List'; +import ListItemDecorator, { listItemDecoratorClasses } from '@mui/joy/ListItemDecorator'; +import ListDivider from '@mui/joy/ListDivider'; +import ListItem from '@mui/joy/ListItem'; +import Typography from '@mui/joy/Typography'; +import Check from '@mui/icons-material/Check'; + +export default function SelectGroupedOptions() { + const group = { + Land: ['Cat', 'Dog', 'Tiger', 'Reindeer', 'Raccoon'], + Water: ['Dolphin', 'Flounder', 'Eel'], + Air: ['Falcon', 'Winged Horse', 'Owl'], + }; + const colors = { + Land: 'neutral', + Water: 'primary', + Air: 'success', + }; + return ( + + + + ); +}