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] Use stable context API #13498

Merged
merged 2 commits into from
Nov 4, 2018
Merged
Show file tree
Hide file tree
Changes from all 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
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);
Loading