Merge pull request #15 from react-toolbox/lists

Add basic list component
old
Javi Velasco 2015-10-19 21:28:01 +02:00
commit ff5a8c3e49
12 changed files with 502 additions and 144 deletions

View File

@ -13,7 +13,7 @@ export default React.createClass({
checked: React.PropTypes.bool,
className: React.PropTypes.string,
disabled: React.PropTypes.bool,
label: React.PropTypes.string,
label: React.PropTypes.any,
name: React.PropTypes.string,
onBlur: React.PropTypes.func,
onChange: React.PropTypes.func,
@ -76,7 +76,7 @@ export default React.createClass({
<span role='checkbox' className={checkboxClassName} onMouseDown={this.handleMouseDown}>
<Ripple ref='ripple' role='ripple' className={style.ripple} spread={3} centered />
</span>
{ this.props.label ? <span className={style.text}>{this.props.label}</span> : null }
{ this.props.label ? <span role='label' className={style.text}>{this.props.label}</span> : null }
</label>
);
},

View File

@ -41,6 +41,7 @@ strong, sub, sup, table, tbody, td, tfoot, th, thead, time, tr, ul, var, video {
h1, h2, h3, h4, h5, h6, label, p, button, abbr, a, span, small {
font-smoothing: antialiased;
-webkit-font-smoothing: antialiased;
text-size-adjust: 100%;
}

View File

@ -0,0 +1,51 @@
import React from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import Checkbox from '../checkbox';
import ListItemContent from './content';
import style from './style';
const ListCheckbox = React.createClass({
mixins: [PureRenderMixin],
propTypes: {
caption: React.PropTypes.string.isRequired,
checked: React.PropTypes.bool,
className: React.PropTypes.string,
disabled: React.PropTypes.bool,
legend: React.PropTypes.string,
name: React.PropTypes.string,
onBlur: React.PropTypes.func,
onChange: React.PropTypes.func,
onFocus: React.PropTypes.func
},
getDefaultProps () {
return {
disabled: false
};
},
render () {
let className = `${style.item} ${style['checkbox-item']}`;
if (this.props.legend) className += ` ${style['with-legend']}`;
if (this.props.disabled) className += ` ${style.disabled}`;
if (this.props.className) className += ` ${this.props.className}`;
return (
<li className={className}>
<Checkbox
checked={this.props.checked}
className={style.checkbox}
disabled={this.props.disabled}
label={<ListItemContent caption={this.props.caption} legend={this.props.legend} />}
name={this.props.name}
onBlur={this.props.onBlur}
onChange={this.props.onChange}
onFocus={this.props.onFocus}
/>
</li>
);
}
});
export default ListCheckbox;

View File

@ -0,0 +1,18 @@
import React from 'react';
import style from './style';
const ListItemContent = ({ caption, legend }) => {
return (
<span className={style.text}>
<span className={style.caption}>{caption}</span>
<span className={style.legend}>{legend}</span>
</span>
);
};
ListItemContent.propTypes = {
caption: React.PropTypes.string.isRequired,
legend: React.PropTypes.any
};
export default ListItemContent;

View File

@ -0,0 +1,17 @@
import React from 'react';
import style from './style';
const ListDivider = ({inset}) => {
const className = inset ? `${style.divider} ${style.inset}` : style.divider;
return <hr className={className} />;
};
ListDivider.propTypes = {
inset: React.PropTypes.bool
};
ListDivider.defaultProps = {
inset: false
};
export default ListDivider;

View File

@ -1,51 +1,7 @@
import React from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import style from './style';
export default React.createClass({
mixins: [PureRenderMixin],
displayName: 'List',
propTypes: {
className: React.PropTypes.string,
dataSource: React.PropTypes.array,
template: React.PropTypes.func,
onClick: React.PropTypes.func,
type: React.PropTypes.string
},
getDefaultProps () {
return {
attributes: '',
className: '',
dataSource: [],
type: 'default'
};
},
onClick (event, data, index) {
if (this.props.onClick) {
this.props.onClick(event, data, (this.refs[index] ? this.refs[index] : null));
}
},
render () {
let className = `${style.root} ${this.props.className}`;
if (this.props.type) className += ` ${this.props.type}`;
const items = this.props.dataSource.map((data, index) => {
return (
<li key={index} onClick={this.onClick.bind(null, data, index)}>
{this.props.template(data, index)}
</li>
);
});
return (
<ul data-react-toolbox='list' className={className}>
{ items }
</ul>
);
}
});
module.exports = {
List: require('./list'),
ListItem: require('./item'),
ListDivider: require('./divider'),
ListCheckbox: require('./checkbox'),
ListSubHeader: require('./subheader')
};

65
components/list/item.jsx Normal file
View File

@ -0,0 +1,65 @@
import React from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import FontIcon from '../font_icon';
import ListItemContent from './content';
import Ripple from '../ripple';
import style from './style';
const ListItem = React.createClass({
mixins: [PureRenderMixin],
propTypes: {
avatar: React.PropTypes.string,
caption: React.PropTypes.string.isRequired,
disabled: React.PropTypes.bool,
leftIcon: React.PropTypes.string,
legend: React.PropTypes.string,
rightIcon: React.PropTypes.string,
ripple: React.PropTypes.bool,
selectable: React.PropTypes.bool
},
getDefaultProps () {
return {
disabled: false,
ripple: false,
selectable: false
};
},
handleClick (event) {
if (this.props.onClick && !this.props.disabled) {
this.props.onClick(event);
}
},
handleMouseDown (event) {
if (this.refs.ripple && !this.props.disabled) {
this.refs.ripple.start(event);
}
},
render () {
let className = style.item;
if (this.props.legend) className += ` ${style['with-legend']}`;
if (this.props.disabled) className += ` ${style.disabled}`;
if (this.props.selectable) className += ` ${style.selectable}`;
if (this.props.className) className += ` ${this.props.className}`;
return (
<li
className={className}
onClick={this.handleClick}
onMouseDown={this.handleMouseDown}
>
{ this.props.leftIcon ? <FontIcon className={`${style.icon} ${style.left}`} value={this.props.leftIcon} /> : null }
{ this.props.avatar ? <img className={style.avatar} src={this.props.avatar} /> : null }
<ListItemContent caption={this.props.caption} legend={this.props.legend} />
{ this.props.ripple ? <Ripple ref='ripple' className={style.ripple} spread={2} /> : null }
{ this.props.rightIcon ? <FontIcon className={`${style.icon} ${style.right}`} value={this.props.rightIcon} /> : null }
</li>
);
}
});
export default ListItem;

48
components/list/list.jsx Normal file
View File

@ -0,0 +1,48 @@
import React from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import ListItem from './item';
import style from './style';
const List = React.createClass({
mixins: [PureRenderMixin],
propTypes: {
className: React.PropTypes.string,
onClick: React.PropTypes.func,
ripple: React.PropTypes.bool,
selectable: React.PropTypes.bool
},
getDefaultProps () {
return {
className: '',
ripple: false,
selectable: false
};
},
renderItems () {
return React.Children.map(this.props.children, (item) => {
if (item.type === ListItem) {
return React.cloneElement(item, {
ripple: this.props.ripple,
selectable: this.props.selectable
});
} else {
return React.cloneElement(item);
}
});
},
render () {
let className = style.list;
if (this.props.className) className += ` ${this.props.className}`;
return (
<ul className={className}>
{ this.renderItems() }
</ul>
);
}
});
export default List;

View File

@ -1,25 +1,146 @@
@import "../variables";
$list-color: unquote("rgb(#{$color-black})") !default;
$list-background: unquote("rgb(#{$color-white})") !default;
$list-vertical-padding: .8 * $unit;
$list-horizontal-padding: 1.6 * $unit;
$list-content-left-spacing: 7.2 * $unit;
$list-subheader-height: 4.8 * $unit;
$list-subheader-font-size: 1.4 * $unit;
$list-subheader-font-weight: 500;
$list-divider-height: .1 * $unit;
$list-item-min-height: 4.8 * $unit;
$list-item-min-height-legend: 7.2 * $unit;
$list-item-hover-color: unquote("rgb(#{$palette-grey-200})");
$list-item-legend-margin-top: .3 * $unit;
$list-item-icon-font-size: 2.4 * $unit;
$list-item-icon-size: 1.8 * $unit;
$list-item-right-icon-margin: $list-content-left-spacing - $list-horizontal-padding - $list-item-icon-size;
$list-item-avatar-height: 4 * $unit;
$list-item-avatar-margin: .8 * $unit;
.root {
.list {
position: relative;
display: inline-block;
width: 100%;
padding: $list-vertical-padding 0;
text-align: left;
white-space: nowrap;
list-style: none;
}
&, a {
color: $list-color;
.subheader {
padding-left: $list-horizontal-padding;
margin: - $list-vertical-padding 0 0;
font-size: $list-subheader-font-size;
font-weight: $list-subheader-font-weight;
line-height: $list-subheader-height;
color: $color-text-secondary;
}
.divider {
height: $list-divider-height;
margin: - $list-divider-height 0 0;
background-color: $color-divider;
border: none;
&.inset {
margin-right: $list-horizontal-padding;
margin-left: $list-content-left-spacing;
}
.list + & {
margin-top: - $list-vertical-padding;
}
.item ~ & {
margin: $list-vertical-padding 0;
}
}
> li {
overflow: hidden;
background-color: $list-background;
box-shadow: $zdepth-shadow-1;
transition-timing-function: $animation-curve-default;
transition-duration: $animation-duration;
transition-property: box-shadow;
&:hover {
box-shadow: $zdepth-shadow-2;
.item {
position: relative;
display: flex;
min-height: $list-item-min-height;
align-items: center;
padding: 0 $list-horizontal-padding;
overflow: hidden;
color: $color-text;
&.selectable:not(.disabled):hover {
cursor: pointer;
background-color: $list-item-hover-color;
}
&.with-legend {
height: $list-item-min-height-legend;
}
> .checkbox {
display: flex;
width: 100%;
min-height: $list-item-min-height;
align-items: center;
margin: 0;
cursor: pointer;
> [role='checkbox'] {
margin-right: $list-item-right-icon-margin;
}
> [role='label'] {
padding-left: 0;
}
}
&.disabled {
pointer-events: none;
&:not(.checkbox-item) {
opacity: .5;
}
> .checkbox > [role='label'] {
opacity: .5;
}
}
}
.ripple {
opacity: .1;
}
.text {
flex-grow: 1;
}
.caption {
display: block;
font-size: 1.6 * $unit;
color: $color-text;
}
.legend {
display: block;
padding-top: $list-item-legend-margin-top;
font-size: 1.4 * $unit;
color: $color-text-secondary;
white-space: normal;
}
.avatar {
display: flex;
width: $list-item-avatar-height;
height: $list-item-avatar-height;
margin: $list-item-avatar-margin $list-horizontal-padding $list-item-avatar-margin 0;
overflow: hidden;
border-radius: 50%;
}
.right, .left {
display: flex;
align-items: center;
vertical-align: middle;
&.icon {
font-size: $list-item-icon-font-size;
color: $color-text-secondary;
}
}
.right {
margin-left: $list-horizontal-padding;
}
.left {
&.icon {
width: 1.8 * $unit;
margin-right: $list-item-right-icon-margin;
}
}

View File

@ -0,0 +1,16 @@
import React from 'react';
import style from './style';
const ListSubHeader = ({ caption }) => {
return (
<h5 className={style.subheader}>
{ caption }
</h5>
);
};
ListSubHeader.propTypes = {
caption: React.PropTypes.string
};
export default ListSubHeader;

View File

@ -1,52 +1,118 @@
/*eslint-disable no-unused-vars*/
import React from 'react';
import List from '../../components/list';
import { ListCheckbox, ListSubHeader, List, ListItem, ListDivider } from '../../components/list';
export default React.createClass({
displayName: 'ListTest',
const listStyle = {
border: '1px solid #EEE',
display: 'inline-block',
minWidth: 340
};
getInitialState () {
return {
countries: [
{ value: 'ES-es', label: 'Spain', img: 'http://' },
{ value: 'TH-th', label: 'Thailand', img: 'http://' },
{ value: 'EN-gb', label: 'England', img: 'http://' },
{ value: 'EN-en', label: 'USA', img: 'http://' },
{ value: 'FR-fr', label: 'France', img: 'http://' }
],
selected: 'TH-th'
};
},
onChange (dropdown) {
console.log('[DROPDOWN]', dropdown.getValue());
},
customDropdownItem (data) {
const style = {
width: 32,
height: 32,
backgroundColor: '#ccc',
marginRight: 8
};
return (
<div data-flex="horizontal grow" data-flex-content="center">
<img src={data.img} data-flex-grow="min" style={style} />
<div data-flex-grow="max" data-flex="vertical" >
<strong>{data.label}</strong>
<small>{data.value}</small>
</div>
const ListTest = () => {
return (
<section>
<h5>With simple text and icons</h5>
<p>This list can be used inside a Drawer for a list of options or as navigation.</p>
<div style={listStyle}>
<List selectable ripple>
<ListSubHeader caption='Contacts' />
<ListItem caption='Inbox' leftIcon='inbox' />
<ListItem caption='Outbox' leftIcon='send' />
<ListItem caption='Trash' leftIcon='delete' />
<ListItem caption='Spam' leftIcon='report' />
</List>
</div>
);
},
render () {
return (
<section>
<h5>List</h5>
<p>lorem ipsum...</p>
<List dataSource={this.state.countries} value={this.state.selected} template={this.customDropdownItem} onChange={this.onChange}/>
</section>
);
}
});
<h5>Two text lines, avatar and right icon</h5>
<p>Useful for a list of contacts or similar.</p>
<div style={listStyle}>
<List selectable ripple>
<ListSubHeader caption='Contacts' />
<ListItem
avatar='https://pbs.twimg.com/profile_images/614407428/s6pTalMzZs-nusCGWqoV.0_400x400.jpeg'
caption='Alfonso Rocha'
legend='Product Manager at Fon'
rightIcon='star'
/>
<ListItem
avatar='https://pbs.twimg.com/profile_images/459485216499720192/ufS4YGOY_400x400.png'
caption='Javi Velasco'
legend='Frontend engineer at Socialbro'
rightIcon='star'
/>
<ListItem
avatar='https://pbs.twimg.com/profile_images/651611834131222528/rKYHs2bd_400x400.jpg'
caption='Javi Jiménez'
legend='Frontend engineer at MediaSmart'
rightIcon='star'
/>
<ListItem
avatar='https://pbs.twimg.com/profile_images/477103691506282499/bsIaPEiM_400x400.jpeg'
caption='Tobias Van Schneider'
legend='Designer at Spotify'
rightIcon='star'
/>
</List>
</div>
<h5>Two line options and checkbox items</h5>
<p>It can be used to embed little checkboxes in the list. These behave as checkboxes.</p>
<div style={listStyle}>
<List>
<ListSubHeader caption='General' />
<ListItem caption='Profile Photo' legend='Change your Google+ profile photo' />
<ListItem disabled caption='Show your status' legend='Your status is visible to everyone you use with' />
</List>
<ListDivider />
<List>
<ListSubHeader caption='Hangout notifications' />
<ListCheckbox caption='Notifications' legend='Allow notifications' />
<ListCheckbox checked caption='Sound' legend='Hangouts message' />
<ListCheckbox disabled caption='Video sounds' legend='Hangouts video call' />
</List>
</div>
<h5>Avatar, sinle text and icon</h5>
<p>Similar to a previous one but only with one text line</p>
<div style={listStyle}>
<List>
<ListItem
avatar='https://pbs.twimg.com/profile_images/614407428/s6pTalMzZs-nusCGWqoV.0_400x400.jpeg'
caption='Alfonso Rocha'
rightIcon='mail'
/>
<ListItem
avatar='https://pbs.twimg.com/profile_images/459485216499720192/ufS4YGOY_400x400.png'
caption='Javi Velasco'
rightIcon='mail'
/>
<ListItem
avatar='https://pbs.twimg.com/profile_images/651611834131222528/rKYHs2bd_400x400.jpg'
caption='Javi Jiménez'
rightIcon='mail'
/>
<ListItem
avatar='https://pbs.twimg.com/profile_images/477103691506282499/bsIaPEiM_400x400.jpeg'
caption='Tobias Van Schneider'
rightIcon='mail'
/>
</List>
</div>
<h5>Simple with just one text line</h5>
<p>The most simple list.</p>
<div style={listStyle}>
<List>
<ListItem caption='Alfonso Rocha' />
<ListItem caption='Javi Velasco' />
<ListItem caption='Javi Jiménez' />
<ListItem caption='Tobias Van Schneider' />
<ListDivider />
<ListItem caption='Other people' />
</List>
</div>
</section>
);
};
export default ListTest;

View File

@ -1,3 +1,4 @@
/*eslint-disable no-unused-vars*/
import React from 'react';
import ReactDOM from 'react-dom';
@ -10,6 +11,7 @@ import Drawer from './components/drawer';
import Dropdown from './components/dropdown';
import IconMenu from './components/icon_menu';
import Input from './components/input';
import List from './components/list';
import Menu from './components/menu';
import Pickers from './components/pickers';
import Progress from './components/progress';
@ -18,33 +20,30 @@ import Slider from './components/slider';
import Switch from './components/switch';
import Tabs from './components/tabs';
const Test = React.createClass({
displayName: 'App',
const App = () => {
return (
<app data-react-toolbox-app>
<h1>React Toolbox</h1>
<h3>Component Spec v0.10.9</h3>
<Autocomplete />
<Button />
<Card />
<Checkbox />
<Dialog />
<Drawer />
<Dropdown />
<IconMenu />
<Input />
<List />
<Menu />
<Pickers />
<Progress />
<RadioGroup />
<Slider />
<Switch />
<Tabs />
</app>
);
};
render () {
return (
<app data-react-toolbox-app>
<h1>React Toolbox</h1>
<h3>Component Spec v0.10.9</h3>
<Autocomplete />
<Button />
<Card />
<Checkbox />
<Dialog />
<Drawer />
<Dropdown />
<IconMenu />
<Input />
<Menu />
<Pickers />
<Progress />
<RadioGroup />
<Slider />
<Switch />
<Tabs />
</app>
);
}
});
ReactDOM.render(<Test/>, document.getElementById('toolbox-test'));
ReactDOM.render(<App/>, document.getElementById('toolbox-test'));