Skip to content

Commit

Permalink
[List] Use stable context API (#13498)
Browse files Browse the repository at this point in the history
* [List] Use stable context API

* Fix yarn docs:api
  • Loading branch information
eps1lon authored and oliviertassinari committed Nov 4, 2018
1 parent 485bd18 commit 371e505
Show file tree
Hide file tree
Showing 13 changed files with 445 additions and 380 deletions.
4 changes: 2 additions & 2 deletions docs/src/pages/demos/lists/CheckboxListSecondary.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ class CheckboxListSecondary extends React.Component {

return (
<div className={classes.root}>
<List>
<List dense>
{[0, 1, 2, 3].map(value => (
<ListItem key={value} dense button>
<ListItem key={value} button>
<Avatar alt="Remy Sharp" src="/static/images/remy.jpg" />
<ListItemText primary={`Line item ${value + 1}`} />
<ListItemSecondaryAction>
Expand Down
65 changes: 29 additions & 36 deletions packages/material-ui/src/List/List.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import withStyles from '../styles/withStyles';
import ListContext from './ListContext';

export const styles = {
/* Styles applied to the root element. */
Expand All @@ -27,41 +28,37 @@ export const styles = {
},
};

class List extends React.Component {
getChildContext() {
return {
dense: this.props.dense,
};
}
function List(props) {
const {
children,
classes,
className,
component: Component,
dense,
disablePadding,
subheader,
...other
} = props;

render() {
const {
children,
classes,
className: classNameProp,
component: Component,
dense,
disablePadding,
subheader,
...other
} = this.props;
const className = classNames(
classes.root,
{
[classes.dense]: dense && !disablePadding,
[classes.padding]: !disablePadding,
[classes.subheader]: subheader,
},
classNameProp,
);

return (
<Component className={className} {...other}>
return (
<Component
className={classNames(
classes.root,
{
[classes.dense]: dense && !disablePadding,
[classes.padding]: !disablePadding,
[classes.subheader]: subheader,
},
className,
)}
{...other}
>
<ListContext.Provider value={{ dense }}>
{subheader}
{children}
</Component>
);
}
</ListContext.Provider>
</Component>
);
}

List.propTypes = {
Expand Down Expand Up @@ -105,8 +102,4 @@ List.defaultProps = {
disablePadding: false,
};

List.childContextTypes = {
dense: PropTypes.bool,
};

export default withStyles(styles, { name: 'MuiList' })(List);
4 changes: 2 additions & 2 deletions packages/material-ui/src/List/List.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,13 @@ describe('<List />', () => {
it('should forward the context', () => {
const wrapper1 = shallow(<List />);
assert.strictEqual(
wrapper1.instance().getChildContext().dense,
wrapper1.hasClass(classes.dense),
false,
'dense should be false by default',
);

const wrapper2 = shallow(<List dense />);
assert.strictEqual(wrapper2.instance().getChildContext().dense, true);
assert.strictEqual(wrapper2.hasClass(classes.dense), true);
});
});
});
4 changes: 4 additions & 0 deletions packages/material-ui/src/List/ListContext.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { Context } from 'react';

declare const ListContext: Context<{ dense?: boolean }>;
export default ListContext;
8 changes: 8 additions & 0 deletions packages/material-ui/src/List/ListContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from 'react';

/**
* @ignore - internal component.
*/
const ListContext = React.createContext({});

export default ListContext;
169 changes: 80 additions & 89 deletions packages/material-ui/src/ListItem/ListItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import classNames from 'classnames';
import withStyles from '../styles/withStyles';
import ButtonBase from '../ButtonBase';
import { isMuiElement } from '../utils/reactHelpers';
import MergeListContext from './MergeListContext';

export const styles = theme => ({
/* Styles applied to the (normally root) `component` element. May be wrapped by a `container`. */
Expand Down Expand Up @@ -73,90 +74,88 @@ export const styles = theme => ({
selected: {},
});

class ListItem extends React.Component {
getChildContext() {
return {
dense: this.props.dense || this.context.dense || false,
};
}

render() {
const {
button,
children: childrenProp,
classes,
className: classNameProp,
component: componentProp,
ContainerComponent,
ContainerProps: { className: ContainerClassName, ...ContainerProps } = {},
dense,
disabled,
disableGutters,
divider,
focusVisibleClassName,
selected,
...other
} = this.props;

const isDense = dense || this.context.dense || false;
const children = React.Children.toArray(childrenProp);
const hasAvatar = children.some(value => isMuiElement(value, ['ListItemAvatar']));
const hasSecondaryAction =
children.length && isMuiElement(children[children.length - 1], ['ListItemSecondaryAction']);

const className = classNames(
classes.root,
classes.default,
{
[classes.dense]: isDense || hasAvatar,
[classes.gutters]: !disableGutters,
[classes.divider]: divider,
[classes.disabled]: disabled,
[classes.button]: button,
[classes.secondaryAction]: hasSecondaryAction,
[classes.selected]: selected,
},
classNameProp,
);

const componentProps = { className, disabled, ...other };
let Component = componentProp || 'li';

if (button) {
componentProps.component = componentProp || 'div';
componentProps.focusVisibleClassName = classNames(
classes.focusVisible,
focusVisibleClassName,
);
Component = ButtonBase;
}

if (hasSecondaryAction) {
// Use div by default.
Component = !componentProps.component && !componentProp ? 'div' : Component;

// Avoid nesting of li > li.
if (ContainerComponent === 'li') {
if (Component === 'li') {
Component = 'div';
} else if (componentProps.component === 'li') {
componentProps.component = 'div';
function ListItem(props) {
const {
button,
children: childrenProp,
classes,
className: classNameProp,
component: componentProp,
ContainerComponent,
ContainerProps: { className: ContainerClassName, ...ContainerProps } = {},
dense: denseProp,
disabled,
disableGutters,
divider,
focusVisibleClassName,
selected,
...other
} = props;

return (
<MergeListContext dense={denseProp}>
{({ dense }) => {
const children = React.Children.toArray(childrenProp);
const hasAvatar = children.some(value => isMuiElement(value, ['ListItemAvatar']));
const hasSecondaryAction =
children.length &&
isMuiElement(children[children.length - 1], ['ListItemSecondaryAction']);

const className = classNames(
classes.root,
classes.default,
{
[classes.dense]: dense || hasAvatar,
[classes.gutters]: !disableGutters,
[classes.divider]: divider,
[classes.disabled]: disabled,
[classes.button]: button,
[classes.secondaryAction]: hasSecondaryAction,
[classes.selected]: selected,
},
classNameProp,
);

const componentProps = { className, disabled, ...other };
let Component = componentProp || 'li';

if (button) {
componentProps.component = componentProp || 'div';
componentProps.focusVisibleClassName = classNames(
classes.focusVisible,
focusVisibleClassName,
);
Component = ButtonBase;
}
}

return (
<ContainerComponent
className={classNames(classes.container, ContainerClassName)}
{...ContainerProps}
>
<Component {...componentProps}>{children}</Component>
{children.pop()}
</ContainerComponent>
);
}
if (hasSecondaryAction) {
// Use div by default.
Component = !componentProps.component && !componentProp ? 'div' : Component;

// Avoid nesting of li > li.
if (ContainerComponent === 'li') {
if (Component === 'li') {
Component = 'div';
} else if (componentProps.component === 'li') {
componentProps.component = 'div';
}
}

return (
<ContainerComponent
className={classNames(classes.container, ContainerClassName)}
{...ContainerProps}
>
<Component {...componentProps}>{children}</Component>
{children.pop()}
</ContainerComponent>
);
}

return <Component {...componentProps}>{children}</Component>;
}
return <Component {...componentProps}>{children}</Component>;
}}
</MergeListContext>
);
}

ListItem.propTypes = {
Expand Down Expand Up @@ -228,12 +227,4 @@ ListItem.defaultProps = {
selected: false,
};

ListItem.contextTypes = {
dense: PropTypes.bool,
};

ListItem.childContextTypes = {
dense: PropTypes.bool,
};

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

0 comments on commit 371e505

Please sign in to comment.