emoji-picker-react 3.6.1 → 4.0.0-rc.3

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 (135) hide show
  1. package/LICENSE +17 -20
  2. package/README.md +129 -168
  3. package/dist/DomUtils/categoryNameFromDom.d.ts +1 -0
  4. package/dist/DomUtils/scrollCategoryIntoView.d.ts +1 -0
  5. package/dist/DomUtils/scrollTo.d.ts +1 -0
  6. package/dist/DomUtils/selectors.d.ts +12 -0
  7. package/dist/components/Layout/Flex.d.ts +14 -0
  8. package/dist/components/Layout/Relative.d.ts +7 -0
  9. package/dist/components/body/Body.d.ts +3 -0
  10. package/dist/components/body/EmojiCategory.d.ts +10 -0
  11. package/dist/components/body/EmojiList.d.ts +3 -0
  12. package/dist/components/body/EmojiVariationPicker.d.ts +3 -0
  13. package/dist/components/body/RecentlyUsed.d.ts +7 -0
  14. package/dist/components/context/ElementRefContext.d.ts +17 -0
  15. package/dist/components/context/PickerConfigContext.d.ts +8 -0
  16. package/dist/components/context/PickerContext.d.ts +21 -0
  17. package/dist/components/emoji/Emoji.d.ts +14 -0
  18. package/dist/components/footer/Preview.d.ts +7 -0
  19. package/dist/components/header/Header.d.ts +3 -0
  20. package/dist/components/header/Search.d.ts +3 -0
  21. package/dist/components/header/SkinTonePicker.d.ts +3 -0
  22. package/dist/components/main/PickerMain.d.ts +7 -0
  23. package/dist/components/navigation/CategoryNavigation.d.ts +3 -0
  24. package/dist/config/categoryConfig.d.ts +21 -0
  25. package/dist/config/cdnUrls.d.ts +2 -0
  26. package/dist/config/config.d.ts +26 -0
  27. package/dist/config/useConfig.d.ts +11 -0
  28. package/dist/data/emojis.d.ts +40 -0
  29. package/dist/data/groups.d.ts +2 -0
  30. package/dist/data/skinToneVariations.d.ts +3 -0
  31. package/dist/dataUtils/DataTypes.d.ts +8 -0
  32. package/dist/dataUtils/alphaNumericEmojiIndex.d.ts +4 -0
  33. package/dist/dataUtils/asEmoji.d.ts +2 -0
  34. package/dist/dataUtils/emojiSelectors.d.ts +15 -0
  35. package/dist/dataUtils/parseNativeEmoji.d.ts +1 -0
  36. package/dist/dataUtils/recentlyUsed.d.ts +11 -0
  37. package/dist/emoji-picker-react.cjs.development.css +13 -0
  38. package/dist/emoji-picker-react.cjs.development.js +7419 -0
  39. package/dist/emoji-picker-react.cjs.development.js.map +1 -0
  40. package/dist/emoji-picker-react.cjs.production.min.js +2 -0
  41. package/dist/emoji-picker-react.cjs.production.min.js.map +1 -0
  42. package/dist/emoji-picker-react.esm.js +7485 -0
  43. package/dist/emoji-picker-react.esm.js.map +1 -0
  44. package/dist/hooks/preloadEmoji.d.ts +4 -0
  45. package/dist/hooks/useActiveCategoryScrollDetection.d.ts +2 -0
  46. package/dist/hooks/useCloseAllOpenToggles.d.ts +4 -0
  47. package/dist/hooks/useEmojiElements.d.ts +1 -0
  48. package/dist/hooks/useEmojiMouseEnter.d.ts +3 -0
  49. package/dist/hooks/useFilter.d.ts +8 -0
  50. package/dist/hooks/useInitialLoad.d.ts +2 -0
  51. package/dist/hooks/useIsEmojiHidden.d.ts +2 -0
  52. package/dist/hooks/useIsSearchMode.d.ts +1 -0
  53. package/dist/hooks/useMouseDownHandlers.d.ts +2 -0
  54. package/dist/hooks/useOnScroll.d.ts +2 -0
  55. package/dist/index.d.ts +7 -0
  56. package/dist/index.js +8 -3
  57. package/dist/types/exposedTypes.d.ts +28 -0
  58. package/package.json +72 -52
  59. package/src/DomUtils/categoryNameFromDom.ts +3 -0
  60. package/src/DomUtils/scrollCategoryIntoView.ts +18 -0
  61. package/src/DomUtils/scrollTo.ts +11 -0
  62. package/src/DomUtils/selectors.ts +93 -0
  63. package/src/EmojiPickerReact.css +85 -0
  64. package/src/components/Layout/Flex.css +11 -0
  65. package/src/components/Layout/Flex.tsx +28 -0
  66. package/src/components/Layout/Relative.tsx +14 -0
  67. package/src/components/body/Body.css +6 -0
  68. package/src/components/body/Body.tsx +24 -0
  69. package/src/components/body/EmojiCategory.css +38 -0
  70. package/src/components/body/EmojiCategory.tsx +27 -0
  71. package/src/components/body/EmojiList.css +5 -0
  72. package/src/components/body/EmojiList.tsx +101 -0
  73. package/src/components/body/EmojiVariationPicker.css +65 -0
  74. package/src/components/body/EmojiVariationPicker.tsx +154 -0
  75. package/src/components/body/RecentlyUsed.tsx +50 -0
  76. package/src/components/body/svg/triangle.svg +11 -0
  77. package/src/components/context/ElementRefContext.tsx +61 -0
  78. package/src/components/context/PickerConfigContext.tsx +29 -0
  79. package/src/components/context/PickerContext.tsx +139 -0
  80. package/src/components/emoji/Emoji.css +44 -0
  81. package/src/components/emoji/Emoji.tsx +104 -0
  82. package/src/components/footer/Preview.css +14 -0
  83. package/src/components/footer/Preview.tsx +80 -0
  84. package/src/components/header/Header.css +4 -0
  85. package/src/components/header/Header.tsx +21 -0
  86. package/src/components/header/Search.css +72 -0
  87. package/src/components/header/Search.tsx +36 -0
  88. package/src/components/header/SkinTonePicker.css +86 -0
  89. package/src/components/header/SkinTonePicker.tsx +55 -0
  90. package/src/components/header/svg/magnifier.svg +13 -0
  91. package/src/components/header/svg/times.svg +33 -0
  92. package/src/components/main/PickerMain.css +11 -0
  93. package/src/components/main/PickerMain.tsx +42 -0
  94. package/src/components/navigation/CategoryNavigation.css +82 -0
  95. package/src/components/navigation/CategoryNavigation.tsx +32 -0
  96. package/src/components/navigation/svg/CategoryNav.svg +508 -0
  97. package/src/config/categoryConfig.ts +107 -0
  98. package/src/config/cdnUrls.ts +24 -0
  99. package/src/config/config.ts +50 -0
  100. package/src/config/useConfig.ts +52 -0
  101. package/src/data/emojis.ts +5350 -0
  102. package/src/data/groups.ts +12 -0
  103. package/src/data/skinToneVariations.ts +12 -0
  104. package/src/dataUtils/DataTypes.ts +10 -0
  105. package/src/dataUtils/alphaNumericEmojiIndex.ts +23 -0
  106. package/src/dataUtils/asEmoji.ts +5 -0
  107. package/src/dataUtils/emojiSelectors.ts +94 -0
  108. package/src/dataUtils/parseNativeEmoji.ts +6 -0
  109. package/src/dataUtils/recentlyUsed.ts +61 -0
  110. package/src/hooks/preloadEmoji.ts +29 -0
  111. package/src/hooks/useActiveCategoryScrollDetection.ts +46 -0
  112. package/src/hooks/useCloseAllOpenToggles.ts +26 -0
  113. package/src/hooks/useEmojiElements.ts +7 -0
  114. package/src/hooks/useEmojiMouseEnter.ts +59 -0
  115. package/src/hooks/useFilter.ts +142 -0
  116. package/src/hooks/useInitialLoad.ts +9 -0
  117. package/src/hooks/useIsEmojiHidden.ts +16 -0
  118. package/src/hooks/useIsSearchMode.ts +7 -0
  119. package/src/hooks/useMouseDownHandlers.ts +160 -0
  120. package/src/hooks/useOnScroll.ts +28 -0
  121. package/src/index.tsx +27 -0
  122. package/src/types/exposedTypes.ts +31 -0
  123. package/.babelrc +0 -30
  124. package/.eslintrc.js +0 -37
  125. package/.github/workflows/npmpublish.yml +0 -30
  126. package/.prettierignore +0 -2
  127. package/.storybook/addons.js +0 -2
  128. package/.storybook/config.js +0 -9
  129. package/assets/captured.gif +0 -0
  130. package/dist/index.js.LICENSE.txt +0 -5
  131. package/dist/index.js.map +0 -1
  132. package/dist/main.css +0 -206
  133. package/dist/main.css.map +0 -1
  134. package/emoji-picker-react.d.ts +0 -45
  135. package/webpack.config.js +0 -52
