emoji-picker-react 4.0.0-rc.9 → 4.0.0

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.
Files changed (117) hide show
  1. package/README.md +176 -43
  2. package/dist/DomUtils/classNames.d.ts +9 -1
  3. package/dist/DomUtils/elementPositionInRow.d.ts +10 -0
  4. package/dist/DomUtils/focusElement.d.ts +5 -0
  5. package/dist/DomUtils/getActiveElement.d.ts +2 -0
  6. package/dist/DomUtils/isDarkTheme.d.ts +1 -0
  7. package/dist/DomUtils/keyboardNavigation.d.ts +8 -0
  8. package/dist/DomUtils/scrollTo.d.ts +3 -1
  9. package/dist/DomUtils/selectors.d.ts +24 -11
  10. package/dist/EmojiPickerReact.d.ts +6 -0
  11. package/dist/components/ErrorBoundary.d.ts +15 -0
  12. package/dist/components/body/{RecentlyUsed.d.ts → Suggested.d.ts} +1 -1
  13. package/dist/components/context/ElementRefContext.d.ts +10 -13
  14. package/dist/components/context/PickerContext.d.ts +3 -3
  15. package/dist/components/emoji/Emoji.d.ts +11 -7
  16. package/dist/components/emoji/ExportedEmoji.d.ts +8 -0
  17. package/dist/components/footer/Preview.d.ts +1 -1
  18. package/dist/config/categoryConfig.d.ts +7 -3
  19. package/dist/config/config.d.ts +18 -15
  20. package/dist/config/useConfig.d.ts +11 -4
  21. package/dist/data/skinToneVariations.d.ts +2 -0
  22. package/dist/dataUtils/emojiSelectors.d.ts +3 -2
  23. package/dist/dataUtils/suggested.d.ts +11 -0
  24. package/dist/emoji-picker-react.cjs.development.css +9 -9
  25. package/dist/emoji-picker-react.cjs.development.js +3771 -3068
  26. package/dist/emoji-picker-react.cjs.development.js.map +1 -1
  27. package/dist/emoji-picker-react.cjs.production.min.js +1 -1
  28. package/dist/emoji-picker-react.cjs.production.min.js.map +1 -1
  29. package/dist/emoji-picker-react.esm.js +3781 -3078
  30. package/dist/emoji-picker-react.esm.js.map +1 -1
  31. package/dist/hooks/preloadEmoji.d.ts +1 -1
  32. package/dist/hooks/useActiveCategoryScrollDetection.d.ts +1 -2
  33. package/dist/hooks/useCloseAllOpenToggles.d.ts +2 -4
  34. package/dist/hooks/useDebouncedState.d.ts +1 -1
  35. package/dist/hooks/useDisallowMouseMove.d.ts +4 -0
  36. package/dist/hooks/useEmojiPreviewEvents.d.ts +3 -0
  37. package/dist/hooks/useFilter.d.ts +3 -3
  38. package/dist/hooks/useFocus.d.ts +3 -0
  39. package/dist/hooks/useInitialLoad.d.ts +1 -1
  40. package/dist/hooks/useIsEmojiHidden.d.ts +7 -1
  41. package/dist/hooks/useKeyboardNavigation.d.ts +1 -0
  42. package/dist/hooks/useMouseDownHandlers.d.ts +3 -2
  43. package/dist/hooks/useOnFocus.d.ts +1 -0
  44. package/dist/hooks/useScrollCategoryIntoView.d.ts +1 -0
  45. package/dist/hooks/useSetVariationPicker.d.ts +2 -0
  46. package/dist/index.d.ts +2 -2
  47. package/dist/types/exposedTypes.d.ts +5 -1
  48. package/package.json +13 -5
  49. package/src/DomUtils/classNames.ts +12 -1
  50. package/src/DomUtils/elementPositionInRow.ts +167 -0
  51. package/src/DomUtils/focusElement.ts +35 -0
  52. package/src/DomUtils/getActiveElement.ts +5 -0
  53. package/src/DomUtils/isDarkTheme.ts +7 -0
  54. package/src/DomUtils/keyboardNavigation.ts +161 -0
  55. package/src/DomUtils/scrollTo.ts +22 -1
  56. package/src/DomUtils/selectors.ts +200 -41
  57. package/src/EmojiPickerReact.css +99 -48
  58. package/src/EmojiPickerReact.tsx +26 -0
  59. package/src/components/ErrorBoundary.tsx +28 -0
  60. package/src/components/body/Body.tsx +3 -2
  61. package/src/components/body/EmojiCategory.css +14 -28
  62. package/src/components/body/EmojiCategory.tsx +5 -4
  63. package/src/components/body/EmojiList.tsx +13 -13
  64. package/src/components/body/EmojiVariationPicker.css +13 -5
  65. package/src/components/body/EmojiVariationPicker.tsx +25 -17
  66. package/src/components/body/Suggested.tsx +54 -0
  67. package/src/components/context/ElementRefContext.tsx +54 -19
  68. package/src/components/context/PickerContext.tsx +36 -33
  69. package/src/components/emoji/Emoji.css +42 -12
  70. package/src/components/emoji/Emoji.tsx +73 -34
  71. package/src/components/emoji/ExportedEmoji.tsx +30 -0
  72. package/src/components/footer/Preview.css +1 -1
  73. package/src/components/footer/Preview.tsx +16 -23
  74. package/src/components/header/Search.css +7 -7
  75. package/src/components/header/Search.tsx +31 -7
  76. package/src/components/header/SkinTonePicker.css +15 -8
  77. package/src/components/header/SkinTonePicker.tsx +23 -5
  78. package/src/components/main/PickerMain.css +0 -2
  79. package/src/components/main/PickerMain.tsx +14 -1
  80. package/src/components/navigation/CategoryNavigation.css +41 -10
  81. package/src/components/navigation/CategoryNavigation.tsx +22 -10
  82. package/src/components/navigation/svg/CategoryNav.svg +343 -379
  83. package/src/config/categoryConfig.ts +40 -14
  84. package/src/config/config.ts +59 -13
  85. package/src/config/useConfig.ts +35 -7
  86. package/src/data/emojis.json +1 -1
  87. package/src/data/skinToneVariations.ts +19 -0
  88. package/src/dataUtils/emojiSelectors.ts +24 -4
  89. package/src/dataUtils/{recentlyUsed.ts → suggested.ts} +20 -18
  90. package/src/hooks/preloadEmoji.ts +13 -2
  91. package/src/hooks/useActiveCategoryScrollDetection.ts +10 -8
  92. package/src/hooks/useCloseAllOpenToggles.ts +15 -4
  93. package/src/hooks/useDebouncedState.ts +10 -7
  94. package/src/hooks/useDisallowMouseMove.ts +47 -0
  95. package/src/hooks/useEmojiPreviewEvents.ts +103 -0
  96. package/src/hooks/useFilter.ts +46 -47
  97. package/src/hooks/useFocus.ts +40 -0
  98. package/src/hooks/useInitialLoad.ts +1 -0
  99. package/src/hooks/useIsEmojiHidden.ts +16 -3
  100. package/src/hooks/useKeyboardNavigation.ts +438 -0
  101. package/src/hooks/useMouseDownHandlers.ts +97 -89
  102. package/src/hooks/useOnFocus.ts +46 -0
  103. package/src/hooks/useOnScroll.ts +2 -2
  104. package/src/hooks/useScrollCategoryIntoView.ts +28 -0
  105. package/src/hooks/useSetVariationPicker.ts +28 -0
  106. package/src/index.tsx +9 -17
  107. package/src/types/exposedTypes.ts +6 -1
  108. package/dist/DomUtils/emojiElementRef.d.ts +0 -10
  109. package/dist/DomUtils/scrollCategoryIntoView.d.ts +0 -1
  110. package/dist/dataUtils/recentlyUsed.d.ts +0 -11
  111. package/dist/hooks/useEmojiElements.d.ts +0 -1
  112. package/dist/hooks/useEmojiMouseEnter.d.ts +0 -3
  113. package/src/DomUtils/emojiElementRef.ts +0 -42
  114. package/src/DomUtils/scrollCategoryIntoView.ts +0 -18
  115. package/src/components/body/RecentlyUsed.tsx +0 -50
  116. package/src/hooks/useEmojiElements.ts +0 -7
  117. package/src/hooks/useEmojiMouseEnter.ts +0 -59
