@fibery/ui-kit 1.14.0 → 1.15.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 +5 -5
- package/src/antd/styles.ts +20 -3
- package/src/designSystem.ts +12 -11
- package/src/emoji-picker/app-icon-picker.tsx +1 -1
- package/src/emoji-picker/emoji-picker.tsx +28 -26
- package/src/emoji-picker/emoji.tsx +4 -7
- package/src/emoji-picker/icon-emoji-picker.tsx +27 -7
- package/src/emoji-picker/primitives/category.tsx +16 -3
- package/src/emoji-picker/primitives/emoji.tsx +47 -46
- package/src/emoji-picker/primitives/root.tsx +13 -3
- package/src/emoji-picker/primitives/search.tsx +18 -10
- package/src/emoji-picker/primitives/skin-tone.tsx +6 -2
- package/src/emoji-picker/{lazy-icon-data-store.tsx → stores/lazy-icon-data-store.tsx} +5 -5
- package/src/emoji-picker/stores/static-emoji-data-store.tsx +16 -0
- package/src/emoji-picker/utils/emoji-set.ts +1 -1
- package/src/icons/ast/LineDivider.ts +8 -0
- package/src/icons/ast/index.tsx +1 -0
- package/src/icons/react/LineDivider.tsx +12 -0
- package/src/icons/react/index.tsx +1 -0
- /package/src/emoji-picker/{lazy-emoji-data-store.tsx → stores/lazy-emoji-data-store.tsx} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fibery/ui-kit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.0",
|
|
4
4
|
"main": "index.ts",
|
|
5
5
|
"private": false,
|
|
6
6
|
"files": [
|
|
@@ -59,9 +59,9 @@
|
|
|
59
59
|
"react-windowed-select": "5.0.0",
|
|
60
60
|
"screenfull": "6.0.1",
|
|
61
61
|
"ua-parser-js": "0.7.24",
|
|
62
|
+
"@fibery/react": "1.0.3",
|
|
62
63
|
"@fibery/helpers": "1.1.0",
|
|
63
|
-
"@fibery/emoji-data": "2.
|
|
64
|
-
"@fibery/react": "1.0.3"
|
|
64
|
+
"@fibery/emoji-data": "2.1.0"
|
|
65
65
|
},
|
|
66
66
|
"peerDependencies": {
|
|
67
67
|
"react": "^18.2.0",
|
|
@@ -93,8 +93,8 @@
|
|
|
93
93
|
"svgo": "2.8.0",
|
|
94
94
|
"typescript": "5.0.3",
|
|
95
95
|
"unist-util-reduce": "0.2.2",
|
|
96
|
-
"@fibery/
|
|
97
|
-
"@fibery/
|
|
96
|
+
"@fibery/eslint-config": "8.3.0",
|
|
97
|
+
"@fibery/babel-preset": "7.2.0"
|
|
98
98
|
},
|
|
99
99
|
"jest": {
|
|
100
100
|
"testEnvironment": "jsdom",
|
package/src/antd/styles.ts
CHANGED
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
import {css} from "@linaria/core";
|
|
2
2
|
import {colors, layout, border, shadows, space, textStyles, themeVars, transition} from "../designSystem";
|
|
3
3
|
|
|
4
|
+
const inputVariables = {
|
|
5
|
+
default: {
|
|
6
|
+
"--input-border": themeVars.inputBorderColor,
|
|
7
|
+
"--input-hover-border": `0 0 0 1px ${themeVars.opacity.opacity40}`,
|
|
8
|
+
"--input-focus-border": `0 0 0 1px ${themeVars.opacity.opacity25}`,
|
|
9
|
+
"--input-focus-shadow": `0 0 0 3px ${themeVars.focus}`,
|
|
10
|
+
},
|
|
11
|
+
error: {
|
|
12
|
+
"--input-border": themeVars.inputErrorBorderColor,
|
|
13
|
+
"--input-hover-border": themeVars.inputErrorBorderHoverColor,
|
|
14
|
+
"--input-focus-border": themeVars.inputErrorBorderFocusColor,
|
|
15
|
+
"--input-focus-shadow": themeVars.inputErrorBorderFocusShadow,
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
4
19
|
export const inputOverrides = {
|
|
5
20
|
main: {
|
|
21
|
+
...inputVariables.default,
|
|
6
22
|
font: "inherit",
|
|
7
23
|
color: themeVars.textColor,
|
|
8
24
|
outline: "none",
|
|
@@ -16,18 +32,18 @@ export const inputOverrides = {
|
|
|
16
32
|
border: 0,
|
|
17
33
|
borderColor: colors.transparent,
|
|
18
34
|
transition: `box-shadow ${transition}`,
|
|
19
|
-
boxShadow:
|
|
35
|
+
boxShadow: "var(--input-border)",
|
|
20
36
|
letterSpacing: 0,
|
|
21
37
|
"::placeholder": {
|
|
22
38
|
color: themeVars.inputPlaceholderTextColor,
|
|
23
39
|
},
|
|
24
40
|
},
|
|
25
41
|
hover: {
|
|
26
|
-
boxShadow:
|
|
42
|
+
boxShadow: "var(--input-hover-border) !important",
|
|
27
43
|
transition: `box-shadow ${transition}`,
|
|
28
44
|
},
|
|
29
45
|
focus: {
|
|
30
|
-
boxShadow:
|
|
46
|
+
boxShadow: "var(--input-focus-border), var(--input-focus-shadow) !important",
|
|
31
47
|
transition: `box-shadow ${transition}`,
|
|
32
48
|
},
|
|
33
49
|
disabled: {
|
|
@@ -63,6 +79,7 @@ export const inputStyles = css`
|
|
|
63
79
|
},
|
|
64
80
|
"& .ant-input-group": {borderSpacing: 1, ...textStyles.regular},
|
|
65
81
|
"& .ant-input": inputOverrides.main,
|
|
82
|
+
"& .ant-input-status-error": inputVariables.error,
|
|
66
83
|
"& .ant-input[readonly].ant-input[readonly]": {...inputOverrides.disabled, cursor: "inherit"},
|
|
67
84
|
"& .ant-input:hover[readonly].ant-input:hover[readonly]": {...inputOverrides.disabled, cursor: "inherit"},
|
|
68
85
|
"& .ant-input-affix-wrapper:hover .ant-input[readonly]": {...inputOverrides.disabled, cursor: "inherit"},
|
package/src/designSystem.ts
CHANGED
|
@@ -157,6 +157,10 @@ const lightColors = {
|
|
|
157
157
|
inputBorderFocusColor: `0 0 0 1px ${getOpacities(slate.slate8).opacity100}`,
|
|
158
158
|
inputBorderBlendMode: "multiply",
|
|
159
159
|
inputPlaceholderTextColor: slate.slate9,
|
|
160
|
+
inputErrorBorderColor: `0 0 0 1px ${red.red11}`,
|
|
161
|
+
inputErrorBorderHoverColor: `0 0 0 1px ${getOpacities(red.red11).opacity40}`,
|
|
162
|
+
inputErrorBorderFocusColor: `0 0 0 1px ${getOpacities(red.red11).opacity25}`,
|
|
163
|
+
inputErrorBorderFocusShadow: `0 0 0 3px ${getOpacities(red.red11).opacity25}`,
|
|
160
164
|
buttonPrimaryColor: indigo.indigo10,
|
|
161
165
|
buttonColor: slate.slate10,
|
|
162
166
|
buttonPrimaryTextColor: slate.slate2,
|
|
@@ -269,12 +273,12 @@ const lightColors = {
|
|
|
269
273
|
gridHeaderTextColor: slate.slate9,
|
|
270
274
|
// cell
|
|
271
275
|
gridCellBgColor: whiteA.whiteA0,
|
|
272
|
-
gridCellBorderColor: slate.
|
|
276
|
+
gridCellBorderColor: slate.slate5,
|
|
273
277
|
gridPinnedCellBorderColor: slate.slate8,
|
|
274
278
|
gridDisabledCellBgColor: slate.slate2,
|
|
275
279
|
gridSelectedCellBgColor: indigo.indigo3,
|
|
276
280
|
gridHighlightedCellBgColor: indigo.indigo4,
|
|
277
|
-
gridSelectedCellBorderColor: indigo.
|
|
281
|
+
gridSelectedCellBorderColor: indigo.indigo6,
|
|
278
282
|
gridSelectedPinnedCellBorderColor: indigo.indigo7,
|
|
279
283
|
gridDisabledSelectedCellBgColor: indigo.indigo4,
|
|
280
284
|
gridActiveCellBorderColor: indigo.indigo8,
|
|
@@ -341,6 +345,10 @@ const darkColors = {
|
|
|
341
345
|
inputBorderFocusColor: `0 0 0 1px ${getOpacities(slateDark.slate9).opacity100}`,
|
|
342
346
|
inputBorderBlendMode: "lighten",
|
|
343
347
|
inputPlaceholderTextColor: slateDark.slate9,
|
|
348
|
+
inputErrorBorderColor: `0 0 0 1px ${red.red7}`,
|
|
349
|
+
inputErrorBorderHoverColor: `0 0 0 1px ${getOpacities(red.red7).opacity40}`,
|
|
350
|
+
inputErrorBorderFocusColor: `0 0 0 1px ${getOpacities(red.red7).opacity25}`,
|
|
351
|
+
inputErrorBorderFocusShadow: `0 0 0 3px ${getOpacities(red.red7).opacity25}`,
|
|
344
352
|
buttonPrimaryColor: indigoDark.indigo10,
|
|
345
353
|
buttonColor: slateDark.slate10,
|
|
346
354
|
buttonPrimaryTextColor: slate.slate6,
|
|
@@ -450,12 +458,12 @@ const darkColors = {
|
|
|
450
458
|
gridHeaderTextColor: slateDark.slate10,
|
|
451
459
|
// cell
|
|
452
460
|
gridCellBgColor: slateDark.slate2,
|
|
453
|
-
gridCellBorderColor: slateDark.
|
|
461
|
+
gridCellBorderColor: slateDark.slate6,
|
|
454
462
|
gridPinnedCellBorderColor: slateDark.slate8,
|
|
455
463
|
gridDisabledCellBgColor: slateDark.slate3,
|
|
456
464
|
gridSelectedCellBgColor: indigoDark.indigo3,
|
|
457
465
|
gridHighlightedCellBgColor: indigoDark.indigo5,
|
|
458
|
-
gridSelectedCellBorderColor: indigoDark.
|
|
466
|
+
gridSelectedCellBorderColor: indigoDark.indigo7,
|
|
459
467
|
gridSelectedPinnedCellBorderColor: indigoDark.indigo7,
|
|
460
468
|
gridDisabledSelectedCellBgColor: indigoDark.indigo5,
|
|
461
469
|
gridActiveCellBorderColor: indigoDark.indigo9,
|
|
@@ -925,13 +933,6 @@ export const textStyles = {
|
|
|
925
933
|
fontWeight: fontWeight.regular,
|
|
926
934
|
color: themeVars.textColor,
|
|
927
935
|
},
|
|
928
|
-
fake: {
|
|
929
|
-
fontFamily,
|
|
930
|
-
fontSize: typeSizes[7],
|
|
931
|
-
lineHeight: lineHeight.regular,
|
|
932
|
-
fontWeight: fontWeight.regular,
|
|
933
|
-
color: colors.shades.opacity25,
|
|
934
|
-
},
|
|
935
936
|
link: {
|
|
936
937
|
color: themeVars.linkColor,
|
|
937
938
|
borderBottom: themeVars.linkBorder,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {EmojiItem} from "@fibery/emoji-data/lib/src";
|
|
2
2
|
import {ColorPicker} from "../ColorPicker";
|
|
3
3
|
import {$TSFixMe} from "../tsfixme";
|
|
4
|
-
import {LazyIconDataStore} from "./lazy-icon-data-store";
|
|
4
|
+
import {LazyIconDataStore} from "./stores/lazy-icon-data-store";
|
|
5
5
|
import {EmojiPickerContent} from "./primitives/content";
|
|
6
6
|
import {EmojiPickerFooter} from "./primitives/footer";
|
|
7
7
|
import {EmojiPickerGrid} from "./primitives/grid";
|
|
@@ -7,7 +7,6 @@ import {EmojiPickerSearch} from "./primitives/search";
|
|
|
7
7
|
import {cx, css} from "@linaria/core";
|
|
8
8
|
import {EmojiPickerSkinTone} from "./primitives/skin-tone";
|
|
9
9
|
import {EmojiPickerGrid} from "./primitives/grid";
|
|
10
|
-
import {LazyEmojiDataStore} from "./lazy-emoji-data-store";
|
|
11
10
|
import {ColorPicker} from "../ColorPicker";
|
|
12
11
|
import {EmojiPickerFooter} from "./primitives/footer";
|
|
13
12
|
import {cardTypeColors} from "../designSystem";
|
|
@@ -19,45 +18,48 @@ export type EmojiPickerProps = {
|
|
|
19
18
|
getHoverColor?: (color: string) => string | undefined;
|
|
20
19
|
colors?: string[];
|
|
21
20
|
onColorSelect?: (c: $TSFixMe) => void;
|
|
21
|
+
onCustomCategorySettings?: () => void;
|
|
22
|
+
onSelect: (emoji: EmojiItem) => void;
|
|
22
23
|
showSkinTones?: boolean;
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
searchRef?: React.RefObject<HTMLInputElement>;
|
|
25
26
|
};
|
|
26
27
|
export const EmojiPicker: React.FC<EmojiPickerProps> = ({
|
|
27
28
|
onSelect,
|
|
28
29
|
color,
|
|
29
30
|
colors = cardTypeColors,
|
|
30
31
|
onColorSelect,
|
|
32
|
+
onCustomCategorySettings,
|
|
31
33
|
getHoverColor,
|
|
32
34
|
showSkinTones,
|
|
35
|
+
searchRef,
|
|
33
36
|
}) => {
|
|
34
37
|
return (
|
|
35
|
-
<EmojiPickerRoot i18n={i18n} onEmojiSelect={onSelect}>
|
|
38
|
+
<EmojiPickerRoot i18n={i18n} onEmojiSelect={onSelect} onCustomCategorySettings={onCustomCategorySettings}>
|
|
36
39
|
<EmojiPickerContent emojiHoverColor={color ? getHoverColor?.(color) : undefined} emojiSize={20}>
|
|
37
|
-
<
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
<EmojiPickerHeader>
|
|
41
|
+
<EmojiPickerSearch
|
|
42
|
+
searchRef={searchRef}
|
|
43
|
+
autoFocus
|
|
44
|
+
className={cx(
|
|
45
|
+
showSkinTones &&
|
|
46
|
+
css`
|
|
47
|
+
width: 250px !important;
|
|
48
|
+
`
|
|
49
|
+
)}
|
|
50
|
+
/>
|
|
51
|
+
{/* Temporary hacks. SkinTone should move to footer */}
|
|
52
|
+
{showSkinTones ? (
|
|
53
|
+
<EmojiPickerSkinTone
|
|
54
|
+
className={css`
|
|
55
|
+
position: absolute;
|
|
56
|
+
top: 4px;
|
|
57
|
+
right: 12px;
|
|
58
|
+
`}
|
|
47
59
|
/>
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
className={css`
|
|
52
|
-
position: absolute;
|
|
53
|
-
top: 4px;
|
|
54
|
-
right: 12px;
|
|
55
|
-
`}
|
|
56
|
-
/>
|
|
57
|
-
) : null}
|
|
58
|
-
</EmojiPickerHeader>
|
|
59
|
-
<EmojiPickerGrid />
|
|
60
|
-
</LazyEmojiDataStore>
|
|
60
|
+
) : null}
|
|
61
|
+
</EmojiPickerHeader>
|
|
62
|
+
<EmojiPickerGrid />
|
|
61
63
|
|
|
62
64
|
{onColorSelect ? (
|
|
63
65
|
<EmojiPickerFooter>
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import {LazyEmojiDataStore} from "./lazy-emoji-data-store";
|
|
2
1
|
import {Emoji as EmojiPrimitive, EmojiProps as EmojiPrimitiveProps} from "./primitives/emoji";
|
|
2
|
+
import {isApple} from "./utils/emoji-set";
|
|
3
|
+
import {detectEmojiSupportLevel} from "./utils/emoji-support";
|
|
3
4
|
|
|
4
5
|
export type EmojiProps = EmojiPrimitiveProps;
|
|
5
|
-
export const Emoji: React.FC<EmojiPrimitiveProps> =
|
|
6
|
-
return (
|
|
7
|
-
<LazyEmojiDataStore fallback={<div style={{width: props.size, height: props.size}} />}>
|
|
8
|
-
<EmojiPrimitive {...props} />
|
|
9
|
-
</LazyEmojiDataStore>
|
|
10
|
-
);
|
|
6
|
+
export const Emoji: React.FC<EmojiPrimitiveProps> = (props) => {
|
|
7
|
+
return <EmojiPrimitive version={detectEmojiSupportLevel()} native={isApple()} {...props} />;
|
|
11
8
|
};
|
|
@@ -23,8 +23,11 @@ export type IconEmojiPickerProps = React.PropsWithChildren<{
|
|
|
23
23
|
onIconSelect?: AppIconPickerProps["onSelect"];
|
|
24
24
|
onEmojiSelect?: EmojiPickerProps["onSelect"];
|
|
25
25
|
onColorSelect?: (c: $TSFixMe) => void;
|
|
26
|
+
onCreateClick?: () => void;
|
|
27
|
+
onCustomEmojisSettings?: () => void;
|
|
26
28
|
onReset?: () => void;
|
|
27
29
|
resetLabel?: string;
|
|
30
|
+
createLabel?: string;
|
|
28
31
|
defaultTab?: TabKey;
|
|
29
32
|
showColorPickerOnTabs?: TabKey[];
|
|
30
33
|
color?: string;
|
|
@@ -34,8 +37,11 @@ export const IconEmojiPicker: React.FC<IconEmojiPickerProps> = ({
|
|
|
34
37
|
onIconSelect,
|
|
35
38
|
onEmojiSelect,
|
|
36
39
|
onColorSelect,
|
|
40
|
+
onCreateClick,
|
|
41
|
+
onCustomEmojisSettings,
|
|
37
42
|
defaultTab,
|
|
38
43
|
onReset,
|
|
44
|
+
createLabel = "Add Emoji",
|
|
39
45
|
resetLabel = "Reset",
|
|
40
46
|
showColorPickerOnTabs = ["icons", "emojis"],
|
|
41
47
|
color,
|
|
@@ -51,13 +57,27 @@ export const IconEmojiPicker: React.FC<IconEmojiPickerProps> = ({
|
|
|
51
57
|
key: "emojis",
|
|
52
58
|
label: <TabHeader>Emojis</TabHeader>,
|
|
53
59
|
children: (
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
<>
|
|
61
|
+
<EmojiPicker
|
|
62
|
+
color={color}
|
|
63
|
+
onColorSelect={showColorPickerOnTabs.includes("emojis") ? onColorSelect : undefined}
|
|
64
|
+
onCustomCategorySettings={onCustomEmojisSettings}
|
|
65
|
+
getHoverColor={getHoverColor}
|
|
66
|
+
onSelect={onEmojiSelect}
|
|
67
|
+
showSkinTones
|
|
68
|
+
/>
|
|
69
|
+
{onCreateClick && (
|
|
70
|
+
<div
|
|
71
|
+
className={css`
|
|
72
|
+
padding: ${space.l}px;
|
|
73
|
+
`}
|
|
74
|
+
>
|
|
75
|
+
<Button size=":button-size/small" onClick={onCreateClick}>
|
|
76
|
+
{createLabel}
|
|
77
|
+
</Button>
|
|
78
|
+
</div>
|
|
79
|
+
)}
|
|
80
|
+
</>
|
|
61
81
|
),
|
|
62
82
|
},
|
|
63
83
|
onIconSelect && {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import {Category} from "@fibery/emoji-data";
|
|
2
2
|
import {CSSProperties, css} from "@linaria/core";
|
|
3
|
+
import {memo} from "react";
|
|
4
|
+
import {IconButton} from "../../Button/icon-button";
|
|
3
5
|
import {space, textStyles, themeVars} from "../../designSystem";
|
|
6
|
+
import {Settings} from "../../icons/react";
|
|
4
7
|
import {useEmojiPickerContentSettings} from "./content";
|
|
5
8
|
import {Emoji} from "./emoji";
|
|
6
9
|
import {emojiPadding} from "./layout";
|
|
@@ -17,6 +20,10 @@ const categoryLabelCss = css`
|
|
|
17
20
|
position: sticky;
|
|
18
21
|
top: 0;
|
|
19
22
|
|
|
23
|
+
display: flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
gap: ${space.xs}px;
|
|
26
|
+
|
|
20
27
|
/* is required only because contentVisibility lifts emoji up */
|
|
21
28
|
z-index: 1;
|
|
22
29
|
`;
|
|
@@ -58,7 +65,7 @@ const NotFound = () => {
|
|
|
58
65
|
type EmojiPickerCategoryProps = {
|
|
59
66
|
category: Category;
|
|
60
67
|
};
|
|
61
|
-
export const EmojiPickerCategory
|
|
68
|
+
export const EmojiPickerCategory = memo<EmojiPickerCategoryProps>(function EmojiPickerCategoryComponent({category}) {
|
|
62
69
|
const i18n = useEmojiPickerI18N();
|
|
63
70
|
const emojiPickerCtx = useEmojiPickerCtx();
|
|
64
71
|
const emojiContentSettings = useEmojiPickerContentSettings();
|
|
@@ -71,6 +78,11 @@ export const EmojiPickerCategory: React.FC<EmojiPickerCategoryProps> = ({categor
|
|
|
71
78
|
<section className={categoryCss} aria-label={label}>
|
|
72
79
|
<div className={categoryLabelCss}>
|
|
73
80
|
<span aria-hidden={true /* already labeled by the section aria-label */}>{label}</span>
|
|
81
|
+
{category.id === "custom" && emojiPickerCtx.onCustomCategorySettings && (
|
|
82
|
+
<IconButton onClick={emojiPickerCtx.onCustomCategorySettings}>
|
|
83
|
+
<Settings iconSize={16} />
|
|
84
|
+
</IconButton>
|
|
85
|
+
)}
|
|
74
86
|
</div>
|
|
75
87
|
|
|
76
88
|
{category.emojis.length > 0 ? (
|
|
@@ -90,10 +102,11 @@ export const EmojiPickerCategory: React.FC<EmojiPickerCategoryProps> = ({categor
|
|
|
90
102
|
<Emoji
|
|
91
103
|
onClick={emojiPickerCtx.onEmojiSelect}
|
|
92
104
|
emoji={id}
|
|
93
|
-
|
|
105
|
+
size={emojiContentSettings.emojiSize}
|
|
94
106
|
version={emojiPickerCtx.version}
|
|
95
107
|
skin={skin}
|
|
96
108
|
tooltip
|
|
109
|
+
native={emojiPickerCtx.native}
|
|
97
110
|
/>
|
|
98
111
|
</li>
|
|
99
112
|
);
|
|
@@ -104,4 +117,4 @@ export const EmojiPickerCategory: React.FC<EmojiPickerCategoryProps> = ({categor
|
|
|
104
117
|
)}
|
|
105
118
|
</section>
|
|
106
119
|
);
|
|
107
|
-
};
|
|
120
|
+
});
|
|
@@ -5,15 +5,31 @@ import {useEmojiDataStoreSelector} from "../stores/emoji-data-store";
|
|
|
5
5
|
import {emojiPadding} from "./layout";
|
|
6
6
|
|
|
7
7
|
const emojiWrapperCss = css`
|
|
8
|
+
color: ${themeVars.textColor};
|
|
9
|
+
font-family: "Segoe UI Emoji", "Segoe UI Symbol", "Segoe UI", "Apple Color Emoji", "Twemoji Mozilla",
|
|
10
|
+
"Noto Color Emoji", "Android Emoji";
|
|
11
|
+
`;
|
|
12
|
+
const emojiWrapperSizedCss = css`
|
|
8
13
|
display: inline-flex;
|
|
9
14
|
align-items: center;
|
|
10
15
|
justify-content: center;
|
|
11
|
-
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
const emojiInlineImgCss = css`
|
|
19
|
+
display: inline-block;
|
|
20
|
+
width: 1em;
|
|
21
|
+
height: 1em;
|
|
22
|
+
vertical-align: -0.1em;
|
|
23
|
+
`;
|
|
24
|
+
const emojiSizedImgCss = css`
|
|
25
|
+
width: 100%;
|
|
26
|
+
height: 100%;
|
|
12
27
|
`;
|
|
13
28
|
|
|
14
29
|
const clickableCss = css`
|
|
15
30
|
padding: ${emojiPadding}px;
|
|
16
31
|
cursor: pointer;
|
|
32
|
+
font-size: 0;
|
|
17
33
|
margin: 0;
|
|
18
34
|
border: none;
|
|
19
35
|
background: none;
|
|
@@ -25,11 +41,8 @@ const clickableCss = css`
|
|
|
25
41
|
}
|
|
26
42
|
`;
|
|
27
43
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
width: size,
|
|
31
|
-
height: size,
|
|
32
|
-
});
|
|
44
|
+
// This is needed to keep text-selection working for image based emojis Details: https://the.fibery.io/SoftDev/bug/Improve-selection-text-with-emojis-8963
|
|
45
|
+
const emptyPixel = "data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==";
|
|
33
46
|
|
|
34
47
|
export type EmojiProps = {
|
|
35
48
|
size?: number;
|
|
@@ -40,17 +53,17 @@ export type EmojiProps = {
|
|
|
40
53
|
version?: number;
|
|
41
54
|
onClick?: (emoji: EmojiItem, event: React.MouseEvent) => void;
|
|
42
55
|
skin?: EmojiSkin;
|
|
43
|
-
|
|
44
|
-
|
|
56
|
+
/** render emoji as native unicode if possible */
|
|
57
|
+
native?: boolean;
|
|
45
58
|
};
|
|
46
59
|
export const Emoji: React.FC<EmojiProps> = ({
|
|
47
60
|
emoji: idOrColons,
|
|
48
|
-
size = 20,
|
|
49
61
|
tooltip,
|
|
50
|
-
|
|
62
|
+
size,
|
|
51
63
|
onClick,
|
|
52
64
|
version = 14,
|
|
53
65
|
skin,
|
|
66
|
+
native,
|
|
54
67
|
}) => {
|
|
55
68
|
const emoji = useEmojiDataStoreSelector((store) => store.get(idOrColons, {skin}));
|
|
56
69
|
|
|
@@ -61,33 +74,15 @@ export const Emoji: React.FC<EmojiProps> = ({
|
|
|
61
74
|
let emojiNode: React.ReactNode;
|
|
62
75
|
|
|
63
76
|
if ("native" in emoji) {
|
|
64
|
-
if (version >= emoji.version) {
|
|
65
|
-
emojiNode =
|
|
66
|
-
<span
|
|
67
|
-
style={{
|
|
68
|
-
fontSize: size,
|
|
69
|
-
fontFamily:
|
|
70
|
-
'"Segoe UI Emoji", "Segoe UI Symbol", "Segoe UI", "Apple Color Emoji", "Twemoji Mozilla", "Noto Color Emoji", "Android Emoji"',
|
|
71
|
-
|
|
72
|
-
...(forceSize
|
|
73
|
-
? {
|
|
74
|
-
...getSizeStyle(size),
|
|
75
|
-
textAlign: "center",
|
|
76
|
-
lineHeight: 1,
|
|
77
|
-
wordBreak: "keep-all",
|
|
78
|
-
}
|
|
79
|
-
: {}),
|
|
80
|
-
}}
|
|
81
|
-
>
|
|
82
|
-
{emoji.native}
|
|
83
|
-
</span>
|
|
84
|
-
);
|
|
77
|
+
if (native && version >= emoji.version) {
|
|
78
|
+
emojiNode = emoji.native;
|
|
85
79
|
} else {
|
|
86
80
|
emojiNode = (
|
|
87
|
-
<
|
|
81
|
+
<img
|
|
82
|
+
alt={emoji.name}
|
|
83
|
+
className={cx(size ? emojiSizedImgCss : emojiInlineImgCss)}
|
|
84
|
+
src={emptyPixel}
|
|
88
85
|
style={{
|
|
89
|
-
...getSizeStyle(size),
|
|
90
|
-
|
|
91
86
|
backgroundImage: `url(${emoji.sprite.src})`,
|
|
92
87
|
backgroundSize: emoji.sprite.size,
|
|
93
88
|
backgroundPosition: emoji.sprite.position,
|
|
@@ -97,33 +92,39 @@ export const Emoji: React.FC<EmojiProps> = ({
|
|
|
97
92
|
}
|
|
98
93
|
} else {
|
|
99
94
|
emojiNode = (
|
|
100
|
-
<
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
backgroundRepeat: "no-repeat",
|
|
106
|
-
backgroundPosition: "center",
|
|
107
|
-
}}
|
|
95
|
+
<img
|
|
96
|
+
alt={emoji.name}
|
|
97
|
+
loading="lazy"
|
|
98
|
+
className={cx(size ? emojiSizedImgCss : emojiInlineImgCss)}
|
|
99
|
+
src={emoji.imageUrl}
|
|
108
100
|
/>
|
|
109
101
|
);
|
|
110
102
|
}
|
|
111
103
|
|
|
112
104
|
// TODO: use fibery's tooltip?
|
|
113
|
-
const title = tooltip ? emoji.
|
|
105
|
+
const title = tooltip ? emoji.shortNames[0] : undefined;
|
|
106
|
+
|
|
107
|
+
const style = size ? {fontSize: size, width: size, height: size} : {};
|
|
114
108
|
|
|
115
109
|
return onClick ? (
|
|
116
110
|
<button
|
|
117
111
|
type="button"
|
|
118
112
|
onClick={(e) => onClick(emoji, e)}
|
|
119
|
-
className={cx(
|
|
113
|
+
className={cx(clickableCss)}
|
|
120
114
|
title={title}
|
|
121
115
|
aria-label={emoji.name}
|
|
122
116
|
>
|
|
123
|
-
{
|
|
117
|
+
<span style={style} className={cx(emojiWrapperCss, size && emojiWrapperSizedCss)}>
|
|
118
|
+
{emojiNode}
|
|
119
|
+
</span>
|
|
124
120
|
</button>
|
|
125
121
|
) : (
|
|
126
|
-
<span
|
|
122
|
+
<span
|
|
123
|
+
style={style}
|
|
124
|
+
className={cx(emojiWrapperCss, size && emojiWrapperSizedCss)}
|
|
125
|
+
title={title}
|
|
126
|
+
aria-label={emoji.name}
|
|
127
|
+
>
|
|
127
128
|
{emojiNode}
|
|
128
129
|
</span>
|
|
129
130
|
);
|
|
@@ -5,10 +5,14 @@ import {EmojiPickerSearchProvider} from "./search-provider";
|
|
|
5
5
|
import {detectEmojiSupportLevel} from "../utils/emoji-support";
|
|
6
6
|
import {EmojiSkinProvider} from "./skin-provider";
|
|
7
7
|
import {add as addToFrequentlyUsed} from "../utils/frequently";
|
|
8
|
+
import {isApple} from "../utils/emoji-set";
|
|
9
|
+
import {useCallbackRef} from "@fibery/react/src/use-callback-ref";
|
|
8
10
|
|
|
9
11
|
type EmojiPickerCtx = {
|
|
10
12
|
version: number;
|
|
11
13
|
onEmojiSelect: (emoji: EmojiItem) => void;
|
|
14
|
+
onCustomCategorySettings?: () => void;
|
|
15
|
+
native: boolean;
|
|
12
16
|
};
|
|
13
17
|
const [Provider, useCtx] = createContext<EmojiPickerCtx>("EmojiPickerRoot");
|
|
14
18
|
|
|
@@ -49,19 +53,25 @@ export const EmojiPickerRoot: React.FC<
|
|
|
49
53
|
React.PropsWithChildren<{
|
|
50
54
|
i18n?: Partial<I18N>;
|
|
51
55
|
onEmojiSelect: (emoji: EmojiItem) => void;
|
|
56
|
+
onCustomCategorySettings?: () => void;
|
|
52
57
|
}>
|
|
53
|
-
> = ({i18n, onEmojiSelect, children}) => {
|
|
58
|
+
> = ({i18n, onEmojiSelect, onCustomCategorySettings, children}) => {
|
|
54
59
|
const mergedI18N = useMemo(() => ({...defaultI18N, ...i18n}), [i18n]);
|
|
55
60
|
|
|
61
|
+
const onEmojiSelectCb = useCallbackRef(onEmojiSelect);
|
|
62
|
+
const onCustomCategorySettingsCb = useCallbackRef(onCustomCategorySettings);
|
|
63
|
+
|
|
56
64
|
const emojiPickerCtx = useMemo(
|
|
57
65
|
() => ({
|
|
58
66
|
version: detectEmojiSupportLevel(),
|
|
67
|
+
native: isApple(),
|
|
68
|
+
onCustomCategorySettings: onCustomCategorySettingsCb,
|
|
59
69
|
onEmojiSelect(emoji: EmojiItem) {
|
|
60
|
-
|
|
70
|
+
onEmojiSelectCb(emoji);
|
|
61
71
|
addToFrequentlyUsed(emoji.id);
|
|
62
72
|
},
|
|
63
73
|
}),
|
|
64
|
-
[
|
|
74
|
+
[onEmojiSelectCb, onCustomCategorySettingsCb]
|
|
65
75
|
);
|
|
66
76
|
|
|
67
77
|
return (
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
+
import {EmojiItem} from "@fibery/emoji-data";
|
|
2
|
+
import {useComposedRefs} from "@fibery/react/src/use-composed-refs";
|
|
1
3
|
import {css, cx} from "@linaria/core";
|
|
2
|
-
import {useEmojiPickerCtx, useEmojiPickerI18N} from "./root";
|
|
3
4
|
import {startTransition, useId, useRef} from "react";
|
|
4
|
-
import
|
|
5
|
-
import Close from "../../icons/react/Close";
|
|
5
|
+
import {IconButton} from "../../Button/icon-button";
|
|
6
6
|
import {border, space, textStyles, themeVars} from "../../designSystem";
|
|
7
|
+
import Close from "../../icons/react/Close";
|
|
8
|
+
import Search from "../../icons/react/Search";
|
|
7
9
|
import {useEmojiDataStore} from "../stores/emoji-data-store";
|
|
10
|
+
import {useEmojiPickerCtx, useEmojiPickerI18N} from "./root";
|
|
8
11
|
import {useFoundEmojis, useSetFoundEmojis} from "./search-provider";
|
|
9
|
-
import {
|
|
10
|
-
import {EmojiItem} from "@fibery/emoji-data";
|
|
12
|
+
import {useEmojiSkin} from "./skin-provider";
|
|
11
13
|
|
|
12
14
|
const searchCss = css`
|
|
13
15
|
${textStyles.regular}
|
|
@@ -61,18 +63,24 @@ export type EmojiPickerSearchProps = {
|
|
|
61
63
|
autoFocus?: boolean;
|
|
62
64
|
|
|
63
65
|
className?: string;
|
|
66
|
+
|
|
67
|
+
searchRef?: React.RefObject<HTMLInputElement>;
|
|
64
68
|
};
|
|
65
69
|
|
|
66
|
-
export const EmojiPickerSearch: React.FC<EmojiPickerSearchProps> = ({autoFocus, className}) => {
|
|
70
|
+
export const EmojiPickerSearch: React.FC<EmojiPickerSearchProps> = ({autoFocus, className, searchRef}) => {
|
|
67
71
|
const ref = useRef<HTMLInputElement>(null);
|
|
72
|
+
|
|
73
|
+
const setRefs = useComposedRefs(ref, searchRef);
|
|
74
|
+
|
|
68
75
|
const i18n = useEmojiPickerI18N();
|
|
69
76
|
const emojiPicker = useEmojiPickerCtx();
|
|
70
77
|
const emojiData = useEmojiDataStore();
|
|
71
78
|
const foundEmojis = useFoundEmojis();
|
|
79
|
+
const skin = useEmojiSkin();
|
|
72
80
|
const _setFoundEmojis = useSetFoundEmojis();
|
|
73
81
|
|
|
74
82
|
const setFoundEmojis = (emojis: EmojiItem[] | null) => {
|
|
75
|
-
//
|
|
83
|
+
// improves responsiveness of input.
|
|
76
84
|
// TODO: better just implement a proper virtualization
|
|
77
85
|
startTransition(() => _setFoundEmojis(emojis));
|
|
78
86
|
};
|
|
@@ -98,17 +106,17 @@ export const EmojiPickerSearch: React.FC<EmojiPickerSearchProps> = ({autoFocus,
|
|
|
98
106
|
return (
|
|
99
107
|
<section className={cx(searchCss, className)} aria-label={i18n.search}>
|
|
100
108
|
<input
|
|
101
|
-
ref={
|
|
109
|
+
ref={setRefs}
|
|
110
|
+
autoFocus={autoFocus}
|
|
102
111
|
id={inputId}
|
|
103
112
|
className={searchInputCss}
|
|
104
113
|
type="search"
|
|
105
114
|
placeholder={i18n.search}
|
|
106
|
-
autoFocus={autoFocus}
|
|
107
115
|
onKeyDown={onKeyDown}
|
|
108
116
|
onChange={(e) => {
|
|
109
117
|
const value = e.target.value;
|
|
110
118
|
|
|
111
|
-
setFoundEmojis(value ? emojiData.search(value) : null);
|
|
119
|
+
setFoundEmojis(value ? emojiData.search(value, {skin}) : null);
|
|
112
120
|
}}
|
|
113
121
|
/>
|
|
114
122
|
{/*
|
|
@@ -7,14 +7,17 @@ import {useEmojiPickerCtx, useEmojiPickerI18N} from "./root";
|
|
|
7
7
|
import {useEmojiSkin, useSetEmojiSkin} from "./skin-provider";
|
|
8
8
|
|
|
9
9
|
const skinToneWrapperCss = css`
|
|
10
|
+
display: flex;
|
|
11
|
+
align-items: center;
|
|
10
12
|
background-color: ${themeVars.actionMenuBg};
|
|
11
13
|
font-size: 0;
|
|
12
14
|
line-height: 1.15;
|
|
15
|
+
|
|
16
|
+
/* magic constant until skin tone is removed from picker search bar */
|
|
17
|
+
height: 36px;
|
|
13
18
|
`;
|
|
14
19
|
|
|
15
20
|
const skinToneSwatchCss = css`
|
|
16
|
-
display: inline-block;
|
|
17
|
-
vertical-align: middle;
|
|
18
21
|
overflow: hidden;
|
|
19
22
|
width: 0;
|
|
20
23
|
padding: 0;
|
|
@@ -83,6 +86,7 @@ export const EmojiPickerSkinTone: React.FC<EmojiPickerSkinToneProps> = ({classNa
|
|
|
83
86
|
}}
|
|
84
87
|
emoji={`:hand::skin-tone-${skinTone}:`}
|
|
85
88
|
version={emojiPickerCtx.version}
|
|
89
|
+
native={emojiPickerCtx.native}
|
|
86
90
|
size={20}
|
|
87
91
|
/>
|
|
88
92
|
</span>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {makeEmojiData} from "@fibery/emoji-data";
|
|
2
2
|
import {Suspense, lazy, useEffect, useState} from "react";
|
|
3
|
-
import {getThemedUrl} from "
|
|
4
|
-
import {useThemeMode} from "
|
|
5
|
-
import {alignTheme} from "
|
|
6
|
-
import {EmojiDataStoreProvider, useEmojiDataStore} from "./
|
|
3
|
+
import {getThemedUrl} from "../../AppIconWithFallback";
|
|
4
|
+
import {useThemeMode} from "../../ThemeProvider";
|
|
5
|
+
import {alignTheme} from "../../theme-settings";
|
|
6
|
+
import {EmojiDataStoreProvider, useEmojiDataStore} from "./emoji-data-store";
|
|
7
7
|
|
|
8
8
|
const EMPTY_DATA = {
|
|
9
9
|
aliases: {},
|
|
@@ -13,7 +13,7 @@ const EMPTY_DATA = {
|
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
const LazyAppIcons = lazy(async () => {
|
|
16
|
-
const {default: appIcons} = await import("
|
|
16
|
+
const {default: appIcons} = await import("../../appIcons.json");
|
|
17
17
|
|
|
18
18
|
return {
|
|
19
19
|
default: function EmojiDataStore({children}: React.PropsWithChildren) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {FC, ReactNode, useMemo} from "react";
|
|
2
|
+
import {makeEmojiData} from "@fibery/emoji-data";
|
|
3
|
+
import {getEmojiSet} from "../utils/emoji-set";
|
|
4
|
+
import {EmojiDataStoreProvider} from "./emoji-data-store";
|
|
5
|
+
import rawData from "@fibery/emoji-data/data/all.json";
|
|
6
|
+
|
|
7
|
+
// Static means "preloaded", "non-lazy" here
|
|
8
|
+
|
|
9
|
+
interface EmojiDataProviderProps {
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const StaticEmojiDataStore: FC<EmojiDataProviderProps> = ({children}) => {
|
|
14
|
+
const emojiData = useMemo(() => makeEmojiData(rawData, {set: getEmojiSet()}), []);
|
|
15
|
+
return <EmojiDataStoreProvider data={emojiData}>{children}</EmojiDataStoreProvider>;
|
|
16
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {SpritesheetSet} from "@fibery/emoji-data";
|
|
2
2
|
|
|
3
|
-
const isApple = () => /Mac|iPhone|iPad/.test(navigator.userAgent);
|
|
3
|
+
export const isApple = () => /Mac|iPhone|iPad/.test(navigator.userAgent);
|
|
4
4
|
|
|
5
5
|
export const getEmojiSet = (): SpritesheetSet => (isApple() ? "apple" : "twitter");
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
|
|
2
|
+
// This icon file is generated automatically.
|
|
3
|
+
|
|
4
|
+
import { IconDefinition } from '../types';
|
|
5
|
+
|
|
6
|
+
const LineDivider: IconDefinition = {"icon":{"type":"root","children":[{"type":"element","tagName":"svg","properties":{"viewBox":"0 0 20 20"},"children":[{"type":"element","tagName":"path","properties":{"fillRule":"evenodd","clipRule":"evenodd","d":"M2 10a.75.75 0 0 1 .75-.75h14.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 10Z"},"children":[]}],"metadata":""}]},"name":"line-divider"};
|
|
7
|
+
|
|
8
|
+
export default LineDivider;
|
package/src/icons/ast/index.tsx
CHANGED
|
@@ -83,6 +83,7 @@ export { default as Items } from './Items';
|
|
|
83
83
|
export { default as Jira } from './Jira';
|
|
84
84
|
export { default as LeftPanel } from './LeftPanel';
|
|
85
85
|
export { default as Levels } from './Levels';
|
|
86
|
+
export { default as LineDivider } from './LineDivider';
|
|
86
87
|
export { default as Lock } from './Lock';
|
|
87
88
|
export { default as Markdown } from './Markdown';
|
|
88
89
|
export { default as MenuCollapser } from './MenuCollapser';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// This icon file is generated automatically.
|
|
2
|
+
|
|
3
|
+
import LineDividerSvg from '../ast/LineDivider';
|
|
4
|
+
import { Icon } from '../Icon';
|
|
5
|
+
import { IconBaseProps } from '../types';
|
|
6
|
+
|
|
7
|
+
const LineDivider = (
|
|
8
|
+
props: IconBaseProps,
|
|
9
|
+
): JSX.Element => <Icon {...props} icon={LineDividerSvg} />;
|
|
10
|
+
|
|
11
|
+
LineDivider.displayName = 'LineDivider';
|
|
12
|
+
export default LineDivider;
|
|
@@ -83,6 +83,7 @@ export { default as Items } from './Items';
|
|
|
83
83
|
export { default as Jira } from './Jira';
|
|
84
84
|
export { default as LeftPanel } from './LeftPanel';
|
|
85
85
|
export { default as Levels } from './Levels';
|
|
86
|
+
export { default as LineDivider } from './LineDivider';
|
|
86
87
|
export { default as Lock } from './Lock';
|
|
87
88
|
export { default as Markdown } from './Markdown';
|
|
88
89
|
export { default as MenuCollapser } from './MenuCollapser';
|
|
File without changes
|