Skip to content

Commit

Permalink
[List] Use stable context API
Browse files Browse the repository at this point in the history
  • Loading branch information
eps1lon committed Nov 3, 2018
1 parent 3b79b42 commit b90d33d
Show file tree
Hide file tree
Showing 10 changed files with 187 additions and 152 deletions.
63 changes: 27 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,35 @@ export const styles = {
},
};

class List extends React.Component {
getChildContext() {
return {
dense: this.props.dense,
};
}
function List(props) {
const {
children,
classes,
className: classNameProp,
component: Component,
dense,
disablePadding,
subheader,
...other
} = props;
const className = classNames(
classes.root,
{
[classes.dense]: dense && !disablePadding,
[classes.padding]: !disablePadding,
[classes.subheader]: subheader,
},
classNameProp,
);

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={className} {...other}>
<ListContext.Provider value={{ dense }}>
{subheader}
{children}
</Component>
);
}
</ListContext.Provider>
</Component>
);
}

List.propTypes = {
Expand Down Expand Up @@ -105,8 +100,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;
5 changes: 5 additions & 0 deletions packages/material-ui/src/List/ListContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from 'react';

const ListContext = React.createContext({});

export default ListContext;
1 change: 1 addition & 0 deletions packages/material-ui/src/List/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { default } from './List';
export * from './List';
export { default as ListContext } from './ListContext';
1 change: 1 addition & 0 deletions packages/material-ui/src/List/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default } from './List';
export { default as ListContext } from './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 MergeWithListContext from './MergeWithListContext';

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,
disabled,
disableGutters,
divider,
focusVisibleClassName,
selected,
...other
} = props;

return (
<MergeWithListContext dense={dense}>
{({ dense: isDense }) => {
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;
}
}

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>;
}}
</MergeWithListContext>
);
}

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);
9 changes: 9 additions & 0 deletions packages/material-ui/src/ListItem/MergeWithListContext.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as React from 'react';

export interface MergeWithListContextProps {
dense?: boolean;
}

declare const MergeWithListContext: React.ComponentType<MergeWithListContextProps>;

export default MergeWithListContext;
30 changes: 30 additions & 0 deletions packages/material-ui/src/ListItem/MergeWithListContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ListContext } from '../List';

/**
* Consumes a context and passes that context merged with its props
* @param props
*/
function MergeWithListContext(props) {
const { children, dense } = props;
return (
<ListContext.Consumer>
{context => {
const isDense = dense || context.dense || false;
const childContext = { dense: isDense };

return (
<ListContext.Provider value={childContext}>{children(childContext)}</ListContext.Provider>
);
}}
</ListContext.Consumer>
);
}

MergeWithListContext.propTypes = {
children: PropTypes.func.isRequired,
dense: PropTypes.bool,
};

export default MergeWithListContext;
Loading

0 comments on commit b90d33d

Please sign in to comment.