Skip to content

Commit d93a134

Browse files
committed
Add Custom Emoji Support
1 parent 5cc46f4 commit d93a134

34 files changed

+463
-209
lines changed

.storybook/main.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
11
module.exports = {
22
stories: ['../stories/**/*.stories.@(ts|tsx|js|jsx)'],
3-
addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
3+
addons: [
4+
'@storybook/addon-links',
5+
'@storybook/addon-essentials',
6+
{
7+
name: '@storybook/addon-postcss',
8+
options: {
9+
postcssLoaderOptions: {
10+
implementation: require('postcss')
11+
}
12+
}
13+
}
14+
],
415
// https://storybook.js.org/docs/react/configure/typescript#mainjs-configuration
516
typescript: {
6-
check: true, // type-check stories during Storybook build
17+
check: true // type-check stories during Storybook build
718
}
819
};

README.md

Lines changed: 65 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
![Picker](https://user-images.githubusercontent.com/11255103/192167134-8205eb89-a71d-4463-8f3a-940e844917d5.gif)
44

5-
An emoji picker component for React applications.
6-
75
## What to know before using
86

97
- This package assumes it runs in the browser. I have taken many steps to prevent it from failing on the server, but still, it is recommended to only render the component on the client. See troubleshooting section for more information.
@@ -30,16 +28,15 @@ function App() {
3028

3129
## Shout Outs!
3230

33-
34-
| Component Design 🎨 |
35-
|:-:|
36-
| <a href="https://pavelbolo.com" target="_blank">![317751726_1277528579755086_5320360926126813336_n copy](https://user-images.githubusercontent.com/11255103/205937426-a570b4a1-7243-4d3e-a7e5-ea04b61d940a.png)</a> |
37-
| <a href="https://pavelbolo.com" target="_blank">Pavel Bolo</a> |
38-
31+
| Component Design 🎨 |
32+
| :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
33+
| <a href="https://pavelbolo.com" target="_blank">![317751726_1277528579755086_5320360926126813336_n copy](https://user-images.githubusercontent.com/11255103/205937426-a570b4a1-7243-4d3e-a7e5-ea04b61d940a.png)</a> |
34+
| <a href="https://pavelbolo.com" target="_blank">Pavel Bolo</a> |
3935

4036
## Features
4137

4238
- Custom click handler
39+
- Custom Emojis Support
4340
- Dark mode
4441
- Customizable styles via css variables
4542
- Default skin tone selection
@@ -52,24 +49,26 @@ function App() {
5249

5350
The following props are accepted by them picker:
5451

55-
| Prop | Type | Default | Description |
56-
| ---------------------- | ----------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
57-
| onEmojiClick | function | | Callback function that is called when an emoji is clicked. The function receives the emoji object as a parameter. |
58-
| autoFocusSearch | boolean | `true` | Controls the auto focus of the search input. |
59-
| Theme | string | `light` | Controls the theme of the picker. Possible values are `light`, `dark` and `auto`. |
60-
| emojiStyle | string | `apple` | Controls the emoji style. Possible values are `google`, `apple`, `facebook`, `twitter` and `native`. |
61-
| defaultSkinTone | string | `neutral` | Controls the default skin tone. |
62-
| lazyLoadEmojis | boolean | `false` | Controls whether the emojis are loaded lazily or not. |
63-
| previewConfig | object | `{}` | Controls the preview of the emoji. See below for more information. |
64-
| searchPlaceholder | string | `Search` | Controls the placeholder of the search input. |
65-
| suggestedEmojisMode | string | `frequent` | Controls the suggested emojis mode. Possible values are `frequent` and `recent`. |
66-
| skinTonesDisabled | boolean | `false` | Controls whether the skin tones are disabled or not. |
67-
| searchDisabled | boolean | `false` | Controls whether the search is disabled or not. When disabled, the skin tone picker will be shown in the preview component. |
68-
| skinTonePickerLocation | string | `SEARCH` | Controls the location of the skin tone picker. Possible values are `SEARCH` and `PREVIEW`. |
69-
| `width` | `number`/`string` | `350` | Controls the width of the picker. You can provide a number that will be treated as pixel size, or your any accepted css width as string. |
70-
| emojiVersion | `string` | - | Allows displaying emojis up to a certain version for compatibility. |
71-
| `height` | `number`/`string` | `450` | Controls the height of the picker. You can provide a number that will be treated as pixel size, or your any accepted css height as string. |
72-
| getEmojiUrl | `Function` | - | Allows to customize the emoji url and provide your own image host. |
52+
| Prop | Type | Default | Description |
53+
| ---------------------- | ------------------------------------------------------ | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
54+
| onEmojiClick | function | | Callback function that is called when an emoji is clicked. The function receives the emoji object as a parameter. |
55+
| autoFocusSearch | boolean | `true` | Controls the auto focus of the search input. |
56+
| Theme | string | `light` | Controls the theme of the picker. Possible values are `light`, `dark` and `auto`. |
57+
| emojiStyle | string | `apple` | Controls the emoji style. Possible values are `google`, `apple`, `facebook`, `twitter` and `native`. |
58+
| defaultSkinTone | string | `neutral` | Controls the default skin tone. |
59+
| lazyLoadEmojis | boolean | `false` | Controls whether the emojis are loaded lazily or not. |
60+
| previewConfig | object | `{}` | Controls the preview of the emoji. See below for more information. |
61+
| searchPlaceholder | string | `Search` | Controls the placeholder of the search input. |
62+
| suggestedEmojisMode | string | `frequent` | Controls the suggested emojis mode. Possible values are `frequent` and `recent`. |
63+
| skinTonesDisabled | boolean | `false` | Controls whether the skin tones are disabled or not. |
64+
| searchDisabled | boolean | `false` | Controls whether the search is disabled or not. When disabled, the skin tone picker will be shown in the preview component. |
65+
| skinTonePickerLocation | string | `SEARCH` | Controls the location of the skin tone picker. Possible values are `SEARCH` and `PREVIEW`. |
66+
| `width` | `number`/`string` | `350` | Controls the width of the picker. You can provide a number that will be treated as pixel size, or your any accepted css width as string. |
67+
| emojiVersion | `string` | - | Allows displaying emojis up to a certain version for compatibility. |
68+
| `height` | `number`/`string` | `450` | Controls the height of the picker. You can provide a number that will be treated as pixel size, or your any accepted css height as string. |
69+
| getEmojiUrl | `Function` | - | Allows to customize the emoji url and provide your own image host. |
70+
| categories | `Array` | - | Allows full config over ordering, naming and display of categories. |
71+
| customEmojis | `Array<{names: string[], imgUrl: string, id: string}>` | - | Allows adding custom emojis to the picker. |
7372

7473
## Full details
7574

@@ -145,6 +144,7 @@ import { SkinTones } from 'emoji-picker-react';
145144
To only sort/omit categories, you can simply pass an array of category names to display:
146145

147146
- 'suggested',
147+
- 'custom', - Hidden by default
148148
- 'smileys_people',
149149
- 'animals_nature',
150150
- 'food_drink',
@@ -186,6 +186,45 @@ import { SkinTones } from 'emoji-picker-react';
186186

187187
* `getEmojiUrl`: `(unified: string, emojiStyle: EmojiStyle) => string` - Allows to customize the emoji url and provide your own image host. The function receives the emoji unified and the emoji style as parameters. The function should return the url of the emoji image.
188188

189+
## Custom Emojis
190+
191+
The customEmojis prop allows you to add custom emojis to the picker. The custom emojis prop takes an array of custom emojis, each custom emoji has three keys:
192+
193+
id: Unique identifier for each of the custom emojis
194+
names: an array of string identifiers, will be used both for display, search and indexing.
195+
imgUrl: URL for the emoji image
196+
197+
### Usage Example:
198+
199+
```jsx
200+
<Picker
201+
customEmojis={[
202+
{
203+
names: ['Film'],
204+
imgUrl: 'https://cdn.jsdelivr.net/npm/eva-icons/fill/svg/film.svg',
205+
id: 'film'
206+
},
207+
{
208+
names: ['Bar Chart'],
209+
imgUrl: 'https://cdn.jsdelivr.net/npm/eva-icons/fill/svg/bar-chart.svg',
210+
id: 'bar_chart'
211+
},
212+
{
213+
names: ['champagne', 'bottle', 'sparkling wine'],
214+
imgUrl:
215+
'https://cdn.jsdelivr.net/gh/hfg-gmuend/openmoji/src/food-drink/drink/1F37E.svg',
216+
id: 'champagne'
217+
}
218+
]}
219+
/>
220+
```
221+
222+
Here are some additional things to keep in mind about custom emojis:
223+
224+
- The custom emojis will be added to the Custom category.
225+
- The location or name of the Custom category can be controlled via the categories prop.
226+
- The custom emojis will be indexed by their id property. This means that you can search for custom emojis by their id or names.
227+
189228
# Customization
190229

191230
## Custom Picker Width and Height

package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "4.4.12",
2+
"version": "4.5.0",
33
"license": "MIT",
44
"main": "dist/index.js",
55
"typings": "dist/index.d.ts",
@@ -77,6 +77,9 @@
7777
"emoji-datasource": "^14.0.0",
7878
"eslint-import-resolver-typescript": "^3.3.0",
7979
"eslint-plugin-import": "^2.26.0",
80+
"eslint-plugin-jsx-a11y": "^6.6.1",
81+
"eslint-plugin-react": "^7.31.8",
82+
"eslint-plugin-react-hooks": "^4.6.0",
8083
"fs-extra": "^10.1.0",
8184
"glob": "^8.0.3",
8285
"husky": "^8.0.1",
@@ -92,10 +95,7 @@
9295
"tiny-invariant": "^1.2.0",
9396
"tsdx": "^0.14.1",
9497
"tslib": "^2.4.0",
95-
"typescript": "^4.7.4",
96-
"eslint-plugin-jsx-a11y": "^6.6.1",
97-
"eslint-plugin-react": "^7.31.8",
98-
"eslint-plugin-react-hooks": "^4.6.0"
98+
"typescript": "^4.7.4"
9999
},
100100
"dependencies": {
101101
"clsx": "^1.2.1"

postcss.config.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module.exports = {
2+
plugins: [
3+
require('postcss-inline-svg'),
4+
require('postcss-svgo'),
5+
require('autoprefixer'),
6+
require('cssnano')
7+
],
8+
inject: true
9+
};

scripts/prepare.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@ const keys = {
1616
GROUP_NAME_OBJECTS: 'objects',
1717
GROUP_NAME_SYMBOLS: 'symbols',
1818
GROUP_NAME_FLAGS: 'flags',
19-
GROUP_NAME_SUGGESTED: 'suggested'
19+
GROUP_NAME_SUGGESTED: 'suggested',
20+
GROUP_NAME_CUSTOM: 'custom'
2021
};
2122

2223
const groupConversion = {
2324
[keys.GROUP_NAME_PEOPLE]: keys.GROUP_NAME_PEOPLE,
2425
smileys_emotion: keys.GROUP_NAME_PEOPLE,
26+
custom: keys.GROUP_NAME_CUSTOM,
2527
people_body: keys.GROUP_NAME_PEOPLE,
2628
animals_nature: keys.GROUP_NAME_NATURE,
2729
food_drink: keys.GROUP_NAME_FOOD,
@@ -82,7 +84,7 @@ const { groupedEmojis } = emojis.reduce(
8284
groupedEmojis[category].push(cleanEmoji(emoji));
8385
return { groupedEmojis };
8486
},
85-
{ groupedEmojis: {} }
87+
{ groupedEmojis: { [keys.GROUP_NAME_CUSTOM]: [] } }
8688
);
8789

8890
writeJSONSync('./src/data/emojis.json', groupedEmojis, 'utf8');

src/components/context/PickerConfigContext.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ const ConfigContext = React.createContext<PickerConfigInternal>(
1717
);
1818

1919
export function PickerConfigProvider({ children, ...config }: Props) {
20+
const [mergedConfig] = React.useState(() => mergeConfig(config));
2021
return (
21-
<ConfigContext.Provider value={mergeConfig(config)}>
22+
<ConfigContext.Provider value={mergedConfig}>
2223
{children}
2324
</ConfigContext.Provider>
2425
);
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { CustomEmoji } from '../../config/customEmojiConfig';
2+
import { DataEmoji } from '../../dataUtils/DataTypes';
3+
import { EmojiStyle } from '../../types/exposedTypes';
4+
5+
export type BaseEmojiProps = {
6+
emoji?: DataEmoji | CustomEmoji;
7+
emojiStyle: EmojiStyle;
8+
unified: string;
9+
size?: number;
10+
lazyLoad?: boolean;
11+
getEmojiUrl?: GetEmojiUrl;
12+
};
13+
export type GetEmojiUrl = (unified: string, style: EmojiStyle) => string;
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import clsx from 'clsx';
2+
import * as React from 'react';
3+
4+
import { ClassNames } from '../../DomUtils/classNames';
5+
import { Button } from '../atoms/Button';
6+
import './Emoji.css';
7+
8+
type ClickableEmojiButtonProps = Readonly<{
9+
hidden?: boolean;
10+
showVariations?: boolean;
11+
hiddenOnSearch?: boolean;
12+
emojiNames: string[];
13+
children: React.ReactNode;
14+
hasVariations: boolean;
15+
unified?: string;
16+
}>;
17+
18+
export function ClickableEmojiButton({
19+
emojiNames,
20+
unified,
21+
hidden,
22+
hiddenOnSearch,
23+
showVariations = true,
24+
hasVariations,
25+
children
26+
}: ClickableEmojiButtonProps) {
27+
return (
28+
<Button
29+
className={clsx(ClassNames.emoji, {
30+
[ClassNames.hidden]: hidden,
31+
[ClassNames.hiddenOnSearch]: hiddenOnSearch,
32+
[ClassNames.visible]: !hidden && !hiddenOnSearch,
33+
[ClassNames.emojiHasVariations]: hasVariations && showVariations
34+
})}
35+
data-unified={unified}
36+
aria-label={emojiNames[0]}
37+
data-full-name={emojiNames}
38+
>
39+
{children}
40+
</Button>
41+
);
42+
}

src/components/emoji/Emoji.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
.EmojiPickerReact button.epr-emoji .epr-emoji-img {
3131
max-width: var(--epr-emoji-fullsize);
3232
max-height: var(--epr-emoji-fullsize);
33+
min-width: var(--epr-emoji-fullsize);
34+
min-height: var(--epr-emoji-fullsize);
3335
padding: var(--epr-emoji-padding);
3436
}
3537

0 commit comments

Comments
 (0)