emoji-picker-react 4.19.4 → 4.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,218 +1,77 @@
1
- # 🥒 Emoji Picker React (v4)
1
+ # Emoji Picker React
2
2
 
3
- **The most popular, fully customizable emoji picker for React apps.**
3
+ The most popular fully customizable emoji picker for React.
4
4
 
5
- **[🔴 Live Demo](https://ealush.com/emoji-picker-react)** | **[🐛 Report a Bug](https://github.com/ealush/emoji-picker-react/issues)**
5
+ [![npm downloads](https://img.shields.io/npm/dm/emoji-picker-react.svg)](https://www.npmjs.com/package/emoji-picker-react)
6
+
7
+ **[Live Demo](https://ealush.com/emoji-picker-react)** | **[Report a Bug](https://github.com/ealush/emoji-picker-react/issues)**
6
8
 
7
9
  ![image](https://github.com/ealush/emoji-picker-react/assets/11255103/48901306-e7fd-49cd-8f1e-9b214083a61d)
8
10
 
9
11
  ![reactions](https://github.com/ealush/emoji-picker-react/assets/11255103/c28cc954-dc1d-4d82-91a8-64a74cf1d598)
10
12
 
11
- ---
12
-
13
- ## ✨ Features
14
-
15
- - 🎨 **Fully Customizable**: Control styles via CSS variables.
16
- - 🌗 **Dark Mode**: Native support for light, dark, and auto themes.
17
- - 🖱️ **Interactivity**: Custom click handlers and reactions picker mode.
18
- - 🌍 **Multi-Language**: Built-in support for dozens of languages.
19
- - 🦄 **Custom Emojis**: seamless support for custom image-based emojis.
20
- - 📦 **Zero-Config**: Works out of the box with sensible defaults.
21
- - 📱 **Responsive**: Mobile-friendly design.
22
- - 🍎 **Multiple Styles**: Support for Apple, Google, Facebook, Twitter, and Native system emojis.
23
-
24
- ---
13
+ ## Features
25
14
 
26
- ## 🚀 Getting Started
15
+ - Fully customizable through props and CSS variables
16
+ - Light, dark, and auto themes
17
+ - Reactions picker mode and custom click handlers
18
+ - Dozens of built-in languages
19
+ - Custom image-based emojis
20
+ - Apple, Google, Facebook, Twitter, and native emoji styles
21
+ - Responsive and mobile-friendly
22
+ - SSR-safe
27
23
 
28
- ### Installation
24
+ ## Installation
29
25
 
30
26
  ```bash
31
27
  npm install emoji-picker-react
32
-
33
28
  ```
34
29
 
35
- ### Basic Usage
36
-
37
- Render the component in your app. It works immediately with no required props.
30
+ ## Usage
38
31
 
39
32
  ```jsx
40
33
  import EmojiPicker from 'emoji-picker-react';
41
34
 
42
35
  function App() {
43
36
  return (
44
- <div>
45
- <EmojiPicker onEmojiClick={(emojiObject) => console.log(emojiObject)} />
46
- </div>
37
+ <EmojiPicker onEmojiClick={(emojiData) => console.log(emojiData.emoji)} />
47
38
  );
48
39
  }
49
40
  ```
50
41
 
51
- ---
52
-
53
- ## 📚 Props Reference
54
-
55
- The following is a complete list of all props accepted by `EmojiPicker`.
56
-
57
- ### 🎛️ General Configuration
58
-
59
- | Prop | Type | Default | Description |
60
- | ----------------- | ------------ | ------------------ | -------------------------------------------------------------------------------------------- |
61
- | `open` | `boolean` | `true` | Controls the visibility of the picker. |
62
- | `theme` | `Theme` | `Theme.LIGHT` | The visual theme. Options: `'light'`, `'dark'`, `'auto'`. |
63
- | `emojiStyle` | `EmojiStyle` | `EmojiStyle.APPLE` | The emoji set to use. Options: `'apple'`, `'google'`, `'facebook'`, `'twitter'`, `'native'`. |
64
- | `emojiVersion` | `string` | `null` | Limit emojis to a specific unicode version (e.g., `"14.0"`). |
65
- | `lazyLoadEmojis` | `boolean` | `false` | If true, emoji images are loaded only when they scroll into view. |
66
- | `autoFocusSearch` | `boolean` | `true` | Focuses the search input automatically when the picker mounts. |
67
- | `emojiData` | `object` | `undefined` | Pass imported locale data here for internationalization. |
68
-
69
- ### 📐 Dimensions & Styling
70
-
71
- | Prop | Type | Default | Description |
72
- | ----------- | --------------- | -------- | ------------------------------------------ |
73
- | `width` | `string` | `number` | `350` |
74
- | `height` | `string` | `number` | `450` |
75
- | `style` | `CSSProperties` | `{}` | Inline styles applied to the root element. |
76
- | `className` | `string` | `""` | CSS class applied to the root element. |
77
-
78
- ### 🖱️ Events & Interaction
79
-
80
- | Prop | Type | Description |
81
- | ------------------ | -------------------------------------------------------- | -------------------------------------------------------------------- |
82
- | `onEmojiClick` | `(emojiData: EmojiClickData, event: MouseEvent) => void` | Callback triggered when a user clicks an emoji. |
83
- | `onReactionClick` | `(emojiData: EmojiClickData, event: MouseEvent) => void` | Callback triggered when a user clicks a reaction (in reaction mode). |
84
- | `onSkinToneChange` | `(skinTone: SkinTones) => void` | Callback triggered when the user selects a new skin tone. |
85
-
86
- ### 🔎 Search & Categories
87
-
88
- | Prop | Type | Default | Description |
89
- | ------------------------ | ------------------------ | ------------------------- | -------------------------------------------------------------------- |
90
- | `searchDisabled` | `boolean` | `false` | If true, the search bar is completely removed. |
91
- | `searchPlaceholder` | `string` | `"Search"` | Placeholder text for the search input. |
92
- | `searchClearButtonLabel` | `string` | `"Clear"` | Aria label for the search clear button. |
93
- | `categories` | `CategoryConfig[]` | _(All)_ | Array of category objects to customize order or visibility. |
94
- | `suggestedEmojisMode` | `SuggestionMode` | `SuggestionMode.FREQUENT` | Logic for "Suggested" category. Options: `'recent'`, `'frequent'`. |
95
- | `defaultSkinTone` | `SkinTones` | `SkinTones.NEUTRAL` | The initial skin tone. |
96
- | `skinTonesDisabled` | `boolean` | `false` | If true, users cannot change the skin tone. |
97
- | `skinTonePickerLocation` | `SkinTonePickerLocation` | `SEARCH` | Location of the skin tone trigger. Options: `'SEARCH'`, `'PREVIEW'`. |
98
-
99
- ### 🦄 Customization & Advanced
100
-
101
- | Prop | Type | Default | Description |
102
- | --------------- | ------------------------------------------------ | ----------------------- | ------------------------------------------------------------------------ |
103
- | `customEmojis` | `CustomEmoji[]` | `[]` | Array of custom image-based emojis to inject. |
104
- | `hiddenEmojis` | `string[]` | `[]` | Array of unified IDs (e.g., `'1f921'`) to hide from the picker. |
105
- | `previewConfig` | `PreviewConfig` | `{ showPreview: true }` | Configuration for the bottom preview bar. |
106
- | `getEmojiUrl` | `(unified: string, style: EmojiStyle) => string` | - | Function to override the default CDN URL for emoji images. |
107
- | `categoryIcons` | `CategoryIcons` | `{}` | Map `Categories` enum values to custom React nodes for navigation icons. |
108
- | `nonce` | `string` | `undefined` | Content Security Policy (CSP) nonce for the inline style tag. |
109
-
110
- ### ❤️ Reactions Picker Mode
111
-
112
- | Prop | Type | Default | Description |
113
- | ---------------------- | ---------- | --------------- | ------------------------------------------------------------------------ |
114
- | `reactionsDefaultOpen` | `boolean` | `false` | If true, mounts in "Reactions" mode (single row) instead of full picker. |
115
- | `reactions` | `string[]` | _(Default Set)_ | Array of unified IDs to display in the reactions bar. |
116
- | `allowExpandReactions` | `boolean` | `true` | If true, shows a `+` button to switch from reactions to full picker. |
117
-
118
- ---
119
-
120
- ## 🛠️ Detailed Configuration
121
-
122
- ### CSS Variables
123
-
124
- Override default variables by targeting `.EmojiPickerReact` or `aside.EmojiPickerReact`.
125
-
126
- ### CSS Variables
127
-
128
- You can customize the picker by overriding CSS variables.
129
-
130
- **[🎨 View Full List of CSS Variables](CSS_VARIABLES.md)**
131
-
132
- ### Custom Emojis Data Structure
133
-
134
- When passing `customEmojis`, use this format:
135
-
136
- ```ts
137
- {
138
- id: string; // Unique ID
139
- names: string[]; // Search keywords
140
- imgUrl: string; // Image source
141
- }
142
-
143
- ```
144
-
145
- ### Content Security Policy (CSP)
42
+ `onEmojiClick` receives an `EmojiClickData` object (unified code, names, image URL, active skin tone) and the underlying mouse event.
146
43
 
147
- If your site has a CSP that blocks inline styles, you can pass a `nonce` to the `EmojiPicker` component. This nonce will be applied to the inline `<style>` tag.
44
+ ## Configuration
148
45
 
149
46
  ```jsx
150
- <EmojiPicker nonce="your-nonce-value" />
151
- ```
152
-
153
- ### Preview Config
154
-
155
- Control the footer preview area using `previewConfig`:
156
-
157
- ```ts
158
- {
159
- defaultEmoji: string; // Default: "1f60a"
160
- defaultCaption: string; // Default: "What's your mood?"
161
- showPreview: boolean; // Default: true
162
- }
163
- ```
164
-
165
- ### Custom Category Icons
166
-
167
- You can customize the navigation icons using one of two methods.
168
-
169
- **Method 1: The `categoryIcons` prop**
170
-
171
- Map `Categories` enum values to valid React nodes:
172
-
173
- ```tsx
174
- import EmojiPicker, { Categories } from 'emoji-picker-react';
175
-
176
47
  <EmojiPicker
177
- categoryIcons={{
178
- [Categories.SUGGESTED]: <img src="recent.png" alt="Recent" />,
179
- [Categories.SMILEYS_PEOPLE]: <MyCustomFaceIcon />,
180
- }}
181
- />;
48
+ theme="dark"
49
+ emojiStyle="native"
50
+ width={320}
51
+ height={400}
52
+ previewConfig={{ showPreview: false }}
53
+ />
182
54
  ```
183
55
 
184
- **Method 2: The `categories` configuration array**
185
-
186
- Define the icon directly within the category configuration object:
56
+ See [PROPS.md](PROPS.md) for the complete props reference.
187
57
 
188
- ```tsx
189
- import EmojiPicker, { Categories } from 'emoji-picker-react';
58
+ ## Styling
190
59
 
191
- <EmojiPicker
192
- categories={[
193
- {
194
- category: Categories.SUGGESTED,
195
- name: 'Recently Used',
196
- icon: <img src="recent.png" alt="Recent" />,
197
- },
198
- {
199
- category: Categories.SMILEYS_PEOPLE,
200
- name: 'Smileys & People',
201
- icon: <MyCustomFaceIcon />,
202
- },
203
- ]}
204
- />;
205
- ```
60
+ No stylesheet import needed. All styles are scoped via [Flairup](https://github.com/ealush/flairup) — generated class names are hashed, so the picker's CSS won't leak into or clash with your app's styles.
206
61
 
207
- > **Note:** If both methods are used for the same category, the icon from the `categories` configuration takes precedence over the `categoryIcons` prop.
62
+ Restyle the picker by overriding [CSS variables](CSS_VARIABLES.md) on `.EmojiPickerReact`:
208
63
 
209
- ---
64
+ ```css
65
+ .EmojiPickerReact {
66
+ --epr-emoji-size: 32px;
67
+ }
68
+ ```
210
69
 
211
- ## 🌍 Internationalization (i18n)
70
+ ## Internationalization
212
71
 
213
- Import the dictionary you need and pass it to the `emojiData` prop.
72
+ Pass imported locale data via the `emojiData` prop:
214
73
 
215
- ```javascript
74
+ ```jsx
216
75
  import EmojiPicker from 'emoji-picker-react';
217
76
  import es from 'emoji-picker-react/dist/data/emojis-es'; // Spanish
218
77
 
@@ -221,58 +80,27 @@ function App() {
221
80
  }
222
81
  ```
223
82
 
224
- <details>
225
- <summary><strong>View Supported Languages</strong></summary>
226
-
227
- - `emojis-bn` (Bengali 🇧🇩)
228
- - `emojis-da` (Danish 🇩🇰)
229
- - `emojis-de` (German 🇩🇪)
230
- - `emojis-en-gb` (English, GB 🇬🇧)
231
- - `emojis-en` (English, US 🇺🇸)
232
- - `emojis-es-mx` (Spanish, Mexico 🇲🇽)
233
- - `emojis-es` (Spanish 🇪🇸)
234
- - `emojis-et` (Estonian 🇪🇪)
235
- - `emojis-fi` (Finnish 🇫🇮)
236
- - `emojis-fr` (French 🇫🇷)
237
- - `emojis-hi` (Hindi 🇮🇳)
238
- - `emojis-hu` (Hungarian 🇭🇺)
239
- - `emojis-it` (Italian 🇮🇹)
240
- - `emojis-ja` (Japanese 🇯🇵)
241
- - `emojis-ko` (Korean 🇰🇷)
242
- - `emojis-lt` (Lithuanian 🇱🇹)
243
- - `emojis-ms` (Malay 🇲🇾)
244
- - `emojis-nb` (Norwegian Bokmål 🇳🇴)
245
- - `emojis-nl` (Dutch 🇳🇱)
246
- - `emojis-pl` (Polish 🇵🇱)
247
- - `emojis-pt` (Portuguese 🇵🇹)
248
- - `emojis-ru` (Russian 🇷🇺)
249
- - `emojis-sv` (Swedish 🇸🇪)
250
- - `emojis-th` (Thai 🇹🇭)
251
- - `emojis-uk` (Ukrainian 🇺🇦)
252
- - `emojis-zh-hant` (Traditional Chinese 🇹🇼)
253
- - `emojis-zh` (Simplified Chinese 🇨🇳)
254
-
255
- </details>
256
-
257
- ---
258
-
259
- ## ⚠️ Troubleshooting
260
-
261
- ### Server-Side Rendering (Next.js / Remix)
262
-
263
- This package relies on the `window` object and must be rendered on the client.
264
-
265
- **Next.js Example:**
83
+ See [INTERNATIONALIZATION.md](INTERNATIONALIZATION.md) for the supported languages.
84
+
85
+ ## Customization
86
+
87
+ Custom emojis, custom category icons, preview configuration, and CSP nonces are covered in [CUSTOMIZATION.md](CUSTOMIZATION.md).
88
+
89
+ ## Server-Side Rendering
90
+
91
+ The picker renders on the server, with styles inlined into the server HTML — no setup needed. Since the picker is usually opened on demand rather than shown immediately, lazy-loading it is still recommended to keep the initial bundle small:
266
92
 
267
93
  ```javascript
268
94
  import dynamic from 'next/dynamic';
269
95
 
270
- const Picker = dynamic(() => import('emoji-picker-react'), { ssr: false });
96
+ const Picker = dynamic(() => import('emoji-picker-react'));
271
97
  ```
272
98
 
273
- ### Vite
99
+ ## Troubleshooting
274
100
 
275
- If you encounter `global is not defined`, add this to your HTML:
101
+ ### `global is not defined` (Vite)
102
+
103
+ Add this to your HTML:
276
104
 
277
105
  ```html
278
106
  <script>
@@ -280,17 +108,12 @@ If you encounter `global is not defined`, add this to your HTML:
280
108
  </script>
281
109
  ```
282
110
 
283
- ---
284
-
285
111
  ## More from the maintainer
286
112
 
287
113
  Building complex forms? Check out [**Vest**](https://vestjs.dev) — a validation framework for stateful, async, and dependent validation.
288
114
 
289
- ---
290
-
291
- ## 🤝 Contributing
115
+ ## Contributing
292
116
 
293
- We welcome contributions! Please check out the [Contributing Guide](https://github.com/ealush/emoji-picker-react/blob/master/CONTRIBUTING.md) for how to run the project locally.
117
+ Contributions are welcome — see the [Contributing Guide](https://github.com/ealush/emoji-picker-react/blob/master/CONTRIBUTING.md).
294
118
 
295
- **Shout Outs:**
296
119
  Design inspiration by [Pavel Bolo](https://pavelbolo.com).
@@ -1,10 +1,16 @@
1
1
  import { Styles } from 'flairup';
2
2
  import * as React from 'react';
3
3
  export declare const stylesheet: {
4
- create: <K extends string>(styles: Partial<{ [k in K]: Partial<Partial<CSSStyleDeclaration>> | Partial<{
4
+ create: <K extends string>(styles: Partial<{ [k in K]: Partial<{
5
5
  '.'?: string | string[] | undefined;
6
6
  '--'?: any;
7
- }>; }> | Partial<{}>) => Record<Exclude<Exclude<K, any>, any>, Set<string>> & Record<never, Set<string>>;
7
+ }> | Partial<Partial<CSSStyleDeclaration> & Partial<{
8
+ '.'?: string | string[] | undefined;
9
+ '--'?: any;
10
+ }>>; }> | Partial<{}>) => Record<Exclude<Exclude<K, any>, any>, Set<string>> & Record<never, Set<string>>;
11
+ keyframes: <KF extends string>(keyframesInput: Record<KF, {
12
+ [stage: string]: Partial<CSSStyleDeclaration>;
13
+ }>) => Record<KF, string>;
8
14
  getStyle: () => string;
9
15
  isApplied: () => boolean;
10
16
  };
@@ -15,11 +21,17 @@ export declare const PickerStyleTag: React.NamedExoticComponent<{
15
21
  export declare const commonInteractionStyles: Record<never, Set<string>>;
16
22
  export declare function darkMode(key: string, value: Styles): {
17
23
  '.epr-dark-theme': {
18
- [x: string]: Partial<Partial<CSSStyleDeclaration>>;
24
+ [x: string]: Partial<Partial<CSSStyleDeclaration> & Partial<{
25
+ '.'?: string | string[] | undefined;
26
+ '--'?: any;
27
+ }>>;
19
28
  };
20
29
  '.epr-auto-theme': {
21
30
  [x: string]: {
22
- '@media (prefers-color-scheme: dark)': Partial<Partial<CSSStyleDeclaration>>;
31
+ '@media (prefers-color-scheme: dark)': Partial<Partial<CSSStyleDeclaration> & Partial<{
32
+ '.'?: string | string[] | undefined;
33
+ '--'?: any;
34
+ }>>;
23
35
  };
24
36
  };
25
37
  };
@@ -521,11 +521,10 @@ function useDebouncedState(initialValue, delay) {
521
521
  var timer = React.useRef(null);
522
522
  function debouncedSetState(value) {
523
523
  return new Promise(function (resolve) {
524
- var _window;
525
524
  if (timer.current) {
526
525
  clearTimeout(timer.current);
527
526
  }
528
- timer.current = (_window = window) == null ? void 0 : _window.setTimeout(function () {
527
+ timer.current = setTimeout(function () {
529
528
  setState(value);
530
529
  resolve(value);
531
530
  }, delay);
@@ -26745,14 +26744,24 @@ var defaultEmojiData = {
26745
26744
  emojis: emojis
26746
26745
  };
26747
26746
 
26747
+ function useIsMounted() {
26748
+ var _React$useState = React.useState(false),
26749
+ isMounted = _React$useState[0],
26750
+ setIsMounted = _React$useState[1];
26751
+ React.useEffect(function () {
26752
+ setIsMounted(true);
26753
+ }, []);
26754
+ return isMounted;
26755
+ }
26756
+
26748
26757
  var SUGGESTED_LS_KEY = 'epr_suggested';
26749
26758
  function getSuggested(mode) {
26750
26759
  try {
26751
- var _window, _window$localStorage$, _window2;
26752
- if (!((_window = window) != null && _window.localStorage)) {
26760
+ var _window$localStorage$;
26761
+ if (typeof window === 'undefined' || !window.localStorage) {
26753
26762
  return [];
26754
26763
  }
26755
- var recent = JSON.parse((_window$localStorage$ = (_window2 = window) == null ? void 0 : _window2.localStorage.getItem(SUGGESTED_LS_KEY)) != null ? _window$localStorage$ : '[]');
26764
+ var recent = JSON.parse((_window$localStorage$ = window.localStorage.getItem(SUGGESTED_LS_KEY)) != null ? _window$localStorage$ : '[]');
26756
26765
  if (mode === exports.SuggestionMode.FREQUENT) {
26757
26766
  return recent.sort(function (a, b) {
26758
26767
  return b.count - a.count;
@@ -26787,8 +26796,10 @@ function setSuggested(emoji, skinTone) {
26787
26796
  existing.count++;
26788
26797
  nextList.length = Math.min(nextList.length, 14);
26789
26798
  try {
26790
- var _window3;
26791
- (_window3 = window) == null ? void 0 : _window3.localStorage.setItem(SUGGESTED_LS_KEY, JSON.stringify(nextList));
26799
+ if (typeof window === 'undefined') {
26800
+ return;
26801
+ }
26802
+ window.localStorage.setItem(SUGGESTED_LS_KEY, JSON.stringify(nextList));
26792
26803
  // Prevents the change from being seen immediately.
26793
26804
  } catch (_unused2) {
26794
26805
  // ignore
@@ -26827,15 +26838,21 @@ function useGetEmojisByCategory() {
26827
26838
  var suggestedEmojisModeConfig = useSuggestedEmojisModeConfig();
26828
26839
  var _useUpdateSuggested = useUpdateSuggested(),
26829
26840
  suggestedUpdated = _useUpdateSuggested[0];
26841
+ // Suggestions come from localStorage, which doesn't exist during SSR.
26842
+ // Read them only after mount so the first client render matches the server.
26843
+ var isMounted = useIsMounted();
26830
26844
  var suggested = React__default.useMemo(function () {
26831
26845
  var _getSuggested;
26846
+ if (!isMounted) {
26847
+ return [];
26848
+ }
26832
26849
  var suggested = (_getSuggested = getSuggested(suggestedEmojisModeConfig)) != null ? _getSuggested : [];
26833
26850
  return suggested.map(function (s) {
26834
26851
  return emojiByUnified(s.unified);
26835
26852
  }).filter(Boolean);
26836
26853
  },
26837
26854
  // eslint-disable-next-line react-hooks/exhaustive-deps
26838
- [suggestedUpdated, suggestedEmojisModeConfig]);
26855
+ [isMounted, suggestedUpdated, suggestedEmojisModeConfig]);
26839
26856
  return function getEmojisByCategory(category) {
26840
26857
  var _emojis$emojis$catego;
26841
26858
  if (category === exports.Categories.SUGGESTED) {
@@ -26958,8 +26975,14 @@ function useGetEmojisByCategory$1() {
26958
26975
  var suggestedEmojisModeConfig = useSuggestedEmojisModeConfig();
26959
26976
  var _useUpdateSuggested = useUpdateSuggested(),
26960
26977
  suggestedUpdated = _useUpdateSuggested[0];
26978
+ // Suggestions come from localStorage, which doesn't exist during SSR.
26979
+ // Read them only after mount so the first client render matches the server.
26980
+ var isMounted = useIsMounted();
26961
26981
  var suggested = React__default.useMemo(function () {
26962
26982
  var _getSuggested;
26983
+ if (!isMounted) {
26984
+ return [];
26985
+ }
26963
26986
  var suggested = (_getSuggested = getSuggested(suggestedEmojisModeConfig)) != null ? _getSuggested : [];
26964
26987
  return suggested.map(function (s) {
26965
26988
  var _extends2;
@@ -26968,7 +26991,7 @@ function useGetEmojisByCategory$1() {
26968
26991
  return _extends({}, emoji, (_extends2 = {}, _extends2[EmojiProperties.unified] = s.unified, _extends2));
26969
26992
  }).filter(Boolean);
26970
26993
  // eslint-disable-next-line react-hooks/exhaustive-deps
26971
- }, [suggestedUpdated, suggestedEmojisModeConfig, emojiByUnified]);
26994
+ }, [isMounted, suggestedUpdated, suggestedEmojisModeConfig, emojiByUnified]);
26972
26995
  return function getEmojisByCategory(category) {
26973
26996
  var _emojiData$emojis$cat, _emojiData$emojis;
26974
26997
  if (category === exports.Categories.SUGGESTED) {
@@ -28800,7 +28823,6 @@ function useMouseDownHandlers(ContainerRef, mouseEventSource) {
28800
28823
  onEmojiClick(emojiClickOutput(emoji, skinToneToUse, activeEmojiStyle, getEmojiUrl), event);
28801
28824
  }, [activeSkinTone, closeAllOpenToggles, disallowClickRef, emojiByUnified, onEmojiClick, updateSuggested, getEmojiUrl, activeEmojiStyle]);
28802
28825
  var onMouseDown = React.useCallback(function onMouseDown(event) {
28803
- var _window;
28804
28826
  if (mouseDownTimerRef.current) {
28805
28827
  clearTimeout(mouseDownTimerRef.current);
28806
28828
  }
@@ -28809,7 +28831,7 @@ function useMouseDownHandlers(ContainerRef, mouseEventSource) {
28809
28831
  if (!emoji || !emojiHasVariations$1(emoji)) {
28810
28832
  return;
28811
28833
  }
28812
- mouseDownTimerRef.current = (_window = window) == null ? void 0 : _window.setTimeout(function () {
28834
+ mouseDownTimerRef.current = setTimeout(function () {
28813
28835
  disallowClickRef.current = true;
28814
28836
  mouseDownTimerRef.current = undefined;
28815
28837
  closeAllOpenToggles();
@@ -28918,7 +28940,12 @@ var styles$1 = /*#__PURE__*/stylesheet.create({
28918
28940
  '.': 'epr-btn',
28919
28941
  cursor: 'pointer',
28920
28942
  border: '0',
28921
- background: 'none',
28943
+ // Longhand, not `background: 'none'`: a shorthand reset shares its
28944
+ // conflict keys with every background longhand, so cx() would drop
28945
+ // this whole class wherever a later class sets background-image
28946
+ // (e.g. the category nav sprite) and the native button face would
28947
+ // show through.
28948
+ backgroundColor: 'transparent',
28922
28949
  outline: 'none'
28923
28950
  }
28924
28951
  });
@@ -29048,7 +29075,11 @@ function NativeEmoji(_ref) {
29048
29075
  style = _ref.style,
29049
29076
  className = _ref.className;
29050
29077
  return React.createElement("span", {
29051
- className: flairup.cx(styles$4.nativeEmoji, emojiStyles.common, emojiStyles.external, className),
29078
+ className: flairup.cx(
29079
+ // Order matters: cx() lets the later class win same-property
29080
+ // conflicts, so the zeroing reset must come first and the real
29081
+ // font-size last — otherwise the emoji renders at 0px.
29082
+ emojiStyles.external, emojiStyles.common, styles$4.nativeEmoji, className),
29052
29083
  "data-unified": unified,
29053
29084
  style: style
29054
29085
  }, parseNativeEmoji(unified));
@@ -29538,6 +29569,10 @@ var styles$7 = /*#__PURE__*/stylesheet.create({
29538
29569
  }
29539
29570
  });
29540
29571
 
29572
+ // `useLayoutEffect` warns during SSR since it never runs on the server.
29573
+ // Fall back to `useEffect` there: measurement only matters on the client.
29574
+ var useIsomorphicLayoutEffect = typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect;
29575
+
29541
29576
  function MeasureEmoji() {
29542
29577
  var categories = useCategoriesConfig();
29543
29578
  var getEmojisByCategory = useGetEmojisByCategory();
@@ -29550,7 +29585,7 @@ function MeasureEmoji() {
29550
29585
  emojiSize = _useEmojiSizeState[0],
29551
29586
  setEmojiSize = _useEmojiSizeState[1];
29552
29587
  var ref = React.useRef(null);
29553
- React.useLayoutEffect(function () {
29588
+ useIsomorphicLayoutEffect(function () {
29554
29589
  if (ref.current) {
29555
29590
  setEmojiSize(ref.current.clientHeight);
29556
29591
  }