emoji-mart-lazyload/src/components/picker.js

369 lines
9.0 KiB
JavaScript
Raw Normal View History

2016-07-06 19:45:34 +00:00
import '../vendor/raf-polyfill'
2016-06-02 15:26:48 +00:00
2016-05-31 14:36:52 +00:00
import React from 'react'
import data from '../../data'
2016-06-02 17:21:31 +00:00
2016-07-20 18:45:28 +00:00
import store from '../utils/store'
import frequently from '../utils/frequently'
2016-10-27 03:27:55 +00:00
import { deepMerge } from '../utils'
2016-07-20 18:45:28 +00:00
2016-10-27 03:27:55 +00:00
import { Anchors, Category, Emoji, Preview, Search } from '.'
2016-06-02 15:26:48 +00:00
2016-07-08 17:56:29 +00:00
const RECENT_CATEGORY = { name: 'Recent', emojis: null }
const SEARCH_CATEGORY = { name: 'Search', emojis: null, anchor: RECENT_CATEGORY }
let CATEGORIES = [];
2016-05-31 14:36:52 +00:00
2016-10-27 03:22:59 +00:00
const I18N = {
search: 'Search',
categories: {
search: 'Search Results',
recent: 'Frequently Used',
people: 'Smileys & People',
nature: 'Animals & Nature',
foods: 'Food & Drink',
activity: 'Activity',
places: 'Travel & Places',
objects: 'Objects',
symbols: 'Symbols',
flags: 'Flags',
},
}
2016-05-31 14:36:52 +00:00
export default class Picker extends React.Component {
2016-05-31 18:39:30 +00:00
constructor(props) {
super(props)
2016-10-27 03:22:59 +00:00
this.i18n = deepMerge(I18N, props.i18n)
2016-05-31 18:39:30 +00:00
this.state = {
skin: store.get('skin') || props.skin,
firstRender: true,
2016-05-31 18:39:30 +00:00
}
let filteredCategories = [];
for (let hash of data.categories) {
let new_emojis = [];
for (let emoji of hash.emojis) {
let unified = data.emojis[emoji].unified;
if (props.emojisToShowFilter(unified)) {
new_emojis.push(emoji)
}
}
if (new_emojis.length) {
let new_hash = {
emojis: new_emojis,
name: hash.name
}
filteredCategories.push(new_hash);
}
}
CATEGORIES = [
SEARCH_CATEGORY,
RECENT_CATEGORY,
].concat(filteredCategories);
this.categories = CATEGORIES;
2016-05-31 18:39:30 +00:00
}
2016-06-09 00:22:06 +00:00
componentWillReceiveProps(props) {
if (props.skin && !store.get('skin')) {
this.setState({ skin: props.skin })
}
2016-06-09 00:22:06 +00:00
}
componentDidMount() {
if (this.state.firstRender) {
this.testStickyPosition()
2016-07-15 16:31:22 +00:00
this.firstRenderTimeout = setTimeout(() => {
this.setState({ firstRender: false })
}, 60)
}
}
2016-05-31 18:39:30 +00:00
componentDidUpdate() {
2016-07-08 17:56:29 +00:00
this.updateCategoriesSize()
2016-05-31 18:39:30 +00:00
this.handleScroll()
}
2016-07-15 16:31:22 +00:00
componentWillUnmount() {
SEARCH_CATEGORY.emojis = null
2016-07-15 16:31:22 +00:00
clearTimeout(this.leaveTimeout)
clearTimeout(this.firstRenderTimeout)
}
2016-05-31 18:39:30 +00:00
testStickyPosition() {
2016-05-31 16:35:08 +00:00
var stickyTestElement = document.createElement('div')
for (let prefix of ['', '-webkit-', '-ms-', '-moz-', '-o-']) {
stickyTestElement.style.position = `${prefix}sticky`
}
this.hasStickyPosition = !!stickyTestElement.style.position.length
}
2016-05-31 14:36:52 +00:00
handleEmojiOver(emoji) {
var { preview } = this.refs
preview.setState({ emoji: emoji })
clearTimeout(this.leaveTimeout)
}
handleEmojiLeave(emoji) {
this.leaveTimeout = setTimeout(() => {
var { preview } = this.refs
preview.setState({ emoji: null })
}, 16)
2016-05-31 14:36:52 +00:00
}
2016-07-21 19:10:33 +00:00
handleEmojiClick(emoji, e) {
this.props.onClick(emoji, e)
2016-07-07 18:22:02 +00:00
frequently.add(emoji)
2016-07-08 17:56:29 +00:00
var component = this.refs['category-1']
if (component) {
2016-07-07 18:22:02 +00:00
let maxMargin = component.maxMargin
component.forceUpdate()
window.requestAnimationFrame(() => {
2016-07-08 17:56:29 +00:00
component.memoizeSize()
2016-07-07 18:22:02 +00:00
if (maxMargin == component.maxMargin) return
2016-07-08 17:56:29 +00:00
this.updateCategoriesSize()
2016-07-07 18:22:02 +00:00
this.handleScrollPaint()
if (SEARCH_CATEGORY.emojis) {
component.updateDisplay('none')
}
2016-07-07 18:22:02 +00:00
})
}
}
2016-05-31 16:35:08 +00:00
handleScroll() {
2016-06-02 15:26:48 +00:00
if (!this.waitingForPaint) {
this.waitingForPaint = true
window.requestAnimationFrame(this.handleScrollPaint.bind(this))
}
}
handleScrollPaint() {
this.waitingForPaint = false
if (!this.refs.scroll) {
return
}
2016-05-31 16:35:08 +00:00
var target = this.refs.scroll,
2016-06-02 15:26:48 +00:00
scrollTop = target.scrollTop,
2016-07-08 17:56:29 +00:00
scrollingDown = scrollTop > (this.scrollTop || 0),
activeCategory = null,
minTop = 0
2016-05-31 16:35:08 +00:00
2016-07-08 17:56:29 +00:00
for (let i = 0, l = CATEGORIES.length; i < l; i++) {
let ii = scrollingDown ? (CATEGORIES.length - 1 - i) : i,
category = CATEGORIES[ii],
2016-06-02 18:58:19 +00:00
component = this.refs[`category-${ii}`]
2016-06-02 15:26:48 +00:00
if (component) {
let active = component.handleScroll(scrollTop)
if (!minTop || component.top < minTop) {
if (component.top > 0) {
minTop = component.top
}
}
2016-06-02 15:26:48 +00:00
if (active && !activeCategory) {
2016-07-08 17:56:29 +00:00
if (category.anchor) category = category.anchor
2016-06-02 15:26:48 +00:00
activeCategory = category
2016-05-31 16:35:08 +00:00
}
2016-06-02 15:26:48 +00:00
}
}
if (scrollTop < minTop) {
activeCategory = RECENT_CATEGORY
}
2016-06-02 15:26:48 +00:00
if (activeCategory) {
let { anchors } = this.refs,
{ name: categoryName } = activeCategory
if (anchors.state.selected != categoryName) {
anchors.setState({ selected: categoryName })
}
2016-05-31 16:35:08 +00:00
}
2016-06-02 15:26:48 +00:00
this.scrollTop = scrollTop
2016-05-31 16:35:08 +00:00
}
2016-05-31 18:39:30 +00:00
handleSearch(emojis) {
2016-07-08 17:56:29 +00:00
SEARCH_CATEGORY.emojis = emojis
for (let i = 0, l = CATEGORIES.length; i < l; i++) {
let component = this.refs[`category-${i}`]
if (component && component.props.name != 'Search') {
let display = emojis ? 'none' : 'inherit'
component.updateDisplay(display)
2016-07-08 17:56:29 +00:00
}
2016-05-31 18:39:30 +00:00
}
2016-07-08 17:56:29 +00:00
this.forceUpdate()
2016-05-31 18:39:30 +00:00
}
2016-06-02 18:58:19 +00:00
handleAnchorClick(category, i) {
var component = this.refs[`category-${i}`],
{ scroll, anchors } = this.refs,
scrollToComponent = null
2016-06-02 18:58:19 +00:00
scrollToComponent = () => {
if (component) {
let { top } = component
if (category.name == 'Recent') {
top = 0
} else {
top += 1
}
2016-06-02 18:58:19 +00:00
scroll.scrollTop = top
2016-06-02 18:58:19 +00:00
}
}
if (SEARCH_CATEGORY.emojis) {
this.handleSearch(null)
this.refs.search.clear()
2016-06-02 18:58:19 +00:00
window.requestAnimationFrame(scrollToComponent)
} else {
scrollToComponent()
2016-06-02 18:58:19 +00:00
}
}
2016-06-09 00:22:06 +00:00
handleSkinChange(skin) {
var newState = { skin: skin }
this.setState(newState)
store.update(newState)
2016-06-09 00:22:06 +00:00
}
2016-07-08 17:56:29 +00:00
updateCategoriesSize() {
for (let i = 0, l = CATEGORIES.length; i < l; i++) {
let component = this.refs[`category-${i}`]
if (component) component.memoizeSize()
}
}
getCategories() {
var categories = CATEGORIES
return this.state.firstRender ? categories.slice(0, 3) : categories
}
2016-05-31 14:36:52 +00:00
render() {
var { perLine, emojiSize, set, sheetSize, style, title, emoji, color, native, backgroundImageFn, emojisToShowFilter } = this.props,
2017-02-17 15:36:50 +00:00
{ skin } = this.state,
width = (perLine * (emojiSize + 12)) + 12 + 2
2016-05-31 14:36:52 +00:00
return <div style={{width: width, ...style}} className='emoji-mart'>
2016-07-27 15:35:12 +00:00
<div className='emoji-mart-bar'>
2016-06-02 15:26:48 +00:00
<Anchors
ref='anchors'
2016-10-27 03:22:59 +00:00
i18n={this.i18n}
2016-07-21 19:33:18 +00:00
color={color}
2016-07-08 17:56:29 +00:00
categories={CATEGORIES}
2016-06-02 18:58:19 +00:00
onAnchorClick={this.handleAnchorClick.bind(this)}
2016-06-02 15:26:48 +00:00
/>
2016-05-31 14:36:52 +00:00
</div>
2016-07-27 15:35:12 +00:00
<div ref="scroll" className='emoji-mart-scroll' onScroll={this.handleScroll.bind(this)}>
2016-05-31 18:39:30 +00:00
<Search
ref='search'
2016-05-31 18:39:30 +00:00
onSearch={this.handleSearch.bind(this)}
2016-10-27 03:22:59 +00:00
i18n={this.i18n}
emojisToShowFilter={emojisToShowFilter}
2016-05-31 14:36:52 +00:00
/>
{this.getCategories().map((category, i) => {
2016-05-31 14:36:52 +00:00
return <Category
2016-05-31 16:35:08 +00:00
ref={`category-${i}`}
2016-05-31 14:36:52 +00:00
key={category.name}
name={category.name}
emojis={category.emojis}
2016-07-07 18:22:02 +00:00
perLine={perLine}
native={native}
2016-05-31 16:35:08 +00:00
hasStickyPosition={this.hasStickyPosition}
2016-10-27 03:22:59 +00:00
i18n={this.i18n}
2016-05-31 14:36:52 +00:00
emojiProps={{
native: native,
2016-05-31 23:36:50 +00:00
skin: skin,
2016-05-31 14:36:52 +00:00
size: emojiSize,
set: set,
sheetSize: sheetSize,
2017-02-17 15:36:50 +00:00
forceSize: native,
backgroundImageFn: backgroundImageFn,
2016-05-31 14:36:52 +00:00
onOver: this.handleEmojiOver.bind(this),
onLeave: this.handleEmojiLeave.bind(this),
2016-07-07 18:22:02 +00:00
onClick: this.handleEmojiClick.bind(this),
2016-05-31 14:36:52 +00:00
}}
/>
})}
</div>
2016-07-27 15:35:12 +00:00
<div className='emoji-mart-bar'>
2016-05-31 14:36:52 +00:00
<Preview
ref='preview'
title={title}
emoji={emoji}
2016-05-31 14:36:52 +00:00
emojiProps={{
native: native,
2016-05-31 14:36:52 +00:00
size: 38,
2016-07-19 16:27:24 +00:00
skin: skin,
set: set,
sheetSize: sheetSize,
backgroundImageFn: backgroundImageFn,
2016-05-31 14:36:52 +00:00
}}
2016-06-09 00:22:06 +00:00
skinsProps={{
skin: skin,
onChange: this.handleSkinChange.bind(this)
}}
2016-05-31 14:36:52 +00:00
/>
</div>
</div>
}
}
Picker.propTypes = {
onClick: React.PropTypes.func,
perLine: React.PropTypes.number,
emojiSize: React.PropTypes.number,
2016-10-27 03:22:59 +00:00
i18n: React.PropTypes.object,
2016-07-11 18:08:41 +00:00
style: React.PropTypes.object,
title: React.PropTypes.string,
emoji: React.PropTypes.string,
2016-07-21 19:33:18 +00:00
color: React.PropTypes.string,
2016-10-27 00:55:44 +00:00
set: Emoji.propTypes.set,
2016-10-27 01:04:05 +00:00
skin: Emoji.propTypes.skin,
native: React.PropTypes.bool,
backgroundImageFn: Emoji.propTypes.backgroundImageFn,
sheetSize: Emoji.propTypes.sheetSize,
emojisToShowFilter: React.PropTypes.func,
2016-05-31 14:36:52 +00:00
}
Picker.defaultProps = {
onClick: (() => {}),
2016-05-31 21:23:51 +00:00
emojiSize: 24,
2016-05-31 14:36:52 +00:00
perLine: 9,
2016-10-27 03:22:59 +00:00
i18n: {},
2016-07-11 18:08:41 +00:00
style: {},
2016-07-27 15:35:12 +00:00
title: 'Emoji Mart™',
emoji: 'department_store',
2016-07-21 19:33:18 +00:00
color: '#ae65c5',
2016-10-27 00:55:44 +00:00
set: Emoji.defaultProps.set,
2016-10-27 01:04:05 +00:00
skin: Emoji.defaultProps.skin,
native: Emoji.defaultProps.native,
sheetSize: Emoji.defaultProps.sheetSize,
backgroundImageFn: Emoji.defaultProps.backgroundImageFn,
emojisToShowFilter: (codePoint) => true,
2016-05-31 14:36:52 +00:00
}