-
-
Notifications
You must be signed in to change notification settings - Fork 32.4k
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] Making list meet Material Guidelines #6316
Changes from 7 commits
4be924d
ed0e996
40b90a0
e8277e2
a852b81
2eed341
510f6f2
8a3da54
21f4cd6
7328c84
26428d4
06846b5
2af14ec
aee5667
6b137ae
33d1f41
c7c3f1d
824709c
87e4f6d
ba004b1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
// @flow weak | ||
|
||
import React, { Component } from 'react'; | ||
import { createStyleSheet } from 'jss-theme-reactor'; | ||
import customPropTypes from 'material-ui/utils/customPropTypes'; | ||
import { | ||
List, | ||
ListItem, | ||
ListItemText, | ||
ListItemIcon, | ||
ListItemSecondaryAction, | ||
} from 'material-ui/List'; | ||
import Avatar from 'material-ui/Avatar'; | ||
import FolderIcon from 'material-ui/svg-icons/folder'; | ||
import IconButton from 'material-ui/IconButton'; | ||
import { LabelCheckbox } from 'material-ui/Checkbox'; | ||
import Layout from 'material-ui/Layout'; | ||
import SvgIcon from 'material-ui/SvgIcon'; | ||
import Text from 'material-ui/Text'; | ||
|
||
const SqureIcon = (props) => ( | ||
<SvgIcon {...props} viewBox="3 3 18 18"> | ||
<path d="M3,3V21H21V3" /> | ||
</SvgIcon> | ||
); | ||
|
||
const styleSheet = createStyleSheet('SingleLineList', (theme) => ({ | ||
root: { | ||
flexGrow: 1, | ||
maxWidth: 752, | ||
}, | ||
container: { | ||
width: '100%', | ||
}, | ||
demoContainer: { | ||
margin: '0 auto', | ||
maxWidth: 360, | ||
}, | ||
demo: { | ||
background: theme.palette.background.paper, | ||
border: `solid ${theme.palette.text.divider}`, | ||
borderWidth: '1px 1px 0', | ||
marginBottom: 16, | ||
maxHeight: 160, | ||
overflow: 'hidden', | ||
position: 'relative', | ||
width: '100%', | ||
'&:before': { | ||
backgroundImage: `linear-gradient(45deg, ${theme.palette.background.contentFrame} 25%, transparent 25%), | ||
linear-gradient(-45deg, ${theme.palette.background.contentFrame} 25%, transparent 25%)`, | ||
backgroundSize: '20px 20px', | ||
backgroundPosition: '10px -10px', | ||
bottom: 0, | ||
content: '""', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good, doing the other way around crash on IE11 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @oliviertassinari So |
||
display: 'block', | ||
height: 10, | ||
position: 'absolute', | ||
width: '100%', | ||
zIndex: 99, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why using that value? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @oliviertassinari You mean There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In that case, you could be using the |
||
}, | ||
}, | ||
title: { | ||
margin: '32px 0 16px', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would rathe be using the unit to show it's existing and that spacing should be consitent. margin: `${theme.spacing.unit * 4}px 0 ${theme.spacing.unit * 2}px`, |
||
}, | ||
})); | ||
|
||
class SingleLineList extends Component { | ||
static contextTypes = { | ||
styleManager: customPropTypes.muiRequired, | ||
}; | ||
|
||
state = { | ||
dense: false, | ||
}; | ||
|
||
render() { | ||
const classes = this.context.styleManager.render(styleSheet); | ||
const { dense } = this.state | ||
|
||
return ( | ||
<div className={classes.root}> | ||
<LabelCheckbox | ||
checked={dense} | ||
onChange={(event, checked) => this.setState({ dense: checked })} | ||
label="Enable dense preview" | ||
value="dense" | ||
/> | ||
<Layout container className={classes.container}> | ||
<Layout item xs={12} md={6}> | ||
<div className={classes.demoContainer}> | ||
<Text type="title" className={classes.title}>Text only</Text> | ||
<div className={classes.demo}> | ||
<List dense={dense}> | ||
<ListItem button> | ||
<ListItemText primary="Single-line item" /> | ||
</ListItem> | ||
<ListItem button> | ||
<ListItemText primary="Single-line item" /> | ||
</ListItem> | ||
<ListItem button> | ||
<ListItemText primary="Single-line item" /> | ||
</ListItem> | ||
<ListItem button> | ||
<ListItemText primary="Single-line item" /> | ||
</ListItem> | ||
</List> | ||
</div> | ||
<Text type="body1" secondary>Single-line list</Text> | ||
</div> | ||
</Layout> | ||
<Layout item xs={12} md={6}> | ||
<div className={classes.demoContainer}> | ||
<Text type="title" className={classes.title}>Icon with text</Text> | ||
<div className={classes.demo}> | ||
<List dense={dense}> | ||
<ListItem button> | ||
<ListItemIcon><SqureIcon /></ListItemIcon> | ||
<ListItemText primary="Single-line item" /> | ||
</ListItem> | ||
<ListItem button> | ||
<ListItemIcon><SqureIcon /></ListItemIcon> | ||
<ListItemText primary="Single-line item" /> | ||
</ListItem> | ||
<ListItem button> | ||
<ListItemIcon><SqureIcon /></ListItemIcon> | ||
<ListItemText primary="Single-line item" /> | ||
</ListItem> | ||
<ListItem button> | ||
<ListItemIcon><SqureIcon /></ListItemIcon> | ||
<ListItemText primary="Single-line item" /> | ||
</ListItem> | ||
</List> | ||
</div> | ||
<Text type="body1" secondary>Single-line list with icon</Text> | ||
</div> | ||
</Layout> | ||
</Layout> | ||
<Layout container className={classes.container}> | ||
<Layout item xs={12} md={6}> | ||
<div className={classes.demoContainer}> | ||
<Text type="title" className={classes.title}>Avatar with text</Text> | ||
<div className={classes.demo} style={{ maxHeight: 184 }}> | ||
<List dense={dense}> | ||
<ListItem button> | ||
<Avatar><FolderIcon /></Avatar> | ||
<ListItemText primary="Single-line item" /> | ||
</ListItem> | ||
<ListItem button> | ||
<Avatar><FolderIcon /></Avatar> | ||
<ListItemText primary="Single-line item" /> | ||
</ListItem> | ||
<ListItem button> | ||
<Avatar><FolderIcon /></Avatar> | ||
<ListItemText primary="Single-line item" /> | ||
</ListItem> | ||
<ListItem button> | ||
<Avatar><FolderIcon /></Avatar> | ||
<ListItemText primary="Single-line item" /> | ||
</ListItem> | ||
</List> | ||
</div> | ||
<Text type="body1" secondary>Single-line item with avatar</Text> | ||
</div> | ||
</Layout> | ||
<Layout item xs={12} md={6}> | ||
<div className={classes.demoContainer}> | ||
<Text type="title" className={classes.title}>Avatar with text and icon</Text> | ||
<div className={classes.demo} style={{ maxHeight: 184 }}> | ||
<List dense={dense}> | ||
<ListItem button> | ||
<Avatar><FolderIcon /></Avatar> | ||
<ListItemText primary="Single-line item" /> | ||
<ListItemSecondaryAction> | ||
<IconButton>comment</IconButton> | ||
</ListItemSecondaryAction> | ||
</ListItem> | ||
<ListItem button> | ||
<Avatar><FolderIcon /></Avatar> | ||
<ListItemText primary="Single-line item" /> | ||
<ListItemSecondaryAction> | ||
<IconButton>comment</IconButton> | ||
</ListItemSecondaryAction> | ||
</ListItem> | ||
<ListItem button> | ||
<Avatar><FolderIcon /></Avatar> | ||
<ListItemText primary="Single-line item" /> | ||
<ListItemSecondaryAction> | ||
<IconButton>comment</IconButton> | ||
</ListItemSecondaryAction> | ||
</ListItem> | ||
<ListItem button> | ||
<Avatar><FolderIcon /></Avatar> | ||
<ListItemText primary="Single-line item" /> | ||
<ListItemSecondaryAction> | ||
<IconButton>comment</IconButton> | ||
</ListItemSecondaryAction> | ||
</ListItem> | ||
</List> | ||
</div> | ||
<Text type="body1" secondary>Single-line item with avatar and icon</Text> | ||
</div> | ||
</Layout> | ||
</Layout> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
export default SingleLineList; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,10 @@ export const styleSheet = createStyleSheet('MuiList', () => { | |
paddingTop: 8, | ||
paddingBottom: 8, | ||
}, | ||
dense: { | ||
paddingTop: 4, | ||
paddingBottom: 4, | ||
}, | ||
subheader: { | ||
paddingTop: 0, | ||
}, | ||
|
@@ -33,20 +37,24 @@ export default function List(props, context) { | |
component: ComponentProp, | ||
padding, | ||
children, | ||
dense, | ||
subheader, | ||
rootRef, | ||
...other | ||
} = props; | ||
const classes = context.styleManager.render(styleSheet); | ||
const className = classNames(classes.root, { | ||
[classes.dense]: dense, | ||
[classes.padding]: padding, | ||
[classes.subheader]: subheader, | ||
}, classNameProp); | ||
|
||
return ( | ||
<ComponentProp ref={rootRef} className={className} {...other}> | ||
{subheader} | ||
{children} | ||
{React.Children.map(children, (child) => | ||
React.cloneElement(child, { dense }), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would rather be using the context. I think that it would perform much better. |
||
)} | ||
</ComponentProp> | ||
); | ||
} | ||
|
@@ -65,6 +73,7 @@ List.propTypes = { | |
PropTypes.string, | ||
PropTypes.func, | ||
]), | ||
dense: PropTypes.bool, | ||
padding: PropTypes.bool, | ||
/** | ||
* @ignore | ||
|
@@ -75,6 +84,7 @@ List.propTypes = { | |
|
||
List.defaultProps = { | ||
component: 'div', | ||
dense: false, | ||
padding: true, | ||
}; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,13 +22,18 @@ export const styleSheet = createStyleSheet('MuiListItem', (theme) => { | |
background: palette.text.divider, | ||
}, | ||
default: { | ||
paddingTop: 19, | ||
paddingBottom: 19, | ||
paddingTop: 12, | ||
paddingBottom: 12, | ||
}, | ||
dense: { | ||
paddingTop: 8, | ||
paddingBottom: 8, | ||
}, | ||
avatarDense: { | ||
height: '32px !important', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
marginRight: 8, | ||
width: '32px !important', | ||
}, | ||
disabled: { | ||
opacity: 0.5, | ||
}, | ||
|
@@ -54,6 +59,14 @@ export const styleSheet = createStyleSheet('MuiListItem', (theme) => { | |
}; | ||
}); | ||
|
||
const mapListIemChildren = (children, classes, dense) => React.Children.map(children, (child) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we have to perform such expensive operation/brittle logic, I think that relying on the context would be better. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @oliviertassinari Definitely context will rock here |
||
const props = {}; | ||
if (child.type.name === 'ListItemIcon' || child.type.name === 'ListItemText') props.dense = dense; | ||
if (child.type.name === 'Avatar' && dense) props.className = classes.avatarDense; | ||
|
||
return React.cloneElement(child, props); | ||
}); | ||
|
||
export default class ListItem extends Component { | ||
static propTypes = { | ||
button: PropTypes.bool, | ||
|
@@ -106,12 +119,21 @@ export default class ListItem extends Component { | |
} = this.props; | ||
|
||
const classes = this.context.styleManager.render(styleSheet); | ||
const children = React.Children.toArray(childrenProp); | ||
|
||
// let hasIcon; | ||
let hasAvatar; | ||
React.Children.map(children, (child) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use forEach on the children instead, you are paying the Children method cost twice. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or even better use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That perform better |
||
// if (child.type.name === 'ListItemIcon') hasIcon = true; | ||
if (child.type.name === 'Avatar') hasAvatar = true; | ||
}); | ||
|
||
const className = classNames(classes.listItem, { | ||
[classes.gutters]: gutters, | ||
[classes.divider]: divider, | ||
[classes.disabled]: disabled, | ||
[classes.button]: button, | ||
[dense ? classes.dense : classes.default]: true, | ||
[dense || hasAvatar ? classes.dense : classes.default]: true, | ||
}, classNameProp); | ||
|
||
const listItemProps = { className, disabled, ...other }; | ||
|
@@ -123,8 +145,6 @@ export default class ListItem extends Component { | |
listItemProps.keyboardFocusedClassName = classes.keyboardFocused; | ||
} | ||
|
||
const children = React.Children.toArray(childrenProp); | ||
|
||
if ( | ||
children.length && | ||
children[children.length - 1].type && | ||
|
@@ -134,7 +154,7 @@ export default class ListItem extends Component { | |
return ( | ||
<div className={classes.listItemContainer}> | ||
<ComponentMain {...listItemProps}> | ||
{children} | ||
{mapListIemChildren(children, classes, dense)} | ||
</ComponentMain> | ||
{secondaryAction} | ||
</div> | ||
|
@@ -143,7 +163,7 @@ export default class ListItem extends Component { | |
|
||
return ( | ||
<ComponentMain {...listItemProps}> | ||
{children} | ||
{mapListIemChildren(children, classes, dense)} | ||
</ComponentMain> | ||
); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mbrookes This one is for you 💃