Merge pull request #209 from Rena-Ryumae/rena/customskin
Custom emoji for skin tone selectorrelease
commit
f5a4a488f8
|
@ -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',
|
||||
|
|
|
@ -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%;
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -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 {
|
|||
</div>
|
||||
|
||||
{showSkinTones && (
|
||||
<div className="emoji-mart-preview-skins">
|
||||
<Skins {...skinsProps} />
|
||||
<div
|
||||
className={`emoji-mart-preview-skins${
|
||||
skinsProps.skinEmoji ? ' custom' : ''
|
||||
}`}
|
||||
>
|
||||
{skinsProps.skinEmoji ? (
|
||||
<SkinsEmoji
|
||||
skin={skinsProps.skin}
|
||||
emojiProps={emojiProps}
|
||||
data={this.data}
|
||||
skinEmoji={skinsProps.skinEmoji}
|
||||
i18n={i18n}
|
||||
onChange={skinsProps.onChange}
|
||||
/>
|
||||
) : (
|
||||
<SkinsDot
|
||||
skin={skinsProps.skin}
|
||||
i18n={i18n}
|
||||
onChange={skinsProps.onChange}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -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(
|
||||
<span
|
||||
key={`skin-tone-${skinTone}`}
|
||||
className={`emoji-mart-skin-swatch${selected ? ' selected' : ''}`}
|
||||
>
|
||||
<span
|
||||
onClick={this.handleClick}
|
||||
data-skin={skinTone}
|
||||
className={`emoji-mart-skin emoji-mart-skin-tone-${skinTone}`}
|
||||
/>
|
||||
</span>,
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`emoji-mart-skin-swatches${opened ? ' opened' : ''}`}>
|
||||
{skinToneNodes}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
SkinsDot.propTypes = {
|
||||
onChange: PropTypes.func,
|
||||
skin: PropTypes.number.isRequired,
|
||||
i18n: PropTypes.object,
|
||||
}
|
||||
|
||||
SkinsDot.defaultProps = {
|
||||
onChange: () => {},
|
||||
}
|
|
@ -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(
|
||||
<span
|
||||
key={`skin-tone-${skinTone}`}
|
||||
className={`emoji-mart-skin-swatch custom${
|
||||
selected ? ' selected' : ''
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
onClick={this.handleClick}
|
||||
data-skin={skinTone}
|
||||
className={`emoji-mart-skin-tone-${skinTone}`}
|
||||
>
|
||||
{NimbleEmoji({
|
||||
emoji: skinEmoji,
|
||||
data: data,
|
||||
skin: skinTone,
|
||||
backgroundImageFn: emojiProps.backgroundImageFn,
|
||||
native: emojiProps.native,
|
||||
set: emojiProps.set,
|
||||
sheetSize: emojiProps.sheetSize,
|
||||
size: 23,
|
||||
})}
|
||||
</span>
|
||||
</span>,
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`emoji-mart-skin-swatches custom${opened ? ' opened' : ''}`}
|
||||
>
|
||||
<div className={`emoji-mart-skin-text${opened ? ' opened' : ''}`}>
|
||||
{i18n.skintext}
|
||||
</div>
|
||||
{skinToneNodes}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
|
@ -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(
|
||||
<span
|
||||
key={`skin-tone-${skinTone}`}
|
||||
className={`emoji-mart-skin-swatch ${
|
||||
selected ? 'emoji-mart-skin-swatch-selected' : ''
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
onClick={this.handleClick}
|
||||
data-skin={skinTone}
|
||||
className={`emoji-mart-skin emoji-mart-skin-tone-${skinTone}`}
|
||||
/>
|
||||
</span>,
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className={`emoji-mart-skin-swatches ${
|
||||
opened ? 'emoji-mart-skin-swatches-opened' : ''
|
||||
}`}
|
||||
>
|
||||
{skinToneNodes}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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: {},
|
||||
|
|
|
@ -32,7 +32,7 @@ const CUSTOM_EMOJIS = [
|
|||
|
||||
storiesOf('Picker', module)
|
||||
.addDecorator(withKnobs)
|
||||
.add('default', () => (
|
||||
.add('Default', () => (
|
||||
<Picker
|
||||
onClick={action('clicked')}
|
||||
onSelect={action('selected')}
|
||||
|
@ -51,29 +51,16 @@ storiesOf('Picker', module)
|
|||
custom={CUSTOM_EMOJIS}
|
||||
/>
|
||||
))
|
||||
.add('with a custom not found image', () => (
|
||||
|
||||
.add('Custom “Not found” component', () => (
|
||||
<Picker
|
||||
notFound={() => (
|
||||
<img src="https://assets-cdn.github.com/images/icons/emoji/octocat.png?v7" />
|
||||
)}
|
||||
/>
|
||||
))
|
||||
.add('with a custom not found SVG', () => (
|
||||
<Picker
|
||||
notFound={() => (
|
||||
<svg
|
||||
aria-labelledby="simpleicons-jira-icon"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title id="simpleicons-jira-icon">Jira icon</title>
|
||||
<path d="M23.323 11.33L13.001 1 12 0 4.225 7.775.67 11.33a.96.96 0 0 0 0 1.347l7.103 7.103L12 24l7.771-7.771.121-.121 3.431-3.431a.945.945 0 0 0 0-1.347zM12 15.551L8.449 12 12 8.453 15.548 12 12 15.551z" />
|
||||
</svg>
|
||||
)}
|
||||
/>
|
||||
))
|
||||
.add('with-custom-icons', () => (
|
||||
|
||||
.add('Custom category icons', () => (
|
||||
<Picker
|
||||
custom={CUSTOM_EMOJIS}
|
||||
icons={{
|
||||
|
@ -181,9 +168,17 @@ storiesOf('Picker', module)
|
|||
/>
|
||||
))
|
||||
|
||||
.add('Custom skin emoji', () => (
|
||||
<Picker
|
||||
native={boolean('Unicode', true)}
|
||||
emojiSize={24}
|
||||
skinEmoji={text('Skin Preview Icon', 'v')}
|
||||
/>
|
||||
))
|
||||
|
||||
storiesOf('Emoji', module)
|
||||
.addDecorator(withKnobs)
|
||||
.add('default', () => (
|
||||
.add('Default', () => (
|
||||
<Emoji
|
||||
native={boolean('Unicode', true)}
|
||||
set={select('Emoji pack', SETS, SETS[0])}
|
||||
|
@ -199,7 +194,7 @@ storiesOf('Emoji', module)
|
|||
|
||||
storiesOf('Headless Search', module)
|
||||
.addDecorator(withKnobs)
|
||||
.add('default', () => {
|
||||
.add('Default', () => {
|
||||
let results = emojiIndex.search(text('Search', 'christmas'), {
|
||||
custom: CUSTOM_EMOJIS,
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue