=== List
- selectable-список с возможностью абсолютной кастомизации элементов
.scrollable
– горизонтальный скролл с фейдингом (scrollbar не реализован)
.List { | |
height: 100%; | |
overflow-y: auto; | |
-webkit-overflow-scrolling: touch; | |
} | |
.List__inner { | |
margin: 0; | |
padding: 0; | |
list-style: none; | |
} | |
.List__item { | |
position: relative; | |
display: block; | |
cursor: pointer; | |
} | |
.List__item.selected, | |
.List__item:not(.selected):hover { | |
background-color: rgba(46, 148, 177, 0.2); | |
} | |
.List__item:not(.selected):hover { | |
opacity: .8; | |
} | |
/* scrollable */ | |
.List.scrollable { | |
margin-left: -2px; | |
} | |
.List.scrollable:after { | |
content: ''; | |
position: absolute; | |
top: 0; | |
left: 0; | |
height: 100%; | |
width: 100%; | |
pointer-events: none; | |
} | |
.List.scrollable.fade-left:after { | |
background-image: linear-gradient(to right, #222 0%, rgba(0,0,0,0) 40px); | |
} | |
.List.scrollable.fade-right:after { | |
background-image: linear-gradient(to left, #222 0%, rgba(0,0,0,0) 40px); | |
} | |
.List.scrollable.fade-left.fade-right:after { | |
background-image: linear-gradient(to right, #222 0%, rgba(0,0,0,0) 40px), | |
linear-gradient(to left, #222 0%, rgba(0,0,0,0) 40px); | |
} | |
.List.scrollable .List__inner { | |
white-space: nowrap; | |
transition: transform .1s ease-out; | |
overflow-x: auto; | |
display: inline-block; | |
} | |
.List.scrollable .List__item { | |
display: inline-block; | |
margin-left: 2px; | |
margin-bottom: 2px; | |
transition: opacity .1s ease-out; | |
} |
import React, { Component } from 'react' | |
import classnames from 'classnames' | |
import './List.css' | |
class List extends Component { | |
constructor(props) { | |
super(props) | |
this.innerPos = 0 | |
} | |
componentDidMount() { | |
if (this.props.scrollable) { | |
this.domElem.addEventListener('wheel', this._onMouseWeel.bind(this)) | |
} | |
} | |
componentWillReceiveProps(nextProps) { | |
if (nextProps.selectedID === this.selectedID) { | |
return | |
} | |
setTimeout(() => { | |
const cerrentItemIndex = this.getCurrentItemIndex(this.props.selectedID) | |
const itemWidth = this.inner.offsetWidth / this.props.items.length | |
const newPos = -cerrentItemIndex * itemWidth + itemWidth | |
this.updatePosition(newPos) | |
this.selectedID = this.props.selectedID | |
}, 0) | |
} | |
getCurrentItemIndex(id) { | |
let index = 0; | |
this.props.items.some((data, i) => { | |
if (data.id === id) { | |
index = i | |
return true | |
} | |
}); | |
return index | |
} | |
_onMouseWeel(e) { | |
const pos = this.innerPos + e.wheelDelta | |
this.updatePosition(pos) | |
} | |
updatePosition(pos) { | |
const wrapWidth = this.domElem.offsetWidth | |
const innerWidth = this.inner.offsetWidth | |
const rightLimit = -innerWidth + wrapWidth | |
// left border | |
if (pos > 0) { | |
pos = 0 | |
} | |
// right border | |
if (pos < rightLimit) { | |
pos = rightLimit | |
} | |
this.innerPos = pos | |
this.forceUpdate() | |
} | |
_getFades() { | |
const { scrollable } = this.props | |
if (!this.domElem) { | |
return {} | |
} | |
const classes = {scrollable: this.props.scrollable} | |
const wrapWidth = this.domElem.offsetWidth | |
const innerWidth = this.inner.offsetWidth | |
const rightLimit = -innerWidth + wrapWidth | |
const pos = this.innerPos | |
if (innerWidth > wrapWidth) { | |
classes['fade-left'] = pos < 0 | |
classes['fade-right'] = pos > rightLimit | |
} | |
return classes | |
} | |
_getClasses() { | |
const { mix } = this.props | |
const classes = Object.assign({ | |
'List': true, | |
[mix]: Boolean(mix) | |
}, this._getFades()) | |
return classnames(classes) | |
} | |
_onItemClick(data) { | |
this.props.onItemClick(data) | |
} | |
_renderItem(data) { | |
const id = data.id | |
const name = data.name | |
const classes = classnames({ | |
List__item: true, | |
selected: id === this.props.selectedID | |
}); | |
return ( | |
<li className={classes} | |
key={id} | |
onClick={this._onItemClick.bind(this, id)} | |
title={name}> | |
{data.content} | |
</li> | |
) | |
} | |
render() { | |
const classes = this._getClasses() | |
const innerStyle = {transform: `translateX(${this.innerPos}px)`} | |
const itemsContent = this.props.items.map(item => this._renderItem(item)) | |
return( | |
<div className={classes} | |
ref={elem => this.domElem = elem}> | |
<ul className='List__inner' | |
ref={elem => this.inner = elem} | |
style={innerStyle}> | |
{itemsContent} | |
</ul> | |
</div> | |
); | |
} | |
}; | |
export default List |
#govnocode