@fibery/ui-kit 1.15.0 → 1.16.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 (57) hide show
  1. package/package.json +6 -5
  2. package/src/antd/styles.ts +4 -4
  3. package/src/designSystem.ts +6 -17
  4. package/src/emoji-picker/app-icon-picker.tsx +31 -26
  5. package/src/emoji-picker/emoji-picker-content-with-color.tsx +107 -0
  6. package/src/emoji-picker/emoji-picker.tsx +34 -39
  7. package/src/emoji-picker/icon-emoji-picker.tsx +3 -14
  8. package/src/emoji-picker/primitives/category.tsx +82 -25
  9. package/src/emoji-picker/primitives/emoji.tsx +7 -49
  10. package/src/emoji-picker/primitives/footer.tsx +3 -1
  11. package/src/emoji-picker/primitives/grid.tsx +53 -23
  12. package/src/emoji-picker/primitives/search.tsx +21 -11
  13. package/src/emoji-picker/primitives/skin-tone.tsx +18 -12
  14. package/src/emoji-picker/stores/emoji-data-store.tsx +0 -1
  15. package/src/emoji-picker/stores/lazy-icon-data-store.tsx +19 -30
  16. package/src/icons/ast/AiAssistant.ts +1 -1
  17. package/src/icons/ast/AiAvatar.ts +1 -1
  18. package/src/icons/ast/AppTemplates.ts +1 -1
  19. package/src/icons/ast/BellCircle.ts +8 -0
  20. package/src/icons/ast/BellX.ts +8 -0
  21. package/src/icons/ast/Bolt.ts +8 -0
  22. package/src/icons/ast/CheckCircle.ts +8 -0
  23. package/src/icons/ast/ClockForward.ts +8 -0
  24. package/src/icons/ast/ColorCodingFilled.ts +8 -0
  25. package/src/icons/ast/Delete.ts +1 -1
  26. package/src/icons/ast/EmojiDelete.ts +8 -0
  27. package/src/icons/ast/ExtensionAssignments.ts +1 -1
  28. package/src/icons/ast/Fields.ts +1 -1
  29. package/src/icons/ast/Figma.ts +8 -0
  30. package/src/icons/ast/GoogleDrive.ts +8 -0
  31. package/src/icons/ast/GoogleMap.ts +8 -0
  32. package/src/icons/ast/InvitePeople.ts +1 -1
  33. package/src/icons/ast/Loom.ts +8 -0
  34. package/src/icons/ast/Miro.ts +8 -0
  35. package/src/icons/ast/Mixpanel.ts +8 -0
  36. package/src/icons/ast/Shared.ts +1 -1
  37. package/src/icons/ast/Twitter.ts +8 -0
  38. package/src/icons/ast/Youtube.ts +8 -0
  39. package/src/icons/ast/index.tsx +15 -0
  40. package/src/icons/react/BellCircle.tsx +12 -0
  41. package/src/icons/react/BellX.tsx +12 -0
  42. package/src/icons/react/Bolt.tsx +12 -0
  43. package/src/icons/react/CheckCircle.tsx +12 -0
  44. package/src/icons/react/ClockForward.tsx +12 -0
  45. package/src/icons/react/ColorCodingFilled.tsx +12 -0
  46. package/src/icons/react/EmojiDelete.tsx +12 -0
  47. package/src/icons/react/Figma.tsx +12 -0
  48. package/src/icons/react/GoogleDrive.tsx +12 -0
  49. package/src/icons/react/GoogleMap.tsx +12 -0
  50. package/src/icons/react/Loom.tsx +12 -0
  51. package/src/icons/react/Miro.tsx +12 -0
  52. package/src/icons/react/Mixpanel.tsx +12 -0
  53. package/src/icons/react/Twitter.tsx +12 -0
  54. package/src/icons/react/Youtube.tsx +12 -0
  55. package/src/icons/react/index.tsx +15 -0
  56. package/src/Button/icon-button-with-tooltip.tsx +0 -15
  57. package/src/emoji-picker/index.tsx +0 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fibery/ui-kit",
3
- "version": "1.15.0",
3
+ "version": "1.16.0",
4
4
  "main": "index.ts",
5
5
  "private": false,
6
6
  "files": [
@@ -28,6 +28,7 @@
28
28
  ],
29
29
  "license": "UNLICENSED",
