diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 0000000..ecafee0
Binary files /dev/null and b/.DS_Store differ
diff --git a/README.md b/README.md
index a8f7935..a33fb54 100644
--- a/README.md
+++ b/README.md
@@ -50,6 +50,8 @@ import { Picker } from 'emoji-mart'
| **defaultSkin** | | `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 |
+| **notFoundEmoji** | | `sleuth_or_spy` | The emoji shown when there are no search results |
+| **notFound** | | | [Not Found](#not-found) |
| **icons** | | `{}` | [Custom icons](#custom-icons) |
#### I18n
@@ -159,7 +161,7 @@ import { Emoji } from 'emoji-mart'
| **onClick** | | | Params: `(emoji, event) => {}` |
| **onLeave** | | | Params: `(emoji, event) => {}` |
| **onOver** | | | Params: `(emoji, event) => {}` |
-| [**fallback**](#unsupported-emojis-fallback) | | | Params: `(emoji) => {}` |
+| [**fallback**](#unsupported-emojis-fallback) | | | Params: `(emoji, props) => {}` |
| **set** | | `apple` | The emoji set: `'apple', 'google', 'twitter', 'emojione'` |
| **sheetSize** | | `64` | The emoji [sheet size](#sheet-sizes): `16, 20, 32, 64` |
| **backgroundImageFn** | | ```((set, sheetSize) => `https://unpkg.com/emoji-datasource@3.0.0/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. |
@@ -177,8 +179,8 @@ To have the component render `:shrug:` you would need to:
set={'messenger'}
emoji={'shrug'}
size={24}
- fallback={(emoji) => {
- return `:${emoji.short_names[0]}:`
+ fallback={(emoji, props) => {
+ return emoji ? `:${emoji.short_names[0]}:` : props.emoji
}}
/>
```
@@ -235,6 +237,17 @@ const customEmojis = [
```
+## Not Found
+You can provide a custom Not Found object which will allow the appearance of the not found search results to change. In this case, we change the default 'sleuth_or_spy' emoji to Octocat when our search finds no results.
+
+```js
+import { Picker } from 'emoji-mart'
+
+const notFound = () =>
+
+
+```
+
## Custom icons
You can provide custom icons which will override the default icons.
diff --git a/css/emoji-mart.css b/css/emoji-mart.css
index 9fbdeb6..c8a00f9 100644
--- a/css/emoji-mart.css
+++ b/css/emoji-mart.css
@@ -89,17 +89,29 @@
.emoji-mart-search {
margin-top: 6px;
padding: 0 6px;
+ position: relative;
}
+
.emoji-mart-search input {
font-size: 16px;
display: block;
width: 100%;
- padding: .2em .6em;
+ padding: 5px 25px 6px 10px;
border-radius: 25px;
border: 1px solid #d9d9d9;
outline: 0;
}
+.emoji-mart-search-icon {
+ position: absolute;
+ top: 9px;
+ right: 16px;
+ z-index: 2;
+ padding: 0;
+ border: none;
+ background: none;
+}
+
.emoji-mart-category .emoji-mart-emoji span {
z-index: 1;
position: relative;
@@ -140,12 +152,22 @@
font-size: 0;
}
+.emoji-mart-emoji-native {
+ font-family: "Segoe UI Emoji", "Segoe UI Symbol", "Segoe UI", "Apple Color Emoji";
+}
+
.emoji-mart-no-results {
font-size: 14px;
text-align: center;
padding-top: 70px;
color: #858585;
}
+.emoji-mart-no-results-img {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+ width: 50%;
+}
.emoji-mart-no-results .emoji-mart-category-label {
display: none;
}
diff --git a/docs/emoji-mart.css b/docs/emoji-mart.css
index 93e62d7..3f61082 100644
--- a/docs/emoji-mart.css
+++ b/docs/emoji-mart.css
@@ -138,6 +138,10 @@
font-size: 0;
}
+.emoji-mart-emoji-native {
+ font-family: "Segoe UI Emoji", "Segoe UI Symbol", "Segoe UI", "Apple Color Emoji";
+}
+
.emoji-mart-no-results {
font-size: 14px;
text-align: center;
diff --git a/src/components/category.js b/src/components/category.js
index 890fbc5..dccf99e 100644
--- a/src/components/category.js
+++ b/src/components/category.js
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
import frequently from '../utils/frequently'
import { getData } from '../utils'
-import { NimbleEmoji } from '.'
+import { NimbleEmoji, NotFound } from '.'
export default class Category extends React.Component {
constructor(props) {
@@ -144,7 +144,15 @@ export default class Category extends React.Component {
}
render() {
- var { id, name, hasStickyPosition, emojiProps, i18n } = this.props,
+ var {
+ id,
+ name,
+ hasStickyPosition,
+ emojiProps,
+ i18n,
+ notFound,
+ notFoundEmoji,
+ } = this.props,
emojis = this.getEmojis(),
labelStyles = {},
labelSpanStyles = {},
@@ -169,9 +177,7 @@ export default class Category extends React.Component {
return (
-
- {NimbleEmoji({
- data: this.data,
- ...emojiProps,
- size: 38,
- emoji: 'sleuth_or_spy',
- onOver: null,
- onLeave: null,
- onClick: null,
- })}
-
-
-
{i18n.notfound}
-
+
)}
)
@@ -220,6 +218,8 @@ Category.propTypes = {
perLine: PropTypes.number.isRequired,
emojiProps: PropTypes.object.isRequired,
recent: PropTypes.arrayOf(PropTypes.string),
+ notFound: PropTypes.func,
+ notFoundEmoji: PropTypes.string.isRequired,
}
Category.defaultProps = {
diff --git a/src/components/emoji/nimble-emoji.js b/src/components/emoji/nimble-emoji.js
index 2cc2652..135e735 100644
--- a/src/components/emoji/nimble-emoji.js
+++ b/src/components/emoji/nimble-emoji.js
@@ -87,7 +87,11 @@ const NimbleEmoji = (props) => {
let data = _getData(props)
if (!data) {
- return null
+ if (props.fallback) {
+ return props.fallback(null, props)
+ } else {
+ return null
+ }
}
let { unified, custom, short_names, imageUrl } = data,
@@ -97,7 +101,11 @@ const NimbleEmoji = (props) => {
title = null
if (!unified && !custom) {
- return null
+ if (props.fallback) {
+ return props.fallback(data, props)
+ } else {
+ return null
+ }
}
if (props.tooltip) {
@@ -129,7 +137,7 @@ const NimbleEmoji = (props) => {
if (!setHasEmoji) {
if (props.fallback) {
- return props.fallback(data)
+ return props.fallback(data, props)
} else {
return null
}
diff --git a/src/components/index.js b/src/components/index.js
index 2f59995..81faff2 100644
--- a/src/components/index.js
+++ b/src/components/index.js
@@ -1,6 +1,7 @@
export { default as Anchors } from './anchors'
export { default as Category } from './category'
export { default as Preview } from './preview'
+export { default as NotFound } from './not-found'
export { default as Search } from './search'
export { default as Skins } from './skins'
diff --git a/src/components/not-found.js b/src/components/not-found.js
new file mode 100644
index 0000000..7ffda01
--- /dev/null
+++ b/src/components/not-found.js
@@ -0,0 +1,32 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { NimbleEmoji } from '.'
+
+export default class NotFound extends React.PureComponent {
+ render() {
+ const { data, emojiProps, i18n, notFound, notFoundEmoji } = this.props
+
+ const component = (notFound && notFound()) || (
+
+ {NimbleEmoji({
+ data: data,
+ ...emojiProps,
+ size: 38,
+ emoji: notFoundEmoji,
+ onOver: null,
+ onLeave: null,
+ onClick: null,
+ })}
+
{i18n.notfound}
+
+ )
+
+ return component
+ }
+}
+
+NotFound.propTypes = {
+ notFound: PropTypes.func.isRequired,
+ notFoundString: PropTypes.string.isRequired,
+ emojiProps: PropTypes.object.isRequired,
+}
diff --git a/src/components/picker/nimble-picker.js b/src/components/picker/nimble-picker.js
index 9cd396f..5d54057 100644
--- a/src/components/picker/nimble-picker.js
+++ b/src/components/picker/nimble-picker.js
@@ -3,7 +3,7 @@ import '../../vendor/raf-polyfill'
import React from 'react'
import PropTypes from 'prop-types'
-import SVGs from '../../svgs'
+import * as icons from '../../svgs'
import store from '../../utils/store'
import frequently from '../../utils/frequently'
import { deepMerge, measureScrollbar } from '../../utils'
@@ -30,21 +30,6 @@ const I18N = {
},
}
-const toSVG = (val) => () => (
-
-)
-
-const ICON_FNS = {
- categories: Object.keys(SVGs).reduce((acc, cur) => {
- acc[cur] = toSVG(SVGs[cur])
- return acc
- }, {}),
-}
-
export default class NimblePicker extends React.PureComponent {
constructor(props) {
super(props)
@@ -64,7 +49,7 @@ export default class NimblePicker extends React.PureComponent {
this.data = props.data
this.i18n = deepMerge(I18N, props.i18n)
- this.icons = deepMerge(ICON_FNS, props.icons)
+ this.icons = deepMerge(icons, props.icons)
this.state = {
skin: props.skin || store.get('skin') || props.defaultSkin,
firstRender: true,
@@ -484,6 +469,8 @@ export default class NimblePicker extends React.PureComponent {
exclude,
recent,
autoFocus,
+ notFound,
+ notFoundEmoji,
} = this.props,
{ skin } = this.state,
width = perLine * (emojiSize + 12) + 12 + 2 + measureScrollbar()
@@ -557,6 +544,8 @@ export default class NimblePicker extends React.PureComponent {
onLeave: this.handleEmojiLeave,
onClick: this.handleEmojiClick,
}}
+ notFound={notFound}
+ notFoundEmoji={notFoundEmoji}
/>
)
})}
diff --git a/src/components/search.js b/src/components/search.js
index 21f3cdb..fc8f31d 100644
--- a/src/components/search.js
+++ b/src/components/search.js
@@ -1,20 +1,36 @@
import React from 'react'
import PropTypes from 'prop-types'
+import { search as icons } from '../svgs'
import NimbleEmojiIndex from '../utils/emoji-index/nimble-emoji-index'
export default class Search extends React.PureComponent {
constructor(props) {
super(props)
+ this.state = {
+ icon: icons.search,
+ isSearching: false,
+ }
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)
}
- handleChange() {
- var value = this.input.value
+ search(value) {
+ if (value == '')
+ this.setState({
+ icon: icons.search,
+ isSearching: false,
+ })
+ else
+ this.setState({
+ icon: icons.delete,
+ isSearching: true,
+ })
this.props.onSearch(
this.emojiIndex.search(value, {
@@ -27,16 +43,29 @@ export default class Search extends React.PureComponent {
)
}
+ clear() {
+ if (this.input.value == '') return
+ this.input.value = ''
+ this.search('')
+ }
+
+ handleChange() {
+ this.search(this.input.value)
+ }
+
+ handleKeyUp(e) {
+ if (e.keyCode === 13) {
+ this.clear()
+ }
+ }
+
setRef(c) {
this.input = c
}
- clear() {
- this.input.value = ''
- }
-
render() {
var { i18n, autoFocus } = this.props
+ var { icon, isSearching } = this.state
return (
@@ -47,6 +76,14 @@ export default class Search extends React.PureComponent {
placeholder={i18n.search}
autoFocus={autoFocus}
/>
+
)
}
diff --git a/src/svgs/index.js b/src/svgs/index.js
index 877ce15..5f1a024 100644
--- a/src/svgs/index.js
+++ b/src/svgs/index.js
@@ -1,23 +1,166 @@
-const SVGs = {
- activity: ``,
+import React from 'react'
- custom: ``,
+const categories = {
+ activity: () => (
+
+ ),
- flags: ``,
+ custom: () => (
+
+ ),
- foods: ``,
+ flags: () => (
+
+ ),
- nature: ``,
+ foods: () => (
+
+ ),
- objects: ``,
+ nature: () => (
+
+ ),
- people: ``,
+ objects: () => (
+
+ ),
- places: ``,
+ people: () => (
+
+ ),
- recent: ``,
+ places: () => (
+
+ ),
- symbols: ``,
+ recent: () => (
+
+ ),
+
+ symbols: () => (
+
+ ),
}
-export default SVGs
+const search = {
+ search: () => (
+
+ ),
+
+ delete: () => (
+
+ ),
+}
+
+export { categories, search }
diff --git a/src/utils/shared-props.js b/src/utils/shared-props.js
index 89b6840..fcea08f 100644
--- a/src/utils/shared-props.js
+++ b/src/utils/shared-props.js
@@ -71,6 +71,8 @@ const PickerPropTypes = {
imageUrl: PropTypes.string.isRequired,
}),
),
+ notFound: PropTypes.func,
+ notFoundEmoji: PropTypes.string,
icons: PropTypes.object,
}
@@ -97,6 +99,8 @@ const PickerDefaultProps = {
emojiTooltip: EmojiDefaultProps.tooltip,
autoFocus: false,
custom: [],
+ notFound: () => {},
+ notFoundEmoji: 'sleuth_or_spy',
icons: {},
}
diff --git a/stories/index.js b/stories/index.js
index 60a035b..0a4a6c2 100644
--- a/stories/index.js
+++ b/stories/index.js
@@ -1,11 +1,18 @@
-import React from 'react';
+import React from 'react'
-import { storiesOf } from '@storybook/react';
-import { action } from '@storybook/addon-actions';
-import { withKnobs, text, boolean, number, select, color } from '@storybook/addon-knobs';
+import { storiesOf } from '@storybook/react'
+import { action } from '@storybook/addon-actions'
+import {
+ withKnobs,
+ text,
+ boolean,
+ number,
+ select,
+ color,
+} from '@storybook/addon-knobs'
-import { Picker, Emoji, emojiIndex } from '../dist';
-import '../css/emoji-mart.css';
+import { Picker, Emoji, emojiIndex } from '../dist'
+import '../css/emoji-mart.css'
const SETS = ['apple', 'google', 'twitter', 'emojione', 'messenger', 'facebook']
const CUSTOM_EMOJIS = [
@@ -13,14 +20,14 @@ const CUSTOM_EMOJIS = [
name: 'Octocat',
short_names: ['octocat'],
keywords: ['github'],
- imageUrl: 'https://assets-cdn.github.com/images/icons/emoji/octocat.png?v7'
+ 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'
- }
+ imageUrl: 'https://assets-cdn.github.com/images/icons/emoji/shipit.png?v7',
+ },
]
storiesOf('Picker', module)
@@ -36,33 +43,143 @@ storiesOf('Picker', module)
perLine={number('Per line', 9)}
title={text('Idle text', 'Your Title Here')}
emoji={text('Idle emoji', 'department_store')}
+ notFoundEmoji={text('Not found emoji', 'sleuth_or_spy')}
defaultSkin={number('Default skin tone', 1)}
color={color('Highlight color', '#ae65c5')}
showPreview={boolean('Show preview', true)}
showSkinTones={boolean('Show skin tones', true)}
custom={CUSTOM_EMOJIS}
- />))
- .add('with-custom-icons', () => (
- ,
- people: () => ,
- nature: () => ,
- foods: () => ,
- activity: () => ,
- places: () => ,
- objects: () => ,
- symbols: () => ,
- flags: () => ,
- custom: () => ,
- }
- }
- }
- />
- ));
+ />
+ ))
+ .add('with a custom not found image', () => (
+ (
+
+ )}
+ />
+ ))
+ .add('with a custom not found SVG', () => (
+ (
+
+ )}
+ />
+ ))
+ .add('with-custom-icons', () => (
+ (
+
+ ),
+ people: () => (
+
+ ),
+ nature: () => (
+
+ ),
+ foods: () => (
+
+ ),
+ activity: () => (
+
+ ),
+ places: () => (
+
+ ),
+ objects: () => (
+
+ ),
+ symbols: () => (
+
+ ),
+ flags: () => (
+
+ ),
+ custom: () => (
+
+ ),
+ },
+ }}
+ />
+ ))
storiesOf('Emoji', module)
.addDecorator(withKnobs)
@@ -74,23 +191,31 @@ storiesOf('Emoji', module)
size={number('Emoji size', 64)}
skin={number('Skin tone', 1)}
html={boolean('HTML', false)}
- fallback={(emoji) => {
- return `:${emoji.short_names[0]}:`
+ fallback={(emoji, props) => {
+ return emoji ? `:${emoji.short_names[0]}:` : props.emoji
}}
/>
- ));
+ ))
storiesOf('Headless Search', module)
.addDecorator(withKnobs)
.add('default', () => {
- let results = emojiIndex.search(text('Search', 'christmas'), { custom: CUSTOM_EMOJIS })
- if (!results) { return null }
+ let results = emojiIndex.search(text('Search', 'christmas'), {
+ custom: CUSTOM_EMOJIS,
+ })
+ if (!results) {
+ return null
+ }
- return
- {results.map((emoji) => {
- return
-
-
- })}
-
- });
+ return (
+
+ {results.map((emoji) => {
+ return (
+
+
+
+ )
+ })}
+
+ )
+ })