@@ -1,93 +1,252 @@
1
1
  import { DataEmoji } from '../dataUtils/DataTypes';
2
- import { emojiByUnified } from '../dataUtils/emojiSelectors';
2
+ import {
3
+ emojiByUnified,
4
+ unifiedWithoutSkinTone
5
+ } from '../dataUtils/emojiSelectors';
3
6
 
4
- export function buttonFromEmoji(
5
- emojiElement: HTMLElement | null
7
+ import { asSelectors, ClassNames } from './classNames';
8
+ import { firstVisibleElementInContainer } from './elementPositionInRow';
9
+
10
+ export type NullableElement = HTMLElement | null;
11
+
12
+ export const EmojiButtonSelector = `button${asSelectors(ClassNames.emoji)}`;
13
+ export const VisibleEmojiSelector = [
14
+ EmojiButtonSelector,
15
+ asSelectors(ClassNames.visible),
16
+ `:not(${asSelectors(ClassNames.hidden)})`
17
+ ].join('');
18
+
19
+ export function buttonFromTarget(
20
+ emojiElement: NullableElement
6
21
  ): HTMLButtonElement | null {
7
- return emojiElement?.closest('button.epr-emoji') ?? null;
22
+ return emojiElement?.closest(EmojiButtonSelector) ?? null;
8
23
  }
9
24
 
10
25
  export function emojiFromElement(
11
- element: HTMLElement | null
12
- ): DataEmoji | undefined {
13
- const unified = originalUnifiedFromEmojiElement(element);
26
+ element: NullableElement
27
+ ): [DataEmoji, string] | [] {
28
+ const originalUnified = originalUnifiedFromEmojiElement(element);
29
+ const unified = unifiedFromEmojiElement(element);
30
+
31
+ if (!originalUnified) {
32
+ return [];
33
+ }
14
34
 
15
- if (!unified) {
16
- return;
35
+ const emoji = emojiByUnified(originalUnified);
36
+
37
+ if (!emoji) {
38
+ return [];
17
39
  }
18
- return emojiByUnified(unified);
40
+
41
+ return [emoji, unified as string];
19
42
  }
20
43
 
21
- export function isEmojiElement(element: HTMLElement | null): boolean {
44
+ export function isEmojiElement(element: NullableElement): boolean {
22
45
  return Boolean(
23
- element?.matches('button.epr-emoji') ||
24
- element?.parentElement?.matches('button.epr-emoji')
46
+ element?.matches(EmojiButtonSelector) ||
47
+ element?.parentElement?.matches(EmojiButtonSelector)
25
48
  );
26
49
  }
27
50
 
28
- export function closestCategory(
29
- element: HTMLElement | null
30
- ): HTMLElement | null {
31
- return element?.closest('.epr-emoji-category') ?? null;
32
- }
33
-
34
51
  export function categoryLabelFromCategory(
35
- category: HTMLElement | null
36
- ): HTMLElement | null {
37
- return category?.querySelector('.epr-emoji-category-label') ?? null;
52
+ category: NullableElement
53
+ ): NullableElement {
54
+ return category?.querySelector(asSelectors(ClassNames.label)) ?? null;
38
55
  }
39
56
 
40
57
  export function closestCategoryLabel(
41
- element: null | HTMLElement
42
- ): HTMLElement | null {
58
+ element: NullableElement
59
+ ): NullableElement {
43
60
  const category = closestCategory(element);
44
61
  return categoryLabelFromCategory(category);
45
62
  }
46
63
 
47
- export function elementHeight(element: HTMLElement | null): number {
64
+ export function elementHeight(element: NullableElement): number {
48
65
  return element?.clientHeight ?? 0;
49
66
  }
50
67
 
51
- export function emojiTrueOffsetTop(element: HTMLElement | null): number {
52
- const button = buttonFromEmoji(element);
53
- const category = closestCategory(button);
68
+ export function emojiTrueOffsetTop(element: NullableElement): number {
69
+ if (!element) {
70
+ return 0;
71
+ }
72
+
73
+ const button = buttonFromTarget(element);
74
+ const categoryWithoutLabel = closestCategoryContent(button);
75
+ const category = closestCategory(categoryWithoutLabel);
76
+
77
+ // compensate for the label height
78
+ const labelHeight =
79
+ (category?.clientHeight ?? 0) - (categoryWithoutLabel?.clientHeight ?? 0);
54
80
 
55
- return elementOffsetTop(button) + elementOffsetTop(category);
81
+ return elementOffsetTop(button) + elementOffsetTop(category) + labelHeight;
56
82
  }
57
- export function emojiTruOffsetLeft(element: HTMLElement | null): number {
58
- const button = buttonFromEmoji(element);
83
+ export function emojiTruOffsetLeft(element: NullableElement): number {
84
+ const button = buttonFromTarget(element);
59
85
  const category = closestCategory(button);
60
86
 
61
87
  return elementOffsetLeft(button) + elementOffsetLeft(category);
62
88
  }
63
89
 
64
- function elementOffsetTop(element: HTMLElement | null): number {
90
+ function elementOffsetTop(element: NullableElement): number {
65
91
  return element?.offsetTop ?? 0;
66
92
  }
67
93
 
68
- function elementOffsetLeft(element: HTMLElement | null): number {
94
+ function elementOffsetLeft(element: NullableElement): number {
69
95
  return element?.offsetLeft ?? 0;
70
96
  }
71
97
 
72
- export function unifiedFromEmojiElement(
73
- emoji: HTMLElement | null
74
- ): string | null {
75
- return elementDataSetKey(buttonFromEmoji(emoji), 'unified') ?? null;
98
+ export function unifiedFromEmojiElement(emoji: NullableElement): string | null {
99
+ return elementDataSetKey(buttonFromTarget(emoji), 'unified') ?? null;
76
100
  }
77
101
 
78
102
  export function originalUnifiedFromEmojiElement(
79
- emoji: HTMLElement | null
103
+ emoji: NullableElement
80
104
  ): string | null {
81
- return elementDataSetKey(buttonFromEmoji(emoji), 'original') ?? null;
105
+ const unified = unifiedFromEmojiElement(emoji);
106
+
107
+ if (unified) {
108
+ return unifiedWithoutSkinTone(unified);
109
+ }
110
+ return null;
82
111
  }
83
112
 
84
113
  function elementDataSetKey(
85
- element: HTMLElement | null,
114
+ element: NullableElement,
86
115
  key: string
87
116
  ): string | null {
88
117
  return elementDataSet(element)[key] ?? null;
89
118
  }
90
119
 
91
- function elementDataSet(element: HTMLElement | null): DOMStringMap {
120
+ function elementDataSet(element: NullableElement): DOMStringMap {
92
121
  return element?.dataset ?? {};
93
122
  }
123
+
124
+ export function isVisibleEmoji(element: HTMLElement) {
125
+ return element.classList.contains(ClassNames.visible);
126
+ }
127
+
128
+ export function isHidden(element: NullableElement) {
129
+ if (!element) return true;
130
+
131
+ return element.classList.contains(ClassNames.hidden);
132
+ }
133
+
134
+ export function allVisibleEmojis(parent: NullableElement) {
135
+ if (!parent) {
136
+ return [];
137
+ }
138
+
139
+ return Array.from(
140
+ parent.querySelectorAll(VisibleEmojiSelector)
141
+ ) as HTMLElement[];
142
+ }
143
+
144
+ export function lastVisibleEmoji(element: NullableElement): NullableElement {
145
+ if (!element) return null;
146
+
147
+ const allEmojis = allVisibleEmojis(element);
148
+ const [last] = allEmojis.slice(-1);
149
+ if (!last) {
150
+ return null;
151
+ }
152
+
153
+ if (!isVisibleEmoji(last)) {
154
+ return prevVisibleEmoji(last);
155
+ }
156
+
157
+ return last;
158
+ }
159
+
160
+ export function nextVisibleEmoji(element: HTMLElement): NullableElement {
161
+ const next = element.nextElementSibling as HTMLElement;
162
+
163
+ if (!next) {
164
+ return firstVisibleEmoji(nextCategory(element));
165
+ }
166
+
167
+ if (!isVisibleEmoji(next)) {
168
+ return nextVisibleEmoji(next);
169
+ }
170
+
171
+ return next;
172
+ }
173
+
174
+ export function prevVisibleEmoji(element: HTMLElement): NullableElement {
175
+ const prev = element.previousElementSibling as HTMLElement;
176
+
177
+ if (!prev) {
178
+ return lastVisibleEmoji(prevCategory(element));
179
+ }
180
+
181
+ if (!isVisibleEmoji(prev)) {
182
+ return prevVisibleEmoji(prev);
183
+ }
184
+
185
+ return prev;
186
+ }
187
+
188
+ export function firstVisibleEmoji(parent: NullableElement) {
189
+ if (!parent) {
190
+ return null;
191
+ }
192
+
193
+ const allEmojis = allVisibleEmojis(parent);
194
+
195
+ return firstVisibleElementInContainer(parent, allEmojis);
196
+ }
197
+
198
+ export function prevCategory(element: NullableElement): NullableElement {
199
+ const category = closestCategory(element);
200
+
201
+ if (!category) {
202
+ return null;
203
+ }
204
+
205
+ const prev = category.previousElementSibling as HTMLElement;
206
+
207
+ if (!prev) {
208
+ return null;
209
+ }
210
+
211
+ if (isHidden(prev)) {
212
+ return prevCategory(prev);
213
+ }
214
+
215
+ return prev;
216
+ }
217
+
218
+ export function nextCategory(element: NullableElement): NullableElement {
219
+ const category = closestCategory(element);
220
+
221
+ if (!category) {
222
+ return null;
223
+ }
224
+
225
+ const next = category.nextElementSibling as HTMLElement;
226
+
227
+ if (!next) {
228
+ return null;
229
+ }
230
+
231
+ if (isHidden(next)) {
232
+ return nextCategory(next);
233
+ }
234
+
235
+ return next;
236
+ }
237
+
238
+ export function closestCategory(element: NullableElement) {
239
+ if (!element) {
240
+ return null;
241
+ }
242
+ return element.closest(asSelectors(ClassNames.category)) as HTMLElement;
243
+ }
244
+
245
+ export function closestCategoryContent(element: NullableElement) {
246
+ if (!element) {
247
+ return null;
248
+ }
249
+ return element.closest(
250
+ asSelectors(ClassNames.categoryContent)
251
+ ) as HTMLElement;
252
+ }
@@ -1,68 +1,92 @@
1
1
  .EmojiPickerReact {
2
- --highlight-color: #007aeb;
3
- --hover-backdrop-color: #f1f8ff;
4
- --text-color-base: #858585;
5
- --epr-header-height: 70px;
6
- --epr-picker-width: 350px;
7
- --epr-picker-height: 500px;
8
- --epr-emoji-size: 33px;
9
- --epr-category-navigation-button-size: 30px;
10
- --epr-emoji-variation-picker-bg-color: var(--epr-background-color);
11
- --epr-emoji-variation-picker-height: 55px;
12
- --epr-preview-height: 70px;
13
- --epr-preview-text-color: var(--text-color-base);
14
- --epr-preview-text-size: 14px;
15
- --epr-preview-text-padding: 0 10px;
16
- --epr-search-bar-bg-color: #f6f6f6;
17
- --epr-active-category-indicator-color: var(--highlight-color);
18
- --epr-active-skin-tone-indicator-border-color: var(--highlight-color);
19
- --epr-search-border-color: var(--highlight-color);
20
- --epr-active-skin-hover-color: var(--hover-backdrop-color);
21
- --epr-emoji-hover-color: var(--hover-backdrop-color);
22
- --epr-category-label-text-color: var(--text-color-base);
23
- --epr-category-label-background-color: #ffffffe6;
24
- --epr-emoji-padding: 5px;
25
- --epr-header-padding: 15px 10px;
26
- --epr-emoji-gap: 4px;
27
- --epr-category-padding: 0 10px;
2
+ --epr-highlight-color: #007aeb;
3
+ --epr-hover-bg-color: #f1f8ff;
4
+ --epr-focus-bg-color: #e0f0ff;
5
+ --epr-text-color: #858585;
6
+ --epr-search-input-bg-color: #f6f6f6;
7
+ --epr-picker-border-color: #e7e7e7;
8
+ --epr-bg-color: #fff;
9
+ --epr-category-icon-active-color: #6aa8de;
10
+
11
+ --epr-horizontal-padding: 10px;
12
+
13
+ --epr-picker-border-radius: 8px;
14
+
15
+ /* Header */
16
+ --epr-search-border-color: var(--epr-highlight-color);
17
+ --epr-header-padding: 15px var(--epr-horizontal-padding);
18
+
19
+ /* Skin Tone Picker */
20
+ --epr-active-skin-tone-indicator-border-color: var(--epr-highlight-color);
21
+ --epr-active-skin-hover-color: var(--epr-hover-bg-color);
22
+
23
+ /* Search */
24
+ --epr-search-input-bg-color-active: var(--epr-search-input-bg-color);
28
25
  --epr-search-input-padding: 0 30px;
29
26
  --epr-search-input-border-radius: 8px;
30
27
  --epr-search-input-height: 40px;
31
- --epr-category-label-padding: 0 10px;
28
+ --epr-search-input-text-color: var(--epr-text-color);
29
+ --epr-search-input-placeholder-color: var(--epr-text-color);
30
+ --epr-search-bar-inner-padding: var(--epr-horizontal-padding);
31
+
32
+ /* Category Navigation */
33
+ --epr-category-navigation-button-size: 30px;
34
+
35
+ /* Variation Picker */
36
+ --epr-emoji-variation-picker-height: 45px;
37
+ --epr-emoji-variation-picker-bg-color: var(--epr-bg-color);
38
+
39
+ /* Preview */
40
+ --epr-preview-height: 70px;
41
+ --epr-preview-text-size: 14px;
42
+ --epr-preview-text-padding: 0 var(--epr-horizontal-padding);
43
+ --epr-preview-border-color: var(--epr-picker-border-color);
44
+ --epr-preview-text-color: var(--epr-text-color);
45
+
46
+ /* Category */
47
+ --epr-category-padding: 0 var(--epr-horizontal-padding);
48
+
49
+ /* Category Label */
50
+ --epr-category-label-bg-color: #ffffffe6;
51
+ --epr-category-label-text-color: var(--epr-text-color);
52
+ --epr-category-label-padding: 0 var(--epr-horizontal-padding);
32
53
  --epr-category-label-height: 40px;
33
54
 
55
+ /* Emoji */
56
+ --epr-emoji-size: 30px;
57
+ --epr-emoji-padding: 5px;
58
+ --epr-emoji-fullsize: calc(
59
+ var(--epr-emoji-size) + var(--epr-emoji-padding) * 2
60
+ );
61
+ --epr-emoji-hover-color: var(--epr-hover-bg-color);
62
+ --epr-emoji-variation-indicator-color: var(--epr-picker-border-color);
63
+ --epr-emoji-variation-indicator-color-hover: var(--epr-text-color);
64
+
65
+ /* Z-Index */
34
66
  --epr-header-overlay-z-index: 3;
35
67
  --epr-emoji-variations-indictator-z-index: 1;
36
68
  --epr-category-label-z-index: 2;
37
69
  --epr-skin-variation-picker-z-index: 5;
38
-
39
- --epr-picker-border-radius: 8px;
40
- --epr-search-bar-text-color: var(--text-color-base);
41
- --epr-search-bar-placeholder-color: var(--text-color-base);
42
- --epr-picker-border-color: #e7e7e7;
43
- --epr-background-color: #fff;
44
- --epr-preview-border-color: var(--epr-picker-border-color);
45
- --epr-search-bar-bg-color-active: var(--epr-search-bar-bg-color);
46
- --epr-emoji-variation-indicator-color: rgba(0, 0, 0, 0.1);
47
- --epr-emoji-variation-indicator-color-hover: rgba(0, 0, 0, 0.4);
48
70
  }
49
71
 
50
72
  .EmojiPickerReact.epr-dark-theme {
51
- --epr-emoji-variation-picker-bg-color: #000000;
52
- --highlight-color: #c0c0c0;
53
- --text-color-base: var(--highlight-color);
54
- --hover-backdrop-color: #363636f6;
55
- --epr-search-bar-bg-color: #333333;
56
- --epr-category-label-background-color: #222222e6;
73
+ --epr-dark: #000;
74
+ --epr-emoji-variation-picker-bg-color: var(--epr-dark);
75
+ --epr-highlight-color: #c0c0c0;
76
+ --epr-text-color: var(--epr-highlight-color);
77
+ --epr-hover-bg-color: #363636f6;
78
+ --epr-focus-bg-color: #474747;
79
+ --epr-search-input-bg-color: #333333;
80
+ --epr-category-label-bg-color: #222222e6;
57
81
  --epr-picker-border-color: #151617;
58
- --epr-background-color: #222222;
59
- --epr-search-bar-bg-color-active: #000000;
60
- --epr-emoji-variation-indicator-color: rgba(255, 255, 255, 0.1);
61
- --epr-emoji-variation-indicator-color-hover: rgba(255, 255, 255, 0.4);
82
+ --epr-bg-color: #222222;
83
+ --epr-search-input-bg-color-active: var(--epr-dark);
84
+ --epr-emoji-variation-indicator-color: #444;
85
+ --epr-category-icon-active-color: #3271b7;
62
86
  }
63
87
 
64
88
  .EmojiPickerReact {
65
- background-color: var(--epr-background-color);
89
+ background-color: var(--epr-bg-color);
66
90
  overflow: hidden;
67
91
  }
68
92
 
@@ -80,6 +104,32 @@
80
104
  visibility: hidden !important;
81
105
  }
82
106
 
107
+ .EmojiPickerReact:not(.epr-search-active) .epr-visible-on-search-only {
108
+ display: none !important;
109
+ opacity: 0 !important;
110
+ pointer-events: none !important;
111
+ visibility: hidden !important;
112
+ }
113
+
114
+ /* This is the same as .search-active, only without any js behind it
115
+ Should switch to it full time when :has() gets better adoption
116
+ */
117
+ aside.EmojiPickerReact.epr-main:has(input:not(:placeholder-shown))
118
+ .epr-hidden-on-search {
119
+ display: none !important;
120
+ opacity: 0 !important;
121
+ pointer-events: none !important;
122
+ visibility: hidden !important;
123
+ }
124
+
125
+ aside.EmojiPickerReact.epr-main:has(input:placeholder-shown)
126
+ .epr-visible-on-search-only {
127
+ display: none !important;
128
+ opacity: 0 !important;
129
+ pointer-events: none !important;
130
+ visibility: hidden !important;
131
+ }
132
+
83
133
  .EmojiPickerReact * {
84
134
  box-sizing: border-box;
85
135
  font-family: sans-serif;
@@ -89,4 +139,5 @@
89
139
  cursor: pointer;
90
140
  border: 0;
91
141
  background: none;
142
+ outline: none;
92
143
  }
@@ -0,0 +1,26 @@
1
+ import * as React from 'react';
2
+
3
+ import { Body } from './components/body/Body';
4
+ import { ElementRefContextProvider } from './components/context/ElementRefContext';
5
+ import { PickerConfigProvider } from './components/context/PickerConfigContext';
6
+ import { Preview } from './components/footer/Preview';
7
+ import { Header } from './components/header/Header';
8
+ import PickerMain from './components/main/PickerMain';
9
+ import { PickerConfig } from './config/config';
10
+ import './EmojiPickerReact.css';
11
+
12
+ export interface Props extends PickerConfig {}
13
+
14
+ export default function EmojiPicker(props: Props) {
15
+ return (
16
+ <ElementRefContextProvider>
17
+ <PickerConfigProvider {...props}>
18
+ <PickerMain>
19
+ <Header />
20
+ <Body />
21
+ <Preview />
22
+ </PickerMain>
23
+ </PickerConfigProvider>
24
+ </ElementRefContextProvider>
25
+ );
26
+ }
@@ -0,0 +1,28 @@
1
+ import * as React from 'react';
2
+
3
+ export default class ErrorBoundary extends React.Component<
4
+ { children: React.ReactNode },
5
+ { hasError: boolean }
6
+ > {
7
+ constructor(props: { children: React.ReactNode }) {
8
+ super(props);
9
+ this.state = { hasError: false };
10
+ }
11
+
12
+ static getDerivedStateFromError() {
13
+ return { hasError: true };
14
+ }
15
+
16
+ componentDidCatch(error: Error, errorInfo: any) {
17
+ // eslint-disable-next-line no-console
18
+ console.error('Emoji Picker React failed to render:', error, errorInfo);
19
+ }
20
+
21
+ render() {
22
+ if (this.state.hasError) {
23
+ return null;
24
+ }
25
+
26
+ return this.props.children;
27
+ }
28
+ }
@@ -1,19 +1,20 @@
1
1
  import * as React from 'react';
2
2
 
3
- import { useActiveCategoryScrollDetection } from '../../hooks/useActiveCategoryScrollDetection';
3
+ import { useOnMouseMove } from '../../hooks/useDisallowMouseMove';
4
4
  import { useMouseDownHandlers } from '../../hooks/useMouseDownHandlers';
5
5
  import { useOnScroll } from '../../hooks/useOnScroll';
6
6
  import { useBodyRef } from '../context/ElementRefContext';
7
7
 
8
8
  import { EmojiList } from './EmojiList';
9
9
  import { EmojiVariationPicker } from './EmojiVariationPicker';
10
+
10
11
  import './Body.css';
11
12
 
12
13
  export function Body() {
13
14
  const BodyRef = useBodyRef();
14
- useActiveCategoryScrollDetection(BodyRef);
15
15
  useOnScroll(BodyRef);
16
16
  useMouseDownHandlers(BodyRef);
17
+ useOnMouseMove();
17
18
 
18
19
  return (
19
20
  <div className="epr-body" ref={BodyRef}>
@@ -1,3 +1,16 @@
1
+ .EmojiPickerReact li.epr-emoji-category > .epr-emoji-category-content {
2
+ position: relative;
3
+ margin: var(--epr-category-padding);
4
+ display: grid;
5
+ grid-template-columns: repeat(auto-fill, var(--epr-emoji-fullsize));
6
+ grid-gap: 0;
7
+ justify-content: space-between;
8
+ }
9
+
10
+ .EmojiPickerReact li.epr-emoji-category:not(:has(.epr-visible)) {
11
+ display: none;
12
+ }
13
+
1
14
  .EmojiPickerReact li.epr-emoji-category > .epr-emoji-category-label {
2
15
  position: sticky;
3
16
  top: 0;
@@ -8,35 +21,8 @@
8
21
  text-transform: capitalize;
9
22
  backdrop-filter: blur(3px);
10
23
  padding: var(--epr-category-label-padding);
11
- background-color: var(--epr-category-label-background-color);
24
+ background-color: var(--epr-category-label-bg-color);
12
25
  color: var(--epr-category-label-text-color);
13
26
  z-index: var(--epr-category-label-z-index);
14
27
  height: var(--epr-category-label-height);
15
28
  }
16
-
17
- .EmojiPickerReact.epr-search-active
18
- li.epr-emoji-category
19
- > .epr-emoji-category-label {
20
- position: relative;
21
- opacity: 0.4;
22
- }
23
-
24
- .EmojiPickerReact li.epr-emoji-category {
25
- position: relative;
26
- display: flex;
27
- flex-direction: row;
28
- flex-wrap: wrap;
29
- justify-content: space-around;
30
- padding: var(--epr-category-padding);
31
- gap: var(--epr-emoji-gap);
32
- }
33
-
34
- .EmojiPickerReact li.epr-emoji-category:after {
35
- content: '';
36
- display: inline-block;
37
- flex: 1;
38
- }
39
-
40
- .EmojiPickerReact li.epr-emoji-category:not(:has(.epr-visible)) {
41
- display: none;
42
- }
@@ -1,12 +1,12 @@
1
1
  import clsx from 'clsx';
2
2
  import * as React from 'react';
3
3
 
4
+ import { ClassNames } from '../../DomUtils/classNames';
4
5
  import {
5
6
  CategoryConfig,
6
7
  categoryFromCategoryConfig,
7
8
  categoryNameFromCategoryConfig
8
9
  } from '../../config/categoryConfig';
9
- import { ClassNames } from '../../DomUtils/classNames';
10
10
  import './EmojiCategory.css';
11
11
 
12
12
  type Props = Readonly<{
@@ -27,14 +27,15 @@ export function EmojiCategory({
27
27
 
28
28
  return (
29
29
  <li
30
- className={clsx('epr-emoji-category', {
30
+ className={clsx(ClassNames.category, {
31
31
  [ClassNames.hidden]: hidden,
32
32
  [ClassNames.hiddenOnSearch]: hiddenOnSearch
33
33
  })}
34
34
  data-name={category}
35
+ aria-label={categoryName}
35
36
  >
36
- <div className="epr-emoji-category-label">{categoryName}</div>
37
- {children}
37
+ <div className={ClassNames.label}>{categoryName}</div>
38
+ <div className={ClassNames.categoryContent}>{children}</div>
38
39
  </li>
39
40
  );
40
41
  }