diff --git a/README.md b/README.md index 55e00d7..d1b3900 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ import { Picker } from 'emoji-mart' | **emojiTooltip** | | `false` | Show emojis short name when hovering (title) | | **skin** | | | Forces skin color: `1, 2, 3, 4, 5, 6` | | **defaultSkin** | | `1` | Default skin color: `1, 2, 3, 4, 5, 6` | +| **skinEmoji** | | | The emoji used to pick a skin tone. Uses an emoji-less skin tone picker by default | | **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 | @@ -58,6 +59,7 @@ import { Picker } from 'emoji-mart' ```js search: 'Search', notfound: 'No Emoji Found', +skintext: 'Choose your default skin tone', categories: { search: 'Search Results', recent: 'Frequently Used', diff --git a/css/emoji-mart.css b/css/emoji-mart.css index 8db71f1..8456e23 100644 --- a/css/emoji-mart.css +++ b/css/emoji-mart.css @@ -205,6 +205,11 @@ text-align: right; } +.emoji-mart-preview-skins.custom { + right: 10px; + text-align: right; +} + .emoji-mart-preview-name { font-size: 14px; } @@ -247,12 +252,18 @@ background-color: #fff; } -.emoji-mart-skin-swatches-opened .emoji-mart-skin-swatch { +.emoji-mart-skin-swatches.custom { + font-size: 0; + border: none; + background-color: #fff; +} + +.emoji-mart-skin-swatches.opened .emoji-mart-skin-swatch { width: 16px; padding: 0 2px; } -.emoji-mart-skin-swatches-opened .emoji-mart-skin-swatch-selected:after { +.emoji-mart-skin-swatches.opened .emoji-mart-skin-swatch.selected:after { opacity: .75; } @@ -272,12 +283,13 @@ .emoji-mart-skin-swatch:nth-child(5) { transition-delay: .12s } .emoji-mart-skin-swatch:nth-child(6) { transition-delay: .15s } -.emoji-mart-skin-swatch-selected { +.emoji-mart-skin-swatch.selected { position: relative; width: 16px; padding: 0 2px; } -.emoji-mart-skin-swatch-selected:after { + +.emoji-mart-skin-swatch.selected:after { content: ""; position: absolute; top: 50%; left: 50%; @@ -290,9 +302,63 @@ transition: opacity .2s ease-out; } +.emoji-mart-skin-swatch.custom { + display: inline-block; + width: 0; + height: 38px; + overflow: hidden; + vertical-align: middle; + transition-property: width, height; + transition-duration: .125s; + transition-timing-function: ease-out; + cursor: default; +} + +.emoji-mart-skin-swatch.custom.selected { + position: relative; + width: 36px; + height: 38px; + padding: 0 2px 0 0; +} + +.emoji-mart-skin-swatch.custom.selected:after { + content: ""; + width: 0; + height: 0; +} + +.emoji-mart-skin-swatches.custom .emoji-mart-skin-swatch.custom:hover { + background-color: #f4f4f4; + border-radius: 10%; +} + +.emoji-mart-skin-swatches.custom.opened .emoji-mart-skin-swatch.custom { + width: 36px; + height: 38px; + padding: 0 2px 0 0; +} + +.emoji-mart-skin-swatches.custom.opened .emoji-mart-skin-swatch.custom.selected:after { + opacity: .75; +} + +.emoji-mart-skin-text.opened { + display: inline-block; + vertical-align: middle; + text-align: left; + color: #888; + font-size: 11px; + padding: 5px 2px; + width: 95px; + height: 40px; + border-radius: 10%; + background-color: #fff; +} + .emoji-mart-skin { display: inline-block; - width: 100%; padding-top: 100%; + width: 100%; + padding-top: 100%; max-width: 12px; border-radius: 100%; } diff --git a/src/components/index.js b/src/components/index.js index 81faff2..9c4c019 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -4,6 +4,8 @@ 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' +export { default as SkinsEmoji } from './skins-emoji' +export { default as SkinsDot } from './skins-dot' export { default as Emoji } from './emoji/emoji' export { default as NimbleEmoji } from './emoji/nimble-emoji' diff --git a/src/components/picker/nimble-picker.js b/src/components/picker/nimble-picker.js index 762c950..d7d85cf 100644 --- a/src/components/picker/nimble-picker.js +++ b/src/components/picker/nimble-picker.js @@ -15,6 +15,7 @@ import { Anchors, Category, Preview, Search } from '..' const I18N = { search: 'Search', notfound: 'No Emoji Found', + skintext: 'Choose your default skin tone', categories: { search: 'Search Results', recent: 'Frequently Used', @@ -471,6 +472,7 @@ export default class NimblePicker extends React.PureComponent { exclude, recent, autoFocus, + skinEmoji, notFound, notFoundEmoji, } = this.props, @@ -574,7 +576,9 @@ export default class NimblePicker extends React.PureComponent { skinsProps={{ skin: skin, onChange: this.handleSkinChange, + skinEmoji: skinEmoji, }} + i18n={this.i18n} /> )} diff --git a/src/components/preview.js b/src/components/preview.js index a484055..c6f7b20 100644 --- a/src/components/preview.js +++ b/src/components/preview.js @@ -2,7 +2,7 @@ import React from 'react' import PropTypes from 'prop-types' import { getData } from '../utils' -import { NimbleEmoji, Skins } from '.' +import { NimbleEmoji, SkinsEmoji, SkinsDot } from '.' export default class Preview extends React.PureComponent { constructor(props) { @@ -20,6 +20,7 @@ export default class Preview extends React.PureComponent { showSkinTones, title, emoji: idleEmoji, + i18n, } = this.props if (emoji) { @@ -81,8 +82,27 @@ export default class Preview extends React.PureComponent { {showSkinTones && ( -
- +
+ {skinsProps.skinEmoji ? ( + + ) : ( + + )}
)}
diff --git a/src/components/skins-dot.js b/src/components/skins-dot.js new file mode 100644 index 0000000..bc923ff --- /dev/null +++ b/src/components/skins-dot.js @@ -0,0 +1,50 @@ +import React from 'react' +import PropTypes from 'prop-types' + +import { Skins } from '.' + +export default class SkinsDot extends Skins { + constructor(props) { + super(props) + + this.handleClick = this.handleClick.bind(this) + } + + render() { + const { skin, i18n } = this.props + const { opened } = this.state + const skinToneNodes = [] + + for (let skinTone = 1; skinTone <= 6; skinTone++) { + const selected = skinTone === skin + skinToneNodes.push( + + + , + ) + } + + return ( +
+ {skinToneNodes} +
+ ) + } +} + +SkinsDot.propTypes = { + onChange: PropTypes.func, + skin: PropTypes.number.isRequired, + i18n: PropTypes.object, +} + +SkinsDot.defaultProps = { + onChange: () => {}, +} diff --git a/src/components/skins-emoji.js b/src/components/skins-emoji.js new file mode 100644 index 0000000..192f3b9 --- /dev/null +++ b/src/components/skins-emoji.js @@ -0,0 +1,72 @@ +import React from 'react' +import PropTypes from 'prop-types' + +import { NimbleEmoji, Skins } from '.' + +export default class SkinsEmoji extends Skins { + constructor(props) { + super(props) + + this.handleClick = this.handleClick.bind(this) + } + + render() { + const { skin, emojiProps, data, skinEmoji, i18n } = this.props + const { opened } = this.state + const skinToneNodes = [] + + for (let skinTone = 1; skinTone <= 6; skinTone++) { + const selected = skinTone === skin + skinToneNodes.push( + + + {NimbleEmoji({ + emoji: skinEmoji, + data: data, + skin: skinTone, + backgroundImageFn: emojiProps.backgroundImageFn, + native: emojiProps.native, + set: emojiProps.set, + sheetSize: emojiProps.sheetSize, + size: 23, + })} + + , + ) + } + + return ( +
+
+ {i18n.skintext} +
+ {skinToneNodes} +
+ ) + } +} + +SkinsEmoji.propTypes = { + onChange: PropTypes.func, + skin: PropTypes.number.isRequired, + emojiProps: PropTypes.object.isRequired, + skinTone: PropTypes.number, + skinEmoji: PropTypes.string.isRequired, + i18n: PropTypes.object, +} + +SkinsEmoji.defaultProps = { + onChange: () => {}, + skinTone: null, +} diff --git a/src/components/skins.js b/src/components/skins.js index 1430c50..51942bb 100644 --- a/src/components/skins.js +++ b/src/components/skins.js @@ -1,15 +1,14 @@ import React from 'react' import PropTypes from 'prop-types' +import { NimbleEmoji } from '.' + export default class Skins extends React.PureComponent { constructor(props) { super(props) - this.state = { opened: false, } - - this.handleClick = this.handleClick.bind(this) } handleClick(e) { @@ -27,42 +26,7 @@ export default class Skins extends React.PureComponent { } render() { - const { skin } = this.props - const { opened } = this.state - - const skinToneNodes = [] - - for (let i = 0; i < 6; i++) { - const skinTone = i + 1 - const selected = skinTone == skin - - skinToneNodes.push( - - - , - ) - } - - return ( -
-
- {skinToneNodes} -
-
- ) + return null } } diff --git a/src/utils/shared-props.js b/src/utils/shared-props.js index 4d4e3f8..8a83752 100644 --- a/src/utils/shared-props.js +++ b/src/utils/shared-props.js @@ -75,6 +75,7 @@ const PickerPropTypes = { imageUrl: PropTypes.string.isRequired, }), ), + skinEmoji: PropTypes.string, notFound: PropTypes.func, notFoundEmoji: PropTypes.string, icons: PropTypes.object, @@ -103,6 +104,7 @@ const PickerDefaultProps = { emojiTooltip: EmojiDefaultProps.tooltip, autoFocus: false, custom: [], + skinEmoji: '', notFound: () => {}, notFoundEmoji: 'sleuth_or_spy', icons: {}, diff --git a/stories/index.js b/stories/index.js index 0a4a6c2..3cb54d4 100644 --- a/stories/index.js +++ b/stories/index.js @@ -32,7 +32,7 @@ const CUSTOM_EMOJIS = [ storiesOf('Picker', module) .addDecorator(withKnobs) - .add('default', () => ( + .add('Default', () => ( )) - .add('with a custom not found image', () => ( + + .add('Custom “Not found” component', () => ( ( )} /> )) - .add('with a custom not found SVG', () => ( - ( - - Jira icon - - - )} - /> - )) - .add('with-custom-icons', () => ( + + .add('Custom category icons', () => ( )) + .add('Custom skin emoji', () => ( + + )) + storiesOf('Emoji', module) .addDecorator(withKnobs) - .add('default', () => ( + .add('Default', () => ( { + .add('Default', () => { let results = emojiIndex.search(text('Search', 'christmas'), { custom: CUSTOM_EMOJIS, })