Add categories anchors

release
Etienne Lemay 2016-06-02 11:26:48 -04:00
parent d9ec7c0352
commit bb70daf89a
9 changed files with 219 additions and 21 deletions

View File

@ -21,6 +21,47 @@
border-bottom-right-radius: 5px;
}
.emoji-picker-anchors {
display: flex;
justify-content: space-between;
padding: 0 6px;
color: #858585;
}
.emoji-picker-anchor {
position: relative;
flex: 1;
text-align: center;
padding: 12px 4px;
overflow: hidden;
transition: color .2s ease-out;
}
.emoji-picker-anchor-selected {
color: #ae65c5;
}
.emoji-picker-anchor-selected .emoji-picker-anchor-bar {
bottom: 0;
}
.emoji-picker-anchor-bar {
position: absolute;
bottom: -3px; left: 0;
width: 100%; height: 3px;
background-color: #ae65c5;
transition: bottom .2s ease-out;
}
.emoji-picker-anchors i {
display: inline-block;
width: 100%;
max-width: 22px;
}
.emoji-picker-anchors svg {
fill: currentColor;
}
.emoji-picker-scroll {
overflow: scroll;
height: 270px;

View File

@ -54,7 +54,7 @@ class Example extends React.Component {
<br />
<br /><Operator>&lt;</Operator><Variable>Picker</Variable>
<br /> emojiSize<Operator>=</Operator>&#123;<Variable>{this.state.emojiSize}</Variable>&#125; <input type='range' data-key='emojiSize' onChange={this.handleInput.bind(this)} min='16' max='64' value={this.state.emojiSize} />
<br /> perLine<Operator>=</Operator>&#123;<Variable>{this.state.perLine}</Variable>&#125; {this.state.perLine < 10 ? ' ' : ' '} <input type='range' data-key='perLine' onChange={this.handleInput.bind(this)} min='5' max='16' value={this.state.perLine} />
<br /> perLine<Operator>=</Operator>&#123;<Variable>{this.state.perLine}</Variable>&#125; {this.state.perLine < 10 ? ' ' : ' '} <input type='range' data-key='perLine' onChange={this.handleInput.bind(this)} min='6' max='16' value={this.state.perLine} />
<br /> skin<Operator>=</Operator>&#123;<Variable>{this.state.skin}</Variable>&#125; <input type='range' data-key='skin' onChange={this.handleInput.bind(this)} min='1' max='6' value={this.state.skin} />
<br /> sheetURL<Operator>=</Operator><String>'images/sheet-{this.state.set}-64.png'</String>
<br /> onClick<Operator>=</Operator>&#123;(<Variable>emoji</Variable>) => console.log(<Variable>emoji</Variable>)&#125;

View File

@ -18,6 +18,13 @@ module.exports = {
path.resolve('example'),
],
},
{
test: /\.svg$/,
loader: 'svg-inline',
include: [
path.resolve('src/svgs'),
],
},
],
},

View File

