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.
- package/LICENSE +17 -20
- package/README.md +129 -168
- package/dist/DomUtils/categoryNameFromDom.d.ts +1 -0
- package/dist/DomUtils/scrollCategoryIntoView.d.ts +1 -0
- package/dist/DomUtils/scrollTo.d.ts +1 -0
- package/dist/DomUtils/selectors.d.ts +12 -0
- package/dist/components/Layout/Flex.d.ts +14 -0
- package/dist/components/Layout/Relative.d.ts +7 -0
- package/dist/components/body/Body.d.ts +3 -0
- package/dist/components/body/EmojiCategory.d.ts +10 -0
- package/dist/components/body/EmojiList.d.ts +3 -0
- package/dist/components/body/EmojiVariationPicker.d.ts +3 -0
- package/dist/components/body/RecentlyUsed.d.ts +7 -0
- package/dist/components/context/ElementRefContext.d.ts +17 -0
- package/dist/components/context/PickerConfigContext.d.ts +8 -0
- package/dist/components/context/PickerContext.d.ts +21 -0
- package/dist/components/emoji/Emoji.d.ts +14 -0
- package/dist/components/footer/Preview.d.ts +7 -0
- package/dist/components/header/Header.d.ts +3 -0
- package/dist/components/header/Search.d.ts +3 -0
- package/dist/components/header/SkinTonePicker.d.ts +3 -0
- package/dist/components/main/PickerMain.d.ts +7 -0
- package/dist/components/navigation/CategoryNavigation.d.ts +3 -0
- package/dist/config/categoryConfig.d.ts +21 -0
- package/dist/config/cdnUrls.d.ts +2 -0
- package/dist/config/config.d.ts +26 -0
- package/dist/config/useConfig.d.ts +11 -0
- package/dist/data/emojis.d.ts +40 -0
- package/dist/data/groups.d.ts +2 -0
- package/dist/data/skinToneVariations.d.ts +3 -0
- package/dist/dataUtils/DataTypes.d.ts +8 -0
- package/dist/dataUtils/alphaNumericEmojiIndex.d.ts +4 -0
- package/dist/dataUtils/asEmoji.d.ts +2 -0
- package/dist/dataUtils/emojiSelectors.d.ts +15 -0
- package/dist/dataUtils/parseNativeEmoji.d.ts +1 -0
- package/dist/dataUtils/recentlyUsed.d.ts +11 -0
- package/dist/emoji-picker-react.cjs.development.css +13 -0
- package/dist/emoji-picker-react.cjs.development.js +7419 -0
- package/dist/emoji-picker-react.cjs.development.js.map +1 -0
- package/dist/emoji-picker-react.cjs.production.min.js +2 -0
- package/dist/emoji-picker-react.cjs.production.min.js.map +1 -0
- package/dist/emoji-picker-react.esm.js +7485 -0
- package/dist/emoji-picker-react.esm.js.map +1 -0
- package/dist/hooks/preloadEmoji.d.ts +4 -0
- package/dist/hooks/useActiveCategoryScrollDetection.d.ts +2 -0
- package/dist/hooks/useCloseAllOpenToggles.d.ts +4 -0
- package/dist/hooks/useEmojiElements.d.ts +1 -0
- package/dist/hooks/useEmojiMouseEnter.d.ts +3 -0
- package/dist/hooks/useFilter.d.ts +8 -0
- package/dist/hooks/useInitialLoad.d.ts +2 -0
- package/dist/hooks/useIsEmojiHidden.d.ts +2 -0
- package/dist/hooks/useIsSearchMode.d.ts +1 -0
- package/dist/hooks/useMouseDownHandlers.d.ts +2 -0
- package/dist/hooks/useOnScroll.d.ts +2 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +8 -3
- package/dist/types/exposedTypes.d.ts +28 -0
- package/package.json +72 -52
- package/src/DomUtils/categoryNameFromDom.ts +3 -0
- package/src/DomUtils/scrollCategoryIntoView.ts +18 -0
- package/src/DomUtils/scrollTo.ts +11 -0
- package/src/DomUtils/selectors.ts +93 -0
- package/src/EmojiPickerReact.css +85 -0
- package/src/components/Layout/Flex.css +11 -0
- package/src/components/Layout/Flex.tsx +28 -0
- package/src/components/Layout/Relative.tsx +14 -0
- package/src/components/body/Body.css +6 -0
- package/src/components/body/Body.tsx +24 -0
- package/src/components/body/EmojiCategory.css +38 -0
- package/src/components/body/EmojiCategory.tsx +27 -0
- package/src/components/body/EmojiList.css +5 -0
- package/src/components/body/EmojiList.tsx +101 -0
- package/src/components/body/EmojiVariationPicker.css +65 -0
- package/src/components/body/EmojiVariationPicker.tsx +154 -0
- package/src/components/body/RecentlyUsed.tsx +50 -0
- package/src/components/body/svg/triangle.svg +11 -0
- package/src/components/context/ElementRefContext.tsx +61 -0
- package/src/components/context/PickerConfigContext.tsx +29 -0
- package/src/components/context/PickerContext.tsx +139 -0
- package/src/components/emoji/Emoji.css +44 -0
- package/src/components/emoji/Emoji.tsx +104 -0
- package/src/components/footer/Preview.css +14 -0
- package/src/components/footer/Preview.tsx +80 -0
- package/src/components/header/Header.css +4 -0
- package/src/components/header/Header.tsx +21 -0
- package/src/components/header/Search.css +72 -0
- package/src/components/header/Search.tsx +36 -0
- package/src/components/header/SkinTonePicker.css +86 -0
- package/src/components/header/SkinTonePicker.tsx +55 -0
- package/src/components/header/svg/magnifier.svg +13 -0
- package/src/components/header/svg/times.svg +33 -0
- package/src/components/main/PickerMain.css +11 -0
- package/src/components/main/PickerMain.tsx +42 -0
- package/src/components/navigation/CategoryNavigation.css +82 -0
- package/src/components/navigation/CategoryNavigation.tsx +32 -0
- package/src/components/navigation/svg/CategoryNav.svg +508 -0
- package/src/config/categoryConfig.ts +107 -0
- package/src/config/cdnUrls.ts +24 -0
- package/src/config/config.ts +50 -0
- package/src/config/useConfig.ts +52 -0
- package/src/data/emojis.ts +5350 -0
- package/src/data/groups.ts +12 -0
- package/src/data/skinToneVariations.ts +12 -0
- package/src/dataUtils/DataTypes.ts +10 -0
- package/src/dataUtils/alphaNumericEmojiIndex.ts +23 -0
- package/src/dataUtils/asEmoji.ts +5 -0
- package/src/dataUtils/emojiSelectors.ts +94 -0
- package/src/dataUtils/parseNativeEmoji.ts +6 -0
- package/src/dataUtils/recentlyUsed.ts +61 -0
- package/src/hooks/preloadEmoji.ts +29 -0
- package/src/hooks/useActiveCategoryScrollDetection.ts +46 -0
- package/src/hooks/useCloseAllOpenToggles.ts +26 -0
- package/src/hooks/useEmojiElements.ts +7 -0
- package/src/hooks/useEmojiMouseEnter.ts +59 -0
- package/src/hooks/useFilter.ts +142 -0
- package/src/hooks/useInitialLoad.ts +9 -0
- package/src/hooks/useIsEmojiHidden.ts +16 -0
- package/src/hooks/useIsSearchMode.ts +7 -0
- package/src/hooks/useMouseDownHandlers.ts +160 -0
- package/src/hooks/useOnScroll.ts +28 -0
- package/src/index.tsx +27 -0
- package/src/types/exposedTypes.ts +31 -0
- package/.babelrc +0 -30
- package/.eslintrc.js +0 -37
- package/.github/workflows/npmpublish.yml +0 -30
- package/.prettierignore +0 -2
- package/.storybook/addons.js +0 -2
- package/.storybook/config.js +0 -9
- package/assets/captured.gif +0 -0
- package/dist/index.js.LICENSE.txt +0 -5
- package/dist/index.js.map +0 -1
- package/dist/main.css +0 -206
- package/dist/main.css.map +0 -1
- package/emoji-picker-react.d.ts +0 -45
- 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
|
+
}
|