this.onReaction(name)}
53 | onEmojiClick={(name) => this.onEmojiClick(name)}
54 | />
55 | );
56 | }
57 | }
58 |
59 |
60 | render(, document.getElementById('app'));
61 |
62 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-emoji-react",
3 | "version": "0.3.0",
4 | "description": "a clone of slack emoji reactions in react",
5 | "main": "dist/index.js",
6 | "scripts": {
7 | "demo": "./node_modules/.bin/webpack --config webpack.demo.config.js --watch",
8 | "build": "babel src --out-dir dist",
9 | "watch": "babel src --watch --out-dir dist",
10 | "prepublish": "npm run build"
11 | },
12 | "keywords": [
13 | "react",
14 | "slack",
15 | "emoji",
16 | "reactions",
17 | "like",
18 | "button"
19 | ],
20 | "repository": {
21 | "type": "git",
22 | "url": "git+https://github.com/conorhastings/react-emoji-react.git"
23 | },
24 | "author": "Conor Hastings",
25 | "license": "MIT",
26 | "bugs": {
27 | "url": "https://github.com/conorhastings/react-emoji-react/issues"
28 | },
29 | "devDependencies": {
30 | "babel-cli": "^6.4.5",
31 | "babel-core": "^6.4.5",
32 | "babel-loader": "^6.2.2",
33 | "babel-preset-es2015": "^6.3.13",
34 | "babel-preset-react": "^6.3.13",
35 | "babel-preset-stage-2": "^6.3.13",
36 | "file-loader": "^0.8.5",
37 | "json-loader": "^0.5.4",
38 | "react": "^0.14.7",
39 | "react-dom": "^0.14.7",
40 | "url-loader": "^0.5.7",
41 | "webpack": "^1.12.13"
42 | },
43 | "peerDependencies": {
44 | "react": ">= 0.14.0",
45 | "react-dom": ">= 0.14.0"
46 | },
47 | "dependencies": {
48 | "get-emoji": "^2.0.0"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { emojiList as emoji } from 'get-emoji';
3 | import getEmoji from 'get-emoji';
4 |
5 |
6 | const wrapperStyle = {
7 | display: 'inline-block',
8 | marginTop: '2px',
9 | marginBottom: '2px',
10 | marginRight: '4px',
11 | padding: '1px 3px',
12 | borderRadius: '5px',
13 | backgroundColor: '#fff',
14 | border: '1px solid #E8E8E8',
15 | cursor: 'pointer',
16 | height: '1.4rem',
17 | lineHeight: '23px',
18 | WebkitUserSelect: 'none',
19 | msUserSelect: 'none',
20 | MozUserSelect: 'none'
21 | };
22 |
23 | const emojiStyle = {
24 | lineHeight: '20px',
25 | verticalAlign: 'middle',
26 | display: 'inline-block'
27 | };
28 |
29 | const wrapperHover = {
30 | border: '1px solid #4fb0fc'
31 | };
32 |
33 | const countStyle = {
34 | fontSize: '11px',
35 | fontFamily: 'helvetica, arial',
36 | position: 'relative',
37 | top: '-2px',
38 | padding: '0 1px 3px',
39 | color: '#959595'
40 | };
41 |
42 | const countHover = {
43 | color: "#4fb0fc"
44 | };
45 |
46 | const selectorStyle = {
47 | boxShadow: '0 6px 8px 0 rgba(0, 0, 0, 0.24)',
48 | backgroundColor: '#fff',
49 | width: '250px',
50 | height: '220px',
51 | position: 'relative',
52 | left: '10px',
53 | top: '0px'
54 | };
55 |
56 | const EmojiImage = ({name}) =>
;
57 |
58 | class SingleEmoji extends Component {
59 | constructor() {
60 | super();
61 | this.state = { hovered: false };
62 | }
63 |
64 | render() {
65 | const {
66 | name,
67 | count = 1,
68 | styles = {
69 | wrapperStyle: wrapperStyle,
70 | emojiStyle: emojiStyle,
71 | countStyle: countStyle,
72 | wrapperHover: wrapperHover,
73 | countHover: countHover
74 | },
75 | onClick = () => {}
76 | } = this.props;
77 |
78 | const wrapperFinalStyle = this.state.hovered ? {...wrapperStyle, ...wrapperHover} : wrapperStyle;
79 | const countFinalStyle = this.state.hovered ? {...countStyle, ...countHover} : countStyle;
80 | return (
81 | onClick(name)}
84 | onMouseEnter={() => this.setState({hovered: true})}
85 | onMouseLeave={() => this.setState({hovered: false})}
86 | >
87 |
88 | {count}
89 |
90 | );
91 | }
92 | }
93 |
94 | const PickerEmoji = ({onClick, image}) => (
95 | onClick()}>
96 | {image}
97 |
98 | );
99 |
100 | const EmojiWrapper = ({reactions, onReaction}) => {
101 | return (
102 |
103 | {reactions.map(({name, count}) => (
104 |
105 | ))}
106 |
107 | );
108 | }
109 |
110 | const SINGLE_EMOJI_HEIGHT = 23;
111 | const LOAD_HEIGHT = 500;
112 | const EMOJIS_ACROSS = 8
113 |
114 | class EmojiSelector extends Component {
115 | constructor() {
116 | super();
117 | this.state = {
118 | filter: "",
119 | xHovered: false,
120 | scrollPosition: 0
121 | };
122 | this.onScroll = this.onScroll.bind(this);
123 | }
124 |
125 | onScroll() {
126 | this.setState({ scrollPosition: this.emojiContainer.scrollTop })
127 | }
128 |
129 | componentDidMount() {
130 | this.emojiContainer.addEventListener('scroll', this.onScroll);
131 | }
132 |
133 | componentWillUnMount() {
134 | this.emojiContainer.removeEventListener('scroll', this.onScroll);
135 | }
136 |
137 | render() {
138 | const { showing, onEmojiClick, close } = this.props;
139 | let xStyle = {
140 | color: '#E8E8E8',
141 | fontSize: '20px',
142 | cursor: 'pointer',
143 | float: 'right',
144 | marginTop: '-32px',
145 | marginRight: '5px'
146 | };
147 | if (this.state.xHovered) {
148 | xStyle.color = '#4fb0fc';
149 | }
150 | const searchInput = (
151 |
152 | this.setState({filter: e.target.value})}
158 | />
159 |
160 | );
161 | const x = (
162 | {
165 | this.setState({ xHovered: false});
166 | close();
167 | }}
168 | onMouseEnter={() => this.setState({ xHovered: true})}
169 | onMouseLeave={() => this.setState({ xHovered: false})}
170 | >
171 | x
172 |
173 | );
174 | const show = emoji.filter(name => name.indexOf(this.state.filter) !== -1);
175 | const emptyStyle = {
176 | height: '16px',
177 | width: '16px',
178 | display: 'inline-block'
179 | };
180 | const emojis = show.map((em, i) => {
181 | const row = Math.floor((i + 1) / EMOJIS_ACROSS);
182 | const pixelPosition = row * SINGLE_EMOJI_HEIGHT;
183 | const position = this.state.scrollPosition + LOAD_HEIGHT;
184 | const shouldShowImage = pixelPosition < position && (position - pixelPosition) <= LOAD_HEIGHT;
185 | const image = shouldShowImage ? : ;
186 | return (
187 | {
191 | onEmojiClick(em);
192 | close();
193 | }}
194 | />
195 | );
196 | });
197 | return (
198 |
199 | {searchInput}
200 | {x}
201 |
this.emojiContainer = node}
204 | >
205 | {emojis}
206 |
207 |
208 | );
209 | }
210 | }
211 |
212 | export default class EmojiReact extends Component {
213 | constructor() {
214 | super();
215 | this.state = { hovered: false, showSelector: false };
216 | this.onKeyPress = this.onKeyPress.bind(this);
217 | this.closeSelector = this.closeSelector.bind(this);
218 | this.onClick = this.onClick.bind(this);
219 | }
220 |
221 | onKeyPress(e) {
222 | if (e.keyCode === 27) {
223 | this.closeSelector();
224 | }
225 | }
226 |
227 | onClick({ target }) {
228 | if (!this.node.contains(target) && this.state.showSelector) {
229 | this.closeSelector();
230 | }
231 | }
232 |
233 | componentDidMount() {
234 | document.addEventListener('click', this.onClick);
235 | document.addEventListener('keydown', this.onKeyPress);
236 | }
237 |
238 | componentWillUnMount() {
239 | document.removeEventListener('click', this.onClick);
240 | document.removeEventListener('keydown', this.onKeyPress);
241 | }
242 |
243 | closeSelector() {
244 | this.setState({ showSelector: false });
245 | }
246 |
247 | render() {
248 | const { reactions, onReaction, onEmojiClick } = this.props;
249 | const plusButtonStyle = this.state.hovered ? {...wrapperStyle, ...wrapperHover} : wrapperStyle;
250 | const plusStyle = this.state.hovered ? {...countStyle, ...countHover} : countStyle;
251 | const selector = (
252 | this.node = node}>
253 |
this.setState({ hovered: true })}
256 | onMouseLeave={() => this.setState({ hovered: false}) }
257 | onClick={() => this.setState({ showSelector: !this.state.showSelector})}
258 | >
259 | +
260 |
261 |
266 |
267 | );
268 | return (
269 |
270 |
271 | {selector}
272 |
273 | );
274 | }
275 | }
276 |
--------------------------------------------------------------------------------
/webpack.demo.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 |
4 | module.exports = {
5 | context: path.join(__dirname),
6 | devtool: 'source-map',
7 | entry: {
8 | demo: "./demo/index.js"
9 | },
10 | output: {
11 | path: path.join(__dirname) + '/demo',
12 | filename: "[name].js"
13 | },
14 | module: {
15 | loaders: [
16 | {
17 | test: /\.js?$/,
18 | loader: 'babel',
19 | query: {
20 | presets: ['react', 'es2015'],
21 | plugins: ['transform-object-rest-spread']
22 | },
23 | include: path.join(__dirname) + '/demo'
24 | },
25 | { test: /\.json$/, loader: require.resolve("json-loader") }
26 | ]
27 | },
28 | };
--------------------------------------------------------------------------------