Add categories anchors
parent
d9ec7c0352
commit
bb70daf89a
|
@ -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;
|
||||
|
|
|
@ -54,7 +54,7 @@ class Example extends React.Component {
|
|||
<br />
|
||||
<br /><Operator><</Operator><Variable>Picker</Variable>
|
||||
<br /> emojiSize<Operator>=</Operator>{<Variable>{this.state.emojiSize}</Variable>} <input type='range' data-key='emojiSize' onChange={this.handleInput.bind(this)} min='16' max='64' value={this.state.emojiSize} />
|
||||
<br /> perLine<Operator>=</Operator>{<Variable>{this.state.perLine}</Variable>} {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>{<Variable>{this.state.perLine}</Variable>} {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>{<Variable>{this.state.skin}</Variable>} <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>{(<Variable>emoji</Variable>) => console.log(<Variable>emoji</Variable>)}
|
||||
|
|
|
@ -18,6 +18,13 @@ module.exports = {
|
|||
path.resolve('example'),
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.svg$/,
|
||||
loader: 'svg-inline',
|
||||
include: [
|
||||
path.resolve('src/svgs'),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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: [],
|
||||
}
|
|
@ -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}
|
||||
|
|
|
@ -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)}>
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
}());
|
|
@ -23,6 +23,13 @@ module.exports = {
|
|||
path.resolve('data'),
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.svg$/,
|
||||
loader: 'svg-inline',
|
||||
include: [
|
||||
path.resolve('src/svgs'),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
|
|
Loading…
Reference in New Issue