@fibery/ui-kit 1.9.2 → 1.10.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/package.json +7 -9
- package/src/designSystem.ts +9 -6
- package/src/{app-icon-picker.tsx → emoji-mart/app-icon-picker.tsx} +20 -8
- package/src/emoji-mart/emoji-mart.tsx +226 -0
- package/src/emoji-mart/emoji-picker.tsx +45 -0
- package/src/emoji-mart/emoji-support.ts +101 -0
- package/src/emoji-mart/emoji.tsx +24 -0
- package/src/emoji-mart/icon-emoji-picker.tsx +69 -0
- package/src/emoji-mart.tsx +0 -122
- package/src/emoji-picker.tsx +0 -32
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fibery/ui-kit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.1",
|
|
4
4
|
"main": "index.ts",
|
|
5
5
|
"private": false,
|
|
6
6
|
"files": [
|
|
@@ -23,9 +23,7 @@
|
|
|
23
23
|
"src/tooltip.tsx",
|
|
24
24
|
"src/BackButton.tsx",
|
|
25
25
|
"src/Button",
|
|
26
|
-
"src/emoji-mart
|
|
27
|
-
"src/emoji-picker.tsx",
|
|
28
|
-
"src/app-icon-picker.tsx"
|
|
26
|
+
"src/emoji-mart/*.ts*"
|
|
29
27
|
],
|
|
30
28
|
"license": "UNLICENSED",
|
|
31
29
|
"dependencies": {
|
|
@@ -53,7 +51,7 @@
|
|
|
53
51
|
"rc-input-number": "7.3.11",
|
|
54
52
|
"rc-menu": "9.8.1",
|
|
55
53
|
"rc-progress": "3.4.1",
|
|
56
|
-
"react-color": "2.
|
|
54
|
+
"react-color": "2.19.3",
|
|
57
55
|
"react-day-picker": "8.1.4",
|
|
58
56
|
"react-popper": "2.3.0",
|
|
59
57
|
"react-select": "5.3.2",
|
|
@@ -61,8 +59,8 @@
|
|
|
61
59
|
"react-windowed-select": "5.0.0",
|
|
62
60
|
"screenfull": "6.0.1",
|
|
63
61
|
"ua-parser-js": "0.7.24",
|
|
64
|
-
"@fibery/
|
|
65
|
-
"@fibery/
|
|
62
|
+
"@fibery/react": "1.0.2",
|
|
63
|
+
"@fibery/helpers": "1.0.4"
|
|
66
64
|
},
|
|
67
65
|
"peerDependencies": {
|
|
68
66
|
"react": "^18.2.0",
|
|
@@ -95,8 +93,8 @@
|
|
|
95
93
|
"svgo": "2.8.0",
|
|
96
94
|
"typescript": "5.0.3",
|
|
97
95
|
"unist-util-reduce": "0.2.2",
|
|
98
|
-
"@fibery/
|
|
99
|
-
"@fibery/
|
|
96
|
+
"@fibery/babel-preset": "7.2.0",
|
|
97
|
+
"@fibery/eslint-config": "8.3.0"
|
|
100
98
|
},
|
|
101
99
|
"jest": {
|
|
102
100
|
"testEnvironment": "jsdom",
|
package/src/designSystem.ts
CHANGED
|
@@ -202,6 +202,7 @@ const lightColors = {
|
|
|
202
202
|
cellBackgroundColor: slate.slate2,
|
|
203
203
|
cellBackgroundHoverColor: getOpacities(slate.slate6).opacity30,
|
|
204
204
|
cellBorderColor: getOpacities(slate.slate8).opacity50,
|
|
205
|
+
cellPinnedBorderColor: getOpacities(slate.slate8).opacity70,
|
|
205
206
|
tableRowWarningColor: getOpacities(yellow.yellow6).opacity60,
|
|
206
207
|
errorBgColor: getOpacities(red.red9).opacity20,
|
|
207
208
|
errorTextColor: red.red11,
|
|
@@ -254,6 +255,7 @@ const lightColors = {
|
|
|
254
255
|
progressFillBg: slate.slate11,
|
|
255
256
|
progressFillText: whiteA.whiteA0,
|
|
256
257
|
searchFiltersBg: slate.slate1,
|
|
258
|
+
colorPickerSwatchBorder: blackA.blackA7,
|
|
257
259
|
} as const;
|
|
258
260
|
|
|
259
261
|
const darkMenu = {
|
|
@@ -364,6 +366,7 @@ const darkColors = {
|
|
|
364
366
|
cellBackgroundColor: slateDark.slate2,
|
|
365
367
|
cellBackgroundHoverColor: getOpacities(slateDark.slate6).opacity30,
|
|
366
368
|
cellBorderColor: getOpacities(slateDark.slate9).opacity50,
|
|
369
|
+
cellPinnedBorderColor: getOpacities(slateDark.slate9).opacity70,
|
|
367
370
|
tableRowWarningColor: getOpacities(yellowDark.yellow9).opacity40,
|
|
368
371
|
errorBgColor: getOpacities(redDark.red9).opacity60,
|
|
369
372
|
errorTextColor: red.red7,
|
|
@@ -414,6 +417,7 @@ const darkColors = {
|
|
|
414
417
|
progressFillBg: slate.slate11,
|
|
415
418
|
progressFillText: whiteA.whiteA0,
|
|
416
419
|
searchFiltersBg: slateDark.slate2,
|
|
420
|
+
colorPickerSwatchBorder: blackA.blackA7,
|
|
417
421
|
...darkMenu,
|
|
418
422
|
} as const;
|
|
419
423
|
|
|
@@ -554,6 +558,9 @@ export const getEnumBackgroundColor = _.memoize(
|
|
|
554
558
|
);
|
|
555
559
|
|
|
556
560
|
export const cardTypeColors = [
|
|
561
|
+
"#4A4A4A",
|
|
562
|
+
"#6A849B",
|
|
563
|
+
"#BEC5CC",
|
|
557
564
|
"#D40915",
|
|
558
565
|
"#E72065",
|
|
559
566
|
"#9C2BAF",
|
|
@@ -568,8 +575,6 @@ export const cardTypeColors = [
|
|
|
568
575
|
"#FBA32F",
|
|
569
576
|
"#FC551F",
|
|
570
577
|
"#B04E31",
|
|
571
|
-
"#6A849B",
|
|
572
|
-
"#4A4A4A",
|
|
573
578
|
] as const;
|
|
574
579
|
|
|
575
580
|
export const pastelColors = cardTypeColors.map((color) => makeChromaColor(color).luminance(0.8).hex());
|
|
@@ -579,13 +584,11 @@ export const colors = {
|
|
|
579
584
|
...getTypeColors(brandColors.green, "light"),
|
|
580
585
|
brandColors,
|
|
581
586
|
stateColors,
|
|
582
|
-
textColor: "#2A3844",
|
|
583
|
-
inversedTextColor: "#FFFFFF",
|
|
584
|
-
disabledInversedTextColor: lights.opacity25,
|
|
585
|
-
unitBg: "rgba(220, 224, 228, 0.33)",
|
|
586
587
|
transparent,
|
|
587
588
|
shades,
|
|
588
589
|
lights,
|
|
590
|
+
inversedTextColor: "#FFFFFF",
|
|
591
|
+
disabledInversedTextColor: lights.opacity25,
|
|
589
592
|
} as const;
|
|
590
593
|
|
|
591
594
|
export const space = {
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import {forwardRef, useMemo} from "react";
|
|
2
2
|
import {EmojiMartWrapper, NimblePicker, PickerProps} from "./emoji-mart";
|
|
3
|
-
import {useThemeMode} from "
|
|
4
|
-
import {alignTheme} from "
|
|
5
|
-
import appIcons from "
|
|
6
|
-
import {getThemedUrl} from "
|
|
3
|
+
import {useThemeMode} from "../ThemeProvider";
|
|
4
|
+
import {alignTheme} from "../theme-settings";
|
|
5
|
+
import appIcons from "../appIcons.json";
|
|
6
|
+
import {getThemedUrl} from "../AppIconWithFallback";
|
|
7
|
+
import {$TSFixMe} from "../tsfixme";
|
|
8
|
+
import {cardTypeColors} from "../designSystem";
|
|
9
|
+
import {ColorPicker} from "../ColorPicker";
|
|
7
10
|
|
|
8
11
|
const EMPTY_DATA = {
|
|
9
12
|
aliases: {},
|
|
@@ -12,10 +15,16 @@ const EMPTY_DATA = {
|
|
|
12
15
|
compressed: false,
|
|
13
16
|
};
|
|
14
17
|
|
|
15
|
-
export type AppIconPickerProps = {
|
|
18
|
+
export type AppIconPickerProps = {
|
|
19
|
+
borderless?: boolean;
|
|
20
|
+
color?: string;
|
|
21
|
+
onColorSelect?: (c: $TSFixMe) => void;
|
|
22
|
+
} & PickerProps;
|
|
23
|
+
|
|
24
|
+
const i18n = {search: "Search icon..."};
|
|
16
25
|
|
|
17
26
|
export const AppIconPicker = forwardRef<HTMLDivElement, AppIconPickerProps>(function AppIconPicker(
|
|
18
|
-
{borderless = true, ...pickerProps},
|
|
27
|
+
{borderless = true, onColorSelect, color, ...pickerProps},
|
|
19
28
|
ref
|
|
20
29
|
) {
|
|
21
30
|
const themeMode = useThemeMode();
|
|
@@ -34,17 +43,20 @@ export const AppIconPicker = forwardRef<HTMLDivElement, AppIconPickerProps>(func
|
|
|
34
43
|
);
|
|
35
44
|
|
|
36
45
|
return (
|
|
37
|
-
<EmojiMartWrapper ref={ref} borderless={borderless} hideCategories>
|
|
46
|
+
<EmojiMartWrapper ref={ref} color={color} borderless={borderless} hideCategories>
|
|
38
47
|
<NimblePicker
|
|
39
48
|
data={EMPTY_DATA}
|
|
40
49
|
theme={alignedTheme}
|
|
41
50
|
showSkinTones={false}
|
|
42
51
|
showPreview={false}
|
|
52
|
+
emojiTooltip
|
|
43
53
|
title=""
|
|
44
54
|
custom={customIcons}
|
|
45
|
-
emojiSize={
|
|
55
|
+
emojiSize={20}
|
|
56
|
+
i18n={i18n}
|
|
46
57
|
{...pickerProps}
|
|
47
58
|
/>
|
|
59
|
+
{onColorSelect ? <ColorPicker colors={cardTypeColors} color={color} onChangeComplete={onColorSelect} /> : null}
|
|
48
60
|
</EmojiMartWrapper>
|
|
49
61
|
);
|
|
50
62
|
});
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import {CSSProperties, css, cx} from "@linaria/core";
|
|
2
|
+
import {Emoji, EmojiProps} from "emoji-mart";
|
|
3
|
+
import "emoji-mart/css/emoji-mart.css";
|
|
4
|
+
import {forwardRef} from "react";
|
|
5
|
+
import {useTheme} from "../ThemeProvider";
|
|
6
|
+
import {a11yColor} from "../a11y-color";
|
|
7
|
+
import {border, fontFamily, getOpacities, space, textStyles, themeVars} from "../designSystem";
|
|
8
|
+
|
|
9
|
+
export {NimblePicker, Picker, emojiIndex} from "emoji-mart";
|
|
10
|
+
export type {BaseEmoji, EmojiSkin, PickerProps} from "emoji-mart";
|
|
11
|
+
|
|
12
|
+
export const backgroundImageFn = () => "https://assets-temp.fibery.io/images/emoji-datasource-apple-5_0_1-64.png";
|
|
13
|
+
|
|
14
|
+
const emojiMartCss = css`
|
|
15
|
+
padding: 0 ${space.l}px;
|
|
16
|
+
position: relative;
|
|
17
|
+
|
|
18
|
+
.emoji-mart {
|
|
19
|
+
font-family: ${fontFamily};
|
|
20
|
+
background-color: ${themeVars.actionMenuBg};
|
|
21
|
+
margin-top: ${space.xxs}px;
|
|
22
|
+
display: block;
|
|
23
|
+
width: 288px !important;
|
|
24
|
+
|
|
25
|
+
+ * {
|
|
26
|
+
border-top: 1px solid ${themeVars.separatorColor};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.emoji-mart-bar {
|
|
31
|
+
border-bottom: 1px solid ${themeVars.separatorColor};
|
|
32
|
+
|
|
33
|
+
&:last-child {
|
|
34
|
+
border: 0;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.emoji-mart-search {
|
|
39
|
+
padding-bottom: ${space.s}px;
|
|
40
|
+
padding-left: 0;
|
|
41
|
+
padding-right: 0;
|
|
42
|
+
|
|
43
|
+
& input {
|
|
44
|
+
${textStyles.regular}
|
|
45
|
+
background-color: ${themeVars.inputBgColor};
|
|
46
|
+
color: ${themeVars.textColor};
|
|
47
|
+
box-shadow: ${themeVars.inputBorderColor};
|
|
48
|
+
height: 32px;
|
|
49
|
+
border: 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
& .emoji-mart-search-icon {
|
|
53
|
+
top: 5px;
|
|
54
|
+
right: 4px;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.emoji-mart-scroll {
|
|
59
|
+
padding-left: 0;
|
|
60
|
+
padding-right: 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.emoji-anchor-bar {
|
|
64
|
+
height: ${space.xxs}px;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.emoji-mart-category-label {
|
|
68
|
+
& span {
|
|
69
|
+
${textStyles.heading6}
|
|
70
|
+
background-color: ${themeVars.actionMenuBg};
|
|
71
|
+
color: ${themeVars.textColor};
|
|
72
|
+
color: ${themeVars.accentTextColor};
|
|
73
|
+
padding-top: ${space.l}px;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.emoji-mart-category .emoji-mart-emoji span,
|
|
78
|
+
.emoji-mart-anchor {
|
|
79
|
+
cursor: pointer !important;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.emoji-mart-category .emoji-mart-emoji:hover::before {
|
|
83
|
+
background-color: var(--emoji-bg-color) !important;
|
|
84
|
+
border-radius: ${border.radius8}px !important;
|
|
85
|
+
}
|
|
86
|
+
.emoji-mart-dark .emoji-mart-category-label span {
|
|
87
|
+
background-color: ${themeVars.actionMenuBg};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.emoji-mart-skin-swatch {
|
|
91
|
+
cursor: pointer;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.emoji-mart-preview {
|
|
95
|
+
height: 0px;
|
|
96
|
+
position: unset;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.emoji-mart-emoji {
|
|
100
|
+
/* pathetic attemts to make it a bit faster without forking */
|
|
101
|
+
content-visibility: auto;
|
|
102
|
+
contain-intrinsic-size: 20px;
|
|
103
|
+
cursor: pointer;
|
|
104
|
+
}
|
|
105
|
+
.emoji-mart-category-list,
|
|
106
|
+
.emoji-mart-category-list li {
|
|
107
|
+
/* hack to maintain correct size of hidden emojis becuase of content-visibility */
|
|
108
|
+
line-height: 0;
|
|
109
|
+
}
|
|
110
|
+
.emoji-mart-preview-skins {
|
|
111
|
+
top: 16px;
|
|
112
|
+
right: 12px !important;
|
|
113
|
+
z-index: 2;
|
|
114
|
+
background-color: ${themeVars.actionMenuBg};
|
|
115
|
+
|
|
116
|
+
& .emoji-mart-skin-swatches {
|
|
117
|
+
background-color: ${themeVars.actionMenuBg};
|
|
118
|
+
|
|
119
|
+
&.opened {
|
|
120
|
+
& .emoji-mart-skin-swatch {
|
|
121
|
+
width: 32px !important;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
& .emoji-mart-skin-swatch {
|
|
126
|
+
height: 32px !important;
|
|
127
|
+
|
|
128
|
+
&.selected {
|
|
129
|
+
width: 32px !important;
|
|
130
|
+
padding: 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
& .emoji-mart-emoji-native span {
|
|
134
|
+
width: 20px;
|
|
135
|
+
font-size: 20px !important;
|
|
136
|
+
height: 20px;
|
|
137
|
+
display: inline-block;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
&:hover {
|
|
141
|
+
background-color: ${themeVars.badgeBgColor} !important;
|
|
142
|
+
border-radius: ${border.radius8}px !important;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
& .emoji-mart-skin-text {
|
|
146
|
+
height: auto;
|
|
147
|
+
padding: 0;
|
|
148
|
+
background-color: ${themeVars.actionMenuBg};
|
|
149
|
+
color: ${themeVars.accentTextColor};
|
|
150
|
+
border-radius: 0;
|
|
151
|
+
|
|
152
|
+
&.opened {
|
|
153
|
+
width: 98px;
|
|
154
|
+
padding-left: 8px;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
`;
|
|
160
|
+
|
|
161
|
+
const borderlessEmojiMartCss = css`
|
|
162
|
+
.emoji-mart {
|
|
163
|
+
border-radius: 0;
|
|
164
|
+
border: none;
|
|
165
|
+
}
|
|
166
|
+
`;
|
|
167
|
+
const hideCategoriesEmojiMartCss = css`
|
|
168
|
+
.emoji-mart-bar:first-child {
|
|
169
|
+
display: none;
|
|
170
|
+
}
|
|
171
|
+
`;
|
|
172
|
+
|
|
173
|
+
const emojiMartWithShortSearchCss = css`
|
|
174
|
+
.emoji-mart-search {
|
|
175
|
+
width: 250px;
|
|
176
|
+
}
|
|
177
|
+
`;
|
|
178
|
+
|
|
179
|
+
type EmojiMartWrapperProps = React.PropsWithChildren<{
|
|
180
|
+
borderless?: boolean;
|
|
181
|
+
hideCategories?: boolean;
|
|
182
|
+
shortSearch?: boolean;
|
|
183
|
+
color?: string;
|
|
184
|
+
}>;
|
|
185
|
+
|
|
186
|
+
export const EmojiMartWrapper = forwardRef<HTMLDivElement, EmojiMartWrapperProps>(function EmojiMartWrapper(
|
|
187
|
+
{borderless, hideCategories, color, shortSearch, children},
|
|
188
|
+
ref
|
|
189
|
+
) {
|
|
190
|
+
const theme = useTheme();
|
|
191
|
+
|
|
192
|
+
const bgColor = color ? a11yColor(getOpacities(color).opacity20, theme.menuBg, 1.3) : theme.badgeBgColor;
|
|
193
|
+
|
|
194
|
+
return (
|
|
195
|
+
<div
|
|
196
|
+
style={{"--emoji-bg-color": bgColor} as CSSProperties}
|
|
197
|
+
ref={ref}
|
|
198
|
+
className={cx(
|
|
199
|
+
emojiMartCss,
|
|
200
|
+
borderless && borderlessEmojiMartCss,
|
|
201
|
+
hideCategories && hideCategoriesEmojiMartCss,
|
|
202
|
+
shortSearch && emojiMartWithShortSearchCss
|
|
203
|
+
)}
|
|
204
|
+
>
|
|
205
|
+
{children}
|
|
206
|
+
</div>
|
|
207
|
+
);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const emojiWrapperCss = css`
|
|
211
|
+
& span.emoji-mart-emoji-native {
|
|
212
|
+
line-height: 1;
|
|
213
|
+
vertical-align: middle;
|
|
214
|
+
& span {
|
|
215
|
+
vertical-align: middle;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
`;
|
|
219
|
+
|
|
220
|
+
export const EmojiMartEmoji: React.FC<EmojiProps> = (props) => {
|
|
221
|
+
return (
|
|
222
|
+
<span className={emojiWrapperCss}>
|
|
223
|
+
<Emoji backgroundImageFn={backgroundImageFn} {...props} />
|
|
224
|
+
</span>
|
|
225
|
+
);
|
|
226
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {forwardRef} from "react";
|
|
2
|
+
import {useTheme, useThemeMode} from "../ThemeProvider";
|
|
3
|
+
import {EmojiMartWrapper, Picker, PickerProps, backgroundImageFn} from "./emoji-mart";
|
|
4
|
+
import {alignTheme} from "../theme-settings";
|
|
5
|
+
import {$TSFixMe} from "../tsfixme";
|
|
6
|
+
import {ColorPicker} from "../ColorPicker";
|
|
7
|
+
import {cardTypeColors} from "../designSystem";
|
|
8
|
+
|
|
9
|
+
export type EmojiPickerProps = {
|
|
10
|
+
borderless?: boolean;
|
|
11
|
+
color?: string;
|
|
12
|
+
colors?: string[];
|
|
13
|
+
onColorSelect?: (c: $TSFixMe) => void;
|
|
14
|
+
} & PickerProps;
|
|
15
|
+
|
|
16
|
+
const i18n = {search: "Search emoji..."};
|
|
17
|
+
|
|
18
|
+
export const EmojiPicker = forwardRef<HTMLDivElement, EmojiPickerProps>(function EmojiPickerComponent(
|
|
19
|
+
{borderless = true, color, colors = cardTypeColors, onColorSelect, showSkinTones = true, ...pickerProps},
|
|
20
|
+
ref
|
|
21
|
+
) {
|
|
22
|
+
const themeMode = useThemeMode();
|
|
23
|
+
const theme = useTheme();
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<EmojiMartWrapper ref={ref} color={color} borderless={borderless} hideCategories shortSearch={showSkinTones}>
|
|
27
|
+
<Picker
|
|
28
|
+
theme={alignTheme(themeMode)}
|
|
29
|
+
backgroundImageFn={backgroundImageFn}
|
|
30
|
+
native
|
|
31
|
+
title={""}
|
|
32
|
+
showPreview={false}
|
|
33
|
+
showSkinTones={showSkinTones}
|
|
34
|
+
skinEmoji="hand"
|
|
35
|
+
color={theme.primary}
|
|
36
|
+
emoji=""
|
|
37
|
+
emojiSize={20}
|
|
38
|
+
emojiTooltip
|
|
39
|
+
i18n={i18n}
|
|
40
|
+
{...pickerProps}
|
|
41
|
+
/>
|
|
42
|
+
{onColorSelect ? <ColorPicker colors={colors} color={color} onChangeComplete={onColorSelect} /> : null}
|
|
43
|
+
</EmojiMartWrapper>
|
|
44
|
+
);
|
|
45
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// copied from https://github.com/nolanlawson/emoji-picker-element/
|
|
2
|
+
const versionsAndTestEmoji = {
|
|
3
|
+
"🫠": 14,
|
|
4
|
+
"🥲": 13.1, // smiling face with tear, technically from v13 but see note above
|
|
5
|
+
"🥻": 12.1, // sari, technically from v12 but see note above
|
|
6
|
+
"🥰": 11,
|
|
7
|
+
"🤩": 5,
|
|
8
|
+
"👱♀️": 4,
|
|
9
|
+
"🤣": 3,
|
|
10
|
+
"👁️🗨️": 2,
|
|
11
|
+
"😀": 1,
|
|
12
|
+
"😐️": 0.7,
|
|
13
|
+
"😃": 0.6,
|
|
14
|
+
};
|
|
15
|
+
const FONT_FAMILY =
|
|
16
|
+
'"Twemoji Mozilla","Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol",' +
|
|
17
|
+
'"Noto Color Emoji","EmojiOne Color","Android Emoji",sans-serif';
|
|
18
|
+
const rIC = typeof requestIdleCallback === "function" ? requestIdleCallback : setTimeout;
|
|
19
|
+
|
|
20
|
+
// only used in jest tests
|
|
21
|
+
const simulateCanvasError = false;
|
|
22
|
+
const simulateOldBrowser = false;
|
|
23
|
+
|
|
24
|
+
const getTextFeature = (text: string, color: string) => {
|
|
25
|
+
const canvas = document.createElement("canvas");
|
|
26
|
+
canvas.width = canvas.height = 1;
|
|
27
|
+
|
|
28
|
+
const ctx = canvas.getContext("2d") as CanvasRenderingContext2D;
|
|
29
|
+
ctx.textBaseline = "top";
|
|
30
|
+
ctx.font = `100px ${FONT_FAMILY}`;
|
|
31
|
+
ctx.fillStyle = color;
|
|
32
|
+
ctx.scale(0.01, 0.01);
|
|
33
|
+
ctx.fillText(text, 0, 0);
|
|
34
|
+
|
|
35
|
+
return ctx.getImageData(0, 0, 1, 1).data;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const compareFeatures = (feature1: Uint8ClampedArray, feature2: Uint8ClampedArray) => {
|
|
39
|
+
const feature1Str = [...feature1].join(",");
|
|
40
|
+
const feature2Str = [...feature2].join(",");
|
|
41
|
+
// This is RGBA, so for 0,0,0, we are checking that the first RGB is not all zeroes.
|
|
42
|
+
// Most of the time when unsupported this is 0,0,0,0, but on Chrome on Mac it is
|
|
43
|
+
// 0,0,0,61 - there is a transparency here.
|
|
44
|
+
return feature1Str === feature2Str && !feature1Str.startsWith("0,0,0,");
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function testColorEmojiSupported(text: string) {
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
49
|
+
// @ts-ignore TODO: fix type error
|
|
50
|
+
if (process.env.NODE_ENV === "test") {
|
|
51
|
+
if (simulateCanvasError) {
|
|
52
|
+
throw new Error("canvas error");
|
|
53
|
+
} else if (simulateOldBrowser) {
|
|
54
|
+
return Object.entries(versionsAndTestEmoji)
|
|
55
|
+
.filter(([, version]) => version < 12)
|
|
56
|
+
.map(([emoji]) => emoji)
|
|
57
|
+
.includes(text);
|
|
58
|
+
}
|
|
59
|
+
return true; // avoid using canvas in jest
|
|
60
|
+
}
|
|
61
|
+
// Render white and black and then compare them to each other and ensure they're the same
|
|
62
|
+
// color, and neither one is black. This shows that the emoji was rendered in color.
|
|
63
|
+
const feature1 = getTextFeature(text, "#000");
|
|
64
|
+
const feature2 = getTextFeature(text, "#fff");
|
|
65
|
+
return feature1 && feature2 && compareFeatures(feature1, feature2);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function determineEmojiSupportLevel() {
|
|
69
|
+
performance.mark("determineEmojiSupportLevel");
|
|
70
|
+
const entries = Object.entries(versionsAndTestEmoji);
|
|
71
|
+
try {
|
|
72
|
+
// start with latest emoji and work backwards
|
|
73
|
+
for (const [emoji, version] of entries) {
|
|
74
|
+
if (testColorEmojiSupported(emoji)) {
|
|
75
|
+
return version;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
} catch (e) {
|
|
79
|
+
// canvas error
|
|
80
|
+
// eslint-disable-next-line no-console
|
|
81
|
+
console.log("Ignoring canvas error", e);
|
|
82
|
+
} finally {
|
|
83
|
+
performance.measure("determineEmojiSupportLevel", "determineEmojiSupportLevel");
|
|
84
|
+
}
|
|
85
|
+
// In case of an error, be generous and just assume all emoji are supported (e.g. for canvas errors
|
|
86
|
+
// due to anti-fingerprinting add-ons). Better to show some gray boxes than nothing at all.
|
|
87
|
+
return entries[0][1]; // first one in the list is the most recent version
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Check which emojis we know for sure aren't supported, based on Unicode version level
|
|
91
|
+
let promise: Promise<number>;
|
|
92
|
+
export const detectEmojiSupportLevel = () => {
|
|
93
|
+
if (!promise) {
|
|
94
|
+
promise = new Promise((resolve) => {
|
|
95
|
+
rIC(
|
|
96
|
+
() => resolve(determineEmojiSupportLevel()) // delay so ideally this can run while IDB is first populating
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return promise;
|
|
101
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {lazy, Suspense} from "react";
|
|
2
|
+
import {detectEmojiSupportLevel} from "./emoji-support";
|
|
3
|
+
|
|
4
|
+
const LazyEmojiComponent = lazy(async () => {
|
|
5
|
+
const {EmojiMartEmoji} = await import("./emoji-mart");
|
|
6
|
+
const level = await detectEmojiSupportLevel();
|
|
7
|
+
|
|
8
|
+
function Emoji(props: React.ComponentPropsWithoutRef<typeof EmojiMartEmoji>) {
|
|
9
|
+
return <EmojiMartEmoji {...props} native={level >= 5} />;
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
default: Emoji,
|
|
13
|
+
};
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export const Emoji: React.FC<React.ComponentPropsWithoutRef<typeof LazyEmojiComponent>> = function EmojiOrLoader(
|
|
17
|
+
props
|
|
18
|
+
) {
|
|
19
|
+
return (
|
|
20
|
+
<Suspense fallback={<div style={{width: props.size, height: props.size}} />}>
|
|
21
|
+
<LazyEmojiComponent {...props} />
|
|
22
|
+
</Suspense>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {css} from "@linaria/core";
|
|
2
|
+
import {Button} from "../Button";
|
|
3
|
+
import {AntTabs} from "../antd";
|
|
4
|
+
import {space, textStyles} from "../designSystem";
|
|
5
|
+
import {$TSFixMe} from "../tsfixme";
|
|
6
|
+
import {AppIconPicker, AppIconPickerProps} from "./app-icon-picker";
|
|
7
|
+
import {EmojiPicker, EmojiPickerProps} from "./emoji-picker";
|
|
8
|
+
|
|
9
|
+
const TabHeader: React.FC<React.PropsWithChildren> = ({children}) => (
|
|
10
|
+
<div
|
|
11
|
+
className={css`
|
|
12
|
+
${textStyles.heading5}
|
|
13
|
+
`}
|
|
14
|
+
>
|
|
15
|
+
{children}
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
type IconEmojiPickerProps = React.PropsWithChildren<{
|
|
20
|
+
onIconSelect: NonNullable<AppIconPickerProps["onSelect"]>;
|
|
21
|
+
onEmojiSelect: NonNullable<EmojiPickerProps["onSelect"]>;
|
|
22
|
+
onColorSelect?: (c: $TSFixMe) => void;
|
|
23
|
+
onReset?: () => void;
|
|
24
|
+
resetLabel?: string;
|
|
25
|
+
defaultTab?: "icons" | "emojis";
|
|
26
|
+
color?: string;
|
|
27
|
+
}>;
|
|
28
|
+
export const IconEmojiPicker: React.FC<IconEmojiPickerProps> = ({
|
|
29
|
+
onIconSelect,
|
|
30
|
+
onEmojiSelect,
|
|
31
|
+
onColorSelect,
|
|
32
|
+
defaultTab,
|
|
33
|
+
onReset,
|
|
34
|
+
resetLabel = "Reset",
|
|
35
|
+
color,
|
|
36
|
+
}) => {
|
|
37
|
+
return (
|
|
38
|
+
<AntTabs
|
|
39
|
+
animated={false}
|
|
40
|
+
destroyInactiveTabPane
|
|
41
|
+
defaultActiveKey={defaultTab}
|
|
42
|
+
items={[
|
|
43
|
+
{
|
|
44
|
+
key: "icons",
|
|
45
|
+
label: <TabHeader>Icons</TabHeader>,
|
|
46
|
+
children: <AppIconPicker color={color} onColorSelect={onColorSelect} onSelect={onIconSelect} />,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
key: "emojis",
|
|
50
|
+
label: <TabHeader>Emojis</TabHeader>,
|
|
51
|
+
children: <EmojiPicker color={color} onColorSelect={onColorSelect} onSelect={onEmojiSelect} />,
|
|
52
|
+
},
|
|
53
|
+
]}
|
|
54
|
+
tabBarExtraContent={
|
|
55
|
+
onReset ? (
|
|
56
|
+
<div
|
|
57
|
+
className={css`
|
|
58
|
+
padding-right: ${space.l}px;
|
|
59
|
+
`}
|
|
60
|
+
>
|
|
61
|
+
<Button size={":button-size/small"} onClick={onReset}>
|
|
62
|
+
{resetLabel}
|
|
63
|
+
</Button>
|
|
64
|
+
</div>
|
|
65
|
+
) : null
|
|
66
|
+
}
|
|
67
|
+
/>
|
|
68
|
+
);
|
|
69
|
+
};
|
package/src/emoji-mart.tsx
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import "emoji-mart/css/emoji-mart.css";
|
|
2
|
-
import {forwardRef} from "react";
|
|
3
|
-
import {border, space, textStyles, themeVars} from "./designSystem";
|
|
4
|
-
import {cx, css} from "@linaria/core";
|
|
5
|
-
import {Emoji as EmojiMartEmoji, EmojiProps} from "emoji-mart";
|
|
6
|
-
|
|
7
|
-
export {detectEmojiSupportLevel} from "./emojiSupport";
|
|
8
|
-
export {emojiIndex, Picker, NimblePicker} from "emoji-mart";
|
|
9
|
-
export type {BaseEmoji, PickerProps, EmojiSkin} from "emoji-mart";
|
|
10
|
-
|
|
11
|
-
export const backgroundImageFn = () => "https://assets-temp.fibery.io/images/emoji-datasource-apple-5_0_1-64.png";
|
|
12
|
-
|
|
13
|
-
const emojiMartCss = css`
|
|
14
|
-
.emoji-mart {
|
|
15
|
-
background-color: ${themeVars.actionMenuBg};
|
|
16
|
-
margin-top: ${space.xxs}px;
|
|
17
|
-
display: block;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
.emoji-mart-bar {
|
|
21
|
-
border-bottom: 1px solid ${themeVars.separatorColor};
|
|
22
|
-
|
|
23
|
-
&:last-child {
|
|
24
|
-
border-top: 1px solid ${themeVars.separatorColor};
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
.emoji-mart-search {
|
|
29
|
-
padding-bottom: ${space.s}px;
|
|
30
|
-
|
|
31
|
-
& input {
|
|
32
|
-
${textStyles.regular}
|
|
33
|
-
background-color: ${themeVars.inputBgColor};
|
|
34
|
-
color: ${themeVars.textColor};
|
|
35
|
-
box-shadow: ${themeVars.inputBorderColor};
|
|
36
|
-
height: 34px;
|
|
37
|
-
border: 0;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
.emoji-anchor-bar {
|
|
42
|
-
height: ${space.xxs}px;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
.emoji-mart-category-label {
|
|
46
|
-
& span {
|
|
47
|
-
${textStyles.heading6}
|
|
48
|
-
background-color: ${themeVars.actionMenuBg};
|
|
49
|
-
color: ${themeVars.textColor};
|
|
50
|
-
color: ${themeVars.accentTextColor};
|
|
51
|
-
padding-top: ${space.l}px;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.emoji-mart-category .emoji-mart-emoji span,
|
|
56
|
-
.emoji-mart-anchor {
|
|
57
|
-
cursor: pointer !important;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
.emoji-mart-category .emoji-mart-emoji:hover::before {
|
|
61
|
-
background-color: ${themeVars.badgeBgColor} !important;
|
|
62
|
-
border-radius: ${border.radius8}px !important;
|
|
63
|
-
}
|
|
64
|
-
.emoji-mart-dark .emoji-mart-category-label span {
|
|
65
|
-
background-color: ${themeVars.actionMenuBg};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
.emoji-mart-skin-swatch {
|
|
69
|
-
cursor: pointer;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
.emoji-mart-preview {
|
|
73
|
-
height: 50px;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.emoji-mart-preview-skins {
|
|
77
|
-
right: 15px;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.emoji-mart-emoji {
|
|
81
|
-
/* pathetic attemts to make it a bit faster without forking */
|
|
82
|
-
content-visibility: auto;
|
|
83
|
-
contain-intrinsic-size: 22px;
|
|
84
|
-
}
|
|
85
|
-
.emoji-mart-category-list,
|
|
86
|
-
.emoji-mart-category-list li {
|
|
87
|
-
/* hack to maintain correct size of hidden emojis becuase of content-visibility */
|
|
88
|
-
line-height: 0;
|
|
89
|
-
}
|
|
90
|
-
`;
|
|
91
|
-
|
|
92
|
-
const borderlessEmojiMartCss = css`
|
|
93
|
-
.emoji-mart {
|
|
94
|
-
border-radius: 0;
|
|
95
|
-
border: none;
|
|
96
|
-
}
|
|
97
|
-
`;
|
|
98
|
-
const hideCategoriesEmojiMartCss = css`
|
|
99
|
-
.emoji-mart-bar:first-child {
|
|
100
|
-
display: none;
|
|
101
|
-
}
|
|
102
|
-
`;
|
|
103
|
-
|
|
104
|
-
type EmojiMartWrapperProps = React.PropsWithChildren<{borderless?: boolean; hideCategories?: boolean}>;
|
|
105
|
-
|
|
106
|
-
export const EmojiMartWrapper = forwardRef<HTMLDivElement, EmojiMartWrapperProps>(function EmojiMartWrapper(
|
|
107
|
-
{borderless, hideCategories, children},
|
|
108
|
-
ref
|
|
109
|
-
) {
|
|
110
|
-
return (
|
|
111
|
-
<div
|
|
112
|
-
ref={ref}
|
|
113
|
-
className={cx(emojiMartCss, borderless && borderlessEmojiMartCss, hideCategories && hideCategoriesEmojiMartCss)}
|
|
114
|
-
>
|
|
115
|
-
{children}
|
|
116
|
-
</div>
|
|
117
|
-
);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
export const Emoji: React.FC<EmojiProps> = (props) => {
|
|
121
|
-
return <EmojiMartEmoji backgroundImageFn={backgroundImageFn} {...props} />;
|
|
122
|
-
};
|
package/src/emoji-picker.tsx
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import {forwardRef} from "react";
|
|
2
|
-
import {useTheme, useThemeMode} from "./ThemeProvider";
|
|
3
|
-
import {EmojiMartWrapper, Picker, PickerProps, backgroundImageFn} from "./emoji-mart";
|
|
4
|
-
import {alignTheme} from "./theme-settings";
|
|
5
|
-
|
|
6
|
-
type EmojiPickerProps = {
|
|
7
|
-
borderless?: boolean;
|
|
8
|
-
} & PickerProps;
|
|
9
|
-
|
|
10
|
-
export const EmojiPicker = forwardRef<HTMLDivElement, EmojiPickerProps>(function EmojiPickerComponent(
|
|
11
|
-
{borderless = true, ...pickerProps},
|
|
12
|
-
ref
|
|
13
|
-
) {
|
|
14
|
-
const themeMode = useThemeMode();
|
|
15
|
-
const theme = useTheme();
|
|
16
|
-
|
|
17
|
-
return (
|
|
18
|
-
<EmojiMartWrapper ref={ref} borderless={borderless}>
|
|
19
|
-
<Picker
|
|
20
|
-
theme={alignTheme(themeMode)}
|
|
21
|
-
backgroundImageFn={backgroundImageFn}
|
|
22
|
-
native
|
|
23
|
-
title={""}
|
|
24
|
-
showPreview={false}
|
|
25
|
-
color={theme.primary}
|
|
26
|
-
emoji=""
|
|
27
|
-
emojiSize={22}
|
|
28
|
-
{...pickerProps}
|
|
29
|
-
/>
|
|
30
|
-
</EmojiMartWrapper>
|
|
31
|
-
);
|
|
32
|
-
});
|