@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.
Files changed (72) hide show
  1. package/package.json +8 -6
  2. package/src/Button/actions-button-compact.tsx +13 -0
  3. package/src/Button/actions-button.tsx +30 -0
  4. package/src/Button/button-base.tsx +2 -12
  5. package/src/Button/button.tsx +3 -34
  6. package/src/Select/components.tsx +6 -0
  7. package/src/Select/styles.ts +9 -4
  8. package/src/antd/styles.ts +5 -5
  9. package/src/designSystem.ts +11 -26
  10. package/src/emoji-picker/app-icon-picker.tsx +31 -26
  11. package/src/emoji-picker/emoji-picker-content-with-color.tsx +110 -0
  12. package/src/emoji-picker/emoji-picker.tsx +34 -39
  13. package/src/emoji-picker/icon-emoji-picker.tsx +3 -14
  14. package/src/emoji-picker/primitives/category.tsx +82 -25
  15. package/src/emoji-picker/primitives/emoji.tsx +7 -49
  16. package/src/emoji-picker/primitives/footer.tsx +3 -1
  17. package/src/emoji-picker/primitives/grid.tsx +56 -24
  18. package/src/emoji-picker/primitives/search.tsx +21 -11
  19. package/src/emoji-picker/primitives/skin-tone.tsx +18 -12
  20. package/src/emoji-picker/stores/emoji-data-store.tsx +0 -1
  21. package/src/emoji-picker/stores/lazy-icon-data-store.tsx +19 -30
  22. package/src/icons/ast/AiAssistant.ts +1 -1
  23. package/src/icons/ast/AiAvatar.ts +1 -1
  24. package/src/icons/ast/AppDetails.ts +1 -1
  25. package/src/icons/ast/AppTemplates.ts +1 -1
  26. package/src/icons/ast/BellCircle.ts +8 -0
  27. package/src/icons/ast/BellX.ts +8 -0
  28. package/src/icons/ast/Bolt.ts +8 -0
  29. package/src/icons/ast/CheckCircle.ts +8 -0
  30. package/src/icons/ast/ClockForward.ts +8 -0
  31. package/src/icons/ast/ColorCodingFilled.ts +8 -0
  32. package/src/icons/ast/Delete.ts +1 -1
  33. package/src/icons/ast/EmojiDelete.ts +8 -0
  34. package/src/icons/ast/ExtensionAssignments.ts +1 -1
  35. package/src/icons/ast/Fields.ts +1 -1
  36. package/src/icons/ast/Figma.ts +8 -0
  37. package/src/icons/ast/FocusMode.ts +1 -1
  38. package/src/icons/ast/GoogleDrive.ts +8 -0
  39. package/src/icons/ast/GoogleMap.ts +8 -0
  40. package/src/icons/ast/InvitePeople.ts +1 -1
  41. package/src/icons/ast/Loom.ts +8 -0
  42. package/src/icons/ast/Miro.ts +8 -0
  43. package/src/icons/ast/Mixpanel.ts +8 -0
  44. package/src/icons/ast/RicheditorBlockCode.ts +1 -1
  45. package/src/icons/ast/RicheditorBlockEmbed.ts +8 -0
  46. package/src/icons/ast/RicheditorImageZoom.ts +1 -1
  47. package/src/icons/ast/RicheditorMarkCode.ts +1 -1
  48. package/src/icons/ast/RicheditorOpenLink.ts +1 -1
  49. package/src/icons/ast/Shared.ts +1 -1
  50. package/src/icons/ast/Twitter.ts +8 -0
  51. package/src/icons/ast/Youtube.ts +8 -0
  52. package/src/icons/ast/index.tsx +16 -0
  53. package/src/icons/react/BellCircle.tsx +12 -0
  54. package/src/icons/react/BellX.tsx +12 -0
  55. package/src/icons/react/Bolt.tsx +12 -0
  56. package/src/icons/react/CheckCircle.tsx +12 -0
  57. package/src/icons/react/ClockForward.tsx +12 -0
  58. package/src/icons/react/ColorCodingFilled.tsx +12 -0
  59. package/src/icons/react/EmojiDelete.tsx +12 -0
  60. package/src/icons/react/Figma.tsx +12 -0
  61. package/src/icons/react/GoogleDrive.tsx +12 -0
  62. package/src/icons/react/GoogleMap.tsx +12 -0
  63. package/src/icons/react/Loom.tsx +12 -0
  64. package/src/icons/react/Miro.tsx +12 -0
  65. package/src/icons/react/Mixpanel.tsx +12 -0
  66. package/src/icons/react/RicheditorBlockEmbed.tsx +12 -0
  67. package/src/icons/react/Twitter.tsx +12 -0
  68. package/src/icons/react/Youtube.tsx +12 -0
  69. package/src/icons/react/index.tsx +16 -0
  70. package/src/tooltip.tsx +75 -53
  71. package/src/Button/icon-button-with-tooltip.tsx +0 -15
  72. 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 {CSSProperties, css} from "@linaria/core";
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 EmojiPickerCategoryProps = {
66
- category: Category;
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
- export const EmojiPickerCategory = memo<EmojiPickerCategoryProps>(function EmojiPickerCategoryComponent({category}) {
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 skin = useEmojiSkin();
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
- <span aria-hidden={true /* already labeled by the section aria-label */}>{label}</span>
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
- <Emoji
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 {border, themeVars} from "../../designSystem";
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 onClick ? (
110
- <button
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: 0 ${space.s}px;
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, useRef, useState} from "react";
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: 238px;
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
- const AllCategories = () => {
18
+ type AllCategoriesProps = Pick<EmojiPickerCategoryProps, "renderEmoji" | "scrollParent">;
19
+ const AllCategories: React.FC<AllCategoriesProps> = ({renderEmoji, scrollParent}) => {
19
20
  const contentSettings = useEmojiPickerContentSettings();
20
21
 
21
- // TODO: filter frequently used to not have zombie dom nodes
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 ? <EmojiPickerCategory category={recentCategory} /> : null}
33
- {categories.map((category) => {
34
- return <EmojiPickerCategory key={category.id} category={category} />;
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 ref = useRef<HTMLDivElement>(null);
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 (ref.current) {
58
- ref.current.scrollTop = 0;
85
+ if (gridElement) {
86
+ gridElement.scrollTop = 0;
59
87
  }
60
- }, [foundEmojis]);
88
+ }, [foundEmojis, gridElement]);
61
89
 
62
90
  return (
63
- <div ref={ref} className={gridWrapperCss}>
64
- {searchCategory ? <EmojiPickerCategory category={searchCategory} /> : <AllCategories />}
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
- {foundEmojis ? (
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
- <span data-skin={skinTone}>
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
- </span>
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, useEffect, useState} from "react";
3
- import {getThemedUrl} from "../../AppIconWithFallback";
4
- import {useThemeMode} from "../../ThemeProvider";
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 {default: appIcons} = await import("../../appIcons.json");
14
+ const appIconsEmojiData = makeEmojiData(EMPTY_DATA);
17
15
 
18
- return {
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
- useEffect(() => {
25
- // TODO: icons should be rendered as svg elements
26
- const custom = appIcons.map((icon) => {
27
- return {
28
- ...icon,
29
- imageUrl: getThemedUrl(icon.file, alignedTheme),
30
- };
31
- });
18
+ const custom = appIcons.map(
19
+ (appIcon): RawCustomEmoji => ({
20
+ ...appIcon,
21
+ imageUrl: getImageUrl(appIcon) as string,
22
+ })
23
+ );
32
24
 
33
- emojiDataStore.setCustom(custom);
34
- }, [alignedTheme, emojiDataStore]);
25
+ appIconsEmojiData.setCustom(custom);
35
26
 
36
- return <>{children}</>;
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
- <EmojiDataStoreProvider data={emojiData}>
50
- <Suspense fallback={fallback}>
51
- <LazyAppIcons>{children}</LazyAppIcons>
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":{"width":20,"height":20},"children":[{"type":"element","tagName":"path","properties":{"d":"M3.27.185a.29.29 0 0 0-.54 0l-.522 1.358c-.09.235-.12.305-.16.36a.614.614 0 0 1-.144.146c-.056.04-.126.069-.36.159L.184 2.73a.29.29 0 0 0 0 .54l1.358.522c.235.09.305.12.36.16.057.04.106.088.146.144.04.056.069.126.159.36l.522 1.359a.29.29 0 0 0 .54 0l.522-1.358c.09-.235.12-.305.16-.36a.614.614 0 0 1 .144-.146c.056-.04.126-.069.36-.159l1.359-.522a.29.29 0 0 0 0-.54l-1.358-.522c-.235-.09-.305-.12-.36-.16a.614.614 0 0 1-.146-.144c-.04-.056-.069-.126-.159-.36L3.27.184Z"},"children":[]},{"type":"element","tagName":"path","properties":{"fillRule":"evenodd","clipRule":"evenodd","d":"M10.833 1.7a.8.8 0 0 1 .747.513l1.445 3.757c.25.65.33.842.44.997.11.155.246.29.4.4.156.111.349.191.998.441l3.757 1.445a.8.8 0 0 1 0 1.494l-3.757 1.445c-.65.25-.842.33-.997.44-.155.11-.29.246-.4.4-.111.156-.191.348-.441.998l-1.445 3.757a.8.8 0 0 1-1.494 0L8.641 14.03c-.25-.65-.33-.842-.44-.997a1.699 1.699 0 0 0-.4-.4c-.156-.111-.348-.191-.998-.441l-3.757-1.445a.8.8 0 0 1 0-1.494l3.757-1.445c.65-.25.842-.33.998-.44a1.7 1.7 0 0 0 .4-.4c.11-.156.19-.349.44-.998l1.445-3.757a.8.8 0 0 1 .747-.513Zm0 3.028-.73 1.9c-.204.53-.356.926-.598 1.266a3.3 3.3 0 0 1-.777.778c-.341.242-.737.394-1.267.598l-1.9.73 1.901.73c.53.204.925.356 1.266.598.3.214.563.477.777.777.242.341.394.736.597 1.266l.731 1.9.731-1.9c.204-.53.355-.925.598-1.266.213-.3.476-.563.777-.777.34-.242.735-.394 1.265-.597l1.9-.731-1.898-.73c-.53-.204-.926-.356-1.267-.598a3.302 3.302 0 0 1-.777-.778c-.243-.34-.395-.736-.598-1.266l-.73-1.9Z"},"children":[]},{"type":"element","tagName":"path","properties":{"d":"M3 14a.29.29 0 0 1 .27.185l.522 1.358c.09.235.12.305.16.36.04.057.088.106.144.146.056.04.126.069.36.159l1.359.522a.29.29 0 0 1 0 .54l-1.358.522c-.235.09-.305.12-.36.16a.616.616 0 0 0-.146.144c-.04.056-.069.126-.159.36l-.522 1.359a.29.29 0 0 1-.54 0l-.522-1.359c-.09-.234-.12-.304-.16-.36a.616.616 0 0 0-.144-.145c-.056-.04-.126-.069-.36-.159L.184 17.27a.29.29 0 0 1 0-.54l1.358-.522c.235-.09.305-.12.36-.16a.614.614 0 0 0 .146-.144c.04-.056.069-.126.159-.36l.522-1.359A.29.29 0 0 1 3 14Z"},"children":[]}],"metadata":""}]},"name":"ai-assistant"};
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;