@fibery/ui-kit 1.9.1 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fibery/ui-kit",
3
- "version": "1.9.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.tsx",
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": {
@@ -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/helpers": "1.0.3",
65
- "@fibery/react": "1.0.1"
62
+ "@fibery/helpers": "1.0.4",
63
+ "@fibery/react": "1.0.2"
66
64
  },
67
65
  "peerDependencies": {
68
66
  "react": "^18.2.0",
@@ -579,16 +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
- danger: brandColors.red,
586
- cardSelected: "#FFFB8F",
587
- cardSelectedHover: "rgba(240, 244, 247, 0.6)",
588
- unitBg: "rgba(220, 224, 228, 0.33)",
589
582
  transparent,
590
583
  shades,
591
584
  lights,
585
+ inversedTextColor: "#FFFFFF",
586
+ disabledInversedTextColor: lights.opacity25,
592
587
  } as const;
593
588
 
594
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 "./ThemeProvider";
4
- import {alignTheme} from "./theme-settings";
5
- import appIcons from "./appIcons.json";
6
- import {getThemedUrl} from "./AppIconWithFallback";
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 "./designSystem";
3
+ import {border, space, textStyles, themeVars} from "../designSystem";
4
4
  import {cx, css} from "@linaria/core";
5
- import {Emoji as EmojiMartEmoji, EmojiProps} from "emoji-mart";
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
- export const Emoji: React.FC<EmojiProps> = (props) => {
121
- return <EmojiMartEmoji backgroundImageFn={backgroundImageFn} {...props} />;
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 "./ThemeProvider";
2
+ import {useTheme, useThemeMode} from "../ThemeProvider";
3
3
  import {EmojiMartWrapper, Picker, PickerProps, backgroundImageFn} from "./emoji-mart";
4
- import {alignTheme} from "./theme-settings";
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
+ };