Initial commit
commit
e108b00768
|
@ -0,0 +1,5 @@
|
|||
node_modules/
|
||||
build/
|
||||
data/
|
||||
dist/
|
||||
lib/
|
|
@ -0,0 +1,94 @@
|
|||
.emoji-picker,
|
||||
.emoji-picker * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.emoji-picker {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif;
|
||||
font-size: 16px;
|
||||
display: inline-block;
|
||||
color: #222427;
|
||||
border: 1px solid rgba(0, 0, 0, .15);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.emoji-picker-bar {
|
||||
background-color: #f4f4f4;
|
||||
padding: 12px;
|
||||
}
|
||||
.emoji-picker-bar:first-child {
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
}
|
||||
.emoji-picker-bar:last-child {
|
||||
border-bottom-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
|
||||
.emoji-picker-scroll {
|
||||
overflow: scroll;
|
||||
max-height: 270px;
|
||||
padding: 6px;
|
||||
border: solid rgba(0, 0, 0, .15);
|
||||
border-width: 1px 0;
|
||||
}
|
||||
|
||||
.emoji-picker-search {
|
||||
font-size: 16px;
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: .2em .6em;
|
||||
border-radius: 25px;
|
||||
border: 1px solid rgba(0, 0, 0, .15);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.emoji-picker-category .emoji-picker-emoji:hover {
|
||||
background-color: #b7e887;
|
||||
}
|
||||
|
||||
.emoji-picker-category-label {
|
||||
font-weight: bold;
|
||||
padding: 5px 6px;
|
||||
background-color: rgba(255, 255, 255, .95);
|
||||
}
|
||||
|
||||
.emoji-picker-emoji {
|
||||
display: inline-block;
|
||||
padding: 4px 6px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.emoji-picker-preview {
|
||||
position: relative;
|
||||
height: 70px;
|
||||
}
|
||||
|
||||
.emoji-picker-preview-emoji,
|
||||
.emoji-picker-preview-data {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
}
|
||||
|
||||
.emoji-picker-preview-emoji {
|
||||
left: 12px;
|
||||
}
|
||||
|
||||
.emoji-picker-preview-data {
|
||||
top: 50%;
|
||||
left: 68px; right: 12px;
|
||||
transform: translateY(-50%);
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.emoji-picker-preview-name {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.emoji-picker-preview-shortname {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
.emoji-picker-preview-shortname + .emoji-picker-preview-shortname {
|
||||
margin-left: .5em;
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 7.2 MiB |
Binary file not shown.
After Width: | Height: | Size: 2.2 MiB |
Binary file not shown.
After Width: | Height: | Size: 3.4 MiB |
Binary file not shown.
After Width: | Height: | Size: 4.1 MiB |
|
@ -0,0 +1,10 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>EmojiPicker</title>
|
||||
<link rel="stylesheet" href="../css/emoji-picker.css">
|
||||
</head>
|
||||
<body>
|
||||
<div></div>
|
||||
<script src="../build/example.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,96 @@
|
|||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
|
||||
import {Picker} from '../src'
|
||||
|
||||
class Example extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
emojiSize: 24,
|
||||
perLine: 9,
|
||||
set: 'apple',
|
||||
}
|
||||
}
|
||||
|
||||
handleInput(e) {
|
||||
var { currentTarget } = e,
|
||||
{ value } = currentTarget,
|
||||
key = currentTarget.getAttribute('data-key'),
|
||||
state = {}
|
||||
|
||||
state[key] = parseInt(value)
|
||||
this.setState(state)
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div>
|
||||
<h1>EmojiPicker</h1>
|
||||
|
||||
<div>
|
||||
{['Apple', 'Google', 'Twitter', 'EmojiOne'].map((set) => {
|
||||
var value = set.toLowerCase(),
|
||||
props = { disabled: value == this.state.set }
|
||||
|
||||
return <button
|
||||
key={value}
|
||||
value={value}
|
||||
onClick={() => this.setState({ set: value })}
|
||||
{...props}>
|
||||
{set}
|
||||
</button>
|
||||
})}
|
||||
</div>
|
||||
|
||||
<pre style={{
|
||||
fontSize: 18,
|
||||
display: 'inline-block',
|
||||
verticalAlign: 'top',
|
||||
margin: '1em',
|
||||
width: '460px',
|
||||
}}>
|
||||
<Operator>import</Operator> {Picker} <Operator>from</Operator> <String>'emoji-picker'</String>
|
||||
<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 /> sheetURL<Operator>=</Operator><String>'images/sheet-{this.state.set}-64.png'</String>
|
||||
<br /> onClick<Operator>=</Operator>{(<Variable>emoji</Variable>) => console.log(<Variable>emoji</Variable>)}
|
||||
<br /><Operator>/></Operator>
|
||||
</pre>
|
||||
|
||||
<Picker
|
||||
emojiSize={this.state.emojiSize}
|
||||
perLine={this.state.perLine}
|
||||
sheetURL={`images/sheet-${this.state.set}-64.png`}
|
||||
onClick={(emoji) => console.log(emoji)}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
class Operator extends React.Component {
|
||||
render() {
|
||||
return <span style={{color: '#a71d5d'}}>
|
||||
{this.props.children}
|
||||
</span>
|
||||
}
|
||||
}
|
||||
|
||||
class Variable extends React.Component {
|
||||
render() {
|
||||
return <span style={{color: '#0086b3'}}>
|
||||
{this.props.children}
|
||||
</span>
|
||||
}
|
||||
}
|
||||
|
||||
class String extends React.Component {
|
||||
render() {
|
||||
return <span style={{color: '#183691'}}>
|
||||
{this.props.children}
|
||||
</span>
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render(<Example />, document.querySelector('div'))
|
|
@ -0,0 +1,27 @@
|
|||
var path = require('path')
|
||||
|
||||
module.exports = {
|
||||
entry: path.resolve('example/index.js'),
|
||||
output: {
|
||||
path: path.resolve('build'),
|
||||
filename: 'example.js',
|
||||
},
|
||||
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
include: [
|
||||
path.resolve('src'),
|
||||
path.resolve('data'),
|
||||
path.resolve('example'),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
resolve: {
|
||||
extensions: ['', '.js'],
|
||||
},
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"name": "emoji-picker",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "dist/emoji-picker.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:missive/emoji-picker.git"
|
||||
},
|
||||
"keywords": [
|
||||
"react",
|
||||
"emoji",
|
||||
"picker"
|
||||
],
|
||||
"author": "Etienne Lemay",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/missive/emoji-picker/issues"
|
||||
},
|
||||
"homepage": "https://github.com/missive/emoji-picker",
|
||||
"dependencies": {},
|
||||
"peerDependencies": {
|
||||
"react": "^0.14.0 || ^15.0.0-0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-core": "6.7.2",
|
||||
"babel-loader": "6.2.4",
|
||||
"babel-preset-es2015": "6.6.0",
|
||||
"babel-preset-react": "6.5.0",
|
||||
"emoji-data": "git://github.com/missive/emoji-data.git#01aeccb3adf6cdfaab6e13892f7688740047ff32",
|
||||
"inflection": "1.10.0",
|
||||
"mkdirp": "0.5.1",
|
||||
"react": "0.14.7",
|
||||
"react-dom": "0.14.7",
|
||||
"rimraf": "2.5.2",
|
||||
"webpack": "1.12.14"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf build/ data/ dist/",
|
||||
"build:data": "node scripts/build-data",
|
||||
"build:example": "node scripts/build-example",
|
||||
"build:dist": "node scripts/build-dist",
|
||||
"build": "npm run build:data && npm run build:dist",
|
||||
"react:clean": "rimraf node_modules/{react,react-dom,react-addons-test-utils}",
|
||||
"react:14": "npm run react:clean && npm i react@^0.14 react-dom@^0.14 react-addons-test-utils@^0.14",
|
||||
"react:15": "npm run react:clean && npm i react@^15 react-dom@^15 react-addons-test-utils@^15",
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"prepublish": "npm run clean && npm run build"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
var fs = require('fs')
|
||||
var emojiData = require('emoji-data')
|
||||
var inflection = require('inflection')
|
||||
var mkdirp = require('mkdirp')
|
||||
|
||||
var categories = ['People', 'Nature', 'Foods', 'Activity', 'Places', 'Objects', 'Symbols', 'Flags', 'Skins']
|
||||
var data = { categories: [], emojis: {} }
|
||||
var categoriesIndex = {}
|
||||
|
||||
categories.forEach((category, i) => {
|
||||
data.categories[i] = { name: category, emojis: [] }
|
||||
categoriesIndex[category] = i
|
||||
})
|
||||
|
||||
emojiData.sort((a, b) => {
|
||||
var aTest = a.sort_order || a.short_name
|
||||
var bTest = b.sort_order || b.short_name
|
||||
|
||||
return aTest - bTest
|
||||
})
|
||||
|
||||
emojiData.forEach((datum) => {
|
||||
var category = datum.category,
|
||||
shortName = datum.short_name,
|
||||
categoryIndex
|
||||
|
||||
if (!category) {
|
||||
if (/^skin/.test(shortName)) category = 'Skins'
|
||||
if (/^flag/.test(shortName)) category = 'Flags'
|
||||
if (/^(left_speech_bubble|keycap_star|eject)$/.test(shortName)) category = 'Symbols'
|
||||
}
|
||||
|
||||
if (!category) {
|
||||
throw new Error('“' + datum.short_name + '” doesn’t have a category')
|
||||
}
|
||||
|
||||
datum.name || (datum.name = datum.short_name.replace(/\-/g, ' '))
|
||||
datum.name = inflection.titleize(datum.name || '')
|
||||
|
||||
if (!datum.name) {
|
||||
throw new Error('“' + datum.short_name + '” doesn’t have a name')
|
||||
}
|
||||
|
||||
categoryIndex = categoriesIndex[category]
|
||||
data.categories[categoryIndex].emojis.push(datum.short_name)
|
||||
data.emojis[datum.short_name] = datum
|
||||
})
|
||||
|
||||
mkdirp('data', (err) => {
|
||||
if (err) throw err
|
||||
|
||||
fs.writeFile('data/index.js', `export default ${JSON.stringify(data)}`, (err) => {
|
||||
if (err) throw err
|
||||
})
|
||||
})
|
|
@ -0,0 +1,44 @@
|
|||
var path = require('path')
|
||||
var extend = require('util')._extend
|
||||
var webpack = require('webpack')
|
||||
|
||||
var configs = ['development', 'production'].map((env) => {
|
||||
var configPath = '../src/webpack.config.js',
|
||||
config = require(configPath)
|
||||
|
||||
delete require.cache[require.resolve(configPath)]
|
||||
|
||||
if (env === 'production') {
|
||||
config.output.filename = config.output.filename.replace(/\.js$/, '.min.js')
|
||||
config.plugins.push(
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compressor: { warnings: false }
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
return config
|
||||
})
|
||||
|
||||
webpack(configs, (err, stats) => {
|
||||
if (err) throw err
|
||||
|
||||
console.log(
|
||||
stats.toString({
|
||||
colors: true,
|
||||
hash: true,
|
||||
version: false,
|
||||
timings: true,
|
||||
assets: true,
|
||||
chunks: false,
|
||||
chunkModules: false,
|
||||
modules: false,
|
||||
children: false,
|
||||
cached: false,
|
||||
reasons: false,
|
||||
source: false,
|
||||
errorDetails: false,
|
||||
chunkOrigins: false,
|
||||
})
|
||||
)
|
||||
})
|
|
@ -0,0 +1,28 @@
|
|||
var path = require('path')
|
||||
var webpack = require('webpack')
|
||||
|
||||
var config = require('../example/webpack.config.js'),
|
||||
compiler = webpack(config)
|
||||
|
||||
compiler.watch({}, (err, stats) => {
|
||||
if (err) throw err
|
||||
|
||||
console.log(
|
||||
stats.toString({
|
||||
colors: true,
|
||||
hash: true,
|
||||
version: false,
|
||||
timings: true,
|
||||
assets: true,
|
||||
chunks: false,
|
||||
chunkModules: false,
|
||||
modules: false,
|
||||
children: false,
|
||||
cached: false,
|
||||
reasons: false,
|
||||
source: false,
|
||||
errorDetails: false,
|
||||
chunkOrigins: false,
|
||||
})
|
||||
)
|
||||
})
|
|
@ -0,0 +1,55 @@
|
|||
import React from 'react'
|
||||
import Emoji from './emoji'
|
||||
|
||||
export default class Category extends React.Component {
|
||||
shouldComponentUpdate(props) {
|
||||
if (props.perLine != this.props.perLine) {
|
||||
return true
|
||||
}
|
||||
|
||||
for (let k in props.emojiProps) {
|
||||
if (props.emojiProps[k] != this.props.emojiProps[k]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
render() {
|
||||
var { name, emojis, perLine, emojiProps } = this.props,
|
||||
emojis = emojis.slice(0),
|
||||
lines = [],
|
||||
linesCount = Math.ceil(emojis.length / perLine)
|
||||
|
||||
Array(linesCount).fill().forEach((_, i) =>
|
||||
lines.push(emojis.splice(0, perLine))
|
||||
)
|
||||
|
||||
return <div className='emoji-picker-category'>
|
||||
<div className='emoji-picker-category-label'>{name}</div>
|
||||
{lines.map((emojis, i) =>
|
||||
<div key={`line-${i}`}>
|
||||
{emojis.map((emoji) =>
|
||||
<Emoji
|
||||
key={emoji}
|
||||
emoji={emoji}
|
||||
{...emojiProps}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
Category.propTypes = {
|
||||
emojis: React.PropTypes.array,
|
||||
name: React.PropTypes.string.isRequired,
|
||||
perLine: React.PropTypes.number.isRequired,
|
||||
emojiProps: React.PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
Category.defaultProps = {
|
||||
emojis: [],
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
import React from 'react'
|
||||
import data from '../../data'
|
||||
|
||||
const SHEET_SIZE = 2624
|
||||
const EMOJI_SIZE = 64
|
||||
|
||||
export default class Emoji extends React.Component {
|
||||
componentWillMount() {
|
||||
this.emoji = data.emojis[this.props.emoji]
|
||||
}
|
||||
|
||||
shouldComponentUpdate(props) {
|
||||
if (props.size != this.props.size || props.sheetURL != this.props.sheetURL) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
getSheetSize() {
|
||||
var { size } = this.props,
|
||||
sheetSize = SHEET_SIZE * size / EMOJI_SIZE
|
||||
|
||||
return `${sheetSize}px ${sheetSize}px`
|
||||
}
|
||||
|
||||
getEmojiPosition(emoji) {
|
||||
var { size } = this.props,
|
||||
{ sheet_x, sheet_y } = emoji,
|
||||
x = sheet_x * size,
|
||||
y = sheet_y * size
|
||||
|
||||
return `-${x}px -${y}px`
|
||||
}
|
||||
|
||||
render() {
|
||||
var { sheetURL, size, onOver, onClick } = this.props
|
||||
|
||||
return <span
|
||||
onClick={() => onClick(this.emoji)}
|
||||
onMouseEnter={() => onOver(this.emoji)}
|
||||
className='emoji-picker-emoji'>
|
||||
<span style={{
|
||||
width: size,
|
||||
height: size,
|
||||
display: 'inline-block',
|
||||
backgroundImage: `url(${sheetURL})`,
|
||||
backgroundSize: this.getSheetSize(),
|
||||
backgroundPosition: this.getEmojiPosition(this.emoji),
|
||||
}}>
|
||||
</span>
|
||||
</span>
|
||||
}
|
||||
}
|
||||
|
||||
Emoji.propTypes = {
|
||||
onOver: React.PropTypes.func,
|
||||
onClick: React.PropTypes.func,
|
||||
size: React.PropTypes.number.isRequired,
|
||||
emoji: React.PropTypes.string.isRequired,
|
||||
sheetURL: React.PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
Emoji.defaultProps = {
|
||||
onOver: (() => {}),
|
||||
onClick: (() => {}),
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
import React from 'react'
|
||||
|
||||
import data from '../../data'
|
||||
import Preview from './preview'
|
||||
import Category from './category'
|
||||
|
||||
export default class Picker extends React.Component {
|
||||
handleEmojiOver(emoji) {
|
||||
var { preview } = this.refs
|
||||
preview.setState({ emoji: emoji })
|
||||
}
|
||||
|
||||
render() {
|
||||
var { perLine, emojiSize, sheetURL } = this.props
|
||||
|
||||
return <div className='emoji-picker'>
|
||||
<div className='emoji-picker-bar'>
|
||||
Categories
|
||||
</div>
|
||||
|
||||
<div ref="scroll" className='emoji-picker-scroll'>
|
||||
<input
|
||||
type='text'
|
||||
placeholder='Search'
|
||||
className='emoji-picker-search'
|
||||
/>
|
||||
|
||||
{data.categories.map((category) => {
|
||||
if (category.name == 'Skins') return null
|
||||
return <Category
|
||||
key={category.name}
|
||||
name={category.name}
|
||||
emojis={category.emojis}
|
||||
perLine={perLine}
|
||||
emojiProps={{
|
||||
size: emojiSize,
|
||||
sheetURL: sheetURL,
|
||||
onOver: this.handleEmojiOver.bind(this),
|
||||
onClick: this.props.onClick,
|
||||
}}
|
||||
/>
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className='emoji-picker-bar emoji-picker-preview'>
|
||||
<Preview
|
||||
ref='preview'
|
||||
emojiProps={{
|
||||
size: 38,
|
||||
sheetURL: sheetURL,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
Picker.propTypes = {
|
||||
onClick: React.PropTypes.func,
|
||||
perLine: React.PropTypes.number,
|
||||
emojiSize: React.PropTypes.number,
|
||||
sheetURL: React.PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
Picker.defaultProps = {
|
||||
onClick: (() => {}),
|
||||
emojiSize: 32,
|
||||
perLine: 9,
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
import React from 'react'
|
||||
import Emoji from './emoji'
|
||||
|
||||
export default class Preview extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = { emoji: null }
|
||||
}
|
||||
|
||||
render() {
|
||||
var { emoji } = this.state,
|
||||
{ emojiProps } = this.props
|
||||
|
||||
if (emoji) {
|
||||
return <div>
|
||||
<div className='emoji-picker-preview-emoji'>
|
||||
<Emoji
|
||||
key={emoji.short_name}
|
||||
emoji={emoji.short_name}
|
||||
{...emojiProps}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='emoji-picker-preview-data'>
|
||||
<span className='emoji-picker-preview-name'>{emoji.name}</span><br />
|
||||
<span className='emoji-picker-preview-shortnames'>
|
||||
{emoji.short_names.map((short_name) =>
|
||||
<span key={short_name} className='emoji-picker-preview-shortname'>:{short_name}:</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
} else {
|
||||
return <div>
|
||||
EmojiPicker
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Preview.propTypes = {
|
||||
emojiProps: React.PropTypes.object.isRequired,
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export {default as Category} from './components/category'
|
||||
export {default as Emoji} from './components/emoji'
|
||||
export {default as Picker} from './components/picker'
|
||||
export {default as Preview} from './components/preview'
|
|
@ -0,0 +1,36 @@
|
|||
var path = require('path')
|
||||
|
||||
module.exports = {
|
||||
entry: path.resolve('src/index.js'),
|
||||
output: {
|
||||
path: path.resolve('dist'),
|
||||
filename: 'emoji-picker.js',
|
||||
library: 'EmojiPicker',
|
||||
libraryTarget: 'umd',
|
||||
},
|
||||
|
||||
externals: [
|
||||
'react',
|
||||
],
|
||||
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
include: [
|
||||
path.resolve('src'),
|
||||
path.resolve('data'),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
resolve: {
|
||||
extensions: ['', '.js'],
|
||||
},
|
||||
|
||||
plugins: [],
|
||||
devtool: 'source-map',
|
||||
bail: true,
|
||||
}
|
Loading…
Reference in New Issue