@@ -0,0 +1,65 @@
1
+ .EmojiPickerReact .epr-emoji-variation-picker {
2
+ position: absolute;
3
+ top: 0;
4
+ right: 5px;
5
+ left: 5px;
6
+ padding: 5px;
7
+ height: 0;
8
+ box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.2);
9
+ border-radius: 3px;
10
+ display: flex;
11
+ align-items: center;
12
+ justify-content: space-around;
13
+ opacity: 0;
14
+ visibility: hidden;
15
+ pointer-events: none;
16
+ top: -100%;
17
+ border: 1px solid var(--epr-picker-border-color);
18
+ height: var(--epr-emoji-variation-picker-height);
19
+ z-index: var(--epr-skin-variation-picker-z-index);
20
+ background: var(--epr-emoji-variation-picker-bg-color);
21
+ }
22
+
23
+ .EmojiPickerReact .epr-emoji-variation-picker.visible {
24
+ opacity: 1;
25
+ visibility: visible;
26
+ pointer-events: all;
27
+ }
28
+ .EmojiPickerReact
29
+ .epr-emoji-variation-picker
30
+ .EmojiPickerReact
31
+ .epr-emoji-variation-picker
32
+ button.epr-emoji:hover {
33
+ background: none;
34
+ }
35
+
36
+ .EmojiPickerReact .epr-emoji-variation-picker .epr-emoji-pointer {
37
+ content: '';
38
+ position: absolute;
39
+ top: -10px;
40
+ width: 25px;
41
+ height: 15px;
42
+ }
43
+
44
+ .EmojiPickerReact .epr-emoji-variation-picker .epr-emoji-pointer {
45
+ background-image: svg-load(./svg/triangle.svg);
46
+ background-repeat: no-repeat;
47
+ background-position: 0 0;
48
+ background-size: 50px 15px;
49
+ }
50
+
51
+ .EmojiPickerReact.epr-dark-theme
52
+ .epr-emoji-variation-picker
53
+ .epr-emoji-pointer {
54
+ background-position: -25px 0;
55
+ }
56
+
57
+ .EmojiPickerReact .epr-emoji-variation-picker .epr-emoji-pointer {
58
+ top: 100%;
59
+ transform: translateX(-7px);
60
+ }
61
+
62
+ .EmojiPickerReact .epr-emoji-variation-picker .epr-emoji-pointer.pointing-up {
63
+ top: 0;
64
+ transform: rotate(180deg) translateY(100%) translateX(7px);
65
+ }
@@ -0,0 +1,154 @@
1
+ import clsx from 'clsx';
2
+ import * as React from 'react';
3
+
4
+ import {
5
+ buttonFromEmoji,
6
+ elementHeight,
7
+ emojiTrueOffsetTop,
8
+ emojiTruOffsetLeft
9
+ } from '../../DomUtils/selectors';
10
+ import { useEmojiStyleConfig } from '../../config/useConfig';
11
+ import { asEmoji } from '../../dataUtils/asEmoji';
12
+ import {
13
+ emojiHasVariations,
14
+ emojiUnified,
15
+ emojiVariations
16
+ } from '../../dataUtils/emojiSelectors';
17
+ import {
18
+ useAnchoredEmojiRef,
19
+ useBodyRef,
20
+ useSetAnchoredEmojiRef
21
+ } from '../context/ElementRefContext';
22
+ import { useEmojiVariationPickerState } from '../context/PickerContext';
23
+ import { Emoji } from '../emoji/Emoji';
24
+ import './EmojiVariationPicker.css';
25
+
26
+ enum Direction {
27
+ Up,
28
+ Down
29
+ }
30
+
31
+ export function EmojiVariationPicker() {
32
+ const VariationPickerRef = React.useRef<HTMLDivElement>(null);
33
+ const [emoji] = useEmojiVariationPickerState();
34
+ const emojiStyle = useEmojiStyleConfig();
35
+ const { getTop, getMenuDirection } = useVariationPickerTop(
36
+ VariationPickerRef
37
+ );
38
+ const setAnchoredEmojiRef = useSetAnchoredEmojiRef();
39
+ const getPointerStyle = usePointerStyle(VariationPickerRef);
40
+
41
+ const visible = emoji && emojiHasVariations(emoji);
42
+ let top, pointerStyle;
43
+
44
+ if (!visible) {
45
+ setAnchoredEmojiRef(null);
46
+ } else {
47
+ top = getTop();
48
+ pointerStyle = getPointerStyle();
49
+ }
50
+
51
+ const safeEmoji = asEmoji(emoji);
52
+
53
+ return (
54
+ <div
55
+ ref={VariationPickerRef}
56
+ className={clsx('epr-emoji-variation-picker', {
57
+ visible
58
+ })}
59
+ style={{ top }}
60
+ >
61
+ {visible
62
+ ? [emojiUnified(safeEmoji)]
63
+ .concat(emojiVariations(safeEmoji))
64
+ .slice(0, 6)
65
+ .map(unified => (
66
+ <Emoji
67
+ key={unified}
68
+ emoji={safeEmoji}
69
+ unified={unified}
70
+ emojiStyle={emojiStyle}
71
+ showVariations={false}
72
+ />
73
+ ))
74
+ : null}
75
+ <div
76
+ className={clsx('epr-emoji-pointer', {
77
+ 'pointing-up': getMenuDirection() === Direction.Down
78
+ })}
79
+ style={pointerStyle}
80
+ />
81
+ </div>
82
+ );
83
+ }
84
+
85
+ function usePointerStyle(VariationPickerRef: React.RefObject<HTMLElement>) {
86
+ const AnchoredEmojiRef = useAnchoredEmojiRef();
87
+ return function getPointerStyle() {
88
+ const style: React.CSSProperties = {};
89
+ if (!VariationPickerRef.current) {
90
+ return style;
91
+ }
92
+
93
+ if (AnchoredEmojiRef.current) {
94
+ const button = buttonFromEmoji(AnchoredEmojiRef.current);
95
+
96
+ const offsetLeft = emojiTruOffsetLeft(button);
97
+
98
+ if (!button) {
99
+ return style;
100
+ }
101
+
102
+ // half of the button
103
+ style.left = offsetLeft + button?.clientWidth / 2 - 12;
104
+ }
105
+
106
+ return style;
107
+ };
108
+ }
109
+
110
+ function useVariationPickerTop(
111
+ VariationPickerRef: React.RefObject<HTMLElement>
112
+ ) {
113
+ const AnchoredEmojiRef = useAnchoredEmojiRef();
114
+ const BodyRef = useBodyRef();
115
+ let direction = Direction.Up;
116
+
117
+ return {
118
+ getMenuDirection,
119
+ getTop
120
+ };
121
+
122
+ function getMenuDirection() {
123
+ return direction;
124
+ }
125
+
126
+ function getTop() {
127
+ direction = Direction.Up;
128
+ let emojiOffsetTop = 0;
129
+
130
+ if (!VariationPickerRef.current) {
131
+ return 0;
132
+ }
133
+
134
+ const height = elementHeight(VariationPickerRef.current);
135
+
136
+ if (AnchoredEmojiRef.current) {
137
+ const bodyRef = BodyRef.current;
138
+ const button = buttonFromEmoji(AnchoredEmojiRef.current);
139
+
140
+ const buttonHeight = elementHeight(button);
141
+
142
+ emojiOffsetTop = emojiTrueOffsetTop(button);
143
+
144
+ const scrollTop = bodyRef?.scrollTop ?? 0;
145
+
146
+ if (scrollTop > emojiOffsetTop - height) {
147
+ direction = Direction.Down;
148
+ emojiOffsetTop += buttonHeight + height;
149
+ }
150
+ }
151
+
152
+ return emojiOffsetTop - height;
153
+ }
154
+ }
@@ -0,0 +1,50 @@
1
+ import * as React from 'react';
2
+
3
+ import { CategoryConfig } from '../../config/categoryConfig';
4
+ import { useEmojiStyleConfig } from '../../config/useConfig';
5
+ import { emojiByUnified } from '../../dataUtils/emojiSelectors';
6
+ import { getRecentlyUsed } from '../../dataUtils/recentlyUsed';
7
+ import { useSearchTermState } from '../context/PickerContext';
8
+ import { Emoji } from '../emoji/Emoji';
9
+
10
+ import { EmojiCategory } from './EmojiCategory';
11
+
12
+ type Props = Readonly<{
13
+ categoryConfig: CategoryConfig;
14
+ }>;
15
+
16
+ export function RecentlyUsed({ categoryConfig }: Props) {
17
+ const [searchTerm] = useSearchTermState();
18
+ const recentlyUsed = getRecentlyUsed();
19
+ const emojiStyle = useEmojiStyleConfig();
20
+
21
+ if (searchTerm) {
22
+ return null;
23
+ }
24
+
25
+ if (recentlyUsed.length === 0) {
26
+ return null;
27
+ }
28
+
29
+ return (
30
+ <EmojiCategory categoryConfig={categoryConfig}>
31
+ {recentlyUsed.map(recentlyUsedItem => {
32
+ const emoji = emojiByUnified(recentlyUsedItem.original);
33
+
34
+ if (!emoji) {
35
+ return null;
36
+ }
37
+
38
+ return (
39
+ <Emoji
40
+ showVariations={false}
41
+ unified={recentlyUsedItem.unified}
42
+ emojiStyle={emojiStyle}
43
+ emoji={emoji}
44
+ key={recentlyUsedItem.unified}
45
+ />
46
+ );
47
+ })}
48
+ </EmojiCategory>
49
+ );
50
+ }
@@ -0,0 +1,11 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Generator: Adobe Illustrator 26.3.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="50px"
4
+ height="15px" viewBox="0 0 50 15" enable-background="new 0 0 50 15" xml:space="preserve">
5
+ <g id="Layer_1">
6
+ </g>
7
+ <g id="Layer_2">
8
+ <path fill="#FFFFFF" stroke="#E8E7E7" stroke-miterlimit="10" d="M1.86-0.43l9.83,11.53c0.59,0.69,1.56,0.69,2.14,0l9.83-11.53"/>
9
+ <path fill="#010202" stroke="#151617" stroke-miterlimit="10" d="M26.86-0.43l9.83,11.53c0.59,0.69,1.56,0.69,2.14,0l9.83-11.53"/>
10
+ </g>
11
+ </svg>
@@ -0,0 +1,61 @@
1
+ import * as React from 'react';
2
+
3
+ export function ElementRefContextProvider({
4
+ children
5
+ }: {
6
+ children: React.ReactNode;
7
+ }) {
8
+ const PickerMainRef = React.useRef<HTMLElement>(null);
9
+ const AnchoredEmojiRef = React.useRef<HTMLElement>(null);
10
+ const BodyRef = React.useRef<HTMLElement>(null);
11
+
12
+ return (
13
+ <ElementRefContext.Provider
14
+ value={{ PickerMainRef, AnchoredEmojiRef, BodyRef }}
15
+ >
16
+ {children}
17
+ </ElementRefContext.Provider>
18
+ );
19
+ }
20
+
21
+ export type ElementRef = React.MutableRefObject<HTMLElement | null>;
22
+
23
+ type ElementRefs = {
24
+ PickerMainRef: ElementRef;
25
+ AnchoredEmojiRef: ElementRef;
26
+ BodyRef: React.MutableRefObject<HTMLElement | null>;
27
+ };
28
+
29
+ const ElementRefContext = React.createContext<ElementRefs>({
30
+ PickerMainRef: React.createRef(),
31
+ AnchoredEmojiRef: React.createRef(),
32
+ BodyRef: React.createRef()
33
+ });
34
+
35
+ type ElementRefKeys = keyof ElementRefs;
36
+
37
+ export function useElementRef(key: ElementRefKeys) {
38
+ const { [key]: elementRef } = React.useContext(ElementRefContext);
39
+ return elementRef;
40
+ }
41
+
42
+ export function usePickerMainRef() {
43
+ return useElementRef('PickerMainRef');
44
+ }
45
+
46
+ export function useAnchoredEmojiRef() {
47
+ return useElementRef('AnchoredEmojiRef');
48
+ }
49
+
50
+ export function useSetAnchoredEmojiRef(): (target: null | HTMLElement) => void {
51
+ const AnchoredEmojiRef = useAnchoredEmojiRef();
52
+ return (target: null | HTMLElement) => {
53
+ AnchoredEmojiRef.current = target;
54
+ };
55
+ }
56
+
57
+ export function useBodyRef(): React.MutableRefObject<HTMLDivElement | null> {
58
+ return useElementRef(
59
+ 'BodyRef'
60
+ ) as React.MutableRefObject<HTMLDivElement | null>;
61
+ }
@@ -0,0 +1,29 @@
1
+ import * as React from 'react';
2
+
3
+ import {
4
+ basePickerConfig,
5
+ mergeConfig,
6
+ PickerConfig,
7
+ PickerConfigInternal
8
+ } from '../../config/config';
9
+
10
+ type Props = PickerConfig &
11
+ Readonly<{
12
+ children: React.ReactNode;
13
+ }>;
14
+
15
+ const ConfigContext = React.createContext<PickerConfigInternal>(
16
+ basePickerConfig()
17
+ );
18
+
19
+ export function PickerConfigProvider({ children, ...config }: Props) {
20
+ return (
21
+ <ConfigContext.Provider value={mergeConfig(config)}>
22
+ {children}
23
+ </ConfigContext.Provider>
24
+ );
25
+ }
26
+
27
+ export function usePickerConfig() {
28
+ return React.useContext(ConfigContext);
29
+ }
@@ -0,0 +1,139 @@
1
+ import * as React from 'react';
2
+ import { useState } from 'react';
3
+ import { useDefaultSkinToneConfig } from '../../config/useConfig';
4
+ import { DataEmoji } from '../../dataUtils/DataTypes';
5
+ import { scrollCategoryIntoView } from '../../DomUtils/scrollCategoryIntoView';
6
+ import { FilterDict } from '../../hooks/useFilter';
7
+ import { useMarkInitialLoad } from '../../hooks/useInitialLoad';
8
+ import { SkinTones } from '../../types/exposedTypes';
9
+ import { usePickerMainRef } from './ElementRefContext';
10
+
11
+ export function PickerContextProvider({ children }: Props) {
12
+ const defaultSkinTone = useDefaultSkinToneConfig();
13
+ const filterRef = React.useRef<FilterState>({});
14
+ const disallowClickRef = React.useRef<boolean>(false);
15
+
16
+ const searchTerm = useState<string>('');
17
+ const skinToneFanOpenState = useState<boolean>(false);
18
+ const activeSkinTone = useState<SkinTones>(defaultSkinTone);
19
+ const activeCategoryState = useState<ActiveCategoryState>(null);
20
+ const emojisThatFailedToLoadState = useState<Set<string>>(new Set());
21
+ const emojiVariationPickerState = useState<DataEmoji | null>(null);
22
+ const [isPastInitialLoad, setIsPastInitialLoad] = useState(false);
23
+
24
+ useMarkInitialLoad(setIsPastInitialLoad);
25
+
26
+ return (
27
+ <PickerContext.Provider
28
+ value={{
29
+ filterRef,
30
+ searchTerm,
31
+ activeCategoryState,
32
+ activeSkinTone,
33
+ emojisThatFailedToLoadState,
34
+ isPastInitialLoad,
35
+ emojiVariationPickerState,
36
+ skinToneFanOpenState,
37
+ disallowClickRef
38
+ }}
39
+ >
40
+ {children}
41
+ </PickerContext.Provider>
42
+ );
43
+ }
44
+
45
+ type ReactState<T> = [T, React.Dispatch<React.SetStateAction<T>>];
46
+
47
+ const PickerContext = React.createContext<{
48
+ searchTerm: ReactState<string>;
49
+ activeCategoryState: ReactState<ActiveCategoryState>;
50
+ activeSkinTone: ReactState<SkinTones>;
51
+ emojisThatFailedToLoadState: ReactState<Set<string>>;
52
+ isPastInitialLoad: boolean;
53
+ emojiVariationPickerState: ReactState<DataEmoji | null>;
54
+ skinToneFanOpenState: ReactState<boolean>;
55
+ filterRef: React.MutableRefObject<FilterState>;
56
+ disallowClickRef: React.MutableRefObject<boolean>;
57
+ }>({
58
+ searchTerm: ['', () => {}],
59
+ activeCategoryState: [null, () => {}],
60
+ activeSkinTone: [SkinTones.NEUTRAL, () => {}],
61
+ emojisThatFailedToLoadState: [new Set(), () => {}],
62
+ isPastInitialLoad: true,
63
+ emojiVariationPickerState: [null, () => {}],
64
+ skinToneFanOpenState: [false, () => {}],
65
+ filterRef: { current: {} },
66
+ disallowClickRef: { current: false }
67
+ });
68
+
69
+ type Props = Readonly<{
70
+ children: React.ReactNode;
71
+ }>;
72
+
73
+ export function useFilterRef() {
74
+ const { filterRef } = React.useContext(PickerContext);
75
+ return filterRef;
76
+ }
77
+
78
+ export function useDisallowClickRef() {
79
+ const { disallowClickRef } = React.useContext(PickerContext);
80
+ return disallowClickRef;
81
+ }
82
+
83
+ export function useSearchTermState() {
84
+ const { searchTerm } = React.useContext(PickerContext);
85
+ return searchTerm;
86
+ }
87
+
88
+ export function useActiveCategoryState(): [
89
+ ActiveCategoryState,
90
+ (nextActive: string) => void,
91
+ (nextActive: string) => void
92
+ ] {
93
+ const { activeCategoryState } = React.useContext(PickerContext);
94
+ const PickerMainRef = usePickerMainRef();
95
+
96
+ const [activeCategory, setActiveCategory] = activeCategoryState;
97
+ return [activeCategory, setCategory, setActiveCategoryAndScroll];
98
+
99
+ function setCategory(category: string) {
100
+ setActiveCategory(category);
101
+ }
102
+
103
+ function setActiveCategoryAndScroll(category: string) {
104
+ setCategory(category);
105
+ scrollCategoryIntoView(PickerMainRef.current, category);
106
+ }
107
+ }
108
+
109
+ export function useActiveSkinToneState(): [
110
+ SkinTones,
111
+ (skinTone: SkinTones) => void
112
+ ] {
113
+ const { activeSkinTone } = React.useContext(PickerContext);
114
+ return activeSkinTone;
115
+ }
116
+
117
+ export function useEmojisThatFailedToLoadState() {
118
+ const { emojisThatFailedToLoadState } = React.useContext(PickerContext);
119
+ return emojisThatFailedToLoadState;
120
+ }
121
+
122
+ export function useIsPastInitialLoad(): boolean {
123
+ const { isPastInitialLoad } = React.useContext(PickerContext);
124
+ return isPastInitialLoad;
125
+ }
126
+
127
+ export function useEmojiVariationPickerState() {
128
+ const { emojiVariationPickerState } = React.useContext(PickerContext);
129
+ return emojiVariationPickerState;
130
+ }
131
+
132
+ export function useSkinToneFanOpenState() {
133
+ const { skinToneFanOpenState } = React.useContext(PickerContext);
134
+ return skinToneFanOpenState;
135
+ }
136
+
137
+ export type FilterState = Record<string, FilterDict>;
138
+
139
+ type ActiveCategoryState = null | string;
@@ -0,0 +1,44 @@
1
+ .EmojiPickerReact button.epr-emoji {
2
+ position: relative;
3
+ padding: var(--epr-emoji-padding);
4
+ }
5
+
6
+ .EmojiPickerReact button.epr-emoji.epr-emoji-has-variations:after {
7
+ content: '';
8
+ display: block;
9
+ width: 0;
10
+ height: 0;
11
+ right: -2px;
12
+ bottom: 0px;
13
+ position: absolute;
14
+ border-left: 4px solid transparent;
15
+ border-right: 4px solid transparent;
16
+ transform: rotate(135deg);
17
+ border-bottom: 4px solid var(--epr-emoji-variation-indicator-color);
18
+ z-index: var(--epr-emoji-variations-indictator-z-index);
19
+ }
20
+
21
+ .EmojiPickerReact button.epr-emoji > * {
22
+ height: var(--epr-emoji-size);
23
+ width: var(--epr-emoji-size);
24
+ font-size: var(--epr-emoji-size);
25
+ }
26
+
27
+ .EmojiPickerReact button.epr-emoji.epr-emoji-has-variations:hover:after {
28
+ border-bottom: 4px solid var(--epr-emoji-variation-indicator-color-hover);
29
+ }
30
+
31
+ .EmojiPickerReact button.epr-emoji > .epr-emoji-img {
32
+ font-size: 0;
33
+ }
34
+
35
+ .EmojiPickerReact button.epr-emoji > .epr-emoji-native {
36
+ font-family: 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Emoji', Times,
37
+ Symbola, Aegyptus, Code2000, Code2001, Code2002, Musica, serif, LastResort;
38
+ line-height: 100%;
39
+ }
40
+
41
+ .EmojiPickerReact button.epr-emoji:hover {
42
+ border-radius: 8px;
43
+ background-color: var(--epr-emoji-hover-color);
44
+ }
@@ -0,0 +1,104 @@
1
+ import clsx from 'clsx';
2
+ import * as React from 'react';
3
+
4
+ import { DataEmoji } from '../../dataUtils/DataTypes';
5
+ import {
6
+ emojiHasVariations,
7
+ emojiName,
8
+ emojiUnified,
9
+ emojiUrlByUnified
10
+ } from '../../dataUtils/emojiSelectors';
11
+ import { parseNativeEmoji } from '../../dataUtils/parseNativeEmoji';
12
+ import { EmojiStyle } from '../../types/exposedTypes';
13
+ import { useEmojisThatFailedToLoadState } from '../context/PickerContext';
14
+ import './Emoji.css';
15
+
16
+ type Props = Readonly<{
17
+ hidden?: boolean;
18
+ emojiStyle: EmojiStyle;
19
+ emoji: DataEmoji;
20
+ unified: string;
21
+ showVariations?: boolean;
22
+ size?: number;
23
+ }>;
24
+
25
+ export function Emoji({
26
+ emoji,
27
+ unified,
28
+ hidden,
29
+ emojiStyle,
30
+ showVariations = true,
31
+ size
32
+ }: Props) {
33
+ const hasVariations = emojiHasVariations(emoji);
34
+
35
+ const style = {} as React.CSSProperties;
36
+ if (size) {
37
+ style.width = style.height = style.fontSize = `${size}px`;
38
+ }
39
+
40
+ const base = {
41
+ style,
42
+ unified
43
+ };
44
+
45
+ return (
46
+ <button
47
+ className={clsx('epr-emoji', {
48
+ hidden,
49
+ 'epr-emoji-has-variations': hasVariations && showVariations
50
+ })}
51
+ data-unified={unified}
52
+ data-original={emojiUnified(emoji)}
53
+ >
54
+ {emojiStyle === EmojiStyle.NATIVE ? (
55
+ <NativeEmoji {...base} />
56
+ ) : (
57
+ <EmojiImg {...base} emoji={emoji} emojiStyle={emojiStyle} />
58
+ )}
59
+ </button>
60
+ );
61
+ }
62
+
63
+ function NativeEmoji({
64
+ unified,
65
+ style
66
+ }: {
67
+ unified: string;
68
+ style: React.CSSProperties;
69
+ }) {
70
+ return (
71
+ <span className="epr-emoji-native" data-unified={unified} style={style}>
72
+ {parseNativeEmoji(unified)}
73
+ </span>
74
+ );
75
+ }
76
+
77
+ function EmojiImg({
78
+ emoji,
79
+ unified,
80
+ emojiStyle,
81
+ style
82
+ }: {
83
+ emoji: DataEmoji;
84
+ unified: string;
85
+ emojiStyle: EmojiStyle;
86
+ style: React.CSSProperties;
87
+ }) {
88
+ const [, setEmojisThatFailedToLoad] = useEmojisThatFailedToLoadState();
89
+
90
+ return (
91
+ <img
92
+ src={emojiUrlByUnified(emojiStyle, unified)}
93
+ alt={emojiName(emoji)}
94
+ className="epr-emoji-img"
95
+ loading="lazy"
96
+ onError={onError}
97
+ style={style}
98
+ />
99
+ );
100
+
101
+ function onError() {
102
+ setEmojisThatFailedToLoad(prev => new Set(prev).add(unified));
103
+ }
104
+ }
@@ -0,0 +1,14 @@
1
+ .EmojiPickerReact .epr-preview {
2
+ padding: 10px;
3
+ z-index: 1;
4
+ align-items: center;
5
+ border-top: 1px solid var(--epr-preview-border-color);
6
+ height: var(--epr-preview-height);
7
+ }
8
+
9
+ .EmojiPickerReact .epr-preview .epr-preview-emoji-label {
10
+ text-transform: capitalize;
11
+ color: var(--epr-preview-text-color);
12
+ font-size: var(--epr-preview-text-size);
13
+ padding: var(--epr-preview-text-padding);
14
+ }