Skip to content

Commit

Permalink
[Select][Joy] Fix forwarding listbox component prop (#34172)
Browse files Browse the repository at this point in the history
  • Loading branch information
siriwatknp committed Sep 3, 2022
1 parent 0f88b86 commit 0a66a26
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 10 deletions.
3 changes: 1 addition & 2 deletions docs/data/joy/components/select/SelectGroupedOptions.js
Expand Up @@ -41,11 +41,10 @@ export default function SelectGroupedOptions() {
<React.Fragment key={name}>
{index !== 0 && <ListDivider role="none" />}
<List
role="group"
aria-labelledby={`select-group-${name}`}
sx={{ '--List-decorator-size': '28px' }}
>
<ListItem role="presentation" id={`select-group-${name}`} sticky>
<ListItem id={`select-group-${name}`} sticky>
<Typography level="body3" textTransform="uppercase" letterSpacing="md">
{name} ({animals.length})
</Typography>
Expand Down
2 changes: 1 addition & 1 deletion docs/data/joy/components/select/select.md
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions packages/mui-joy/src/List/List.tsx
Expand Up @@ -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),
Expand Down
1 change: 1 addition & 0 deletions packages/mui-joy/src/ListDivider/ListDivider.tsx
Expand Up @@ -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,
Expand Down
12 changes: 7 additions & 5 deletions packages/mui-joy/src/Select/Select.tsx
Expand Up @@ -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<TValue>(selectedOption: SelectOption<TValue> | null) {
return selectedOption?.label ?? '';
Expand Down Expand Up @@ -450,7 +451,7 @@ const Select = React.forwardRef(function Select<TValue>(
[resolveListboxProps?.modifiers],
);

const listboxProps = useSlotProps({
const { component: listboxComponent, ...listboxProps } = useSlotProps({
elementType: SelectListbox,
getSlotProps: getListboxProps,
externalSlotProps: componentsProps.listbox,
Expand All @@ -460,15 +461,14 @@ const Select = React.forwardRef(function Select<TValue>(
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<any> & ListOwnerState,
className: classes.listbox,
});

Expand Down Expand Up @@ -517,8 +517,10 @@ const Select = React.forwardRef(function Select<TValue>(
{indicator && <SelectIndicator {...indicatorProps}>{indicator}</SelectIndicator>}
</SelectRoot>
{anchorEl && (
<PopperUnstyled {...listboxProps}>
// @ts-ignore internal logic: `listboxComponent` should not replace `SelectListbox`.
<PopperUnstyled {...listboxProps} as={listboxComponent} component={SelectListbox}>
<SelectUnstyledContext.Provider value={context}>
{/* for building grouped options */}
<ListProvider nested>{children}</ListProvider>
</SelectUnstyledContext.Provider>
</PopperUnstyled>
Expand Down
6 changes: 4 additions & 2 deletions packages/mui-joy/src/Select/SelectProps.ts
Expand Up @@ -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 =
Expand All @@ -28,7 +27,10 @@ interface ComponentsProps {
indicator?: SlotComponentProps<'span', { sx?: SxProps }, SelectOwnerState<any>>;
listbox?: SlotComponentProps<
'ul',
Omit<PopperUnstyledOwnProps, 'components' | 'componentsProps' | 'open'> & ListProps,
Omit<PopperUnstyledOwnProps, 'components' | 'componentsProps' | 'open'> & {
component?: React.ElementType;
sx?: SxProps;
},
SelectOwnerState<any>
>;
}
Expand Down
84 changes: 84 additions & 0 deletions 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 (
<Box sx={{ minHeight: 300 }}>
<Select
placeholder="Choose your animal"
defaultListboxOpen
componentsProps={{
listbox: {
component: 'div',
sx: {
maxHeight: 240,
overflow: 'auto',
'--List-padding': '0px',
},
},
}}
sx={{ width: 240 }}
>
{Object.entries(group).map(([name, animals], index) => (
<React.Fragment key={name}>
{index !== 0 && <ListDivider role="none" />}
<List aria-labelledby={`select-group-${name}`} sx={{ '--List-decorator-size': '28px' }}>
<ListItem id={`select-group-${name}`} sticky>
<Typography level="body3" textTransform="uppercase" letterSpacing="md">
{name} ({animals.length})
</Typography>
</ListItem>
{animals.map((anim) => (
<Option
key={anim}
value={anim}
label={
<React.Fragment>
<Chip
size="sm"
color={colors[name]}
sx={{ borderRadius: 'xs', mr: 1, ml: -0.5 }}
>
{name}
</Chip>{' '}
{anim}
</React.Fragment>
}
sx={{
[`&.${optionClasses.selected} .${listItemDecoratorClasses.root}`]: {
opacity: 1,
},
}}
>
<ListItemDecorator sx={{ opacity: 0 }}>
<Check />
</ListItemDecorator>
{anim}
</Option>
))}
</List>
</React.Fragment>
))}
</Select>
</Box>
);
}

0 comments on commit 0a66a26

Please sign in to comment.