Merge branch 'emojimart' into get_emoji_data_from_native

release
Peder Johnsen 2019-03-20 09:08:32 +00:00
commit 22a5ca587f
38 changed files with 7351 additions and 6000 deletions

View File

@ -2,31 +2,8 @@
"presets": ["react"],
"plugins": [
"check-es2015-constants",
"transform-es2015-arrow-functions",
"transform-es2015-block-scoped-functions",
"transform-es2015-block-scoping",
"transform-es2015-classes",
"transform-es2015-computed-properties",
"transform-es2015-destructuring",
"transform-es2015-duplicate-keys",
"transform-es2015-for-of",
"transform-es2015-function-name",
"transform-es2015-literals",
"transform-es2015-object-super",
"transform-es2015-parameters",
"transform-es2015-shorthand-properties",
"transform-es2015-spread",
"transform-es2015-sticky-regex",
"transform-es2015-template-literals",
"transform-es2015-unicode-regex",
"transform-regenerator",
"transform-object-rest-spread",
"transform-runtime",
"transform-react-remove-prop-types",
[
"transform-define", "scripts/define.js"
],
[
"module-resolver",
{
@ -35,16 +12,55 @@
"babel-runtime/helpers/extends": "./src/polyfills/extends",
"babel-runtime/helpers/inherits": "./src/polyfills/inherits",
"babel-runtime/helpers/createClass": "./src/polyfills/createClass",
"babel-runtime/helpers/possibleConstructorReturn": "./src/polyfills/possibleConstructorReturn"
"babel-runtime/helpers/possibleConstructorReturn": "./src/polyfills/possibleConstructorReturn",
"babel-runtime/helpers/classCallCheck": "./src/polyfills/classCallCheck",
"babel-runtime/core-js/object/keys": "./src/polyfills/keys"
}
}
],
[
"transform-define", "scripts/define.js"
]
],
"env": {
"cjs": {
"legacy-es": {
"plugins": [
"transform-es2015-arrow-functions",
"transform-es2015-block-scoped-functions",
"transform-es2015-block-scoping",
"transform-es2015-classes",
"transform-es2015-computed-properties",
"transform-es2015-destructuring",
"transform-es2015-duplicate-keys",
"transform-es2015-for-of",
"transform-es2015-function-name",
"transform-es2015-literals",
"transform-es2015-object-super",
"transform-es2015-parameters",
"transform-es2015-shorthand-properties",
"transform-es2015-spread",
"transform-es2015-sticky-regex",
"transform-es2015-template-literals",
"transform-es2015-unicode-regex",
"transform-regenerator"
]
},
"legacy-cjs": {
"plugins": [
"transform-es2015-modules-commonjs"
]
},
"test": {
"presets": [
[
"env",
{
"targets": {
"node": "current"
}
}
]
]
}
}
}

1
.gitignore vendored
View File

@ -2,5 +2,6 @@
node_modules/
dist/
dist-es/
dist-modern/
stats.json
report.html

View File

@ -4,7 +4,6 @@ scripts/
src/
docs/
spec/
example/
karma.conf.js
yarn.lock

105
README.md
View File