30
30
  "dependencies": {
31
+ "react-intersection-observer": "9.3.5",
31
32
  "@linaria/core": "3.0.0-beta.15",
32
33
  "@linaria/react": "3.0.0-beta.15",
33
34
  "@popperjs/core": "2.11.6",
@@ -59,9 +60,9 @@
59
60
  "react-windowed-select": "5.0.0",
60
61
  "screenfull": "6.0.1",
61
62
  "ua-parser-js": "0.7.24",
62
- "@fibery/react": "1.0.3",
63
63
  "@fibery/helpers": "1.1.0",
64
- "@fibery/emoji-data": "2.1.0"
64
+ "@fibery/react": "1.0.3",
65
+ "@fibery/emoji-data": "2.1.1"
65
66
  },
66
67
  "peerDependencies": {
67
68
  "react": "^18.2.0",
@@ -93,8 +94,8 @@
93
94
  "svgo": "2.8.0",
94
95
  "typescript": "5.0.3",
95
96
  "unist-util-reduce": "0.2.2",
96
- "@fibery/eslint-config": "8.3.0",
97
- "@fibery/babel-preset": "7.2.0"
97
+ "@fibery/babel-preset": "7.2.0",
98
+ "@fibery/eslint-config": "8.3.0"
98
99
  },
