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: () => Reddit icon, - nature: () => , - foods: () => Jira icon, - activity: () => , - places: () => Stack Overflow icon, - objects: () => Atlassian icon, - symbols: () => HipChat icon, - flags: () => , - custom: () => Trello icon, - } - } - } - /> - )); + /> + )) + .add('with a custom not found image', () => ( + ( + + )} + /> + )) + .add('with a custom not found SVG', () => ( + ( + + Jira icon + + + )} + /> + )) + .add('with-custom-icons', () => ( + ( + + ), + people: () => ( + + Reddit icon + + + ), + nature: () => ( + + + + + + ), + foods: () => ( + + Jira icon + + + ), + activity: () => ( + + ), + places: () => ( + + + Stack Overflow icon + + + + ), + objects: () => ( + + Atlassian icon + + + ), + symbols: () => ( + + HipChat icon + + + ), + flags: () => ( + + + + ), + custom: () => ( + + Trello icon + + + ), + }, + }} + /> + )) 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 ( + + + + ) + })} +
+ ) + })