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

[List] Migrate ListItem to emotion #24543

Merged
merged 18 commits into from
Jan 25, 2021
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/pages/api-docs/list-item.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"disabled": { "type": { "name": "bool" } },
"disableGutters": { "type": { "name": "bool" } },
"divider": { "type": { "name": "bool" } },
"selected": { "type": { "name": "bool" } }
"selected": { "type": { "name": "bool" } },
"sx": { "type": { "name": "object" } }
},
"name": "ListItem",
"styles": {
Expand Down Expand Up @@ -47,6 +48,6 @@
"filename": "/packages/material-ui/src/ListItem/ListItem.js",
"inheritance": null,
"demos": "<ul><li><a href=\"/components/lists/\">Lists</a></li>\n<li><a href=\"/components/transfer-list/\">Transfer List</a></li></ul>",
"styledComponent": false,
"styledComponent": true,
"cssComponent": false
}
3 changes: 2 additions & 1 deletion docs/translations/api-docs/list-item/list-item.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"disabled": "If <code>true</code>, the component is disabled.",
"disableGutters": "If <code>true</code>, the left and right padding is removed.",
"divider": "If <code>true</code>, a 1px light border is added to the bottom of the list item.",
"selected": "Use to apply selected styling."
"selected": "Use to apply selected styling.",
"sx": "The system prop that allows defining system overrides as well as additional CSS styles. See the <a href=\"/system/basics/#the-sx-prop\">`sx` page</a> for more details."
},
"classDescriptions": {
"root": {
Expand Down
3 changes: 1 addition & 2 deletions packages/material-ui/src/List/List.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { getClasses, createMount, describeConformance, createClientRender } from
import ListSubheader from '../ListSubheader';
import List from './List';
import ListItem from '../ListItem';
import listItemClasses from '../ListItem/listItemClasses';
mnajdova marked this conversation as resolved.
Show resolved Hide resolved

describe('<List />', () => {
const mount = createMount();
Expand Down Expand Up @@ -76,8 +77,6 @@ describe('<List />', () => {
</List>,
);

const listItemClasses = getClasses(<ListItem />);

const liItems = container.querySelectorAll('li');
for (let i = 0; i < liItems.length; i += 1) {
expect(liItems[i]).to.have.class(listItemClasses.dense);
Expand Down
6 changes: 6 additions & 0 deletions packages/material-ui/src/ListItem/ListItem.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import * as React from 'react';
import { SxProps } from '@material-ui/system';
import { Theme } from '@material-ui/core/styles';
import { ExtendButtonBase } from '../ButtonBase';
import { OverridableComponent, OverrideProps } from '../OverridableComponent';

Expand Down Expand Up @@ -83,6 +85,10 @@ export interface ListItemTypeMap<P, D extends React.ElementType> {
* @default false
*/
selected?: boolean;
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
sx?: SxProps<Theme>;
};
defaultComponent: D;
}
Expand Down
249 changes: 160 additions & 89 deletions packages/material-ui/src/ListItem/ListItem.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,74 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import { chainPropTypes, elementTypeAcceptingRef } from '@material-ui/utils';
import withStyles from '../styles/withStyles';
import { unstable_composeClasses as composeClasses } from '@material-ui/unstyled';
import { deepmerge, chainPropTypes, elementTypeAcceptingRef } from '@material-ui/utils';
import experimentalStyled from '../styles/experimentalStyled';
import useThemeProps from '../styles/useThemeProps';
import { alpha } from '../styles/colorManipulator';
import ButtonBase from '../ButtonBase';
import isMuiElement from '../utils/isMuiElement';
import useEnhancedEffect from '../utils/useEnhancedEffect';
import useForkRef from '../utils/useForkRef';
import ListContext from '../List/ListContext';
import listItemClasses, { getListItemUtilityClass } from './listItemClasses';

export const styles = (theme) => ({
/* Styles applied to the (normally root) `component` element. May be wrapped by a `container`. */
root: {
const overridesResolver = (props, styles) => {
const { styleProps } = props;

return deepmerge(styles.root || {}, {
...(styleProps.dense && { dense: styles.dense }),
...(styleProps.alignItems === 'flex-start' && {
alignItemsFlexStart: styles.alignItemsFlexStart,
}),
...(styleProps.divider && { divider: styles.divider }),
...(!styleProps.disableGutters && { gutters: styles.gutters }),
...(styleProps.button && { button: styles.button }),
...(styleProps.hasSecondaryAction && { secondaryAction: styles.secondaryAction }),
mnajdova marked this conversation as resolved.
Show resolved Hide resolved
});
};

const useUtilityClasses = (styleProps) => {
const {
dense,
alignItems,
divider,
disableGutters,
hasSecondaryAction,
selected,
disabled,
button,
classes,
} = styleProps;

const slots = {
root: [
'root',
dense && 'dense',
alignItems === 'flex-start' && 'alignItemsFlexStart',
divider && 'divider',
!disableGutters && 'gutters',
button && 'button',
hasSecondaryAction && 'secondaryAction',
selected && 'selected',
disabled && 'disabled',
],
container: ['container'],
};

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

const ListItemRoot = experimentalStyled(
'div',
{},
{
name: 'MuiListItem',
slot: 'Root',
overridesResolver,
},
)(({ theme, styleProps }) => {
return {
display: 'flex',
justifyContent: 'flex-start',
alignItems: 'center',
Expand All @@ -23,93 +79,95 @@ export const styles = (theme) => ({
textAlign: 'left',
paddingTop: 8,
paddingBottom: 8,
'&$focusVisible': {
[`&.${listItemClasses.focusVisible}`]: {
backgroundColor: theme.palette.action.focus,
},
'&$selected': {
[`&.${listItemClasses.selected}`]: {
backgroundColor: alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity),
'&$focusVisible': {
[`&.${listItemClasses.focusVisible}`]: {
backgroundColor: alpha(
theme.palette.primary.main,
theme.palette.action.selectedOpacity + theme.palette.action.focusOpacity,
),
},
},
'&$disabled': {
[`&.${listItemClasses.disabled}`]: {
opacity: theme.palette.action.disabledOpacity,
},
},
/* Styles applied to the container element if `children` includes `ListItemSecondaryAction`. */
container: {
position: 'relative',
},
/* Pseudo-class applied to the `component`'s `focusVisibleClassName` prop if `button={true}`. */
focusVisible: {},
/* Styles applied to the component element if dense. */
dense: {
paddingTop: 4,
paddingBottom: 4,
},
/* Styles applied to the component element if `alignItems="flex-start"`. */
alignItemsFlexStart: {
alignItems: 'flex-start',
},
/* Pseudo-class applied to the inner `component` element if `disabled={true}`. */
disabled: {},
/* Styles applied to the inner `component` element if `divider={true}`. */
divider: {
borderBottom: `1px solid ${theme.palette.divider}`,
backgroundClip: 'padding-box',
},
/* Styles applied to the inner `component` element unless `disableGutters={true}`. */
gutters: {
paddingLeft: 16,
paddingRight: 16,
},
/* Styles applied to the inner `component` element if `button={true}`. */
button: {
transition: theme.transitions.create('background-color', {
duration: theme.transitions.duration.shortest,
/* Styles applied to the component element if dense. */
...(styleProps.dense && {
paddingTop: 4,
paddingBottom: 4,
}),
/* Styles applied to the component element if `alignItems="flex-start"`. */
...(styleProps.alignItems === 'flex-start' && {
alignItems: 'flex-start',
}),
'&:hover': {
textDecoration: 'none',
backgroundColor: theme.palette.action.hover,
// Reset on touch devices, it doesn't add specificity
'@media (hover: none)': {
backgroundColor: 'transparent',
/* Styles applied to the inner `component` element if `divider={true}`. */
...(styleProps.divider && {
borderBottom: `1px solid ${theme.palette.divider}`,
backgroundClip: 'padding-box',
}),
/* Styles applied to the inner `component` element unless `disableGutters={true}`. */
...(!styleProps.disableGutters && {
paddingLeft: 16,
paddingRight: 16,
}),
/* Styles applied to the inner `component` element if `button={true}`. */
...(styleProps.button && {
transition: theme.transitions.create('background-color', {
duration: theme.transitions.duration.shortest,
}),
'&:hover': {
textDecoration: 'none',
backgroundColor: theme.palette.action.hover,
// Reset on touch devices, it doesn't add specificity
'@media (hover: none)': {
backgroundColor: 'transparent',
},
},
},
'&$selected:hover': {
backgroundColor: alpha(
theme.palette.primary.main,
theme.palette.action.selectedOpacity + theme.palette.action.hoverOpacity,
),
// Reset on touch devices, it doesn't add specificity
'@media (hover: none)': {
backgroundColor: alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity),
[`&.${listItemClasses.selected}:hover`]: {
backgroundColor: alpha(
theme.palette.primary.main,
theme.palette.action.selectedOpacity + theme.palette.action.hoverOpacity,
),
// Reset on touch devices, it doesn't add specificity
'@media (hover: none)': {
backgroundColor: alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity),
},
},
},
},
/* Styles applied to the component element if `children` includes `ListItemSecondaryAction`. */
secondaryAction: {
// Add some space to avoid collision as `ListItemSecondaryAction`
// is absolutely positioned.
paddingRight: 48,
},
/* Pseudo-class applied to the root element if `selected={true}`. */
selected: {},
}),
/* Styles applied to the component element if `children` includes `ListItemSecondaryAction`. */
...(styleProps.hasSecondaryAction && {
// Add some space to avoid collision as `ListItemSecondaryAction`
// is absolutely positioned.
paddingRight: 48,
}),
};
});

const ListItemContainer = experimentalStyled(
'div',
mnajdova marked this conversation as resolved.
Show resolved Hide resolved
{},
{
name: 'MuiListItem',
slot: 'Container',
overridesResolver,
},
)(() => ({
position: 'relative',
}));

/**
* Uses an additional container component if `ListItemSecondaryAction` is the last child.
*/
const ListItem = React.forwardRef(function ListItem(props, ref) {
const ListItem = React.forwardRef(function ListItem(inProps, ref) {
const props = useThemeProps({ props: inProps, name: 'MuiListItem' });
const {
alignItems = 'center',
autoFocus = false,
button = false,
children: childrenProp,
classes,
className,
component: componentProp,
ContainerComponent = 'li',
Expand Down Expand Up @@ -147,23 +205,25 @@ const ListItem = React.forwardRef(function ListItem(props, ref) {
const hasSecondaryAction =
children.length && isMuiElement(children[children.length - 1], ['ListItemSecondaryAction']);

const styleProps = {
...other,
button,
children: childrenProp,
dense: dense || context.dense || false,
alignItems,
divider,
disableGutters,
selected,
disabled,
hasSecondaryAction,
};
mnajdova marked this conversation as resolved.
Show resolved Hide resolved

const classes = useUtilityClasses(styleProps);

const handleRef = useForkRef(listItemRef, ref);

const componentProps = {
className: clsx(
classes.root,
{
[classes.dense]: childContext.dense,
[classes.gutters]: !disableGutters,
[classes.divider]: divider,
[classes.disabled]: disabled,
[classes.button]: button,
[classes.alignItemsFlexStart]: alignItems === 'flex-start',
[classes.secondaryAction]: hasSecondaryAction,
[classes.selected]: selected,
},
className,
),
className: clsx(classes.root, className),
disabled,
...other,
};
Expand All @@ -172,7 +232,11 @@ const ListItem = React.forwardRef(function ListItem(props, ref) {

if (button) {
componentProps.component = componentProp || 'div';
componentProps.focusVisibleClassName = clsx(classes.focusVisible, focusVisibleClassName);
componentProps.focusVisibleClassName = clsx(
listItemClasses.focusVisible,
focusVisibleClassName,
);

Component = ButtonBase;
}

Expand All @@ -191,23 +255,26 @@ const ListItem = React.forwardRef(function ListItem(props, ref) {

return (
<ListContext.Provider value={childContext}>
<ContainerComponent
<ListItemContainer
as={ContainerComponent}
className={clsx(classes.container, ContainerClassName)}
ref={handleRef}
{...ContainerProps}
>
<Component {...componentProps}>{children}</Component>
<ListItemRoot as={Component} styleProps={styleProps} {...componentProps}>
{children}
</ListItemRoot>
{children.pop()}
</ContainerComponent>
</ListItemContainer>
</ListContext.Provider>
);
}

return (
<ListContext.Provider value={childContext}>
<Component ref={handleRef} {...componentProps}>
<ListItemRoot as={Component} ref={handleRef} styleProps={styleProps} {...componentProps}>
{children}
</Component>
</ListItemRoot>
</ListContext.Provider>
);
});
Expand Down Expand Up @@ -315,6 +382,10 @@ ListItem.propTypes = {
* @default false
*/
selected: PropTypes.bool,
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
sx: PropTypes.object,
};

export default withStyles(styles, { name: 'MuiListItem' })(ListItem);
export default ListItem;
Loading