Skip to content

Commit

Permalink
[List] Migrate ListItem to emotion (#24543)
Browse files Browse the repository at this point in the history
  • Loading branch information
xs9627 committed Jan 25, 2021
1 parent 0ad42fc commit fbb0d9f
Show file tree
Hide file tree
Showing 11 changed files with 204 additions and 89 deletions.
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
2 changes: 1 addition & 1 deletion framer/scripts/framerConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ export const componentSettings = {
template: 'icon_button.txt',
},
ListItem: {
ignoredProps: ['children', 'ContainerComponent', 'ContainerProps'],
ignoredProps: ['children', 'ContainerComponent', 'ContainerProps', 'sx'],
propValues: {
width: 568,
height: 48,
Expand Down
4 changes: 1 addition & 3 deletions packages/material-ui/src/List/List.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { expect } from 'chai';
import { getClasses, createMount, describeConformance, createClientRender } from 'test/utils';
import ListSubheader from '../ListSubheader';
import List from './List';
import ListItem from '../ListItem';
import ListItem, { listItemClasses } from '../ListItem';

describe('<List />', () => {
const mount = createMount();
Expand Down Expand Up @@ -76,8 +76,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
213 changes: 140 additions & 73 deletions packages/material-ui/src/ListItem/ListItem.js
Original file line number Diff line number Diff line change
@@ -1,73 +1,117 @@
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: {
display: 'flex',
justifyContent: 'flex-start',
alignItems: 'center',
position: 'relative',
textDecoration: 'none',
width: '100%',
boxSizing: 'border-box',
textAlign: 'left',
paddingTop: 8,
paddingBottom: 8,
'&$focusVisible': {
backgroundColor: theme.palette.action.focus,
},
'&$selected': {
backgroundColor: alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity),
'&$focusVisible': {
backgroundColor: alpha(
theme.palette.primary.main,
theme.palette.action.selectedOpacity + theme.palette.action.focusOpacity,
),
},
},
'&$disabled': {
opacity: theme.palette.action.disabledOpacity,
const overridesResolver = (props, styles) => {
const { styleProps } = props;

return deepmerge(styles.root || {}, {
...(styleProps.dense && styles.dense),
...(styleProps.alignItems === 'flex-start' && styles.alignItemsFlexStart),
...(styleProps.divider && styles.divider),
...(!styleProps.disableGutters && styles.gutters),
...(styleProps.button && styles.button),
...(styleProps.hasSecondaryAction && styles.secondaryAction),
});
};

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

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

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

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

const ListItemContainer = experimentalStyled(
'li',
{},
{
name: 'MuiListItem',
slot: 'Container',
overridesResolver,
},
/* Pseudo-class applied to the root element if `selected={true}`. */
selected: {},
)({
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 +201,25 @@ const ListItem = React.forwardRef(function ListItem(props, ref) {
const hasSecondaryAction =
children.length && isMuiElement(children[children.length - 1], ['ListItemSecondaryAction']);

const styleProps = {
...props,
alignItems,
autoFocus,
button,
dense: childContext.dense,
disabled,
disableGutters,
divider,
hasSecondaryAction,
selected,
};

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 +228,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 +251,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 +378,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

0 comments on commit fbb0d9f

Please sign in to comment.