{['native', 'apple', 'google', 'twitter', 'emojione', 'messenger', 'facebook'].map((set) => {
var props = { disabled: !this.state.native && set == this.state.set }
diff --git a/karma.conf.js b/karma.conf.js
deleted file mode 100644
index 6a71ce8..0000000
--- a/karma.conf.js
+++ /dev/null
@@ -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,
- })
-}
diff --git a/package.json b/package.json
index b4b81ae..54c6c57 100644
--- a/package.json
+++ b/package.json
@@ -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": [
{
diff --git a/spec/emoji-index-spec.js b/spec/emoji-index-spec.js
deleted file mode 100644
index d656c31..0000000
--- a/spec/emoji-index-spec.js
+++ /dev/null
@@ -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',
- ])
- })
- })
-})
diff --git a/spec/picker-spec.js b/spec/picker-spec.js
deleted file mode 100644
index a75ea77..0000000
--- a/spec/picker-spec.js
+++ /dev/null
@@ -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(
)
-}
-
-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)
- })
- })
-})
diff --git a/spec/webpack.config.js b/spec/webpack.config.js
deleted file mode 100644
index 59d4cfe..0000000
--- a/spec/webpack.config.js
+++ /dev/null
@@ -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
diff --git a/src/components/__tests__/__snapshots__/not-found.test.js.snap b/src/components/__tests__/__snapshots__/not-found.test.js.snap
new file mode 100644
index 0000000..b1d0335
--- /dev/null
+++ b/src/components/__tests__/__snapshots__/not-found.test.js.snap
@@ -0,0 +1,35 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Renders
component 1`] = `
+
+
+
+ No Emoji Found
+
+
+`;
diff --git a/src/components/__tests__/not-found.test.js b/src/components/__tests__/not-found.test.js
new file mode 100644
index 0000000..fbf84b0
--- /dev/null
+++ b/src/components/__tests__/not-found.test.js
@@ -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 component', () => {
+ const emojiProps = {
+ native: true,
+ skin: 1,
+ size: 24,
+ set: 'apple',
+ sheetSize: 64,
+ forceSize: true,
+ tooltip: false,
+ }
+ const component = renderer.create(
+ {}}
+ notFoundEmoji={'sleuth_or_spy'}
+ emojiProps={emojiProps}
+ i18n={i18n}
+ />,
+ )
+ let tree = component.toJSON()
+ expect(tree).toMatchSnapshot()
+})
diff --git a/src/components/anchors.js b/src/components/anchors.js
index 5008d6b..3f61369 100644
--- a/src/components/anchors.js
+++ b/src/components/anchors.js
@@ -28,7 +28,7 @@ export default class Anchors extends React.PureComponent {
{ selected } = this.state
return (
-
+
+
)
}
}
-Anchors.propTypes = {
+Anchors.propTypes /* remove-proptypes */ = {
categories: PropTypes.array,
onAnchorClick: PropTypes.func,
icons: PropTypes.object,
diff --git a/src/components/category.js b/src/components/category.js
index dcd6b73..31beaa3 100644
--- a/src/components/category.js
+++ b/src/components/category.js
@@ -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 (
-
-
+
{i18n.categories[id]}
- {emojis &&
- emojis.map((emoji) =>
- NimbleEmoji({ emoji: emoji, data: this.data, ...emojiProps }),
- )}
+
+ {emojis &&
+ emojis.map((emoji) => (
+ -
+ {NimbleEmoji({ emoji: emoji, data: this.data, ...emojiProps })}
+
+ ))}
+
{emojis &&
!emojis.length && (
@@ -206,12 +220,12 @@ export default class Category extends React.Component {
emojiProps={emojiProps}
/>
)}
-
+
)
}
}
-Category.propTypes = {
+Category.propTypes /* remove-proptypes */ = {
emojis: PropTypes.array,
hasStickyPosition: PropTypes.bool,
name: PropTypes.string.isRequired,
diff --git a/src/components/emoji/emoji.js b/src/components/emoji/emoji.js
index b74cae9..dbe727e 100644
--- a/src/components/emoji/emoji.js
+++ b/src/components/emoji/emoji.js
@@ -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
diff --git a/src/components/emoji/nimble-emoji.js b/src/components/emoji/nimble-emoji.js
index 765a68c..13f6017 100644
--- a/src/components/emoji/nimble-emoji.js
+++ b/src/components/emoji/nimble-emoji.js
@@ -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 `${children || ''}`
+ } class='${className}'>${children || ''}`
} else {
return (
- _handleClick(e, props)}
onMouseEnter={(e) => _handleOver(e, props)}
onMouseLeave={(e) => _handleLeave(e, props)}
+ aria-label={label}
title={title}
className={className}
>
{children}
-
+
)
}
}
-NimbleEmoji.propTypes = { ...EmojiPropTypes, data: PropTypes.object.isRequired }
+NimbleEmoji.propTypes /* remove-proptypes */ = {
+ ...EmojiPropTypes,
+ data: PropTypes.object.isRequired,
+}
NimbleEmoji.defaultProps = EmojiDefaultProps
export default NimbleEmoji
diff --git a/src/components/not-found.js b/src/components/not-found.js
index 46c8af6..d8fbf93 100644
--- a/src/components/not-found.js
+++ b/src/components/not-found.js
@@ -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,
}
diff --git a/src/components/picker/__tests__/nimble-picker.test.js b/src/components/picker/__tests__/nimble-picker.test.js
new file mode 100644
index 0000000..81d2101
--- /dev/null
+++ b/src/components/picker/__tests__/nimble-picker.test.js
@@ -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(
+ ,
+ )
+ 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)
+})
diff --git a/src/components/picker/nimble-picker.js b/src/components/picker/nimble-picker.js
index 24c0ff4..736e92f 100644
--- a/src/components/picker/nimble-picker.js
+++ b/src/components/picker/nimble-picker.js
@@ -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 (
-
@@ -560,7 +578,7 @@ export default class NimblePicker extends React.PureComponent {
})}
- {showPreview && (
+ {(showPreview || showSkinTones) && (
)}
-
+
)
}
}
-NimblePicker.propTypes = {
+NimblePicker.propTypes /* remove-proptypes */ = {
...PickerPropTypes,
data: PropTypes.object.isRequired,
}
diff --git a/src/components/picker/picker.js b/src/components/picker/picker.js
index a96f090..8d68269 100644
--- a/src/components/picker/picker.js
+++ b/src/components/picker/picker.js
@@ -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 }
diff --git a/src/components/preview.js b/src/components/preview.js
index e9ab5ae..f908508 100644
--- a/src/components/preview.js
+++ b/src/components/preview.js
@@ -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 (
-
+
{NimbleEmoji({
key: emoji.id,
emoji: emoji,
@@ -51,7 +52,7 @@ export default class Preview extends React.PureComponent {
})}
-
+
{emoji.name}
{emojiData.short_names.map((short_name) => (
@@ -73,13 +74,13 @@ export default class Preview extends React.PureComponent {
} else {
return (
-
+
{idleEmoji &&
idleEmoji.length &&
NimbleEmoji({ emoji: idleEmoji, data: this.data, ...emojiProps })}
-
+
{title}
@@ -113,7 +114,7 @@ export default class Preview extends React.PureComponent {
}
}
-Preview.propTypes = {
+Preview.propTypes /* remove-proptypes */ = {
showSkinTones: PropTypes.bool,
title: PropTypes.string.isRequired,
emoji: PropTypes.string.isRequired,
diff --git a/src/components/search.js b/src/components/search.js
index fc8f31d..b8cbfc8 100644
--- a/src/components/search.js
+++ b/src/components/search.js
@@ -3,6 +3,9 @@ import PropTypes from 'prop-types'
import { search as icons } from '../svgs'
import NimbleEmojiIndex from '../utils/emoji-index/nimble-emoji-index'
+import { throttleIdleTask } from '../utils/index'
+
+let id = 0
export default class Search extends React.PureComponent {
constructor(props) {
@@ -10,14 +13,25 @@ export default class Search extends React.PureComponent {
this.state = {
icon: icons.search,
isSearching: false,
+ id: ++id,
}
this.data = props.data
this.emojiIndex = new NimbleEmojiIndex(this.data)
this.setRef = this.setRef.bind(this)
- this.handleChange = this.handleChange.bind(this)
this.clear = this.clear.bind(this)
this.handleKeyUp = this.handleKeyUp.bind(this)
+
+ // throttle keyboard input so that typing isn't delayed
+ this.handleChange = throttleIdleTask(this.handleChange.bind(this))
+ }
+
+ componentDidMount() {
+ // in some cases (e.g. preact) the input may already be pre-populated
+ // this.input is undefined in Jest tests
+ if (this.input && this.input.value) {
+ this.search(this.input.value)
+ }
}
search(value) {
@@ -46,6 +60,7 @@ export default class Search extends React.PureComponent {
clear() {
if (this.input.value == '') return
this.input.value = ''
+ this.input.focus()
this.search('')
}
@@ -64,32 +79,42 @@ export default class Search extends React.PureComponent {
}
render() {
- var { i18n, autoFocus } = this.props
- var { icon, isSearching } = this.state
+ const { i18n, autoFocus } = this.props
+ const { icon, isSearching, id } = this.state
+ const inputId = `emoji-mart-search-${id}`
return (
-
+
+
)
}
}
-Search.propTypes = {
+Search.propTypes /* remove-proptypes */ = {
onSearch: PropTypes.func,
maxResults: PropTypes.number,
emojisToShowFilter: PropTypes.func,
diff --git a/src/components/skins-dot.js b/src/components/skins-dot.js
index c9efece..c3e4f5e 100644
--- a/src/components/skins-dot.js
+++ b/src/components/skins-dot.js
@@ -8,6 +8,14 @@ export default class SkinsDot extends Skins {
super(props)
this.handleClick = this.handleClick.bind(this)
+ this.handleKeyDown = this.handleKeyDown.bind(this)
+ }
+
+ handleKeyDown(event) {
+ // if either enter or space is pressed, then execute
+ if (event.keyCode === 13 || event.keyCode === 32) {
+ this.handleClick(event)
+ }
}
render() {
@@ -17,13 +25,29 @@ export default class SkinsDot extends Skins {
for (let skinTone = 1; skinTone <= 6; skinTone++) {
const selected = skinTone === skin
+ const visible = opened || selected
skinToneNodes.push(
@@ -32,14 +56,17 @@ export default class SkinsDot extends Skins {
}
return (
-
- {skinToneNodes}
-
+
)
}
}
-SkinsDot.propTypes = {
+SkinsDot.propTypes /* remove-proptypes */ = {
onChange: PropTypes.func,
skin: PropTypes.number.isRequired,
i18n: PropTypes.object,
diff --git a/src/components/skins-emoji.js b/src/components/skins-emoji.js
index 8de2d19..5624a26 100644
--- a/src/components/skins-emoji.js
+++ b/src/components/skins-emoji.js
@@ -58,7 +58,7 @@ export default class SkinsEmoji extends Skins {
}
}
-SkinsEmoji.propTypes = {
+SkinsEmoji.propTypes /* remove-proptypes */ = {
onChange: PropTypes.func,
skin: PropTypes.number.isRequired,
emojiProps: PropTypes.object.isRequired,
diff --git a/src/components/skins.js b/src/components/skins.js
index dd2b96f..8fd5154 100644
--- a/src/components/skins.js
+++ b/src/components/skins.js
@@ -30,7 +30,7 @@ export default class Skins extends React.PureComponent {
}
}
-Skins.propTypes = {
+Skins.propTypes /* remove-proptypes */ = {
onChange: PropTypes.func,
skin: PropTypes.number.isRequired,
}
diff --git a/src/index.js b/src/index.js
index cb1c3e1..6563e64 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,5 +1,7 @@
export { default as emojiIndex } from './utils/emoji-index/emoji-index'
-export { default as NimbleEmojiIndex } from './utils/emoji-index/nimble-emoji-index'
+export {
+ default as NimbleEmojiIndex,
+} from './utils/emoji-index/nimble-emoji-index'
export { default as store } from './utils/store'
export { default as frequently } from './utils/frequently'
export { getEmojiDataFromNative } from './utils'
diff --git a/src/polyfills/classCallCheck.js b/src/polyfills/classCallCheck.js
new file mode 100644
index 0000000..aa83175
--- /dev/null
+++ b/src/polyfills/classCallCheck.js
@@ -0,0 +1,5 @@
+export default function(instance, Constructor) {
+ if (!(instance instanceof Constructor)) {
+ throw new TypeError('Cannot call a class as a function')
+ }
+}
diff --git a/src/polyfills/keys.js b/src/polyfills/keys.js
new file mode 100644
index 0000000..b683766
--- /dev/null
+++ b/src/polyfills/keys.js
@@ -0,0 +1,38 @@
+// From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
+var hasOwnProperty = Object.prototype.hasOwnProperty,
+ hasDontEnumBug = !{ toString: null }.propertyIsEnumerable('toString'),
+ dontEnums = [
+ 'toString',
+ 'toLocaleString',
+ 'valueOf',
+ 'hasOwnProperty',
+ 'isPrototypeOf',
+ 'propertyIsEnumerable',
+ 'constructor',
+ ],
+ dontEnumsLength = dontEnums.length
+
+export default function(obj) {
+ if (typeof obj !== 'function' && (typeof obj !== 'object' || obj === null)) {
+ throw new TypeError('Object.keys called on non-object')
+ }
+
+ var result = [],
+ prop,
+ i
+
+ for (prop in obj) {
+ if (hasOwnProperty.call(obj, prop)) {
+ result.push(prop)
+ }
+ }
+
+ if (hasDontEnumBug) {
+ for (i = 0; i < dontEnumsLength; i++) {
+ if (hasOwnProperty.call(obj, dontEnums[i])) {
+ result.push(dontEnums[i])
+ }
+ }
+ }
+ return result
+}
diff --git a/src/utils/emoji-index/__tests__/emoji-index.test.js b/src/utils/emoji-index/__tests__/emoji-index.test.js
new file mode 100644
index 0000000..24ac794
--- /dev/null
+++ b/src/utils/emoji-index/__tests__/emoji-index.test.js
@@ -0,0 +1,40 @@
+import emojiIndex from '../emoji-index.js'
+
+test('should work', () => {
+ expect(emojiIndex.search('pineapple')).toEqual([
+ {
+ id: 'pineapple',
+ name: 'Pineapple',
+ colons: ':pineapple:',
+ emoticons: [],
+ unified: '1f34d',
+ skin: null,
+ native: '🍍',
+ },
+ ])
+})
+
+test('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')
+})
+
+test('can include/exclude categories', () => {
+ expect(emojiIndex.search('flag', { include: ['people'] })).toEqual([])
+})
+
+test('can search for thinking_face', () => {
+ expect(emojiIndex.search('thinking_fac').map((x) => x.id)).toEqual([
+ 'thinking_face',
+ ])
+})
+
+test('can search for woman-facepalming', () => {
+ expect(emojiIndex.search('woman-facep').map((x) => x.id)).toEqual([
+ 'woman-facepalming',
+ ])
+})
diff --git a/src/utils/index.js b/src/utils/index.js
index 3bbb118..57b3d46 100644
--- a/src/utils/index.js
+++ b/src/utils/index.js
@@ -224,6 +224,26 @@ function measureScrollbar() {
return scrollbarWidth
}
+// Use requestIdleCallback() if available, else fall back to setTimeout().
+// Throttle so as not to run too frequently.
+function throttleIdleTask(func) {
+ const doIdleTask =
+ typeof requestIdleCallback === 'function' ? requestIdleCallback : setTimeout
+
+ let running = false
+
+ return function throttled() {
+ if (running) {
+ return
+ }
+ running = true
+ doIdleTask(() => {
+ running = false
+ func()
+ })
+ }
+}
+
export {
getData,
getEmojiDataFromNative,
@@ -233,4 +253,5 @@ export {
deepMerge,
unifiedToNative,
measureScrollbar,
+ throttleIdleTask,
}
diff --git a/src/utils/shared-default-props.js b/src/utils/shared-default-props.js
new file mode 100644
index 0000000..489869b
--- /dev/null
+++ b/src/utils/shared-default-props.js
@@ -0,0 +1,46 @@
+const EmojiDefaultProps = {
+ skin: 1,
+ set: 'apple',
+ sheetSize: 64,
+ sheetColumns: 52,
+ sheetRows: 52,
+ native: false,
+ forceSize: false,
+ tooltip: false,
+ backgroundImageFn: (set, sheetSize) =>
+ `https://unpkg.com/emoji-datasource-${set}@${EMOJI_DATASOURCE_VERSION}/img/${set}/sheets-256/${sheetSize}.png`,
+ onOver: () => {},
+ onLeave: () => {},
+ onClick: () => {},
+}
+
+const PickerDefaultProps = {
+ onClick: () => {},
+ onSelect: () => {},
+ onSkinChange: () => {},
+ emojiSize: 24,
+ perLine: 9,
+ i18n: {},
+ style: {},
+ title: 'Emoji Mart™',
+ emoji: 'department_store',
+ color: '#ae65c5',
+ set: EmojiDefaultProps.set,
+ skin: null,
+ defaultSkin: EmojiDefaultProps.skin,
+ native: EmojiDefaultProps.native,
+ sheetSize: EmojiDefaultProps.sheetSize,
+ backgroundImageFn: EmojiDefaultProps.backgroundImageFn,
+ emojisToShowFilter: null,
+ showPreview: true,
+ showSkinTones: true,
+ emojiTooltip: EmojiDefaultProps.tooltip,
+ autoFocus: false,
+ custom: [],
+ skinEmoji: '',
+ notFound: () => {},
+ notFoundEmoji: 'sleuth_or_spy',
+ icons: {},
+}
+
+export { PickerDefaultProps, EmojiDefaultProps }
diff --git a/src/utils/shared-props.js b/src/utils/shared-props.js
index 8a83752..6bc7ff9 100644
--- a/src/utils/shared-props.js
+++ b/src/utils/shared-props.js
@@ -26,22 +26,6 @@ const EmojiPropTypes = {
emoji: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
}
-const EmojiDefaultProps = {
- skin: 1,
- set: 'apple',
- sheetSize: 64,
- sheetColumns: 52,
- sheetRows: 52,
- native: false,
- forceSize: false,
- tooltip: false,
- backgroundImageFn: (set, sheetSize) =>
- `https://unpkg.com/emoji-datasource-${set}@${EMOJI_DATASOURCE_VERSION}/img/${set}/sheets-256/${sheetSize}.png`,
- onOver: () => {},
- onLeave: () => {},
- onClick: () => {},
-}
-
const PickerPropTypes = {
onClick: PropTypes.func,
onSelect: PropTypes.func,
@@ -72,7 +56,13 @@ const PickerPropTypes = {
short_names: PropTypes.arrayOf(PropTypes.string).isRequired,
emoticons: PropTypes.arrayOf(PropTypes.string),
keywords: PropTypes.arrayOf(PropTypes.string),
- imageUrl: PropTypes.string.isRequired,
+ imageUrl: PropTypes.string,
+ spriteUrl: PropTypes.string,
+ sheet_x: PropTypes.number,
+ sheet_y: PropTypes.number,
+ size: PropTypes.number,
+ sheetColumns: PropTypes.number,
+ sheetRows: PropTypes.number,
}),
),
skinEmoji: PropTypes.string,
@@ -81,38 +71,4 @@ const PickerPropTypes = {
icons: PropTypes.object,
}
-const PickerDefaultProps = {
- onClick: () => {},
- onSelect: () => {},
- onSkinChange: () => {},
- emojiSize: 24,
- perLine: 9,
- i18n: {},
- style: {},
- title: 'Emoji Mart™',
- emoji: 'department_store',
- color: '#ae65c5',
- set: EmojiDefaultProps.set,
- skin: null,
- defaultSkin: EmojiDefaultProps.skin,
- native: EmojiDefaultProps.native,
- sheetSize: EmojiDefaultProps.sheetSize,
- backgroundImageFn: EmojiDefaultProps.backgroundImageFn,
- emojisToShowFilter: null,
- showPreview: true,
- showSkinTones: true,
- emojiTooltip: EmojiDefaultProps.tooltip,
- autoFocus: false,
- custom: [],
- skinEmoji: '',
- notFound: () => {},
- notFoundEmoji: 'sleuth_or_spy',
- icons: {},
-}
-
-export {
- EmojiPropTypes,
- EmojiDefaultProps,
- PickerPropTypes,
- PickerDefaultProps,
-}
+export { EmojiPropTypes, PickerPropTypes }
diff --git a/stories/index.js b/stories/index.js
index 5755316..de013ac 100644
--- a/stories/index.js
+++ b/stories/index.js
@@ -21,13 +21,13 @@ 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',
},
]
@@ -56,7 +56,7 @@ storiesOf('Picker', module)
.add('Custom “Not found” component', () => (
(
-
+
)}
/>
))
@@ -67,7 +67,7 @@ storiesOf('Picker', module)
icons={{
categories: {
recent: () => (
-
+
),
people: () => (