Skip to content

Commit

Permalink
[material-ui][Accordion] Render a heading wrapping AccordionSummary
Browse files Browse the repository at this point in the history
… button per W3C Accordion Pattern standards (#42914)
  • Loading branch information
ZeeshanTamboli authored Jul 27, 2024
1 parent 7b632ea commit d69604d
Show file tree
Hide file tree
Showing 9 changed files with 109 additions and 7 deletions.
20 changes: 20 additions & 0 deletions docs/data/material/components/accordion/accordion.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,26 @@ The demo below also shows a bit of visual customization.

{{"demo": "CustomizedAccordions.js", "bg": true}}

### Changing heading level

By default, the Accordion uses an `h3` element for the heading. You can change the heading element using the `slotProps.heading.component` prop to ensure the correct heading hierarchy in your document.

```jsx
<Accordion slotProps={{ heading: { component: 'h4' } }}>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="panel1-content"
id="panel1-header"
>
Accordion
</AccordionSummary>
<AccordionDetails>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada
lacus ex, sit amet blandit leo lobortis eget.
</AccordionDetails>
</Accordion>
```

## Performance

The Accordion content is mounted by default even if it's not expanded.
Expand Down
24 changes: 24 additions & 0 deletions docs/data/material/migration/migrating-to-v6/migrating-to-v6.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,30 @@ These three were previously treated as `"reset"`, so if you are relying on that,

These are added on top of the existing ones: `"input"`, `"reset"`, and `"clear"`.

### Accordion

#### Heading element wrapping Accordion Summary

To meet the [W3C Accordion Pattern standard](https://www.w3.org/WAI/ARIA/apg/patterns/accordion/), the Accordion Summary is now wrapped with a default `h3` heading element. This change may affect customizations relying on the previous DOM structure and CSS specificity. Additionally, the default heading element might conflict with existing heading structures on your page.

If your styles or DOM manipulations depend on the old structure, you will need to update them to accommodate the new heading element. If the default heading element conflicts with your existing structure, you can change the heading element using the `slotProps.heading.component` prop.

```jsx
<Accordion slotProps={{ heading: { component: 'h4' } }}>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="panel1-content"
id="panel1-header"
>
Accordion
</AccordionSummary>
<AccordionDetails>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada
lacus ex, sit amet blandit leo lobortis eget.
</AccordionDetails>
</Accordion>
```

### Chip

Previously, the Chip component lost focus when the escape button was pressed, which differed from how other button-like components work.
Expand Down
16 changes: 14 additions & 2 deletions docs/pages/material-ui/api/accordion.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,17 @@
}
},
"slotProps": {
"type": { "name": "shape", "description": "{ transition?: func<br>&#124;&nbsp;object }" },
"type": {
"name": "shape",
"description": "{ heading?: func<br>&#124;&nbsp;object, transition?: func<br>&#124;&nbsp;object }"
},
"default": "{}"
},
"slots": {
"type": { "name": "shape", "description": "{ transition?: elementType }" },
"type": {
"name": "shape",
"description": "{ heading?: elementType, transition?: elementType }"
},
"default": "{}"
},
"square": { "type": { "name": "bool" }, "default": "false" },
Expand All @@ -38,6 +44,12 @@
"import { Accordion } from '@mui/material';"
],
"slots": [
{
"name": "heading",
"description": "The component that renders the heading.",
"default": "'h3'",
"class": "MuiAccordion-heading"
},
{
"name": "transition",
"description": "The component that renders the transition.\n[Follow this guide](/material-ui/transitions/#transitioncomponent-prop) to learn more about the requirements for this component.",
Expand Down
1 change: 1 addition & 0 deletions docs/translations/api-docs/accordion/accordion.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
}
},
"slotDescriptions": {
"heading": "The component that renders the heading.",
"transition": "The component that renders the transition. <a href=\"/material-ui/transitions/#transitioncomponent-prop\">Follow this guide</a> to learn more about the requirements for this component."
}
}
11 changes: 11 additions & 0 deletions packages/mui-material/src/Accordion/Accordion.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import { ExtendPaperTypeMap } from '../Paper/Paper';
import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types';

