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

[Accordion] Convert to support CSS extraction #41221

Merged
merged 10 commits into from
Mar 13, 2024
33 changes: 33 additions & 0 deletions apps/zero-runtime-next-app/src/app/accordion/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Typography from '@mui/material/Typography';
import AccordionDetails from '@mui/material/AccordionDetails';
import Accordion from '@/components/Accordion/Accordion';
import AccordionSummary from '@/components/Accordion/AccordionSummary';

export default function AccordionExpandDefault() {
return (
<div style={{ padding: '10px' }}>
<Accordion defaultExpanded>
<AccordionSummary aria-controls="panel1-content" id="panel1-header">
<Typography>Expanded by default</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex,
sit amet blandit leo lobortis eget.
</Typography>
</AccordionDetails>
</Accordion>
<Accordion>
<AccordionSummary aria-controls="panel2-content" id="panel2-header">
<Typography>Header</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex,
sit amet blandit leo lobortis eget.
</Typography>
</AccordionDetails>
</Accordion>
</div>
);
}
315 changes: 315 additions & 0 deletions apps/zero-runtime-next-app/src/components/Accordion/Accordion.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
'use client';
import * as React from 'react';
import { isFragment } from 'react-is';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import chainPropTypes from '@mui/utils/chainPropTypes';
import composeClasses from '@mui/utils/composeClasses';
import { styled } from '@mui/zero-runtime';
import { useThemeProps } from '@mui/material/styles';
import Collapse from '@mui/material/Collapse';
import Paper from '@mui/material/Paper';
/* eslint-disable-next-line no-restricted-imports */
import AccordionContext from '@mui/material/Accordion/AccordionContext';
import { useControlled } from '@mui/material/utils';
/* eslint-disable-next-line no-restricted-imports */
import useSlot from '@mui/material/utils/useSlot';
import { accordionClasses, getAccordionUtilityClass } from '@mui/material/Accordion';

const useUtilityClasses = (ownerState) => {
const { classes, square, expanded, disabled, disableGutters } = ownerState;

const slots = {
root: [
'root',
!square && 'rounded',
expanded && 'expanded',
disabled && 'disabled',
!disableGutters && 'gutters',
],
region: ['region'],
};

return composeClasses(slots, getAccordionUtilityClass, classes);
};

const AccordionRoot = styled(Paper, {
name: 'MuiAccordion',
slot: 'Root',
overridesResolver: (props, styles) => {
const { ownerState } = props;

return [
{ [`& .${accordionClasses.region}`]: styles.region },
styles.root,
!ownerState.square && styles.rounded,
!ownerState.disableGutters && styles.gutters,
];
},
})(
({ theme }) => {
const transition = {
duration: theme.transitions.duration.shortest,
};

return {
position: 'relative',
transition: theme.transitions.create(['margin'], transition),
overflowAnchor: 'none', // Keep the same scrolling position
'&::before': {
position: 'absolute',
left: 0,
top: -1,
right: 0,
height: 1,
content: '""',
opacity: 1,
backgroundColor: (theme.vars || theme).palette.divider,
transition: theme.transitions.create(['opacity', 'background-color'], transition),
},
'&:first-of-type': {
'&::before': {
display: 'none',
},
},
[`&.${accordionClasses.expanded}`]: {
'&::before': {
opacity: 0,
},
'&:first-of-type': {
marginTop: 0,
},
'&:last-of-type': {
marginBottom: 0,
},
'& + &': {
'&::before': {
display: 'none',
},
},
},
[`&.${accordionClasses.disabled}`]: {
backgroundColor: (theme.vars || theme).palette.action.disabledBackground,
},
};
},
({ theme }) => ({
variants: [
{
props: (props) => !props.square,
style: {
borderRadius: 0,
'&:first-of-type': {
borderTopLeftRadius: (theme.vars || theme).shape.borderRadius,
borderTopRightRadius: (theme.vars || theme).shape.borderRadius,
},
'&:last-of-type': {
borderBottomLeftRadius: (theme.vars || theme).shape.borderRadius,
borderBottomRightRadius: (theme.vars || theme).shape.borderRadius,
// Fix a rendering issue on Edge
'@supports (-ms-ime-align: auto)': {
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0,
},
},
},
},
{
props: (props) => !props.disableGutters,
style: {
[`&.${accordionClasses.expanded}`]: {
margin: '16px 0',
},
},
},
],
}),
);

