@fibery/ui-kit 1.15.0 → 1.17.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 +8 -6
- package/src/Button/actions-button-compact.tsx +13 -0
- package/src/Button/actions-button.tsx +30 -0
- package/src/Button/button-base.tsx +2 -12
- package/src/Button/button.tsx +3 -34
- package/src/Select/components.tsx +6 -0
- package/src/Select/styles.ts +9 -4
- package/src/antd/styles.ts +5 -5
- package/src/designSystem.ts +11 -26
- package/src/emoji-picker/app-icon-picker.tsx +31 -26
- package/src/emoji-picker/emoji-picker-content-with-color.tsx +110 -0
- package/src/emoji-picker/emoji-picker.tsx +34 -39
- package/src/emoji-picker/icon-emoji-picker.tsx +3 -14
- package/src/emoji-picker/primitives/category.tsx +82 -25
- package/src/emoji-picker/primitives/emoji.tsx +7 -49
- package/src/emoji-picker/primitives/footer.tsx +3 -1
- package/src/emoji-picker/primitives/grid.tsx +56 -24
- package/src/emoji-picker/primitives/search.tsx +21 -11
- package/src/emoji-picker/primitives/skin-tone.tsx +18 -12
- package/src/emoji-picker/stores/emoji-data-store.tsx +0 -1
- package/src/emoji-picker/stores/lazy-icon-data-store.tsx +19 -30
- package/src/icons/ast/AiAssistant.ts +1 -1
- package/src/icons/ast/AiAvatar.ts +1 -1
- package/src/icons/ast/AppDetails.ts +1 -1
- package/src/icons/ast/AppTemplates.ts +1 -1
- package/src/icons/ast/BellCircle.ts +8 -0
- package/src/icons/ast/BellX.ts +8 -0
- package/src/icons/ast/Bolt.ts +8 -0
- package/src/icons/ast/CheckCircle.ts +8 -0
- package/src/icons/ast/ClockForward.ts +8 -0
- package/src/icons/ast/ColorCodingFilled.ts +8 -0
- package/src/icons/ast/Delete.ts +1 -1
- package/src/icons/ast/EmojiDelete.ts +8 -0
- package/src/icons/ast/ExtensionAssignments.ts +1 -1
- package/src/icons/ast/Fields.ts +1 -1
- package/src/icons/ast/Figma.ts +8 -0
- package/src/icons/ast/FocusMode.ts +1 -1
- package/src/icons/ast/GoogleDrive.ts +8 -0
- package/src/icons/ast/GoogleMap.ts +8 -0
- package/src/icons/ast/InvitePeople.ts +1 -1
- package/src/icons/ast/Loom.ts +8 -0
- package/src/icons/ast/Miro.ts +8 -0
- package/src/icons/ast/Mixpanel.ts +8 -0
- package/src/icons/ast/RicheditorBlockCode.ts +1 -1
- package/src/icons/ast/RicheditorBlockEmbed.ts +8 -0
- package/src/icons/ast/RicheditorImageZoom.ts +1 -1
- package/src/icons/ast/RicheditorMarkCode.ts +1 -1
- package/src/icons/ast/RicheditorOpenLink.ts +1 -1
- package/src/icons/ast/Shared.ts +1 -1
- package/src/icons/ast/Twitter.ts +8 -0
- package/src/icons/ast/Youtube.ts +8 -0
- package/src/icons/ast/index.tsx +16 -0
- package/src/icons/react/BellCircle.tsx +12 -0
- package/src/icons/react/BellX.tsx +12 -0
- package/src/icons/react/Bolt.tsx +12 -0
- package/src/icons/react/CheckCircle.tsx +12 -0
- package/src/icons/react/ClockForward.tsx +12 -0
- package/src/icons/react/ColorCodingFilled.tsx +12 -0
- package/src/icons/react/EmojiDelete.tsx +12 -0
- package/src/icons/react/Figma.tsx +12 -0
- package/src/icons/react/GoogleDrive.tsx +12 -0
- package/src/icons/react/GoogleMap.tsx +12 -0
- package/src/icons/react/Loom.tsx +12 -0
- package/src/icons/react/Miro.tsx +12 -0
- package/src/icons/react/Mixpanel.tsx +12 -0
- package/src/icons/react/RicheditorBlockEmbed.tsx +12 -0
- package/src/icons/react/Twitter.tsx +12 -0
- package/src/icons/react/Youtube.tsx +12 -0
- package/src/icons/react/index.tsx +16 -0
- package/src/tooltip.tsx +75 -53
- package/src/Button/icon-button-with-tooltip.tsx +0 -15
- package/src/emoji-picker/index.tsx +0 -5
|
@@ -6,6 +6,7 @@ import {space, textStyles} from "../designSystem";
|
|
|
6
6
|
import {$TSFixMe} from "../tsfixme";
|
|
7
7
|
import {AppIconPicker, AppIconPickerProps} from "./app-icon-picker";
|
|
8
8
|
import {EmojiPicker, EmojiPickerProps} from "./emoji-picker";
|
|
9
|
+
import EmojiDelete from "../icons/react/EmojiDelete";
|
|
9
10
|
|
|
10
11
|
const TabHeader: React.FC<React.PropsWithChildren> = ({children}) => (
|
|
11
12
|
<div
|
|
@@ -27,7 +28,6 @@ export type IconEmojiPickerProps = React.PropsWithChildren<{
|
|
|
27
28
|
onCustomEmojisSettings?: () => void;
|
|
28
29
|
onReset?: () => void;
|
|
29
30
|
resetLabel?: string;
|
|
30
|
-
createLabel?: string;
|
|
31
31
|
defaultTab?: TabKey;
|
|
32
32
|
showColorPickerOnTabs?: TabKey[];
|
|
33
33
|
color?: string;
|
|
@@ -41,7 +41,6 @@ export const IconEmojiPicker: React.FC<IconEmojiPickerProps> = ({
|
|
|
41
41
|
onCustomEmojisSettings,
|
|
42
42
|
defaultTab,
|
|
43
43
|
onReset,
|
|
44
|
-
createLabel = "Add Emoji",
|
|
45
44
|
resetLabel = "Reset",
|
|
46
45
|
showColorPickerOnTabs = ["icons", "emojis"],
|
|
47
46
|
color,
|
|
@@ -62,21 +61,11 @@ export const IconEmojiPicker: React.FC<IconEmojiPickerProps> = ({
|
|
|
62
61
|
color={color}
|
|
63
62
|
onColorSelect={showColorPickerOnTabs.includes("emojis") ? onColorSelect : undefined}
|
|
64
63
|
onCustomCategorySettings={onCustomEmojisSettings}
|
|
64
|
+
onAddCustomEmoji={onCreateClick}
|
|
65
65
|
getHoverColor={getHoverColor}
|
|
66
66
|
onSelect={onEmojiSelect}
|
|
67
67
|
showSkinTones
|
|
68
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
69
|
</>
|
|
81
70
|
),
|
|
82
71
|
},
|
|
@@ -100,7 +89,7 @@ export const IconEmojiPicker: React.FC<IconEmojiPickerProps> = ({
|
|
|
100
89
|
padding-right: ${space.l}px;
|
|
101
90
|
`}
|
|
102
91
|
>
|
|
103
|
-
<Button size={":button-size/small"} onClick={onReset}>
|
|
92
|
+
<Button Icon={EmojiDelete} size={":button-size/small"} onClick={onReset}>
|
|
104
93
|
{resetLabel}
|
|
105
94
|
</Button>
|
|
106
95
|
</div>
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import {Category} from "@fibery/emoji-data";
|
|
2
|
-
import {
|
|
1
|
+
import {Category, EmojiItem} from "@fibery/emoji-data";
|
|
2
|
+
import {css} from "@linaria/core";
|
|
3
3
|
import {memo} from "react";
|
|
4
|
+
import {useInView} from "react-intersection-observer";
|
|
4
5
|
import {IconButton} from "../../Button/icon-button";
|
|
5
6
|
import {space, textStyles, themeVars} from "../../designSystem";
|
|
6
7
|
import {Settings} from "../../icons/react";
|
|
8
|
+
import {useEmojiDataStoreSelector} from "../stores/emoji-data-store";
|
|
7
9
|
import {useEmojiPickerContentSettings} from "./content";
|
|
8
10
|
import {Emoji} from "./emoji";
|
|
9
11
|
import {emojiPadding} from "./layout";
|
|
@@ -24,7 +26,6 @@ const categoryLabelCss = css`
|
|
|
24
26
|
align-items: center;
|
|
25
27
|
gap: ${space.xs}px;
|
|
26
28
|
|
|
27
|
-
/* is required only because contentVisibility lifts emoji up */
|
|
28
29
|
z-index: 1;
|
|
29
30
|
`;
|
|
30
31
|
|
|
@@ -62,22 +63,89 @@ const NotFound = () => {
|
|
|
62
63
|
);
|
|
63
64
|
};
|
|
64
65
|
|
|
65
|
-
type
|
|
66
|
-
|
|
66
|
+
type CategoryItemProps = {
|
|
67
|
+
id: string;
|
|
68
|
+
/** Custom renderer for emoji items */
|
|
69
|
+
renderEmoji?: (args: {emoji: EmojiItem}) => React.ReactNode;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const CategoryItem: React.FC<CategoryItemProps> = ({id, renderEmoji}) => {
|
|
73
|
+
const skin = useEmojiSkin();
|
|
74
|
+
const emojiPickerCtx = useEmojiPickerCtx();
|
|
75
|
+
const emojiContentSettings = useEmojiPickerContentSettings();
|
|
76
|
+
|
|
77
|
+
const emoji = useEmojiDataStoreSelector((store) => store.get(id));
|
|
78
|
+
|
|
79
|
+
if (!emoji) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// TODO: title should be shown in preview in footer
|
|
84
|
+
const title = emoji.shortNames ? emoji.shortNames[0] : undefined;
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<IconButton
|
|
88
|
+
size="big"
|
|
89
|
+
onClick={() => emojiPickerCtx.onEmojiSelect(emoji)}
|
|
90
|
+
className={css`
|
|
91
|
+
&:hover:not(:disabled),
|
|
92
|
+
&:focus-visible {
|
|
93
|
+
background-color: var(--fibery-emoji-hover-color, ${themeVars.badgeBgColor});
|
|
94
|
+
}
|
|
95
|
+
`}
|
|
96
|
+
title={title}
|
|
97
|
+
aria-label={emoji.name}
|
|
98
|
+
>
|
|
99
|
+
{renderEmoji ? (
|
|
100
|
+
renderEmoji({emoji})
|
|
101
|
+
) : (
|
|
102
|
+
<Emoji
|
|
103
|
+
emoji={id}
|
|
104
|
+
size={emojiContentSettings.emojiSize}
|
|
105
|
+
version={emojiPickerCtx.version}
|
|
106
|
+
skin={skin}
|
|
107
|
+
native={emojiPickerCtx.native}
|
|
108
|
+
/>
|
|
109
|
+
)}
|
|
110
|
+
</IconButton>
|
|
111
|
+
);
|
|
67
112
|
};
|
|
68
|
-
|
|
113
|
+
|
|
114
|
+
export type EmojiPickerCategoryProps = {
|
|
115
|
+
category: Category;
|
|
116
|
+
|
|
117
|
+
preRender?: boolean;
|
|
118
|
+
scrollParent: Element | null;
|
|
119
|
+
} & Pick<CategoryItemProps, "renderEmoji">;
|
|
120
|
+
|
|
121
|
+
export const EmojiPickerCategory = memo<EmojiPickerCategoryProps>(function EmojiPickerCategoryComponent({
|
|
122
|
+
category,
|
|
123
|
+
renderEmoji,
|
|
124
|
+
scrollParent,
|
|
125
|
+
preRender = false,
|
|
126
|
+
}) {
|
|
69
127
|
const i18n = useEmojiPickerI18N();
|
|
70
128
|
const emojiPickerCtx = useEmojiPickerCtx();
|
|
71
129
|
const emojiContentSettings = useEmojiPickerContentSettings();
|
|
72
130
|
|
|
131
|
+
const {ref, inView} = useInView({
|
|
132
|
+
root: scrollParent,
|
|
133
|
+
rootMargin: `129px 0px`, // grid height / 2
|
|
134
|
+
initialInView: preRender,
|
|
135
|
+
fallbackInView: true,
|
|
136
|
+
triggerOnce: true,
|
|
137
|
+
});
|
|
138
|
+
|
|
73
139
|
const label = i18n.categories[category.id as keyof (typeof i18n)["categories"]] || category.name;
|
|
74
140
|
|
|
75
|
-
const
|
|
141
|
+
const emojiSize = emojiContentSettings.emojiSize + emojiPadding * 2;
|
|
76
142
|
|
|
77
143
|
return (
|
|
78
144
|
<section className={categoryCss} aria-label={label}>
|
|
79
145
|
<div className={categoryLabelCss}>
|
|
80
|
-
|
|
146
|
+
{/* already labeled by the section aria-label */}
|
|
147
|
+
<span aria-hidden={true}>{label}</span>
|
|
148
|
+
|
|
81
149
|
{category.id === "custom" && emojiPickerCtx.onCustomCategorySettings && (
|
|
82
150
|
<IconButton onClick={emojiPickerCtx.onCustomCategorySettings}>
|
|
83
151
|
<Settings iconSize={16} />
|
|
@@ -86,28 +154,17 @@ export const EmojiPickerCategory = memo<EmojiPickerCategoryProps>(function Emoji
|
|
|
86
154
|
</div>
|
|
87
155
|
|
|
88
156
|
{category.emojis.length > 0 ? (
|
|
89
|
-
<ul className={categoryListCss}>
|
|
157
|
+
<ul ref={ref} className={categoryListCss}>
|
|
90
158
|
{category.emojis.map((id) => {
|
|
91
159
|
return (
|
|
92
160
|
<li
|
|
93
|
-
style={
|
|
94
|
-
// css-only virtualization until implementing the real one
|
|
95
|
-
{
|
|
96
|
-
contentVisibility: "auto",
|
|
97
|
-
containIntrinsicSize: `${emojiContentSettings.emojiSize + emojiPadding * 2}px`,
|
|
98
|
-
} as CSSProperties
|
|
99
|
-
}
|
|
100
161
|
key={id}
|
|
162
|
+
style={{
|
|
163
|
+
width: emojiSize,
|
|
164
|
+
height: emojiSize,
|
|
165
|
+
}}
|
|
101
166
|
>
|
|
102
|
-
<
|
|
103
|
-
onClick={emojiPickerCtx.onEmojiSelect}
|
|
104
|
-
emoji={id}
|
|
105
|
-
size={emojiContentSettings.emojiSize}
|
|
106
|
-
version={emojiPickerCtx.version}
|
|
107
|
-
skin={skin}
|
|
108
|
-
tooltip
|
|
109
|
-
native={emojiPickerCtx.native}
|
|
110
|
-
/>
|
|
167
|
+
{inView ? <CategoryItem id={id} renderEmoji={renderEmoji} /> : null}
|
|
111
168
|
</li>
|
|
112
169
|
);
|
|
113
170
|
})}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {EmojiItem, EmojiSkin} from "@fibery/emoji-data";
|
|
2
2
|
import {css, cx} from "@linaria/core";
|
|
3
|
-
import {
|
|
3
|
+
import {themeVars} from "../../designSystem";
|
|
4
4
|
import {useEmojiDataStoreSelector} from "../stores/emoji-data-store";
|
|
5
|
-
import {emojiPadding} from "./layout";
|
|
6
5
|
|
|
7
6
|
const emojiWrapperCss = css`
|
|
8
7
|
color: ${themeVars.textColor};
|
|
9
8
|
font-family: "Segoe UI Emoji", "Segoe UI Symbol", "Segoe UI", "Apple Color Emoji", "Twemoji Mozilla",
|
|
10
9
|
"Noto Color Emoji", "Android Emoji";
|
|
10
|
+
line-height: 1;
|
|
11
11
|
`;
|
|
12
12
|
const emojiWrapperSizedCss = css`
|
|
13
13
|
display: inline-flex;
|
|
@@ -20,25 +20,12 @@ const emojiInlineImgCss = css`
|
|
|
20
20
|
width: 1em;
|
|
21
21
|
height: 1em;
|
|
22
22
|
vertical-align: -0.1em;
|
|
23
|
+
object-fit: contain;
|
|
23
24
|
`;
|
|
24
25
|
const emojiSizedImgCss = css`
|
|
25
26
|
width: 100%;
|
|
26
27
|
height: 100%;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const clickableCss = css`
|
|
30
|
-
padding: ${emojiPadding}px;
|
|
31
|
-
cursor: pointer;
|
|
32
|
-
font-size: 0;
|
|
33
|
-
margin: 0;
|
|
34
|
-
border: none;
|
|
35
|
-
background: none;
|
|
36
|
-
box-shadow: none;
|
|
37
|
-
border-radius: ${border.radius8}px;
|
|
38
|
-
|
|
39
|
-
&:hover {
|
|
40
|
-
background-color: var(--fibery-emoji-hover-color, ${themeVars.badgeBgColor});
|
|
41
|
-
}
|
|
28
|
+
object-fit: contain;
|
|
42
29
|
`;
|
|
43
30
|
|
|
44
31
|
// 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
|
|
@@ -48,7 +35,6 @@ export type EmojiProps = {
|
|
|
48
35
|
size?: number;
|
|
49
36
|
/** id or colons */
|
|
50
37
|
emoji: string;
|
|
51
|
-
tooltip?: boolean;
|
|
52
38
|
/** max supported version of emojis */
|
|
53
39
|
version?: number;
|
|
54
40
|
onClick?: (emoji: EmojiItem, event: React.MouseEvent) => void;
|
|
@@ -56,15 +42,7 @@ export type EmojiProps = {
|
|
|
56
42
|
/** render emoji as native unicode if possible */
|
|
57
43
|
native?: boolean;
|
|
58
44
|
};
|
|
59
|
-
export const Emoji: React.FC<EmojiProps> = ({
|
|
60
|
-
emoji: idOrColons,
|
|
61
|
-
tooltip,
|
|
62
|
-
size,
|
|
63
|
-
onClick,
|
|
64
|
-
version = 14,
|
|
65
|
-
skin,
|
|
66
|
-
native,
|
|
67
|
-
}) => {
|
|
45
|
+
export const Emoji: React.FC<EmojiProps> = ({emoji: idOrColons, size, version = 14, skin, native}) => {
|
|
68
46
|
const emoji = useEmojiDataStoreSelector((store) => store.get(idOrColons, {skin}));
|
|
69
47
|
|
|
70
48
|
if (!emoji) {
|
|
@@ -101,30 +79,10 @@ export const Emoji: React.FC<EmojiProps> = ({
|
|
|
101
79
|
);
|
|
102
80
|
}
|
|
103
81
|
|
|
104
|
-
// TODO: use fibery's tooltip?
|
|
105
|
-
const title = tooltip ? emoji.shortNames[0] : undefined;
|
|
106
|
-
|
|
107
82
|
const style = size ? {fontSize: size, width: size, height: size} : {};
|
|
108
83
|
|
|
109
|
-
return
|
|
110
|
-
<
|
|
111
|
-
type="button"
|
|
112
|
-
onClick={(e) => onClick(emoji, e)}
|
|
113
|
-
className={cx(clickableCss)}
|
|
114
|
-
title={title}
|
|
115
|
-
aria-label={emoji.name}
|
|
116
|
-
>
|
|
117
|
-
<span style={style} className={cx(emojiWrapperCss, size && emojiWrapperSizedCss)}>
|
|
118
|
-
{emojiNode}
|
|
119
|
-
</span>
|
|
120
|
-
</button>
|
|
121
|
-
) : (
|
|
122
|
-
<span
|
|
123
|
-
style={style}
|
|
124
|
-
className={cx(emojiWrapperCss, size && emojiWrapperSizedCss)}
|
|
125
|
-
title={title}
|
|
126
|
-
aria-label={emoji.name}
|
|
127
|
-
>
|
|
84
|
+
return (
|
|
85
|
+
<span style={style} className={cx(emojiWrapperCss, size && emojiWrapperSizedCss)} aria-label={emoji.name}>
|
|
128
86
|
{emojiNode}
|
|
129
87
|
</span>
|
|
130
88
|
);
|
|
@@ -2,8 +2,10 @@ import {css} from "@linaria/core";
|
|
|
2
2
|
import {space} from "../../designSystem";
|
|
3
3
|
|
|
4
4
|
const footerCss = css`
|
|
5
|
+
position: relative;
|
|
5
6
|
border-top: 1px solid var(--fibery-color-separatorColor);
|
|
6
|
-
padding:
|
|
7
|
+
padding: ${space.m}px ${space.l}px;
|
|
8
|
+
min-height: 40px;
|
|
7
9
|
`;
|
|
8
10
|
|
|
9
11
|
export const EmojiPickerFooter: React.FC<React.PropsWithChildren> = ({children}) => {
|
|
@@ -1,45 +1,73 @@
|
|
|
1
|
-
import {css} from "@linaria/core";
|
|
2
|
-
import {useLayoutEffect, useMemo,
|
|
3
|
-
import {useEmojiDataStoreSelector} from "../stores/emoji-data-store";
|
|
1
|
+
import {css, cx} from "@linaria/core";
|
|
2
|
+
import {useLayoutEffect, useMemo, useState} from "react";
|
|
3
|
+
import {useEmojiDataStore, useEmojiDataStoreSelector} from "../stores/emoji-data-store";
|
|
4
4
|
import {get as getFrequentlyUsed} from "../utils/frequently";
|
|
5
|
-
import {EmojiPickerCategory} from "./category";
|
|
5
|
+
import {EmojiPickerCategory, EmojiPickerCategoryProps} from "./category";
|
|
6
6
|
import {useEmojiPickerContentSettings} from "./content";
|
|
7
7
|
import {contentHorizontalPadding} from "./layout";
|
|
8
8
|
import {useFoundEmojis} from "./search-provider";
|
|
9
9
|
|
|
10
10
|
const gridWrapperCss = css`
|
|
11
|
-
height:
|
|
11
|
+
height: 258px;
|
|
12
12
|
overflow-y: scroll;
|
|
13
13
|
overflow-x: hidden;
|
|
14
14
|
|
|
15
15
|
padding: 0 ${contentHorizontalPadding}px;
|
|
16
16
|
`;
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
type AllCategoriesProps = Pick<EmojiPickerCategoryProps, "renderEmoji" | "scrollParent">;
|
|
19
|
+
const AllCategories: React.FC<AllCategoriesProps> = ({renderEmoji, scrollParent}) => {
|
|
19
20
|
const contentSettings = useEmojiPickerContentSettings();
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
const [recentCategory] = useState(() => ({
|
|
23
|
-
id: "recent",
|
|
24
|
-
name: "Recent",
|
|
25
|
-
emojis: getFrequentlyUsed(contentSettings.perLine),
|
|
26
|
-
}));
|
|
27
|
-
|
|
22
|
+
const emojiData = useEmojiDataStore();
|
|
28
23
|
const categories = useEmojiDataStoreSelector((store) => store.getCategories());
|
|
29
24
|
|
|
25
|
+
const recentCategory = useMemo(
|
|
26
|
+
() => ({
|
|
27
|
+
id: "recent",
|
|
28
|
+
name: "Recent",
|
|
29
|
+
emojis: getFrequentlyUsed(contentSettings.perLine).filter((id) => {
|
|
30
|
+
const emoji = emojiData.get(id);
|
|
31
|
+
const hidden = emoji && "hidden" in emoji ? emoji.hidden : false;
|
|
32
|
+
return emoji && !hidden;
|
|
33
|
+
}),
|
|
34
|
+
}),
|
|
35
|
+
[contentSettings, emojiData]
|
|
36
|
+
);
|
|
37
|
+
|
|
30
38
|
return (
|
|
31
39
|
<>
|
|
32
|
-
{recentCategory.emojis.length > 0 ?
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
40
|
+
{recentCategory.emojis.length > 0 ? (
|
|
41
|
+
<EmojiPickerCategory
|
|
42
|
+
preRender
|
|
43
|
+
category={recentCategory}
|
|
44
|
+
scrollParent={scrollParent}
|
|
45
|
+
renderEmoji={renderEmoji}
|
|
46
|
+
/>
|
|
47
|
+
) : null}
|
|
48
|
+
{categories
|
|
49
|
+
.filter((category) => category.emojis.length !== 0)
|
|
50
|
+
.map((category, idx) => {
|
|
51
|
+
return (
|
|
52
|
+
<EmojiPickerCategory
|
|
53
|
+
preRender={idx < 2}
|
|
54
|
+
key={category.id}
|
|
55
|
+
category={category}
|
|
56
|
+
scrollParent={scrollParent}
|
|
57
|
+
renderEmoji={renderEmoji}
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
})}
|
|
36
61
|
</>
|
|
37
62
|
);
|
|
38
63
|
};
|
|
64
|
+
export type EmojiPickerGridProps = {
|
|
65
|
+
className?: string;
|
|
66
|
+
} & Pick<EmojiPickerCategoryProps, "renderEmoji">;
|
|
39
67
|
|
|
40
|
-
export const EmojiPickerGrid = () => {
|
|
68
|
+
export const EmojiPickerGrid: React.FC<EmojiPickerGridProps> = ({className, renderEmoji}) => {
|
|
41
69
|
const foundEmojis = useFoundEmojis();
|
|
42
|
-
const
|
|
70
|
+
const [gridElement, setGridElement] = useState<HTMLDivElement | null>(null);
|
|
43
71
|
|
|
44
72
|
const searchCategory = useMemo(
|
|
45
73
|
() =>
|
|
@@ -54,14 +82,18 @@ export const EmojiPickerGrid = () => {
|
|
|
54
82
|
);
|
|
55
83
|
|
|
56
84
|
useLayoutEffect(() => {
|
|
57
|
-
if (
|
|
58
|
-
|
|
85
|
+
if (gridElement) {
|
|
86
|
+
gridElement.scrollTop = 0;
|
|
59
87
|
}
|
|
60
|
-
}, [foundEmojis]);
|
|
88
|
+
}, [foundEmojis, gridElement]);
|
|
61
89
|
|
|
62
90
|
return (
|
|
63
|
-
<div ref={
|
|
64
|
-
{searchCategory ?
|
|
91
|
+
<div ref={setGridElement} className={cx(gridWrapperCss, className)}>
|
|
92
|
+
{searchCategory ? (
|
|
93
|
+
<EmojiPickerCategory preRender scrollParent={gridElement} category={searchCategory} renderEmoji={renderEmoji} />
|
|
94
|
+
) : (
|
|
95
|
+
<AllCategories scrollParent={gridElement} renderEmoji={renderEmoji} />
|
|
96
|
+
)}
|
|
65
97
|
</div>
|
|
66
98
|
);
|
|
67
99
|
};
|
|
@@ -13,6 +13,7 @@ import {useEmojiSkin} from "./skin-provider";
|
|
|
13
13
|
|
|
14
14
|
const searchCss = css`
|
|
15
15
|
${textStyles.regular}
|
|
16
|
+
position: relative;
|
|
16
17
|
width: 100%;
|
|
17
18
|
background-color: ${themeVars.inputBgColor};
|
|
18
19
|
box-shadow: ${themeVars.inputBorderColor};
|
|
@@ -22,7 +23,6 @@ const searchCss = css`
|
|
|
22
23
|
display: flex;
|
|
23
24
|
align-items: center;
|
|
24
25
|
padding-left: ${space.l}px;
|
|
25
|
-
padding-right: ${space.m}px;
|
|
26
26
|
gap: ${space.xxs}px;
|
|
27
27
|
|
|
28
28
|
/* For screenreaders only, via https://stackoverflow.com/a/19758620 */
|
|
@@ -59,15 +59,23 @@ const searchIconCss = css`
|
|
|
59
59
|
padding: ${space.xxs}px;
|
|
60
60
|
`;
|
|
61
61
|
|
|
62
|
+
const extraActionSlotCss = css`
|
|
63
|
+
background-color: var(--fibery-emoji-hover-color, ${themeVars.inputCopyBgColor});
|
|
64
|
+
border-top-right-radius: ${space.s}px;
|
|
65
|
+
border-bottom-right-radius: ${space.s}px;
|
|
66
|
+
`;
|
|
67
|
+
|
|
62
68
|
export type EmojiPickerSearchProps = {
|
|
63
69
|
autoFocus?: boolean;
|
|
64
70
|
|
|
65
71
|
className?: string;
|
|
66
72
|
|
|
67
73
|
searchRef?: React.RefObject<HTMLInputElement>;
|
|
74
|
+
|
|
75
|
+
extraAction?: React.ReactNode;
|
|
68
76
|
};
|
|
69
77
|
|
|
70
|
-
export const EmojiPickerSearch: React.FC<EmojiPickerSearchProps> = ({autoFocus, className, searchRef}) => {
|
|
78
|
+
export const EmojiPickerSearch: React.FC<EmojiPickerSearchProps> = ({autoFocus, className, searchRef, extraAction}) => {
|
|
71
79
|
const ref = useRef<HTMLInputElement>(null);
|
|
72
80
|
|
|
73
81
|
const setRefs = useComposedRefs(ref, searchRef);
|
|
@@ -105,6 +113,16 @@ export const EmojiPickerSearch: React.FC<EmojiPickerSearchProps> = ({autoFocus,
|
|
|
105
113
|
|
|
106
114
|
return (
|
|
107
115
|
<section className={cx(searchCss, className)} aria-label={i18n.search}>
|
|
116
|
+
{foundEmojis ? (
|
|
117
|
+
<IconButton size="small" onClick={clearSearch}>
|
|
118
|
+
<Close iconSize={16} />
|
|
119
|
+
</IconButton>
|
|
120
|
+
) : (
|
|
121
|
+
<span className={searchIconCss}>
|
|
122
|
+
<Search iconSize={16} />
|
|
123
|
+
</span>
|
|
124
|
+
)}
|
|
125
|
+
|
|
108
126
|
<input
|
|
109
127
|
ref={setRefs}
|
|
110
128
|
autoFocus={autoFocus}
|
|
@@ -127,15 +145,7 @@ export const EmojiPickerSearch: React.FC<EmojiPickerSearchProps> = ({autoFocus,
|
|
|
127
145
|
{i18n.search}
|
|
128
146
|
</label>
|
|
129
147
|
|
|
130
|
-
{
|
|
131
|
-
<IconButton size="small" onClick={clearSearch}>
|
|
132
|
-
<Close iconSize={16} />
|
|
133
|
-
</IconButton>
|
|
134
|
-
) : (
|
|
135
|
-
<span className={searchIconCss}>
|
|
136
|
-
<Search iconSize={16} />
|
|
137
|
-
</span>
|
|
138
|
-
)}
|
|
148
|
+
{extraAction ? <div className={extraActionSlotCss}>{extraAction}</div> : null}
|
|
139
149
|
</section>
|
|
140
150
|
);
|
|
141
151
|
};
|
|
@@ -5,16 +5,13 @@ import {textStyles, themeVars} from "../../designSystem";
|
|
|
5
5
|
import {Emoji} from "./emoji";
|
|
6
6
|
import {useEmojiPickerCtx, useEmojiPickerI18N} from "./root";
|
|
7
7
|
import {useEmojiSkin, useSetEmojiSkin} from "./skin-provider";
|
|
8
|
+
import {IconButton} from "../../Button/icon-button";
|
|
8
9
|
|
|
9
10
|
const skinToneWrapperCss = css`
|
|
10
11
|
display: flex;
|
|
11
12
|
align-items: center;
|
|
12
13
|
background-color: ${themeVars.actionMenuBg};
|
|
13
14
|
font-size: 0;
|
|
14
|
-
line-height: 1.15;
|
|
15
|
-
|
|
16
|
-
/* magic constant until skin tone is removed from picker search bar */
|
|
17
|
-
height: 36px;
|
|
18
15
|
`;
|
|
19
16
|
|
|
20
17
|
const skinToneSwatchCss = css`
|
|
@@ -53,6 +50,7 @@ const openedSkinTextCss = css`
|
|
|
53
50
|
display: inline-block;
|
|
54
51
|
vertical-align: middle;
|
|
55
52
|
${textStyles.small};
|
|
53
|
+
line-height: 1.3;
|
|
56
54
|
|
|
57
55
|
width: 98px;
|
|
58
56
|
color: ${themeVars.accentTextColor};
|
|
@@ -76,20 +74,28 @@ export const EmojiPickerSkinTone: React.FC<EmojiPickerSkinToneProps> = ({classNa
|
|
|
76
74
|
|
|
77
75
|
skinToneNodes.push(
|
|
78
76
|
<span key={`skin-tone-${skinTone}`} className={cx(skinToneSwatchCss, (selected || opened) && expandedCss)}>
|
|
79
|
-
<
|
|
77
|
+
<IconButton
|
|
78
|
+
size="big"
|
|
79
|
+
onClick={() => {
|
|
80
|
+
if (opened) {
|
|
81
|
+
setSkin(skinTone as EmojiSkin);
|
|
82
|
+
}
|
|
83
|
+
setOpened(!opened);
|
|
84
|
+
}}
|
|
85
|
+
className={css`
|
|
86
|
+
&:hover:not(:disabled),
|
|
87
|
+
&:focus-visible {
|
|
88
|
+
background-color: var(--fibery-emoji-hover-color, ${themeVars.badgeBgColor});
|
|
89
|
+
}
|
|
90
|
+
`}
|
|
91
|
+
>
|
|
80
92
|
<Emoji
|
|
81
|
-
onClick={() => {
|
|
82
|
-
if (opened) {
|
|
83
|
-
setSkin(skinTone as EmojiSkin);
|
|
84
|
-
}
|
|
85
|
-
setOpened(!opened);
|
|
86
|
-
}}
|
|
87
93
|
emoji={`:hand::skin-tone-${skinTone}:`}
|
|
88
94
|
version={emojiPickerCtx.version}
|
|
89
95
|
native={emojiPickerCtx.native}
|
|
90
96
|
size={20}
|
|
91
97
|
/>
|
|
92
|
-
</
|
|
98
|
+
</IconButton>
|
|
93
99
|
</span>
|
|
94
100
|
);
|
|
95
101
|
}
|
|
@@ -28,7 +28,6 @@ export const makeEmojiDataStore = (emojiData: EmojiData): EmojiDataStore => {
|
|
|
28
28
|
};
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
-
// TODO: this should probably wrap the whole app so every component shares the same data
|
|
32
31
|
const [DataStoreProvider, useDataStoreCtx] = createContext<EmojiDataStore>("EmojiDataStore");
|
|
33
32
|
|
|
34
33
|
export const EmojiDataStoreProvider: React.FC<
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import {makeEmojiData} from "@fibery/emoji-data";
|
|
2
|
-
import {Suspense, lazy
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {alignTheme} from "../../theme-settings";
|
|
6
|
-
import {EmojiDataStoreProvider, useEmojiDataStore} from "./emoji-data-store";
|
|
1
|
+
import {RawCustomEmoji, makeEmojiData} from "@fibery/emoji-data";
|
|
2
|
+
import {Suspense, lazy} from "react";
|
|
3
|
+
import {EmojiDataStoreProvider} from "./emoji-data-store";
|
|
4
|
+
import {getImageUrl} from "../../AppIconWithFallback";
|
|
7
5
|
|
|
8
6
|
const EMPTY_DATA = {
|
|
9
7
|
aliases: {},
|
|
@@ -13,27 +11,22 @@ const EMPTY_DATA = {
|
|
|
13
11
|
};
|
|
14
12
|
|
|
15
13
|
const LazyAppIcons = lazy(async () => {
|
|
16
|
-
const
|
|
14
|
+
const appIconsEmojiData = makeEmojiData(EMPTY_DATA);
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
default: function EmojiDataStore({children}: React.PropsWithChildren) {
|
|
20
|
-
const emojiDataStore = useEmojiDataStore();
|
|
21
|
-
const themeMode = useThemeMode();
|
|
22
|
-
const alignedTheme = alignTheme(themeMode);
|
|
16
|
+
const {default: appIcons} = await import("../../appIcons.json");
|
|
23
17
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
};
|
|
31
|
-
});
|
|
18
|
+
const custom = appIcons.map(
|
|
19
|
+
(appIcon): RawCustomEmoji => ({
|
|
20
|
+
...appIcon,
|
|
21
|
+
imageUrl: getImageUrl(appIcon) as string,
|
|
22
|
+
})
|
|
23
|
+
);
|
|
32
24
|
|
|
33
|
-
|
|
34
|
-
}, [alignedTheme, emojiDataStore]);
|
|
25
|
+
appIconsEmojiData.setCustom(custom);
|
|
35
26
|
|
|
36
|
-
|
|
27
|
+
return {
|
|
28
|
+
default: function EmojiDataStore({children}: React.PropsWithChildren) {
|
|
29
|
+
return <EmojiDataStoreProvider data={appIconsEmojiData}>{children}</EmojiDataStoreProvider>;
|
|
37
30
|
},
|
|
38
31
|
};
|
|
39
32
|
});
|
|
@@ -43,13 +36,9 @@ export const LazyIconDataStore: React.FC<
|
|
|
43
36
|
fallback: React.ReactNode;
|
|
44
37
|
}>
|
|
45
38
|
> = ({fallback, children}) => {
|
|
46
|
-
const [emojiData] = useState(() => makeEmojiData(EMPTY_DATA));
|
|
47
|
-
|
|
48
39
|
return (
|
|
49
|
-
<
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
</Suspense>
|
|
53
|
-
</EmojiDataStoreProvider>
|
|
40
|
+
<Suspense fallback={fallback}>
|
|
41
|
+
<LazyAppIcons>{children}</LazyAppIcons>
|
|
42
|
+
</Suspense>
|
|
54
43
|
);
|
|
55
44
|
};
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
|
|
4
4
|
import { IconDefinition } from '../types';
|
|
5
5
|
|
|
6
|
-
const AiAssistant: IconDefinition = {"icon":{"type":"root","children":[{"type":"element","tagName":"svg","properties":{"
|
|
6
|
+
const AiAssistant: IconDefinition = {"icon":{"type":"root","children":[{"type":"element","tagName":"svg","properties":{"viewBox":"0 0 20 20"},"children":[{"type":"element","tagName":"path","properties":{"d":"M3 0c.117 0 .221.072.263.181l.57 1.498a.844.844 0 0 0 .488.488l1.498.57a.281.281 0 0 1 0 .526l-1.498.57a.844.844 0 0 0-.488.488l-.57 1.498a.281.281 0 0 1-.526 0l-.57-1.498a.844.844 0 0 0-.488-.488L.18 3.263a.281.281 0 0 1 0-.526l1.498-.57a.844.844 0 0 0 .488-.488L2.737.18A.281.281 0 0 1 3 0ZM3 14c.117 0 .221.072.263.181l.57 1.498a.844.844 0 0 0 .488.488l1.498.57a.281.281 0 0 1 0 .526l-1.498.57a.844.844 0 0 0-.488.488l-.57 1.498a.281.281 0 0 1-.526 0l-.57-1.498a.844.844 0 0 0-.488-.488l-1.498-.57a.281.281 0 0 1 0-.526l1.498-.57a.844.844 0 0 0 .488-.488l.57-1.498A.281.281 0 0 1 3 14Z"},"children":[]},{"type":"element","tagName":"path","properties":{"fillRule":"evenodd","clipRule":"evenodd","d":"M11.7 2.483a.75.75 0 0 0-1.4 0L8.777 6.476a2.25 2.25 0 0 1-1.302 1.302L3.483 9.299a.75.75 0 0 0 0 1.402l3.993 1.521a2.25 2.25 0 0 1 1.302 1.302l1.521 3.993a.75.75 0 0 0 1.402 0l1.521-3.993a2.25 2.25 0 0 1 1.302-1.302l3.993-1.521a.75.75 0 0 0 0-1.402l-3.993-1.521a2.25 2.25 0 0 1-1.302-1.302l-1.521-3.993ZM10.18 7.01 11 4.857l.82 2.153a3.75 3.75 0 0 0 2.17 2.17l2.153.82-2.153.82a3.75 3.75 0 0 0-2.17 2.17L11 15.143l-.82-2.153a3.75 3.75 0 0 0-2.17-2.17L5.857 10l2.153-.82a3.75 3.75 0 0 0 2.17-2.17Z"},"children":[]}],"metadata":""}]},"name":"ai-assistant"};
|
|
7
7
|
|
|
8
8
|
export default AiAssistant;
|