Custom emoji for skin tone selector

Currently, the skin tone selector
is a row of colored dots

We propose creating a skinIcon field
that uses the emoji-mart dots on
default but otherwise uses the emoji
passed in by the user
release
Rena Ryumae 2018-07-05 17:00:57 -04:00
parent b1c499fc8d
commit f1d5bc0095
9 changed files with 234 additions and 48 deletions

View File

@ -181,6 +181,11 @@
text-align: right; text-align: right;
} }
.emoji-mart-preview-skins.custom {
right: 10px;
text-align: right;
}
.emoji-mart-preview-name { .emoji-mart-preview-name {
font-size: 14px; font-size: 14px;
} }
@ -223,12 +228,18 @@
background-color: #fff; 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; width: 16px;
padding: 0 2px; 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; opacity: .75;
} }
@ -248,12 +259,13 @@
.emoji-mart-skin-swatch:nth-child(5) { transition-delay: .12s } .emoji-mart-skin-swatch:nth-child(5) { transition-delay: .12s }
.emoji-mart-skin-swatch:nth-child(6) { transition-delay: .15s } .emoji-mart-skin-swatch:nth-child(6) { transition-delay: .15s }
.emoji-mart-skin-swatch-selected { .emoji-mart-skin-swatch.selected {
position: relative; position: relative;
width: 16px; width: 16px;
padding: 0 2px; padding: 0 2px;
} }
.emoji-mart-skin-swatch-selected:after {
.emoji-mart-skin-swatch.selected:after {
content: ""; content: "";
position: absolute; position: absolute;
top: 50%; left: 50%; top: 50%; left: 50%;
@ -266,9 +278,63 @@
transition: opacity .2s ease-out; 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: 30%;
height: 40px;
border-radius: 10%;
background-color: #fff;
}
.emoji-mart-skin { .emoji-mart-skin {
display: inline-block; display: inline-block;
width: 100%; padding-top: 100%; width: 100%;
padding-top: 100%;
max-width: 12px; max-width: 12px;
border-radius: 100%; border-radius: 100%;
} }

View File

@ -3,6 +3,8 @@ export { default as Category } from './category'
export { default as Preview } from './preview' export { default as Preview } from './preview'
export { default as Search } from './search' export { default as Search } from './search'
export { default as Skins } from './skins' 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 Emoji } from './emoji/emoji'
export { default as NimbleEmoji } from './emoji/nimble-emoji' export { default as NimbleEmoji } from './emoji/nimble-emoji'

View File

@ -14,6 +14,7 @@ import { Anchors, Category, Preview, Search } from '..'
const I18N = { const I18N = {
search: 'Search', search: 'Search',
notfound: 'No Emoji Found', notfound: 'No Emoji Found',
skintext: 'Choose your default skin tone ',
categories: { categories: {
search: 'Search Results', search: 'Search Results',
recent: 'Frequently Used', recent: 'Frequently Used',
@ -467,6 +468,7 @@ export default class NimblePicker extends React.PureComponent {
exclude, exclude,
recent, recent,
autoFocus, autoFocus,
skinIcon,
} = this.props, } = this.props,
{ skin } = this.state, { skin } = this.state,
width = perLine * (emojiSize + 12) + 12 + 2 + measureScrollbar() width = perLine * (emojiSize + 12) + 12 + 2 + measureScrollbar()
@ -563,7 +565,9 @@ export default class NimblePicker extends React.PureComponent {
skinsProps={{ skinsProps={{
skin: skin, skin: skin,
onChange: this.handleSkinChange, onChange: this.handleSkinChange,
skinIcon: skinIcon,
}} }}
i18n={this.i18n}
/> />
</div> </div>
)} )}

View File

@ -2,7 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { getData } from '../utils' import { getData } from '../utils'
import { NimbleEmoji, Skins } from '.' import { NimbleEmoji, SkinsEmoji, SkinsDot } from '.'
export default class Preview extends React.PureComponent { export default class Preview extends React.PureComponent {
constructor(props) { constructor(props) {
@ -20,6 +20,7 @@ export default class Preview extends React.PureComponent {
showSkinTones, showSkinTones,
title, title,
emoji: idleEmoji, emoji: idleEmoji,
i18n,
} = this.props } = this.props
if (emoji) { if (emoji) {
@ -81,8 +82,27 @@ export default class Preview extends React.PureComponent {
</div> </div>
{showSkinTones && ( {showSkinTones && (
<div className="emoji-mart-preview-skins"> <div
<Skins {...skinsProps} /> className={`emoji-mart-preview-skins${
skinsProps.skinIcon ? ' custom' : ''
}`}
>
{skinsProps.skinIcon ? (
<SkinsEmoji
skin={skinsProps.skin}
emojiProps={emojiProps}
data={this.data}
skinIcon={skinsProps.skinIcon}
i18n={i18n}
onChange={skinsProps.onChange}
/>
) : (
<SkinsDot
skin={skinsProps.skin}
i18n={i18n}
onChange={skinsProps.onChange}
/>
)}
</div> </div>
)} )}
</div> </div>

View File

@ -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: () => {},
}

View File

@ -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, skinIcon, 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: skinIcon,
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,
skinIcon: PropTypes.string.isRequired,
i18n: PropTypes.object,
}
SkinsEmoji.defaultProps = {
onChange: () => {},
skinTone: null,
}

View File

@ -1,15 +1,14 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { NimbleEmoji } from '.'
export default class Skins extends React.PureComponent { export default class Skins extends React.PureComponent {
constructor(props) { constructor(props) {
super(props) super(props)
this.state = { this.state = {
opened: false, opened: false,
} }
this.handleClick = this.handleClick.bind(this)
} }
handleClick(e) { handleClick(e) {
@ -27,42 +26,7 @@ export default class Skins extends React.PureComponent {
} }
render() { render() {
const { skin } = this.props return null
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>
)
} }
} }

View File

@ -71,6 +71,7 @@ const PickerPropTypes = {
imageUrl: PropTypes.string.isRequired, imageUrl: PropTypes.string.isRequired,
}), }),
), ),
skinIcon: PropTypes.string,
} }
const PickerDefaultProps = { const PickerDefaultProps = {
@ -96,6 +97,7 @@ const PickerDefaultProps = {
emojiTooltip: EmojiDefaultProps.tooltip, emojiTooltip: EmojiDefaultProps.tooltip,
autoFocus: false, autoFocus: false,
custom: [], custom: [],
skinIcon: '',
} }
export { export {

View File

@ -41,7 +41,13 @@ storiesOf('Picker', module)
showPreview={boolean('Show preview', true)} showPreview={boolean('Show preview', true)}
showSkinTones={boolean('Show skin tones', true)} showSkinTones={boolean('Show skin tones', true)}
custom={CUSTOM_EMOJIS} custom={CUSTOM_EMOJIS}
/> />))
.add('custom-skin-icon', () => (
<Picker
native={boolean('Unicode', true)}
emojiSize={24}
skinIcon={text('Skin Preview Icon', 'v')}
/>
)); ));
storiesOf('Emoji', module) storiesOf('Emoji', module)