@ -58,6 +58,7 @@ import { Picker } from 'emoji-mart'
#### I18n
```js
search: 'Search',
clear: 'Clear', // Accessible label on "clear" button
notfound: 'No Emoji Found',
skintext: 'Choose your default skin tone',
categories: {
@ -72,7 +73,16 @@ categories: {
symbols: 'Symbols',
flags: 'Flags',
custom: 'Custom',
}
},
categorieslabel: 'Emoji categories', // Accessible title for the list of categories
skintones: {
1: 'Default Skin Tone',
2: 'Light Skin Tone',
3: 'Medium-Light Skin Tone',
4: 'Medium Skin Tone',
5: 'Medium-Dark Skin Tone',
6: 'Dark Skin Tone',
},
```
#### Sheet sizes
@ -141,7 +151,7 @@ import { NimblePicker } from 'emoji-mart'
text: '',
emoticons: [],
custom: true,
imageUrl: 'https://assets-cdn.github.com/images/icons/emoji/octocat.png?v7'
imageUrl: 'https://github.githubassets.com/images/icons/emoji/octocat.png'
}
```
@ -220,7 +230,7 @@ Following the `dangerouslySetInnerHTML` example above, make sure the wrapping `s
```
## Custom emojis
You can provide custom emojis which will show up in their own category.
You can provide custom emojis which will show up in their own category. You can either use a single image as imageUrl or use a spritesheet as shown in the second object.
```js
import { Picker } from 'emoji-mart'
@ -232,7 +242,20 @@ const customEmojis = [
text: '',
emoticons: [],
keywords: ['github'],
imageUrl: 'https://assets-cdn.github.com/images/icons/emoji/octocat.png?v7'
imageUrl: 'https://github.githubassets.com/images/icons/emoji/octocat.png'
},
{
name: 'Test Flag',
short_names: ['test'],
text: '',
emoticons: [],
keywords: ['test', 'flag'],
spriteUrl: 'https://unpkg.com/emoji-datasource-twitter@4.0.4/img/twitter/sheets-256/64.png',
sheet_x: 1,
sheet_y: 1,
size: 64,
sheetColumns: 52,
sheetRows: 52,
},
]
@ -245,7 +268,7 @@ You can provide a custom Not Found object which will allow the appearance of the
```js
import { Picker } from 'emoji-mart'
const notFound = () => <img src='https://assets-cdn.github.com/images/icons/emoji/octocat.png?v7' />
const notFound = () => <img src='https://github.githubassets.com/images/icons/emoji/octocat.png' />
<Picker notFound={notFound} />
```
@ -258,7 +281,7 @@ import { Picker } from 'emoji-mart'
const customIcons = {
categories: {
recent: () => <img src='https://assets-cdn.github.com/images/icons/emoji/octocat.png?v7' />,
recent: () => <img src='https://github.githubassets.com/images/icons/emoji/octocat.png' />,
foods: () => <svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0l6.084 24H8L1.916 0zM21 5h-4l-1-4H4l3 12h3l1 4h13L21 5zM6.563 3h7.875l2 8H8.563l-2-8zm8.832 10l-2.856 1.904L12.063 13h3.332zM19 13l-1.5-6h1.938l2 8H16l3-2z"/></svg>,
people: () => <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M3 2l10 6-10 6z"></path></svg>
}
@ -382,12 +405,72 @@ Apple / Google / Twitter / EmojiOne / Messenger / Facebook
## Not opinionated
**Emoji Mart** doesnt automatically insert anything into a text input, nor does it show or hide itself. It simply returns an `emoji` object. Its up to the developer to mount/unmount (its fast!) and position the picker. You can use the returned object as props for the `EmojiMart.Emoji` component. You could also use `emoji.colons` to insert text into a textarea or `emoji.native` to use the emoji.
## Development
```sh
$ yarn build
$ yarn start
$ yarn storybook
## Optimizing for production
### Modern/ES builds
**Emoji Mart** comes in three flavors:
```
dist
dist-es
dist-modern
```
- `dist` is the standard build with the highest level of compatibility.
- `dist-es` is the same, but uses ES modules for better tree-shaking.
- `dist-modern` removes features not needed in modern evergreen browsers (i.e. latest Chrome, Edge, Firefox, and Safari).
The default builds are `dist` and `dist-es`. (In Webpack, one or the other will be chosen based on your [resolve main fields](https://webpack.js.org/configuration/resolve/#resolve-mainfields).)
If you want to use `dist-modern`, you must explicitly import it:
```js
import { Picker } from 'emoji-mart/dist-modern/index.js'
```
Using something like Babel, you can transpile the modern build to suit your own needs.
### Removing prop-types
To remove [prop-types](https://github.com/facebook/prop-types) in production, use [babel-plugin-transform-react-remove-prop-types](https://github.com/oliviertassinari/babel-plugin-transform-react-remove-prop-types):
```bash
npm install --save-dev babel-plugin-transform-react-remove-prop-types
```
Then add to your `.babelrc`:
```json
"plugins": [
[
"transform-react-remove-prop-types",
{
"removeImport": true,
"additionalLibraries": [
"../../utils/shared-props"
]
}
]
]
```
You'll also need to ensure that Babel is transpiling `emoji-mart`, e.g. [by not excluding `node_modules` in `babel-loader`](https://github.com/babel/babel-loader#usage).
## Development
```bash
yarn build
```
In two separate tabs:
```bash
yarn start
yarn storybook
```
The storybook is hosted at `localhost:6006`, and the code will be built on-the-fly.
## 🎩 Hat tips!
Powered by [iamcal/emoji-data](https://github.com/iamcal/emoji-data) and inspired by [iamcal/js-emoji](https://github.com/iamcal/js-emoji).<br>

View File

@ -49,6 +49,10 @@
padding: 12px 4px;
overflow: hidden;
transition: color .1s ease-out;
margin: 0;
box-shadow: none;
background: none;
border: none;
}
.emoji-mart-anchor:hover,
.emoji-mart-anchor-selected {
@ -74,7 +78,7 @@
.emoji-mart-anchors svg,
.emoji-mart-anchors img {
fill: currentColor;
fill: #858585;
height: 18px;
width: 18px;
}
@ -102,12 +106,22 @@
outline: 0;
}
.emoji-mart-search input,
.emoji-mart-search input::-webkit-search-decoration,
.emoji-mart-search input::-webkit-search-cancel-button,
.emoji-mart-search input::-webkit-search-results-button,
.emoji-mart-search input::-webkit-search-results-decoration {
/* remove webkit/blink styles for <input type="search">
* via https://stackoverflow.com/a/9422689 */
-webkit-appearance: none;
}
.emoji-mart-search-icon {
position: absolute;
top: 9px;
right: 16px;
top: 7px;
right: 11px;
z-index: 2;
padding: 0;
padding: 2px 5px 1px;
border: none;
background: none;
}
@ -146,14 +160,31 @@
background-color: rgba(255, 255, 255, .95);
}
.emoji-mart-category-list {
margin: 0;
padding: 0;
}
.emoji-mart-category-list li {
list-style: none;
margin: 0;
padding: 0;
display: inline-block;
}
.emoji-mart-emoji {
position: relative;
display: inline-block;
font-size: 0;
margin: 0;
padding: 0;
border: none;
background: none;
box-shadow: none;
}
.emoji-mart-emoji-native {
font-family: "Segoe UI Emoji", "Segoe UI Symbol", "Segoe UI", "Apple Color Emoji";
font-family: "Segoe UI Emoji", "Segoe UI Symbol", "Segoe UI", "Apple Color Emoji", "Twemoji Mozilla", "Noto Color Emoji", "EmojiOne Color", "Android Emoji";
}
.emoji-mart-no-results {
@ -369,3 +400,17 @@
.emoji-mart-skin-tone-4 { background-color: #bf8f68 }
.emoji-mart-skin-tone-5 { background-color: #9b643d }
.emoji-mart-skin-tone-6 { background-color: #594539 }
/* For screenreaders only, via https://stackoverflow.com/a/19758620 */
.emoji-mart-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}

File diff suppressed because one or more lines are too long

View File

@ -49,6 +49,10 @@
padding: 12px 4px;
overflow: hidden;
transition: color .1s ease-out;
margin: 0;
box-shadow: none;
background: none;
border: none;
}
.emoji-mart-anchor:hover,
.emoji-mart-anchor-selected {
@ -74,7 +78,7 @@
.emoji-mart-anchors svg,
.emoji-mart-anchors img {
fill: currentColor;
fill: #858585;
height: 18px;
width: 18px;
}
@ -102,12 +106,22 @@
outline: 0;
}
.emoji-mart-search input,
.emoji-mart-search input::-webkit-search-decoration,
.emoji-mart-search input::-webkit-search-cancel-button,
.emoji-mart-search input::-webkit-search-results-button,
.emoji-mart-search input::-webkit-search-results-decoration {
/* remove webkit/blink styles for <input type="search">
* via https://stackoverflow.com/a/9422689 */
-webkit-appearance: none;
}
.emoji-mart-search-icon {
position: absolute;
top: 9px;
right: 16px;
top: 7px;
right: 11px;
z-index: 2;
padding: 0;
padding: 2px 5px 1px;
border: none;
background: none;
}
@ -146,14 +160,31 @@
background-color: rgba(255, 255, 255, .95);
}
.emoji-mart-category-list {
margin: 0;
padding: 0;
}
.emoji-mart-category-list li {
list-style: none;
margin: 0;
padding: 0;
display: inline-block;
}
.emoji-mart-emoji {
position: relative;
display: inline-block;
font-size: 0;
margin: 0;
padding: 0;
border: none;
background: none;
box-shadow: none;
}
.emoji-mart-emoji-native {
font-family: "Segoe UI Emoji", "Segoe UI Symbol", "Segoe UI", "Apple Color Emoji";
font-family: "Segoe UI Emoji", "Segoe UI Symbol", "Segoe UI", "Apple Color Emoji", "Twemoji Mozilla", "Noto Color Emoji", "EmojiOne Color", "Android Emoji";
}
.emoji-mart-no-results {
@ -369,3 +400,17 @@
.emoji-mart-skin-tone-4 { background-color: #bf8f68 }
.emoji-mart-skin-tone-5 { background-color: #9b643d }
.emoji-mart-skin-tone-6 { background-color: #594539 }
/* For screenreaders only, via https://stackoverflow.com/a/19758620 */
.emoji-mart-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}

View File

@ -16,8 +16,8 @@
text-align: center;
}
button + button { margin-left: .5em }
button {
.sets button + button { margin-left: .5em }
.sets button {
padding: .4em .6em;
border-radius: 5px;
border: 1px solid rgba(0, 0, 0, .1);
@ -26,7 +26,7 @@
cursor: pointer;
}
button[disabled] {
.sets button[disabled] {
border-color: #ae65c5;
cursor: default;
}

View File

@ -14,13 +14,24 @@ const CUSTOM_EMOJIS = [
name: 'Octocat',
short_names: ['octocat'],
keywords: ['github'],
imageUrl: 'https://assets-cdn.github.com/images/icons/emoji/octocat.png?v7'
imageUrl: 'https://github.githubassets.com/images/icons/emoji/octocat.png'
},
{
name: 'Squirrel',
short_names: ['shipit', 'squirrel'],
keywords: ['github'],
imageUrl: 'https://assets-cdn.github.com/images/icons/emoji/shipit.png?v7'
imageUrl: 'https://github.githubassets.com/images/icons/emoji/shipit.png'
},
{
name: 'Test Flag',
short_names: ['test'],
keywords: ['test', 'flag'],
spriteUrl: 'https://unpkg.com/emoji-datasource-twitter@4.0.4/img/twitter/sheets-256/64.png',
sheet_x: 1,
sheet_y: 1,
size: 64,
sheetColumns: 52,
sheetRows: 52,
},
]
@ -42,7 +53,7 @@ class Example extends React.Component {
<h1>Emoji Mart 🏬</h1>
</div>
<div className="row">
<div className="row sets">
{['native', 'apple', 'google', 'twitter', 'emojione', 'messenger', 'facebook'].map((set) => {
var props = { disabled: !this.state.native && set == this.state.set }

View File

@ -1,73 +0,0 @@
// Karma configuration
// Generated on Fri Jan 27 2017 13:33:03 GMT-0700 (MST)
var webpackConfig = require('./spec/webpack.config.js');
module.exports = function(config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['jasmine'],
// list of files / patterns to load in the browser
files: [
'spec/*spec.js',
],
// list of files to exclude
exclude: [
],
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
'spec/*spec.js': ['webpack'],
},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress'],
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: true,
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ['Chrome'],
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: true,
// Concurrency level
// how many browser should be started simultaneous
concurrency: Infinity,
webpack: webpackConfig,
})
}

View File

@ -1,6 +1,6 @@
{
"name": "emoji-mart",
"version": "2.9.1",
"version": "2.10.0",
"description": "Customizable Slack-like emoji picker for React",
"main": "dist/index.js",
"module": "dist-es/index.js",
@ -19,7 +19,9 @@
"url": "https://github.com/missive/emoji-mart/issues"
},
"homepage": "https://github.com/missive/emoji-mart",
"dependencies": {},
"dependencies": {
"prop-types": "^15.6.0"
},
"peerDependencies": {
"react": "^0.14.0 || ^15.0.0-0 || ^16.0.0"
},
@ -29,55 +31,52 @@
"@storybook/addon-links": "^3.2.10",
"@storybook/addon-options": "3.2.10",
"@storybook/react": "^3.2.11",
"babel-cli": "^6.26.0",
"babel-core": "6.7.2",
"babel-loader": "^7.1.2",
"babel-cli": "^6.0.0",
"babel-core": "^6.0.0",
"babel-jest": "^23.6.0",
"babel-loader": "^7.0.0",
"babel-plugin-module-resolver": "2.7.1",
"babel-plugin-transform-define": "^1.3.0",
"babel-plugin-transform-es2015-destructuring": "6.9.0",
"babel-plugin-transform-object-rest-spread": "6.8.0",
"babel-plugin-transform-react-remove-prop-types": "^0.4.8",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.7.0",
"babel-preset-es2015": "6.6.0",
"babel-preset-react": "6.5.0",
"babel-runtime": "^6.26.0",
"emoji-datasource": "4.0.4",
"emojilib": "^2.2.1",
"inflection": "1.10.0",
"jasmine-core": "^2.5.2",
"karma": "^1.4.0",
"karma-chrome-launcher": "^2.0.0",
"karma-cli": "^1.0.1",
"karma-jasmine": "^1.1.0",
"karma-webpack": "^2.0.4",
"jest": "^23.0.0",
"mkdirp": "0.5.1",
"prettier": "1.11.1",
"prop-types": "^15.6.0",
"react": "^16.0.0",
"react-dom": "^16.0.0",
"react-test-renderer": "^16.8.4",
"rimraf": "2.5.2",
"size-limit": "^0.11.4",
"webpack": "^3.6.0"
},
"scripts": {
"clean": "rm -rf dist/ dist-es/",
"clean": "rm -rf dist/ dist-es/ dist-modern/",
"build:data": "node scripts/build-data",
"build:dist": "npm run build:cjs && npm run build:es",
"build:cjs": "BABEL_ENV=cjs babel src --out-dir dist --copy-files",
"build:es": "babel src --out-dir dist-es --copy-files",
"build:dist": "npm run build:cjs && npm run build:es && npm run build:modern",
"build:cjs": "BABEL_ENV=legacy-cjs babel src --out-dir dist --copy-files --ignore '**/*.test.js'",
"build:es": "BABEL_ENV=legacy-es babel src --out-dir dist-es --copy-files --ignore '**/*.test.js'",
"build:modern": "babel src --out-dir dist-modern --copy-files --ignore '**/*.test.js'",
"build:docs": "cp css/emoji-mart.css docs && webpack --config ./docs/webpack.config.js",
"build": "npm run clean && npm run build:dist",
"watch": "BABEL_ENV=cjs babel src --watch --out-dir dist --copy-files",
"watch": "BABEL_ENV=cjs babel src --watch --out-dir dist --copy-files --ignore '**/*.test.js'",
"start": "npm run watch",
"stats": "webpack --config ./spec/webpack.config.js --json > spec/stats.json",
"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 --save-dev",
"react:15": "npm run react:clean && npm i react@^15 react-dom@^15 react-addons-test-utils@^15 --save-dev",
"test": "NODE_ENV=test karma start && size-limit",
"test": "npm run clean && jest",
"prepublishOnly": "npm run build",
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook",
"prettier": "prettier --write \"{src,scripts,spec}/**/*.js\""
"prettier": "prettier --write \"{src,scripts}/**/*.js\"",
"prepare": "npm run build:dist"
},
"size-limit": [
{

View File

@ -1,44 +0,0 @@
import emojiIndex from '../src/utils/emoji-index/emoji-index'
describe('#emojiIndex', () => {
describe('search', function() {
it('should work', () => {
expect(emojiIndex.search('pineapple')).toEqual([
{
id: 'pineapple',
name: 'Pineapple',
colons: ':pineapple:',
emoticons: [],
unified: '1f34d',
skin: null,
native: '🍍',
},
])
})
it('should filter only emojis we care about, exclude pineapple', () => {
let emojisToShowFilter = (data) => {
data.unified !== '1F34D'
}
expect(
emojiIndex.search('apple', { emojisToShowFilter }).map((obj) => obj.id),
).not.toContain('pineapple')
})
it('can include/exclude categories', () => {
expect(emojiIndex.search('flag', { include: ['people'] })).toEqual([])
})
it('can search for thinking_face', () => {
expect(emojiIndex.search('thinking_fac').map((x) => x.id)).toEqual([
'thinking_face',
])
})
it('can search for woman-facepalming', () => {
expect(emojiIndex.search('woman-facep').map((x) => x.id)).toEqual([
'woman-facepalming',
])
})
})
})

View File

@ -1,47 +0,0 @@
import React from 'react'
import TestUtils from 'react-dom/test-utils'
import data from '../data/all.json'
import { NimblePicker } from '../src/components'
const { click } = TestUtils.Simulate
const {
renderIntoDocument,
scryRenderedComponentsWithType,
findRenderedComponentWithType,
} = TestUtils
const render = (props = {}) => {
const defaultProps = { data }
return renderIntoDocument(<NimblePicker {...defaultProps} {...props} />)
}
describe('NimblePicker', () => {
let subject
it('works', () => {
subject = render()
expect(subject).toBeDefined()
})
describe('categories', () => {
it('shows 10 by default', () => {
subject = render()
expect(subject.categories.length).toEqual(10)
})
it('will not show some based upon our filter', () => {
subject = render({ emojisToShowFilter: (unified) => false })
expect(subject.categories.length).toEqual(2)
})
it('maintains category ids after it is filtered', () => {
subject = render({ emojisToShowFilter: (emoji) => true })
const categoriesWithIds = subject.categories.filter(
(category) => category.id,
)
expect(categoriesWithIds.length).toEqual(10)
})
})
})

View File

@ -1,61 +0,0 @@
var path = require('path')
var pack = require('../package.json')
var webpack = require('webpack')
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
.BundleAnalyzerPlugin
var PROD = process.env.NODE_ENV === 'production'
var TEST = process.env.NODE_ENV === 'test'
var config = {
entry: path.resolve('src/index.js'),
output: {
path: path.resolve('spec'),
filename: 'bundle.js',
library: 'EmojiMart',
libraryTarget: 'umd',
},
externals: [],
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader',
include: [path.resolve('src'), path.resolve('spec')],
},
],
},
resolve: {
extensions: ['.js'],
},
plugins: [
new webpack.DefinePlugin({
EMOJI_DATASOURCE_VERSION: `'${pack.devDependencies['emoji-datasource']}'`,
}),
],
bail: true,
}
if (!TEST) {
config.externals = config.externals.concat([
{
react: {
root: 'React',
commonjs2: 'react',
commonjs: 'react',
amd: 'react',
},
},
])
config.plugins = config.plugins.concat([
new BundleAnalyzerPlugin({ analyzerMode: 'static', openAnalyzer: false }),
])
}
module.exports = config

View File

@ -0,0 +1,35 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Renders <NotFound> component 1`] = `
<div
className="emoji-mart-no-results"
>
<button
aria-label="🕵️, sleuth_or_spy"
className="emoji-mart-emoji emoji-mart-emoji-native"
onClick={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
title={null}
>
<span
style={
Object {
"display": "inline-block",
"fontSize": 38,
"height": 38,
"width": 38,
"wordBreak": "keep-all",
}
}
>
🕵️
</span>
</button>
<div
className="emoji-mart-no-results-label"
>
No Emoji Found
</div>
</div>
`;

