Skip to content

Commit

Permalink
Merge pull request #85 from MeisterLabs/custom-emojis [Close #2]
Browse files Browse the repository at this point in the history
Add support for custom emojis
  • Loading branch information
EtienneLem committed May 27, 2017
2 parents f4bbee1 + 98f2a95 commit 1e87212
Show file tree
Hide file tree
Showing 12 changed files with 190 additions and 31 deletions.
33 changes: 32 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { Picker } from 'emoji-mart'
| **emoji** | | `department_store` | The emoji shown when no emojis are hovered, set to an empty string to show nothing |
| **include** | | `[]` | Only load included categories. Accepts [I18n categories keys](#i18n). Order will be respected, except for the `recent` category which will always be the first. |
| **exclude** | | `[]` | Don't load excluded categories. Accepts [I18n categories keys](#i18n). |
| **custom** | | `[]` | [Custom emojis](#custom-emojis) |
| **emojiSize** | | `24` | The emoji width and height |
| **onClick** | | | Params: `(emoji, event) => {}` |
| **perLine** | | `9` | Number of emojis per line. While there’s no minimum or maximum, this will affect the picker’s width. This will set *Frequently Used* length as well (`perLine * 4`) |
Expand All @@ -37,7 +38,7 @@ import { Picker } from 'emoji-mart'
| **set** | | `apple` | The emoji set: `'apple', 'google', 'twitter', 'emojione'` |
| **sheetSize** | | `64` | The emoji [sheet size](#sheet-sizes): `16, 20, 32, 64` |
| **backgroundImageFn** | | ```((set, sheetSize) => …)``` | A Fn that returns that image sheet to use for emojis. Useful for avoiding a request if you have the sheet locally. |
| **emojisToShowFilter** | | ```((unicode) => true)``` | A Fn to choose whether an emoji should be displayed or not based on its unicode |
| **emojisToShowFilter** | | ```((emoji) => true)``` | A Fn to choose whether an emoji should be displayed or not |
| **skin** | | `1` | Default skin color: `1, 2, 3, 4, 5, 6` |
| **style** | | | Inline styles applied to the root element. Useful for positioning |
| **title** | | `Emoji Mart™` | The title shown when no emojis are hovered |
Expand All @@ -57,6 +58,7 @@ categories: {
objects: 'Objects',
symbols: 'Symbols',
flags: 'Flags',
custom: 'Custom',
}
```

Expand Down Expand Up @@ -104,6 +106,16 @@ Sheets are served from [unpkg](https://unpkg.com), a global CDN that serves file
skin: 3,
native: '🎅🏼'
}

{
id: 'octocat',
name: 'Octocat',
colons: ':octocat',
emoticons: [],
custom: true,
imageUrl: 'https://assets-cdn.github.com/images/icons/emoji/octocat.png?v7'
}

```

### Emoji
Expand All @@ -128,6 +140,25 @@ import { Emoji } from 'emoji-mart'
| **backgroundImageFn** | | ```((set, sheetSize) => `https://unpkg.com/emoji-datasource@2.4.4/sheet_${set}_${sheetSize}.png`)``` | A Fn that returns that image sheet to use for emojis. Useful for avoiding a request if you have the sheet locally. |
| **skin** | | `1` | Skin color: `1, 2, 3, 4, 5, 6` |

## Custom emojis
You can provide custom emojis which will show up in their own category.

```js
import { Picker } from 'emoji-mart'

const customEmojis = [
{
name: 'Octocat',
short_names: ['octocat'],
emoticons: [],
keywords: ['github'],
imageUrl: 'https://assets-cdn.github.com/images/icons/emoji/octocat.png?v7'
},
]

<Picker custom={customEmojis} />
```

## Headless search
The `Picker` doesn’t have to be mounted for you to take advantage of the advanced search results.

Expand Down
18 changes: 18 additions & 0 deletions example/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,21 @@ import ReactDOM from 'react-dom'

import { Picker, Emoji } from '../src'

const CUSTOM_EMOJIS = [
{
name: 'Octocat',
short_names: ['octocat'],
keywords: ['github'],
imageUrl: 'https://assets-cdn.github.com/images/icons/emoji/octocat.png?v7'
},
{
name: 'Squirrel',
short_names: ['shipit', 'squirrel'],
keywords: ['github'],
imageUrl: 'https://assets-cdn.github.com/images/icons/emoji/shipit.png?v7'
}
]

const CATEGORIES = [
'recent',
'people',
Expand All @@ -13,6 +28,7 @@ const CATEGORIES = [
'objects',
'symbols',
'flags',
'custom',
]

class Example extends React.Component {
Expand Down Expand Up @@ -125,6 +141,7 @@ class Example extends React.Component {
<br /> perLine<Operator>=</Operator>&#123;<Variable>{this.state.perLine}</Variable>&#125; {this.state.perLine < 10 ? ' ' : ' '} <input type='range' data-key='perLine' onChange={this.handleInput.bind(this)} min='7' max='16' value={this.state.perLine} />
<br /> skin<Operator>=</Operator>&#123;<Variable>{this.state.skin}</Variable>&#125; <input type='range' data-key='skin' onChange={this.handleInput.bind(this)} min='1' max='6' value={this.state.skin} />
<br /> set<Operator>=</Operator><String>'{this.state.set}'</String>
<br /> custom<Operator>=</Operator>&#123;<Variable>{'[…]'}</Variable>&#125;
<br /> autoFocus<Operator>=</Operator>&#123;<Variable>{this.state.autoFocus ? 'true' : 'false'}</Variable>&#125;{this.state.autoFocus ? ' ' : ''} <input type='checkbox' data-key='autoFocus' data-mount={true} onChange={this.handleInput.bind(this)} checked={this.state.autoFocus} />
<div style={{ opacity: this.state.exclude.length ? 0.6 : 1 }}> include<Operator>=</Operator>&#91;
{[0, 2, 4, 6, 8].map((i) => {
Expand Down Expand Up @@ -160,6 +177,7 @@ class Example extends React.Component {
skin={this.state.skin}
native={this.state.native}
set={this.state.set}
custom={CUSTOM_EMOJIS}
autoFocus={this.state.autoFocus}
include={this.state.include}
exclude={this.state.exclude}
Expand Down
26 changes: 8 additions & 18 deletions scripts/build-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ var fs = require('fs'),
emojiData = require('emoji-datasource'),
emojiLib = require('emojilib'),
inflection = require('inflection'),
mkdirp = require('mkdirp')
mkdirp = require('mkdirp'),
buildSearch = require('../src/utils/build-search')

var categories = ['People', 'Nature', 'Foods', 'Activity', 'Places', 'Objects', 'Symbols', 'Flags'],
data = { categories: [], emojis: {}, skins: {}, short_names: {} },
Expand Down Expand Up @@ -52,23 +53,12 @@ emojiData.forEach((datum) => {
keywords = emojiLib.lib[datum.short_name].keywords
}

datum.search = []
var addToSearch = (strings, split) => {
(Array.isArray(strings) ? strings : [strings]).forEach((string) => {
(split ? string.split(/[-|_|\s]+/) : [string]).forEach((s) => {
s = s.toLowerCase()

if (datum.search.indexOf(s) == -1) {
datum.search.push(s)
}
})
})
}

addToSearch(datum.short_names, true)
addToSearch(datum.name, true)
addToSearch(keywords, false)
addToSearch(datum.emoticons, false)
datum.search = buildSearch({
short_names: datum.short_names,
name: datum.name,
keywords,
emoticons: datum.emoticons
})

datum.search = datum.search.join(',')

Expand Down
11 changes: 10 additions & 1 deletion src/components/category.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,19 @@ export default class Category extends React.Component {
var { name, emojis, perLine } = this.props

if (name == 'Recent') {
let { custom } = this.props
let frequentlyUsed = frequently.get(perLine)

if (frequentlyUsed.length) {
emojis = frequentlyUsed
emojis = frequentlyUsed.map((id) => {
for (let emoji of custom) {
if (emoji.id === id) {
return emoji
}
}

return id
})
}
}

Expand Down
12 changes: 10 additions & 2 deletions src/components/emoji.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ const Emoji = (props) => {
}
}

var { unified } = _getData(props),
var { unified, custom, imageUrl } = _getData(props),
style = {},
children = props.children

if (!unified) {
if (!unified && !custom) {
return null
}

Expand All @@ -71,6 +71,14 @@ const Emoji = (props) => {
style.width = props.size
style.height = props.size
}
} else if (custom) {
style = {
width: props.size,
height: props.size,
display: 'inline-block',
backgroundImage: `url(${imageUrl})`,
backgroundSize: '100%',
}
} else {
style = {
width: props.size,
Expand Down
43 changes: 38 additions & 5 deletions src/components/picker.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Anchors, Category, Emoji, Preview, Search } from '.'

const RECENT_CATEGORY = { name: 'Recent', emojis: null }
const SEARCH_CATEGORY = { name: 'Search', emojis: null, anchor: false }
const CUSTOM_CATEGORY = { name: 'Custom', emojis: [] }

const I18N = {
search: 'Search',
Expand All @@ -27,6 +28,7 @@ const I18N = {
objects: 'Objects',
symbols: 'Symbols',
flags: 'Flags',
custom: 'Custom',
},
}

Expand All @@ -41,6 +43,20 @@ export default class Picker extends React.Component {
}

this.categories = []
let allCategories = [].concat(data.categories)

if (props.custom.length > 0) {
CUSTOM_CATEGORY.emojis = props.custom.map(emoji => {
return {
...emoji,
// `<Category />` expects emoji to have an `id`.
id: emoji.short_names[0],
custom: true,
}
})

allCategories.push(CUSTOM_CATEGORY)
}

if (props.include != undefined) {
data.categories.sort((a, b) => {
Expand All @@ -55,7 +71,7 @@ export default class Picker extends React.Component {
})
}

for (let category of data.categories) {
for (let category of allCategories) {
let isIncluded = props.include && props.include.length ? props.include.indexOf(category.name.toLowerCase()) > -1 : true
let isExcluded = props.exclude && props.exclude.length ? props.exclude.indexOf(category.name.toLowerCase()) > -1 : false
if (!isIncluded || isExcluded) { continue }
Expand All @@ -64,9 +80,7 @@ export default class Picker extends React.Component {
let newEmojis = []

for (let emoji of category.emojis) {
let unified = data.emojis[emoji].unified

if (props.emojisToShowFilter(unified)) {
if (props.emojisToShowFilter(data.emojis[emoji] || emoji)) {
newEmojis.push(emoji)
}
}
Expand Down Expand Up @@ -135,7 +149,8 @@ export default class Picker extends React.Component {

handleEmojiOver(emoji) {
var { preview } = this.refs
preview.setState({ emoji: emoji })
const emojiData = CUSTOM_CATEGORY.emojis.find(customEmoji => customEmoji.id === emoji.id)
preview.setState({ emoji: Object.assign(emoji, emojiData) })
clearTimeout(this.leaveTimeout)
}

Expand Down Expand Up @@ -216,6 +231,8 @@ export default class Picker extends React.Component {
activeCategory = category
break
}
} else if (scrollTop + this.clientHeight >= this.scrollHeight) {
activeCategory = this.categories[this.categories.length - 1]
}

if (activeCategory) {
Expand Down Expand Up @@ -288,6 +305,12 @@ export default class Picker extends React.Component {
let component = this.refs[`category-${i}`]
if (component) component.memoizeSize()
}

if (this.refs.scroll) {
let target = this.refs.scroll
this.scrollHeight = target.scrollHeight
this.clientHeight = target.clientHeight
}
}

getCategories() {
Expand Down Expand Up @@ -317,6 +340,7 @@ export default class Picker extends React.Component {
emojisToShowFilter={emojisToShowFilter}
include={include}
exclude={exclude}
custom={CUSTOM_CATEGORY.emojis}
autoFocus={autoFocus}
/>

Expand All @@ -331,6 +355,7 @@ export default class Picker extends React.Component {
native={native}
hasStickyPosition={this.hasStickyPosition}
i18n={this.i18n}
custom={category.name == 'Recent' ? CUSTOM_CATEGORY.emojis : undefined}
emojiProps={{
native: native,
skin: skin,
Expand Down Expand Up @@ -388,6 +413,13 @@ Picker.propTypes = {
include: PropTypes.arrayOf(PropTypes.string),
exclude: PropTypes.arrayOf(PropTypes.string),
autoFocus: PropTypes.bool,
custom: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string.isRequired,
short_names: PropTypes.arrayOf(PropTypes.string).isRequired,
emoticons: PropTypes.arrayOf(PropTypes.string),
keywords: PropTypes.arrayOf(PropTypes.string),
imageUrl: PropTypes.string.isRequired,
})),
}

Picker.defaultProps = {
Expand All @@ -406,4 +438,5 @@ Picker.defaultProps = {
backgroundImageFn: Emoji.defaultProps.backgroundImageFn,
emojisToShowFilter: null,
autoFocus: false,
custom: [],
}
1 change: 1 addition & 0 deletions src/components/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default class Search extends React.Component {
maxResults: this.props.maxResults,
include: this.props.include,
exclude: this.props.exclude,
custom: this.props.custom,
}))
}

Expand Down
7 changes: 7 additions & 0 deletions src/svgs/custom.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/svgs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export { default as People } from './people.svg'
export { default as Places } from './places.svg'
export { default as Recent } from './recent.svg'
export { default as Symbols } from './symbols.svg'
export { default as Custom } from './custom.svg'
26 changes: 26 additions & 0 deletions src/utils/build-search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module.exports = data => {
const search = []

var addToSearch = (strings, split) => {
if (!strings) {
return
}

(Array.isArray(strings) ? strings : [strings]).forEach((string) => {
(split ? string.split(/[-|_|\s]+/) : [string]).forEach((s) => {
s = s.toLowerCase()

if (search.indexOf(s) == -1) {
search.push(s)
}
})
})
}

addToSearch(data.short_names, true)
addToSearch(data.name, true)
addToSearch(data.keywords, false)
addToSearch(data.emoticons, false)

return search
}

0 comments on commit 1e87212

Please sign in to comment.