@ -34,6 +34,8 @@
"react": "0.14.7",
"react-dom": "0.14.7",
"rimraf": "2.5.2",
"svg-inline-loader": "0.4.1",
"svg-inline-react": "1.0.1",
"webpack": "1.12.14"
},
"scripts": {

58
src/components/anchors.js Normal file
View File

@ -0,0 +1,58 @@
import React from 'react'
import InlineSVG from 'svg-inline-react'
import Activity from '../svgs/activity.svg'
import Flags from '../svgs/flags.svg'
import Foods from '../svgs/foods.svg'
import Nature from '../svgs/nature.svg'
import Objects from '../svgs/objects.svg'
import People from '../svgs/people.svg'
import Places from '../svgs/places.svg'
import Recent from '../svgs/recent.svg'
import Symbols from '../svgs/symbols.svg'
var svgs = {
Activity,
Flags,
Foods,
Nature,
Objects,
People,
Places,
Recent,
Symbols,
}
export default class Anchors extends React.Component {
constructor(props) {
super(props)
this.state = {
selected: props.categories[0].name
}
}
render() {
var { categories } = this.props,
{ selected } = this.state
return <div className='emoji-picker-anchors'>
{categories.map((category, i) => {
var { name } = category
return <span key={name} className={`emoji-picker-anchor ${name == selected ? 'emoji-picker-anchor-selected' : ''}`}>
<InlineSVG src={svgs[name]} />
<span className='emoji-picker-anchor-bar'></span>
</span>
})}
</div>
}
}
Anchors.propTypes = {
categories: React.PropTypes.array,
}
Anchors.defaultProps = {
categories: [],
}

View File

@ -23,7 +23,11 @@ export default class Category extends React.Component {
var { height: labelHeight } = this.label.getBoundingClientRect()
this.top = top - parentTop + this.parent.scrollTop
this.maxMargin = height - labelHeight
if (height > labelHeight) {
this.maxMargin = height - labelHeight
} else {
this.maxMargin = 1
}
}
handleScroll(scrollTop) {
@ -32,15 +36,22 @@ export default class Category extends React.Component {
margin = margin > this.maxMargin ? this.maxMargin : margin
if (margin == this.margin) return
this.label.style.top = `${margin}px`
var { name } = this.props
if (!this.props.hasStickyPosition) {
this.label.style.top = `${margin}px`
}
this.margin = margin
return true
}
render() {
var { name, emojis, hasStickyPosition, emojiProps } = this.props,
emojis = emojis.slice(0),
emojis = emojis ? emojis.slice(0) : null,
labelStyles = {},
labelSpanStyles = {}
labelSpanStyles = {},
containerStyles = {}
if (!hasStickyPosition) {
labelStyles = {
@ -52,12 +63,19 @@ export default class Category extends React.Component {
}
}
return <div ref='container' className='emoji-picker-category'>
if (!emojis) {
containerStyles = {
height: 1,
overflow: 'hidden',
}
}
return <div ref='container' className='emoji-picker-category' style={containerStyles}>
<div style={labelStyles} data-name={name} className='emoji-picker-category-label'>
<span style={labelSpanStyles} ref='label'>{name}</span>
</div>
{emojis.map((emoji) =>
{emojis && emojis.map((emoji) =>
<Emoji
key={emoji}
emoji={emoji}
@ -65,7 +83,7 @@ export default class Category extends React.Component {
/>
)}
{!emojis.length &&
{emojis && !emojis.length &&
<div className='emoji-picker-no-results'>
<Emoji
{...emojiProps}

View File

@ -1,9 +1,16 @@
import '../utils/raf-polyfill'
import React from 'react'
import data from '../../data'
import Preview from './preview'
import Category from './category'
import Search from './search'
import Anchors from './anchors'
const DEFAULT_CATEGORIES = [
{ name: 'Recent', emojis: null }
].concat(data.categories)
export default class Picker extends React.Component {
constructor(props) {
@ -11,7 +18,7 @@ export default class Picker extends React.Component {
this.testStickyPosition()
this.state = {
categories: data.categories,
categories: DEFAULT_CATEGORIES,
}
}
@ -42,22 +49,46 @@ export default class Picker extends React.Component {
}
handleScroll() {
var target = this.refs.scroll,
scrollTop = target.scrollTop
if (!this.hasStickyPosition) {
Array(this.state.categories.length).fill().forEach((_, i) => {
var category = this.refs[`category-${i}`]
if (category) {
category.handleScroll(scrollTop)
}
})
if (!this.waitingForPaint) {
this.waitingForPaint = true
window.requestAnimationFrame(this.handleScrollPaint.bind(this))
}
}
handleScrollPaint() {
this.waitingForPaint = false
var target = this.refs.scroll,
scrollTop = target.scrollTop,
activeCategory = null
for (let i = 0, l = this.state.categories.length; i < l; i++) {
let category = this.state.categories[i],
component = this.refs[`category-${i}`]
if (component) {
let active = component.handleScroll(scrollTop)
if (active && !activeCategory) {
activeCategory = category
}
}
}
if (activeCategory) {
let { anchors } = this.refs,
{ name: categoryName } = activeCategory
if (anchors.state.selected != categoryName) {
anchors.setState({ selected: categoryName })
}
}
this.scrollTop = scrollTop
}
handleSearch(emojis) {
if (emojis == null) {
this.setState({ categories: data.categories })
this.setState({ categories: DEFAULT_CATEGORIES })
} else {
this.setState({ categories: [{
name: 'Search Results',
@ -72,7 +103,10 @@ export default class Picker extends React.Component {
return <div style={{width: width}} className='emoji-picker'>
<div className='emoji-picker-bar'>
Categories
<Anchors
ref='anchors'
categories={DEFAULT_CATEGORIES}
/>
</div>
<div ref="scroll" className='emoji-picker-scroll' onScroll={this.handleScroll.bind(this)}>

31
src/utils/raf-polyfill.js Normal file
View File

@ -0,0 +1,31 @@
// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel
// MIT license
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame']
|| window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());

View File

@ -23,6 +23,13 @@ module.exports = {
path.resolve('data'),
],
},
{
test: /\.svg$/,
loader: 'svg-inline',
include: [
path.resolve('src/svgs'),
],
},
],
},