View File

@ -0,0 +1,32 @@
import React from 'react'
import NotFound from '../not-found'
import renderer from 'react-test-renderer'
import data from '../../../data/apple'
const i18n = {
notfound: 'No Emoji Found',
}
test('Renders <NotFound> component', () => {
const emojiProps = {
native: true,
skin: 1,
size: 24,
set: 'apple',
sheetSize: 64,
forceSize: true,
tooltip: false,
}
const component = renderer.create(
<NotFound
data={data}
notFound={() => {}}
notFoundEmoji={'sleuth_or_spy'}
emojiProps={emojiProps}
i18n={i18n}
/>,
)
let tree = component.toJSON()
expect(tree).toMatchSnapshot()
})

View File

@ -28,7 +28,7 @@ export default class Anchors extends React.PureComponent {
{ selected } = this.state
return (
<div className="emoji-mart-anchors">
<nav className="emoji-mart-anchors" aria-label={i18n.categorieslabel}>
{categories.map((category, i) => {
var { id, name, anchor } = category,
isSelected = name == selected
@ -38,8 +38,9 @@ export default class Anchors extends React.PureComponent {
}
return (
<span
<button
key={id}
aria-label={i18n.categories[id]}
title={i18n.categories[id]}
data-index={i}
onClick={this.handleClick}
@ -55,15 +56,15 @@ export default class Anchors extends React.PureComponent {
className="emoji-mart-anchor-bar"
style={{ backgroundColor: color }}
/>
</span>
</button>
)
})}
</div>
</nav>
)
}
}
Anchors.propTypes = {
Anchors.propTypes /* remove-proptypes */ = {
categories: PropTypes.array,
onAnchorClick: PropTypes.func,
icons: PropTypes.object,

View File

@ -16,8 +16,6 @@ export default class Category extends React.Component {
}
componentDidMount() {
this.parent = this.container.parentNode
this.margin = 0
this.minMargin = 0
@ -66,11 +64,18 @@ export default class Category extends React.Component {
}
memoizeSize() {
if (!this.container) {
// probably this is a test environment, e.g. jest
this.top = 0
this.maxMargin = 0
return
}
var parent = this.container.parentElement
var { top, height } = this.container.getBoundingClientRect()
var { top: parentTop } = this.parent.getBoundingClientRect()
var { top: parentTop } = parent.getBoundingClientRect()
var { height: labelHeight } = this.label.getBoundingClientRect()
this.top = top - parentTop + this.parent.scrollTop
this.top = top - parentTop + parent.scrollTop
if (height == 0) {
this.maxMargin = 0
@ -176,9 +181,10 @@ export default class Category extends React.Component {
}
return (
<div
<section
ref={this.setContainerRef}
className="emoji-mart-category"
aria-label={i18n.categories[id]}
style={containerStyles}
>
<div
@ -186,15 +192,23 @@ export default class Category extends React.Component {
data-name={name}
className="emoji-mart-category-label"
>
<span style={labelSpanStyles} ref={this.setLabelRef}>
<span
style={labelSpanStyles}
ref={this.setLabelRef}
aria-hidden={true /* already labeled by the section aria-label */}
>
{i18n.categories[id]}
</span>
</div>
{emojis &&
emojis.map((emoji) =>
NimbleEmoji({ emoji: emoji, data: this.data, ...emojiProps }),
)}
<ul className="emoji-mart-category-list">
{emojis &&
emojis.map((emoji) => (
<li key={emoji.id || emoji}>
{NimbleEmoji({ emoji: emoji, data: this.data, ...emojiProps })}
</li>
))}
</ul>
{emojis &&
!emojis.length && (
@ -206,12 +220,12 @@ export default class Category extends React.Component {
emojiProps={emojiProps}
/>
)}
</div>
</section>
)
}
}
Category.propTypes = {
Category.propTypes /* remove-proptypes */ = {
emojis: PropTypes.array,
hasStickyPosition: PropTypes.bool,
name: PropTypes.string.isRequired,

View File

@ -3,7 +3,8 @@ import React from 'react'
import data from '../../../data/all.json'
import NimbleEmoji from './nimble-emoji'
import { EmojiPropTypes, EmojiDefaultProps } from '../../utils/shared-props'
import { EmojiPropTypes } from '../../utils/shared-props'
import { EmojiDefaultProps } from '../../utils/shared-default-props'
const Emoji = (props) => {
for (let k in Emoji.defaultProps) {
@ -15,7 +16,7 @@ const Emoji = (props) => {
return NimbleEmoji({ ...props })
}
Emoji.propTypes = EmojiPropTypes
Emoji.propTypes /* remove-proptypes */ = EmojiPropTypes
Emoji.defaultProps = { ...EmojiDefaultProps, data }
export default Emoji

View File

@ -3,7 +3,8 @@ import PropTypes from 'prop-types'
import { getData, getSanitizedData, unifiedToNative } from '../../utils'
import { uncompress } from '../../utils/data'
import { EmojiPropTypes, EmojiDefaultProps } from '../../utils/shared-props'
import { EmojiPropTypes } from '../../utils/shared-props'
import { EmojiDefaultProps } from '../../utils/shared-default-props'
const _getData = (props) => {
var { emoji, skin, set, data } = props
@ -97,6 +98,12 @@ const NimbleEmoji = (props) => {
style = {},
children = props.children,
className = 'emoji-mart-emoji',
nativeEmoji = unified && unifiedToNative(unified),
// combine the emoji itself and all shortcodes into an accessible label
label = [nativeEmoji]
.concat(short_names)
.filter(Boolean)
.join(', '),
title = null
if (!unified && !custom) {
@ -114,12 +121,13 @@ const NimbleEmoji = (props) => {
if (props.native && unified) {
className += ' emoji-mart-emoji-native'
style = { fontSize: props.size }
children = unifiedToNative(unified)
children = nativeEmoji
if (props.forceSize) {
style.display = 'inline-block'
style.width = props.size
style.height = props.size
style.wordBreak = 'keep-all'
}
} else if (custom) {
className += ' emoji-mart-emoji-custom'
@ -127,8 +135,21 @@ const NimbleEmoji = (props) => {
width: props.size,
height: props.size,
display: 'inline-block',
backgroundImage: `url(${imageUrl})`,
backgroundSize: 'contain',
}
if (data.spriteUrl) {
style = {
...style,
backgroundImage: `url(${data.spriteUrl})`,
backgroundSize: `${100 * props.sheetColumns}% ${100 *
props.sheetRows}%`,
backgroundPosition: _getPosition(props),
}
} else {
style = {
...style,
backgroundImage: `url(${imageUrl})`,
backgroundSize: 'contain',
}
}
} else {
let setHasEmoji =
@ -149,7 +170,8 @@ const NimbleEmoji = (props) => {
props.set,
props.sheetSize,
)})`,
backgroundSize: `${100 * props.sheetColumns}% ${100 * props.sheetRows}%`,
backgroundSize: `${100 * props.sheetColumns}% ${100 *
props.sheetRows}%`,
backgroundPosition: _getPosition(props),
}
}
@ -157,26 +179,29 @@ const NimbleEmoji = (props) => {
if (props.html) {
style = _convertStyleToCSS(style)
return `<span style='${style}' ${
return `<button style='${style}' aria-label='${label}' ${
title ? `title='${title}'` : ''
} class='${className}'>${children || ''}</span>`
} class='${className}'>${children || ''}</button>`
} else {
return (
<span
key={props.emoji.id || props.emoji}
<button
onClick={(e) => _handleClick(e, props)}
onMouseEnter={(e) => _handleOver(e, props)}
onMouseLeave={(e) => _handleLeave(e, props)}
aria-label={label}
title={title}
className={className}
>
<span style={style}>{children}</span>
</span>
</button>
)
}
}
NimbleEmoji.propTypes = { ...EmojiPropTypes, data: PropTypes.object.isRequired }
NimbleEmoji.propTypes /* remove-proptypes */ = {
...EmojiPropTypes,
data: PropTypes.object.isRequired,
}
NimbleEmoji.defaultProps = EmojiDefaultProps
export default NimbleEmoji

View File

@ -26,8 +26,7 @@ export default class NotFound extends React.PureComponent {
}
}
NotFound.propTypes = {
NotFound.propTypes /* remove-proptypes */ = {
notFound: PropTypes.func.isRequired,
notFoundString: PropTypes.string.isRequired,
emojiProps: PropTypes.object.isRequired,
}

View File

@ -0,0 +1,29 @@
import React from 'react'
import NimblePicker from '../nimble-picker'
import renderer from 'react-test-renderer'
import data from '../../../../data/apple'
function render(props = {}) {
const defaultProps = { data }
const component = renderer.create(
<NimblePicker {...props} {...defaultProps} />,
)
return component.getInstance()
}
test('shows 10 categories by default', () => {
const subject = render()
expect(subject.categories.length).toEqual(10)
})
test('will not show some categories based upon our filter', () => {
const subject = render({ emojisToShowFilter: () => false })
expect(subject.categories.length).toEqual(2)
})
test('maintains category ids after it is filtered', () => {
const subject = render({ emojisToShowFilter: () => true })
const categoriesWithIds = subject.categories.filter((category) => category.id)
expect(categoriesWithIds.length).toEqual(10)
})

View File

@ -6,17 +6,19 @@ import PropTypes from 'prop-types'
import * as icons from '../../svgs'
import store from '../../utils/store'
import frequently from '../../utils/frequently'
import { deepMerge, measureScrollbar } from '../../utils'
import { deepMerge, measureScrollbar, getSanitizedData } from '../../utils'
import { uncompress } from '../../utils/data'
import { PickerPropTypes, PickerDefaultProps } from '../../utils/shared-props'
import { PickerPropTypes } from '../../utils/shared-props'
import Anchors from '../anchors'
import Category from '../category'
import Preview from '../preview'
import Search from '../search'
import { PickerDefaultProps } from '../../utils/shared-default-props'
const I18N = {
search: 'Search',
clear: 'Clear', // Accessible label on "clear" button
notfound: 'No Emoji Found',
skintext: 'Choose your default skin tone',
categories: {
@ -32,6 +34,15 @@ const I18N = {
flags: 'Flags',
custom: 'Custom',
},
categorieslabel: 'Emoji categories', // Accessible title for the list of categories
skintones: {
1: 'Default Skin Tone',
2: 'Light Skin Tone',
3: 'Medium-Light Skin Tone',
4: 'Medium Skin Tone',
5: 'Medium-Dark Skin Tone',
6: 'Dark Skin Tone',
},
}
export default class NimblePicker extends React.PureComponent {
@ -396,7 +407,13 @@ export default class NimblePicker extends React.PureComponent {
if (
this.SEARCH_CATEGORY.emojis &&
(emoji = this.SEARCH_CATEGORY.emojis[0])
this.SEARCH_CATEGORY.emojis.length &&
(emoji = getSanitizedData(
this.SEARCH_CATEGORY.emojis[0],
this.state.skin,
this.props.set,
this.props.data,
))
) {
this.handleEmojiSelect(emoji)
}
@ -483,9 +500,10 @@ export default class NimblePicker extends React.PureComponent {
width = perLine * (emojiSize + 12) + 12 + 2 + measureScrollbar()
return (
<div
<section
style={{ width: width, ...style }}
className="emoji-mart"
aria-label={title}
onKeyDown={this.handleKeyDown}
>
<div className="emoji-mart-bar">
@ -560,7 +578,7 @@ export default class NimblePicker extends React.PureComponent {
})}
</div>
{showPreview && (
{(showPreview || showSkinTones) && (
<div className="emoji-mart-bar">
<Preview
ref={this.setPreviewRef}
@ -568,6 +586,7 @@ export default class NimblePicker extends React.PureComponent {
title={title}
emoji={emoji}
showSkinTones={showSkinTones}
showPreview={showPreview}
emojiProps={{
native: native,
size: 38,
@ -587,12 +606,12 @@ export default class NimblePicker extends React.PureComponent {
/>
</div>
)}
</div>
</section>
)
}
}
NimblePicker.propTypes = {
NimblePicker.propTypes /* remove-proptypes */ = {
...PickerPropTypes,
data: PropTypes.object.isRequired,
}

View File

@ -3,7 +3,8 @@ import React from 'react'
import data from '../../../data/all.json'
import NimblePicker from './nimble-picker'
import { PickerPropTypes, PickerDefaultProps } from '../../utils/shared-props'
import { PickerPropTypes } from '../../utils/shared-props'
import { PickerDefaultProps } from '../../utils/shared-default-props'
export default class Picker extends React.PureComponent {
render() {
@ -11,5 +12,5 @@ export default class Picker extends React.PureComponent {
}
}
Picker.propTypes = PickerPropTypes
Picker.propTypes /* remove-proptypes */ = PickerPropTypes
Picker.defaultProps = { ...PickerDefaultProps, data }

View File

@ -23,9 +23,10 @@ export default class Preview extends React.PureComponent {
title,
emoji: idleEmoji,
i18n,
showPreview,
} = this.props
if (emoji) {
if (emoji && showPreview) {
var emojiData = getData(emoji, null, null, this.data),
{ emoticons = [] } = emojiData,
knownEmoticons = [],
@ -42,7 +43,7 @@ export default class Preview extends React.PureComponent {
return (
<div className="emoji-mart-preview">
<div className="emoji-mart-preview-emoji">
<div className="emoji-mart-preview-emoji" aria-hidden="true">
{NimbleEmoji({
key: emoji.id,
emoji: emoji,
@ -51,7 +52,7 @@ export default class Preview extends React.PureComponent {
})}
</div>
<div className="emoji-mart-preview-data">
<div className="emoji-mart-preview-data" aria-hidden="true">
<div className="emoji-mart-preview-name">{emoji.name}</div>
<div className="emoji-mart-preview-shortnames">
{emojiData.short_names.map((short_name) => (
@ -73,13 +74,13 @@ export default class Preview extends React.PureComponent {
} else {
return (
<div className="emoji-mart-preview">
<div className="emoji-mart-preview-emoji">
<div className="emoji-mart-preview-emoji" aria-hidden="true">
{idleEmoji &&
idleEmoji.length &&
NimbleEmoji({ emoji: idleEmoji, data: this.data, ...emojiProps })}
</div>
<div className="emoji-mart-preview-data">