@fibery/ui-kit 1.9.2 → 1.10.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.
- package/package.json +4 -6
- package/src/designSystem.ts +2 -4
- package/src/{app-icon-picker.tsx → emoji-mart/app-icon-picker.tsx} +4 -4
- package/src/{emoji-mart.tsx → emoji-mart/emoji-mart.tsx} +18 -5
- package/src/{emoji-picker.tsx → emoji-mart/emoji-picker.tsx} +3 -3
- 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 +61 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fibery/ui-kit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
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": {
|
|
@@ -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
|
@@ -579,13 +579,11 @@ export const colors = {
|
|
|
579
579
|
...getTypeColors(brandColors.green, "light"),
|
|
580
580
|
brandColors,
|
|
581
581
|
stateColors,
|
|
582
|
-
textColor: "#2A3844",
|
|
583
|
-
inversedTextColor: "#FFFFFF",
|
|
584
|
-
disabledInversedTextColor: lights.opacity25,
|
|
585
|
-
unitBg: "rgba(220, 224, 228, 0.33)",
|
|
586
582
|
transparent,
|
|
587
583
|
shades,
|
|
588
584
|
lights,
|
|
585
|
+
inversedTextColor: "#FFFFFF",
|
|
586
|
+
disabledInversedTextColor: lights.opacity25,
|
|
589
587
|
} as const;
|
|
590
588
|
|
|
591
589
|
export const space = {
|
|
@@ -1,9 +1,9 @@
|
|
|
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
7
|
|
|
8
8
|
const EMPTY_DATA = {
|
|
9
9
|
aliases: {},
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import "emoji-mart/css/emoji-mart.css";
|
|
2
2
|
import {forwardRef} from "react";
|
|
3
|
-
import {border, space, textStyles, themeVars} from "
|
|
3
|
+
import {border, space, textStyles, themeVars} from "../designSystem";
|
|
4
4
|
import {cx, css} from "@linaria/core";
|
|
5
|
-
import {Emoji
|
|
5
|
+
import {Emoji, EmojiProps} from "emoji-mart";
|
|
6
6
|
|
|
7
|
-
export {detectEmojiSupportLevel} from "./emojiSupport";
|
|
8
7
|
export {emojiIndex, Picker, NimblePicker} from "emoji-mart";
|
|
9
8
|
export type {BaseEmoji, PickerProps, EmojiSkin} from "emoji-mart";
|
|
10
9
|
|
|
@@ -117,6 +116,20 @@ export const EmojiMartWrapper = forwardRef<HTMLDivElement, EmojiMartWrapperProps
|
|
|
117
116
|
);
|
|
118
117
|
});
|
|
119
118
|
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
const emojiWrapperCss = css`
|
|
120
|
+
& span.emoji-mart-emoji-native {
|
|
121
|
+
line-height: 1;
|
|
122
|
+
vertical-align: middle;
|
|
123
|
+
& span {
|
|
124
|
+
vertical-align: middle;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
`;
|
|
128
|
+
|
|
129
|
+
export const EmojiMartEmoji: React.FC<EmojiProps> = (props) => {
|
|
130
|
+
return (
|
|
131
|
+
<span className={emojiWrapperCss}>
|
|
132
|
+
<Emoji backgroundImageFn={backgroundImageFn} {...props} />
|
|
133
|
+
</span>
|
|
134
|
+
);
|
|
122
135
|
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {forwardRef} from "react";
|
|
2
|
-
import {useTheme, useThemeMode} from "
|
|
2
|
+
import {useTheme, useThemeMode} from "../ThemeProvider";
|
|
3
3
|
import {EmojiMartWrapper, Picker, PickerProps, backgroundImageFn} from "./emoji-mart";
|
|
4
|
-
import {alignTheme} from "
|
|
4
|
+
import {alignTheme} from "../theme-settings";
|
|
5
5
|
|
|
6
|
-
type EmojiPickerProps = {
|
|
6
|
+
export type EmojiPickerProps = {
|
|
7
7
|
borderless?: boolean;
|
|
8
8
|
} & PickerProps;
|
|
9
9
|
|
|
@@ -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,61 @@
|
|
|
1
|
+
import {css} from "@linaria/core";
|
|
2
|
+
import {AntTabs} from "../antd";
|
|
3
|
+
import {AppIconPicker, AppIconPickerProps} from "./app-icon-picker";
|
|
4
|
+
import {space, textStyles} from "../designSystem";
|
|
5
|
+
import {EmojiPicker, EmojiPickerProps} from "./emoji-picker";
|
|
6
|
+
import {Button} from "../Button";
|
|
7
|
+
|
|
8
|
+
const TabHeader: React.FC<React.PropsWithChildren> = ({children}) => (
|
|
9
|
+
<div
|
|
10
|
+
className={css`
|
|
11
|
+
${textStyles.heading5}
|
|
12
|
+
`}
|
|
13
|
+
>
|
|
14
|
+
{children}
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
type IconEmojiPickerProps = React.PropsWithChildren<{
|
|
19
|
+
onIconSelect: NonNullable<AppIconPickerProps["onSelect"]>;
|
|
20
|
+
onEmojiSelect: NonNullable<EmojiPickerProps["onSelect"]>;
|
|
21
|
+
onReset?: () => void;
|
|
22
|
+
resetLabel?: string;
|
|
23
|
+
}>;
|
|
24
|
+
export const IconEmojiPicker: React.FC<IconEmojiPickerProps> = ({
|
|
25
|
+
onIconSelect,
|
|
26
|
+
onEmojiSelect,
|
|
27
|
+
onReset,
|
|
28
|
+
resetLabel = "Reset",
|
|
29
|
+
}) => {
|
|
30
|
+
return (
|
|
31
|
+
<AntTabs
|
|
32
|
+
animated={false}
|
|
33
|
+
destroyInactiveTabPane
|
|
34
|
+
items={[
|
|
35
|
+
{
|
|
36
|
+
key: "Icons",
|
|
37
|
+
label: <TabHeader>Icons</TabHeader>,
|
|
38
|
+
children: <AppIconPicker onSelect={onIconSelect} />,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
key: "Emojis",
|
|
42
|
+
label: <TabHeader>Emojis</TabHeader>,
|
|
43
|
+
children: <EmojiPicker onSelect={onEmojiSelect} />,
|
|
44
|
+
},
|
|
45
|
+
]}
|
|
46
|
+
tabBarExtraContent={
|
|
47
|
+
onReset ? (
|
|
48
|
+
<div
|
|
49
|
+
className={css`
|
|
50
|
+
padding-right: ${space.m}px;
|
|
51
|
+
`}
|
|
52
|
+
>
|
|
53
|
+
<Button size={":button-size/small"} onClick={onReset}>
|
|
54
|
+
{resetLabel}
|
|
55
|
+
</Button>
|
|
56
|
+
</div>
|
|
57
|
+
) : null
|
|
58
|
+
}
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
};
|