99
100
  "jest": {
100
101
  "testEnvironment": "jsdom",
@@ -22,7 +22,7 @@ export const inputOverrides = {
22
22
  font: "inherit",
23
23
  color: themeVars.textColor,
24
24
  outline: "none",
25
- backgroundColor: themeVars.inputBgColor,
25
+ backgroundColor: `${themeVars.inputBgColor} !important`,
26
26
  borderRadius: border.radius6,
27
27
  minHeight: layout.inputHeight,
28
28
  paddingTop: 0,
@@ -32,18 +32,18 @@ export const inputOverrides = {
32
32
  border: 0,
33
33
  borderColor: colors.transparent,
34
34
  transition: `box-shadow ${transition}`,
35
- boxShadow: "var(--input-border)",
35
+ boxShadow: `var(--input-border, ${themeVars.inputBorderColor})`,
36
36
  letterSpacing: 0,
37
37
  "::placeholder": {
38
38
  color: themeVars.inputPlaceholderTextColor,
39
39
  },
40
40
  },
41
41
  hover: {
42
- boxShadow: "var(--input-hover-border) !important",
42
+ boxShadow: `var(--input-hover-border, 0 0 0 1px ${themeVars.opacity.opacity40}) !important`,
43
43
  transition: `box-shadow ${transition}`,
44
44
  },
45
45
  focus: {
46
- boxShadow: "var(--input-focus-border), var(--input-focus-shadow) !important",
46
+ boxShadow: `var(--input-focus-border, 0 0 0 1px ${themeVars.opacity.opacity25}), var(--input-focus-shadow, 0 0 0 3px ${themeVars.focus}) !important`,
47
47
  transition: `box-shadow ${transition}`,
48
48
  },
49
49
  disabled: {
@@ -245,6 +245,8 @@ const lightColors = {
245
245
  filterGroup1Bg: slate.slate2,
246
246
  filterGroup2Bg: slate.slate3,
247
247
  filterGroupBorder: blackA.blackA3,
248
+ colorBgNotificationFocusedDefault: indigo.indigo3,
249
+ colorBgNotificationFocusedHover: indigo.indigo4,
248
250
  //end
249
251
  disabledInversedTextColor: shades.opacity25,
250
252
  danger: red.red9,
@@ -430,6 +432,8 @@ const darkColors = {
430
432
  filterGroup1Bg: slateDark.slate6,
431
433
  filterGroup2Bg: slateDark.slate7,
432
434
  filterGroupBorder: whiteA.whiteA3,
435
+ colorBgNotificationFocusedDefault: getOpacities(indigoDark.indigo11).opacity20,
436
+ colorBgNotificationFocusedHover: getOpacities(indigoDark.indigo11).opacity30,
433
437
  //end
434
438
  disabledInversedTextColor: lights.opacity25,
435
439
  danger: redDark.red10,
@@ -600,24 +604,11 @@ export const getLinkedHighlightBackgroundColor = _.memoize(
600
604
  (themeMode, color) => `${themeMode}-${color}`
601
605
  );
602
606
 
603
- // REMOVE AFTER MIGRATION ON BACKEND
604
- export const migrateEnumColor = (color: string) => {
605
- const pastelIdx = pastelColors.indexOf(color);
606
-
607
- if (pastelIdx > -1) {
608
- return cardTypeColors[pastelIdx];
609
- }
610
-
611
- return color;
612
- };
613
-
614
607
  export const getEnumBackgroundColor = _.memoize(
615
608
  (mode: ThemeMode, color: string) => {
616
- const migratedColor = migrateEnumColor(color);
617
-
618
609
  return getThemeValue(mode, {
619
- light: a11yColor(getOpacities(migratedColor).opacity20, lightColors.whiteColor, 1),
620
- dark: a11yColor(getOpacities(migratedColor).opacity30, darkColors.menuBg, 1.3),
610
+ light: a11yColor(getOpacities(color).opacity20, lightColors.whiteColor, 1),
611
+ dark: a11yColor(getOpacities(color).opacity30, darkColors.menuBg, 1.3),
621
612
  });
622
613
  },
623
614
  (themeMode, color) => `${themeMode}-${color}`
@@ -660,8 +651,6 @@ export const cardTypeColors = [
660
651
  "#B04E31",
661
652
  ] as const;
662
653
 
663
- const pastelColors = cardTypeColors.map((color) => makeChromaColor(color).luminance(0.8).hex());
664
-
665
654
  export const dropCursorColor = chroma(brandColors.blue).alpha(0.7).css();
666
655
  export const colors = {
667
656
  ...getTypeColors(brandColors.green, "light"),
@@ -1,14 +1,11 @@
1
- import {EmojiItem} from "@fibery/emoji-data/lib/src";
2
- import {ColorPicker} from "../ColorPicker";
1
+ import {CustomEmoji, EmojiItem} from "@fibery/emoji-data";
2
+ import {useThemeMode} from "../ThemeProvider";
3
+ import {cardTypeColors, getIconColor} from "../designSystem";
3
4
  import {$TSFixMe} from "../tsfixme";
4
- import {LazyIconDataStore} from "./stores/lazy-icon-data-store";
5
+ import {EmojiPickerContentWithColor} from "./emoji-picker-content-with-color";
5
6
  import {EmojiPickerContent} from "./primitives/content";
6
- import {EmojiPickerFooter} from "./primitives/footer";
7
- import {EmojiPickerGrid} from "./primitives/grid";
8
- import {EmojiPickerHeader} from "./primitives/header";
9
7
  import {EmojiPickerRoot} from "./primitives/root";
10
- import {EmojiPickerSearch} from "./primitives/search";
11
- import {cardTypeColors} from "../designSystem";
8
+ import {LazyIconDataStore} from "./stores/lazy-icon-data-store";
12
9
 
13
10
  const i18n = {search: "Search icon...", notfound: "No Icons Found"};
14
11
 
@@ -19,35 +16,43 @@ export type AppIconPickerProps = {
19
16
  onColorSelect?: (c: $TSFixMe) => void;
20
17
 
21
18
  onSelect: (emoji: EmojiItem) => void;
19
+ autoFocus?: boolean;
22
20
  };
23
21
  export const AppIconPicker: React.FC<AppIconPickerProps> = ({
24
22
  onSelect,
25
23
  color,
26
- colors = cardTypeColors,
24
+ colors = cardTypeColors as unknown as string[],
27
25
  onColorSelect,
28
26
  getHoverColor,
27
+ autoFocus = true,
29
28
  }) => {
29
+ const themeMode = useThemeMode();
30
+
30
31
  return (
31
32
  <EmojiPickerRoot i18n={i18n} onEmojiSelect={onSelect}>
32
33
  <EmojiPickerContent emojiHoverColor={color ? getHoverColor?.(color) : undefined} emojiSize={20}>
33
- <LazyIconDataStore fallback={<div style={{height: "282px"}}></div>}>
34
- <EmojiPickerHeader>
35
- <EmojiPickerSearch autoFocus />
36
- </EmojiPickerHeader>
37
-
38
- <EmojiPickerGrid />
34
+ <LazyIconDataStore fallback={<div style={{height: "302px"}}></div>}>
35
+ <EmojiPickerContentWithColor
36
+ color={color}
37
+ colors={colors}
38
+ getHoverColor={getHoverColor}
39
+ onColorSelect={onColorSelect}
40
+ searchAutofocus={autoFocus}
41
+ renderEmoji={({emoji}) => (
42
+ <svg
43
+ style={{
44
+ display: "block",
45
+ width: 20,
46
+ height: 20,
47
+ fill: color ? getIconColor(themeMode, color) : undefined,
48
+ }}
49
+ viewBox="0 0 24 24"
50
+ >
51
+ <use href={(emoji as CustomEmoji).imageUrl} />
52
+ </svg>
53
+ )}
54
+ />
39
55
  </LazyIconDataStore>
40
-
41
- {onColorSelect ? (
42
- <EmojiPickerFooter>
43
- <ColorPicker
44
- colors={colors}
45
- getSwatchHoverColor={getHoverColor}
46
- color={color}
47
- onChangeComplete={onColorSelect}
48
- />
49
- </EmojiPickerFooter>
50
- ) : null}
51
56
  </EmojiPickerContent>
52
57
  </EmojiPickerRoot>
53
58
  );
@@ -0,0 +1,107 @@
1
+ import {css} from "@linaria/core";
2
+ import {useState} from "react";
3
+ import {IconButton} from "../Button/icon-button";
4
+ import {CollapsibleContent, CollapsibleRoot, CollapsibleTrigger} from "../Collapsible";
5
+ import {ColorPicker} from "../ColorPicker";
6
+ import {useThemeMode} from "../ThemeProvider";
7
+ import {getIconColor, space} from "../designSystem";
8
+ import ColorCoding from "../icons/react/ColorCoding";
9
+ import ColorCodingFilled from "../icons/react/ColorCodingFilled";
10
+ import {$TSFixMe} from "../tsfixme";
11
+ import {EmojiPickerGrid, EmojiPickerGridProps} from "./primitives/grid";
12
+ import {EmojiPickerHeader} from "./primitives/header";
13
+ import {EmojiPickerSearch} from "./primitives/search";
14
+
15
+ const colorPickerWrapperCss = css`
16
+ padding-top: ${space.l}px;
17
+ `;
18
+
19
+ const disableScrollCss = css`
20
+ overflow-y: hidden;
21
+ `;
22
+
23
+ type ColorPickerCollapsibleTrigger = {opened: boolean; color?: string};
24
+
25
+ const ColorPickerCollapsibleTrigger: React.FC<ColorPickerCollapsibleTrigger> = ({opened, color}) => {
26
+ const themeMode = useThemeMode();
27
+ const iconColor = color ? getIconColor(themeMode, color) : undefined;
28
+
29
+ const Icon = opened ? ColorCodingFilled : ColorCoding;
30
+
31
+ const label = opened ? "Close color picker" : "Open color picker";
32
+
33
+ return (
34
+ <CollapsibleTrigger asChild>
35
+ <IconButton
36
+ aria-label={label}
37
+ size="big"
38
+ className={css`
39
+ border-top-left-radius: 0;
40
+ border-bottom-left-radius: 0;
41
+ `}
42
+ >
43
+ <Icon color={iconColor} iconSize={18} containerSize={20} />
44
+ </IconButton>
45
+ </CollapsibleTrigger>
46
+ );
47
+ };
48
+
49
+ const collapsibleWrapperCss = css`
50
+ max-height: 302px; // search + emoji grid
51
+ overflow: hidden;
52
+ `;
53
+
54
+ export type EmojiPickerContentWithColorProps = {
55
+ color?: string;
56
+ getHoverColor?: (color: string) => string | undefined;
57
+ colors?: string[];
58
+ onColorSelect?: (c: $TSFixMe) => void;
59
+
60
+ searchRef?: React.RefObject<HTMLInputElement>;
61
+ searchAutofocus?: boolean;
62
+ } & Pick<EmojiPickerGridProps, "renderEmoji">;
63
+
64
+ export const EmojiPickerContentWithColor: React.FC<EmojiPickerContentWithColorProps> = ({
65
+ searchRef,
66
+ colors,
67
+ color,
68
+ onColorSelect,
69
+ getHoverColor,
70
+ searchAutofocus = true,
71
+ renderEmoji,
72
+ }) => {
73
+ const [colorOpened, setColorOpened] = useState(false);
74
+
75
+ const showColorPicker = Boolean(onColorSelect);
76
+
77
+ return (
78
+ <CollapsibleRoot
79
+ className={collapsibleWrapperCss}
80
+ open={colorOpened}
81
+ onOpenChange={setColorOpened}
82
+ onWheel={() => setColorOpened(false)}
83
+ >
84
+ <EmojiPickerHeader>
85
+ <EmojiPickerSearch
86
+ searchRef={searchRef}
87
+ autoFocus={searchAutofocus}
88
+ extraAction={showColorPicker ? <ColorPickerCollapsibleTrigger opened={colorOpened} color={color} /> : null}
89
+ />
90
+ {showColorPicker ? (
91
+ <CollapsibleContent>
92
+ <div className={colorPickerWrapperCss}>
93
+ <ColorPicker
94
+ colors={colors}
95
+ getSwatchHoverColor={getHoverColor}
96
+ color={color}
97
+ onChangeComplete={onColorSelect}
98
+ />
99
+ </div>
100
+ </CollapsibleContent>
101
+ ) : null}
102
+ </EmojiPickerHeader>
103
+
104
+ <EmojiPickerGrid className={colorOpened ? disableScrollCss : undefined} renderEmoji={renderEmoji} />
105
+ </CollapsibleRoot>
106
+ );
107
+ };
@@ -1,15 +1,13 @@
1
1
  import {EmojiItem} from "@fibery/emoji-data/lib/src";
2
+ import {css} from "@linaria/core";
3
+ import {Button} from "../Button";
4
+ import {cardTypeColors, space} from "../designSystem";
2
5
  import {$TSFixMe} from "../tsfixme";
3
- import {EmojiPickerRoot} from "./primitives/root";
6
+ import {EmojiPickerContentWithColor} from "./emoji-picker-content-with-color";
4
7
  import {EmojiPickerContent} from "./primitives/content";
5
- import {EmojiPickerHeader} from "./primitives/header";
6
- import {EmojiPickerSearch} from "./primitives/search";
7
- import {cx, css} from "@linaria/core";
8
- import {EmojiPickerSkinTone} from "./primitives/skin-tone";
9
- import {EmojiPickerGrid} from "./primitives/grid";
10
- import {ColorPicker} from "../ColorPicker";
11
8
  import {EmojiPickerFooter} from "./primitives/footer";
12
- import {cardTypeColors} from "../designSystem";
9
+ import {EmojiPickerRoot} from "./primitives/root";
10
+ import {EmojiPickerSkinTone} from "./primitives/skin-tone";
13
11
 
14
12
  const i18n = {search: "Search emoji..."};
15
13
 
@@ -19,6 +17,7 @@ export type EmojiPickerProps = {
19
17
  colors?: string[];
20
18
  onColorSelect?: (c: $TSFixMe) => void;
21
19
  onCustomCategorySettings?: () => void;
20
+ onAddCustomEmoji?: () => void;
22
21
  onSelect: (emoji: EmojiItem) => void;
23
22
  showSkinTones?: boolean;
24
23
 
@@ -27,48 +26,44 @@ export type EmojiPickerProps = {
27
26
  export const EmojiPicker: React.FC<EmojiPickerProps> = ({
28
27
  onSelect,
29
28
  color,
30
- colors = cardTypeColors,
29
+ colors = cardTypeColors as unknown as string[],
31
30
  onColorSelect,
32
31
  onCustomCategorySettings,
32
+ onAddCustomEmoji,
33
33
  getHoverColor,
34
34
  showSkinTones,
35
35
  searchRef,
36
36
  }) => {
37
+ const showFooter = Boolean(onAddCustomEmoji) || showSkinTones;
38
+
37
39
  return (
38
40
  <EmojiPickerRoot i18n={i18n} onEmojiSelect={onSelect} onCustomCategorySettings={onCustomCategorySettings}>
39
41
  <EmojiPickerContent emojiHoverColor={color ? getHoverColor?.(color) : undefined} emojiSize={20}>
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
- `}
59
- />
60
- ) : null}
61
- </EmojiPickerHeader>
62
- <EmojiPickerGrid />
42
+ <EmojiPickerContentWithColor
43
+ color={color}
44
+ colors={colors}
45
+ getHoverColor={getHoverColor}
46
+ onColorSelect={onColorSelect}
47
+ searchRef={searchRef}
48
+ />
63
49
 
64
- {onColorSelect ? (
50
+ {showFooter ? (
65
51
  <EmojiPickerFooter>
66
- <ColorPicker
67
- colors={colors}
68
- getSwatchHoverColor={getHoverColor}
69
- color={color}
70
- onChangeComplete={onColorSelect}
71
- />
52
+ {onAddCustomEmoji ? (
53
+ <Button size=":button-size/small" onClick={onAddCustomEmoji}>
54
+ Add Emoji
55
+ </Button>
56
+ ) : null}
57
+
58
+ {showSkinTones ? (
59
+ <EmojiPickerSkinTone
60
+ className={css`
61
+ position: absolute;
62
+ top: 4px;
63
+ right: ${space.l}px;
64
+ `}
65
+ />
66
+ ) : null}
72
67
  </EmojiPickerFooter>
73
68
  ) : null}
74
69
  </EmojiPickerContent>
@@ -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
  })}