const Accordion = React.forwardRef(function Accordion(inProps, ref) {
const props = useThemeProps({ props: inProps, name: 'MuiAccordion' });
const {
children: childrenProp,
className,
defaultExpanded = false,
disabled = false,
disableGutters = false,
expanded: expandedProp,
onChange,
square = false,
slots = {},
slotProps = {},
TransitionComponent: TransitionComponentProp,
TransitionProps: TransitionPropsProp,
...other
} = props;

const [expanded, setExpandedState] = useControlled({
controlled: expandedProp,
default: defaultExpanded,
name: 'Accordion',
state: 'expanded',
});

const handleChange = React.useCallback(
(event) => {
setExpandedState(!expanded);

if (onChange) {
onChange(event, !expanded);
}
},
[expanded, onChange, setExpandedState],
);

const [summary, ...children] = React.Children.toArray(childrenProp);
const contextValue = React.useMemo(
() => ({ expanded, disabled, disableGutters, toggle: handleChange }),
[expanded, disabled, disableGutters, handleChange],
);

const ownerState = {
...props,
square,
disabled,
disableGutters,
expanded,
};

const classes = useUtilityClasses(ownerState);

const backwardCompatibleSlots = { transition: TransitionComponentProp, ...slots };
const backwardCompatibleSlotProps = { transition: TransitionPropsProp, ...slotProps };

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

return (
<AccordionRoot
className={clsx(classes.root, className)}
ref={ref}
ownerState={ownerState}
square={square}
{...other}
>
<AccordionContext.Provider value={contextValue}>{summary}</AccordionContext.Provider>
<TransitionSlot in={expanded} timeout="auto" {...transitionProps}>
<div
aria-labelledby={summary.props.id}
id={summary.props['aria-controls']}
role="region"
className={classes.region}
>
{children}
</div>
</TransitionSlot>
</AccordionRoot>
);
});

Accordion.propTypes /* remove-proptypes */ = {
// ┌────────────────────────────── Warning ──────────────────────────────┐
// │ These PropTypes are generated from the TypeScript type definitions. │
// │ To update them, edit the d.ts file and run `pnpm proptypes`. │
// └─────────────────────────────────────────────────────────────────────┘
/**
* The content of the component.
*/
children: chainPropTypes(PropTypes.node.isRequired, (props) => {
const summary = React.Children.toArray(props.children)[0];
if (isFragment(summary)) {
return new Error(
"MUI: The Accordion doesn't accept a Fragment as a child. " +
'Consider providing an array instead.',
);
}

if (!React.isValidElement(summary)) {
return new Error('MUI: Expected the first child of Accordion to be a valid element.');
}

return null;
}),
/**
* Override or extend the styles applied to the component.
*/
classes: PropTypes.object,
/**
* @ignore
*/
className: PropTypes.string,
/**
* If `true`, expands the accordion by default.
* @default false
*/
defaultExpanded: PropTypes.bool,
/**
* If `true`, the component is disabled.
* @default false
*/
disabled: PropTypes.bool,
/**
* If `true`, it removes the margin between two expanded accordion items and the increase of height.
* @default false
*/
disableGutters: PropTypes.bool,
/**
* If `true`, expands the accordion, otherwise collapse it.
* Setting this prop enables control over the accordion.
*/
expanded: PropTypes.bool,
/**
* Callback fired when the expand/collapse state is changed.
*
* @param {React.SyntheticEvent} event The event source of the callback. **Warning**: This is a generic event not a change event.
* @param {boolean} expanded The `expanded` state of the accordion.
*/
onChange: PropTypes.func,
/**
* The props used for each slot inside.
* @default {}
*/
slotProps: PropTypes.shape({
transition: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
}),
/**
* The components used for each slot inside.
* @default {}
*/
slots: PropTypes.shape({
transition: PropTypes.elementType,
}),
/**
* If `true`, rounded corners are disabled.
* @default false
*/
square: PropTypes.bool,
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
sx: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])),
PropTypes.func,
PropTypes.object,
]),
/**
* The component used for the transition.
* [Follow this guide](/material-ui/transitions/#transitioncomponent-prop) to learn more about the requirements for this component.
* @deprecated Use `slots.transition` instead. This prop will be removed in v7. [How to migrate](/material-ui/migration/migrating-from-deprecated-apis/).
*/
TransitionComponent: PropTypes.elementType,
/**
* Props applied to the transition element.
* By default, the element is based on this [`Transition`](https://reactcommunity.org/react-transition-group/transition/) component.
* @deprecated Use `slotProps.transition` instead. This prop will be removed in v7. [How to migrate](/material-ui/migration/migrating-from-deprecated-apis/).
*/
TransitionProps: PropTypes.object,
};

export default Accordion;
Loading
Loading