export interface AccordionSlots {
/**
* The component that renders the heading.
* @default 'h3'
*/
heading?: React.ElementType;
/**
* The component that renders the transition.
* [Follow this guide](/material-ui/transitions/#transitioncomponent-prop) to learn more about the requirements for this component.
Expand All @@ -19,10 +24,16 @@ export interface AccordionSlots {
}

export interface AccordionTransitionSlotPropsOverrides {}
export interface AccordionHeadingSlotPropsOverrides {}

export type AccordionSlotsAndSlotProps = CreateSlotsAndSlotProps<
AccordionSlots,
{
heading: SlotProps<
React.ElementType<React.HTMLProps<HTMLHeadingElement>>,
AccordionHeadingSlotPropsOverrides,
AccordionOwnerState
>;
transition: SlotProps<
React.ElementType<TransitionProps>,
AccordionTransitionSlotPropsOverrides,
Expand Down
32 changes: 27 additions & 5 deletions packages/mui-material/src/Accordion/Accordion.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const useUtilityClasses = (ownerState) => {
disabled && 'disabled',
!disableGutters && 'gutters',
],
heading: ['heading'],
region: ['region'],
};

Expand Down Expand Up @@ -124,6 +125,14 @@ const AccordionRoot = styled(Paper, {
}),
);

const AccordionHeading = styled('h3', {
name: 'MuiAccordion',
slot: 'Heading',
overridesResolver: (props, styles) => styles.heading,
})({
all: 'unset',
});

const Accordion = React.forwardRef(function Accordion(inProps, ref) {
const props = useDefaultProps({ props: inProps, name: 'MuiAccordion' });
const {
Expand Down Expand Up @@ -179,12 +188,21 @@ const Accordion = React.forwardRef(function Accordion(inProps, ref) {
const backwardCompatibleSlots = { transition: TransitionComponentProp, ...slots };
const backwardCompatibleSlotProps = { transition: TransitionPropsProp, ...slotProps };

const externalForwardedProps = {
slots: backwardCompatibleSlots,
slotProps: backwardCompatibleSlotProps,
};

const [AccordionHeadingSlot, accordionProps] = useSlot('heading', {
elementType: AccordionHeading,
externalForwardedProps,
className: classes.heading,
ownerState,
});

const [TransitionSlot, transitionProps] = useSlot('transition', {
elementType: Collapse,
externalForwardedProps: {
slots: backwardCompatibleSlots,
slotProps: backwardCompatibleSlotProps,
},
externalForwardedProps,
ownerState,
});

Expand All @@ -196,7 +214,9 @@ const Accordion = React.forwardRef(function Accordion(inProps, ref) {
square={square}
{...other}
>
<AccordionContext.Provider value={contextValue}>{summary}</AccordionContext.Provider>
<AccordionHeadingSlot {...accordionProps}>
<AccordionContext.Provider value={contextValue}>{summary}</AccordionContext.Provider>
</AccordionHeadingSlot>
<TransitionSlot in={expanded} timeout="auto" {...transitionProps}>
<div
aria-labelledby={summary.props.id}
Expand Down Expand Up @@ -274,13 +294,15 @@ Accordion.propTypes /* remove-proptypes */ = {
* @default {}
*/
slotProps: PropTypes.shape({
heading: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
transition: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
}),
/**
* The components used for each slot inside.
* @default {}
*/
slots: PropTypes.shape({
heading: PropTypes.elementType,
transition: PropTypes.elementType,
}),
/**
Expand Down
5 changes: 5 additions & 0 deletions packages/mui-material/src/Accordion/Accordion.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,8 @@ const AccordionComponentTest = () => {
</div>
);
};

// slotProps type test. Changing heading level.
<Accordion slotProps={{ heading: { component: 'h4' } }}>
<div />
</Accordion>;
4 changes: 4 additions & 0 deletions packages/mui-material/src/Accordion/Accordion.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ describe('<Accordion />', () => {
transition: {
testWithElement: null,
},
heading: {
testWithElement: 'h4',
expectedClassName: classes.heading,
},
},
skip: ['componentProp', 'componentsProp'],
}));
Expand Down
3 changes: 3 additions & 0 deletions packages/mui-material/src/Accordion/accordionClasses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import generateUtilityClass from '@mui/utils/generateUtilityClass';
export interface AccordionClasses {
/** Styles applied to the root element. */
root: string;
/** Styles applied to the heading element. */
heading: string;
/** Styles applied to the root element unless `square={true}`. */
rounded: string;
/** State class applied to the root element if `expanded={true}`. */
Expand All @@ -24,6 +26,7 @@ export function getAccordionUtilityClass(slot: string): string {

const accordionClasses: AccordionClasses = generateUtilityClasses('MuiAccordion', [
'root',
'heading',
'rounded',
'expanded',
'disabled',
Expand Down

0 comments on commit d69604d

Please sign in to comment.