@fibery/ui-kit 1.22.0 → 1.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fibery/ui-kit",
3
- "version": "1.22.0",
3
+ "version": "1.24.0",
4
4
  "main": "index.ts",
5
5
  "private": false,
6
6
  "files": [
@@ -66,12 +66,13 @@
66
66
  "react-popper": "2.3.0",
67
67
  "react-select": "5.3.2",
68
68
  "react-select-country-list": "2.2.1",
69
+ "react-virtuoso": "4.6.0",
69
70
  "react-windowed-select": "5.0.0",
70
71
  "screenfull": "6.0.1",
71
72
  "ua-parser-js": "0.7.24",
72
- "@fibery/react": "1.3.0",
73
+ "@fibery/emoji-data": "2.3.0",
73
74
  "@fibery/helpers": "1.2.0",
74
- "@fibery/emoji-data": "2.2.1"
75
+ "@fibery/react": "1.3.0"
75
76
  },
76
77
  "peerDependencies": {
77
78
  "react": "^18.2.0",
@@ -746,6 +746,8 @@ export const spaceNestedTreeInterval = 0;
746
746
  export const layout = {
747
747
  logoSize: 16,
748
748
  menuDefaultWidth: 243,
749
+ desktopToolbarHeight: 32,
750
+ desktopMenuMinWidth: 160,
749
751
  menuMinWidth: 47, //TODO: make dependency to itemHeight
750
752
  menuMaxWidth: 480,
751
753
  menuItemMinWidth: 80,
@@ -1,8 +1,9 @@
1
1
  import {CustomEmoji, EmojiItem} from "@fibery/emoji-data";
2
- import {useThemeMode} from "../theme-provider";
2
+ import {useCallback} from "react";
3
3
  import {cardTypeColors, getIconColor} from "../design-system";
4
+ import {useThemeMode} from "../theme-provider";
4
5
  import {$TSFixMe} from "../tsfixme";
5
- import {EmojiPickerContentWithColor} from "./emoji-picker-content-with-color";
6
+ import {EmojiPickerContentWithColor, EmojiPickerContentWithColorProps} from "./emoji-picker-content-with-color";
6
7
  import {EmojiPickerContent} from "./primitives/content";
7
8
  import {EmojiPickerRoot} from "./primitives/root";
8
9
  import {LazyIconDataStore} from "./stores/lazy-icon-data-store";
@@ -28,6 +29,23 @@ export const AppIconPicker: React.FC<AppIconPickerProps> = ({
28
29
  }) => {
29
30
  const themeMode = useThemeMode();
30
31
 
32
+ const renderEmoji = useCallback<NonNullable<EmojiPickerContentWithColorProps["renderEmoji"]>>(
33
+ ({emoji}) => (
34
+ <svg
35
+ style={{
36
+ display: "block",
37
+ width: 20,
38
+ height: 20,
39
+ fill: color ? getIconColor(themeMode, color) : undefined,
40
+ }}
41
+ viewBox="0 0 24 24"
42
+ >
43
+ <use href={(emoji as CustomEmoji).imageUrl} />
44
+ </svg>
45
+ ),
46
+ [color, themeMode]
47
+ );
48
+
31
49
  return (
32
50
  <EmojiPickerRoot i18n={i18n} onEmojiSelect={onSelect}>
33
51
  <EmojiPickerContent emojiHoverColor={color ? getHoverColor?.(color) : undefined} emojiSize={20}>
@@ -38,19 +56,7 @@ export const AppIconPicker: React.FC<AppIconPickerProps> = ({
38
56
  getHoverColor={getHoverColor}
39
57
  onColorSelect={onColorSelect}
40
58
  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
- )}
59
+ renderEmoji={renderEmoji}
54
60
  />
55
61
  </LazyIconDataStore>
56
62
  </EmojiPickerContent>
@@ -1,26 +1,22 @@
1
- import {css, cx} from "@linaria/core";
2
- import {useState} from "react";
1
+ import {css} from "@linaria/core";
2
+ import {useState, useRef} from "react";
3
3
  import {IconButton} from "../button/icon-button";
4
4
  import {CollapsibleContent, CollapsibleRoot, CollapsibleTrigger} from "../collapsible";
5
5
  import {ColorPicker} from "../color-picker";
6
- import {useThemeMode} from "../theme-provider";
7
6
  import {getIconColor, space} from "../design-system";
8
7
  import ColorCoding from "../icons/react/ColorCoding";
9
8
  import ColorCodingFilled from "../icons/react/ColorCodingFilled";
9
+ import {useThemeMode} from "../theme-provider";
10
+ import {Tooltip} from "../tooltip";
10
11
  import {$TSFixMe} from "../tsfixme";
11
- import {EmojiPickerGrid, EmojiPickerGridProps} from "./primitives/grid";
12
+ import {EmojiPickerGrid, type EmojiPickerGridHandle, EmojiPickerGridProps} from "./primitives/grid";
12
13
  import {EmojiPickerHeader} from "./primitives/header";
13
14
  import {EmojiPickerSearch} from "./primitives/search";
14
- import {Tooltip} from "../tooltip";
15
15
 
16
16
  const colorPickerWrapperCss = css`
17
17
  padding-top: ${space.s12}px;
18
18
  `;
19
19
 
20
- const disableScrollCss = css`
21
- overflow-y: hidden;
22
- `;
23
-
24
20
  type ColorPickerCollapsibleTrigger = {opened: boolean; color?: string};
25
21
 
26
22
  const ColorPickerCollapsibleTrigger: React.FC<ColorPickerCollapsibleTrigger> = ({opened, color}) => {
@@ -50,7 +46,7 @@ const ColorPickerCollapsibleTrigger: React.FC<ColorPickerCollapsibleTrigger> = (
50
46
  };
51
47
 
52
48
  const collapsibleWrapperCss = css`
53
- max-height: 302px; // search + emoji grid
49
+ max-height: 308px; // search + emoji grid
54
50
  overflow: hidden;
55
51
  `;
56
52
 
@@ -74,6 +70,7 @@ export const EmojiPickerContentWithColor: React.FC<EmojiPickerContentWithColorPr
74
70
  renderEmoji,
75
71
  }) => {
76
72
  const [colorOpened, setColorOpened] = useState(false);
73
+ const gridRef = useRef<EmojiPickerGridHandle>(null);
77
74
 
78
75
  const showColorPicker = Boolean(onColorSelect);
79
76
 
@@ -87,6 +84,7 @@ export const EmojiPickerContentWithColor: React.FC<EmojiPickerContentWithColorPr
87
84
  <EmojiPickerHeader>
88
85
  <EmojiPickerSearch
89
86
  searchRef={searchRef}
87
+ gridRef={gridRef}
90
88
  autoFocus={searchAutofocus}
91
89
  extraAction={showColorPicker ? <ColorPickerCollapsibleTrigger opened={colorOpened} color={color} /> : null}
92
90
  />
@@ -104,7 +102,7 @@ export const EmojiPickerContentWithColor: React.FC<EmojiPickerContentWithColorPr
104
102
  ) : null}
105
103
  </EmojiPickerHeader>
106
104
 
107
- <EmojiPickerGrid className={cx(colorOpened && disableScrollCss)} renderEmoji={renderEmoji} />
105
+ <EmojiPickerGrid ref={gridRef} disableScroll={colorOpened} renderEmoji={renderEmoji} />
108
106
  </CollapsibleRoot>
109
107
  );
110
108
  };
@@ -0,0 +1,94 @@
1
+ import type {EmojiItem} from "@fibery/emoji-data";
2
+ import {measureScrollbar} from "@fibery/helpers/utils/measure-scrollbar";
3
+ import {css} from "@linaria/core";
4
+ import {Button} from "../button";
5
+ import {fontFamily, space, themeVars, typeSizes} from "../design-system";
6
+ import {Emoji} from "./primitives/emoji";
7
+ import {EmojiPickerFooter} from "./primitives/footer";
8
+ import {useEmojiPreview} from "./primitives/preview-provider";
9
+ import {useEmojiPickerCtx} from "./primitives/root";
10
+ import {useEmojiSkin} from "./primitives/skin-provider";
11
+ import {EmojiPickerSkinTone} from "./primitives/skin-tone";
12
+
13
+ const previewCss = css`
14
+ display: flex;
15
+ align-items: center;
16
+ gap: ${space.s8}px;
17
+ overflow: hidden;
18
+ `;
19
+ const previewTitleWrapperCss = css`
20
+ max-width: 230px;
21
+ `;
22
+ const previewTitleCss = css`
23
+ font-family: ${fontFamily};
24
+ font-size: ${typeSizes[5]}px;
25
+ line-height: 1.25;
26
+ color: ${themeVars.textColor};
27
+
28
+ overflow: hidden;
29
+ text-overflow: ellipsis;
30
+ white-space: nowrap;
31
+ `;
32
+ const previewSubtitleCss = css`
33
+ font-family: ${fontFamily};
34
+ letter-spacing: 0.5px;
35
+ font-size: ${typeSizes[6]}px;
36
+ line-height: 1.25;
37
+ color: ${themeVars.accentTextColor};
38
+
39
+ overflow: hidden;
40
+ text-overflow: ellipsis;
41
+ white-space: nowrap;
42
+ `;
43
+
44
+ const EmojiPreview: React.FC<{emoji: EmojiItem}> = ({emoji}) => {
45
+ const emojiPickerCtx = useEmojiPickerCtx();
46
+ const skin = useEmojiSkin();
47
+
48
+ const title = emoji.name;
49
+ const subTitle = emoji.shortNames.map((short) => `:${short}:`).join(" ");
50
+
51
+ return (
52
+ <div className={previewCss}>
53
+ <Emoji emoji={emoji.id} size={24} version={emojiPickerCtx.version} skin={skin} native={emojiPickerCtx.native} />
54
+ <div className={previewTitleWrapperCss}>
55
+ <div className={previewTitleCss}>{title}</div>
56
+ <div className={previewSubtitleCss}>{subTitle}</div>
57
+ </div>
58
+ </div>
59
+ );
60
+ };
61
+
62
+ export const EmojiPickerPreviewFooter: React.FC<{
63
+ onAddCustomEmoji?: () => void;
64
+ showSkinTones?: boolean;
65
+ }> = ({onAddCustomEmoji, showSkinTones}) => {
66
+ const emojiPreview = useEmojiPreview();
67
+
68
+ return (
69
+ <EmojiPickerFooter>
70
+ {emojiPreview ? (
71
+ <EmojiPreview emoji={emojiPreview.emoji} />
72
+ ) : onAddCustomEmoji ? (
73
+ <Button size=":button-size/small" onClick={onAddCustomEmoji}>
74
+ Add Emoji
75
+ </Button>
76
+ ) : null}
77
+
78
+ {showSkinTones ? (
79
+ <div
80
+ className={css`
81
+ position: absolute;
82
+ top: 4px;
83
+ right: ${space.s12}px;
84
+ `}
85
+ style={{
86
+ right: space.s12 + measureScrollbar(),
87
+ }}
88
+ >
89
+ <EmojiPickerSkinTone />
90
+ </div>
91
+ ) : null}
92
+ </EmojiPickerFooter>
93
+ );
94
+ };
@@ -1,14 +1,10 @@
1
1
  import {EmojiItem} from "@fibery/emoji-data";
2
- import {css} from "@linaria/core";
3
- import {Button} from "../button";
4
- import {cardTypeColors, space} from "../design-system";
2
+ import {cardTypeColors} from "../design-system";
5
3
  import {$TSFixMe} from "../tsfixme";
6
4
  import {EmojiPickerContentWithColor} from "./emoji-picker-content-with-color";
5
+ import {EmojiPickerPreviewFooter} from "./emoji-picker-preview-footer";
7
6
  import {EmojiPickerContent} from "./primitives/content";
8
- import {EmojiPickerFooter} from "./primitives/footer";
9
7
  import {EmojiPickerRoot} from "./primitives/root";
10
- import {EmojiPickerSkinTone} from "./primitives/skin-tone";
11
- import {measureScrollbar} from "@fibery/helpers/utils/measure-scrollbar";
12
8
 
13
9
  const i18n = {search: "Search emoji..."};
14
10
 
@@ -49,28 +45,7 @@ export const EmojiPicker: React.FC<EmojiPickerProps> = ({
49
45
  />
50
46
 
51
47
  {showFooter ? (
52
- <EmojiPickerFooter>
53
- {onAddCustomEmoji ? (
54
- <Button size=":button-size/small" onClick={onAddCustomEmoji}>
55
- Add Emoji
56
- </Button>
57
- ) : null}
58
-
59
- {showSkinTones ? (
60
- <div
61
- className={css`
62
- position: absolute;
63
- top: 4px;
64
- right: ${space.s12}px;
65
- `}
66
- style={{
67
- right: space.s12 + measureScrollbar(),
68
- }}
69
- >
70
- <EmojiPickerSkinTone />
71
- </div>
72
- ) : null}
73
- </EmojiPickerFooter>
48
+ <EmojiPickerPreviewFooter onAddCustomEmoji={onAddCustomEmoji} showSkinTones={showSkinTones} />
74
49
  ) : null}
75
50
  </EmojiPickerContent>
76
51
  </EmojiPickerRoot>
@@ -1,7 +1,7 @@
1
1
  import {css} from "@linaria/core";
2
2
  import {compact} from "lodash";
3
3
  import {Button} from "../button";
4
- import {AntTabs} from "../antd";
4
+ import {AntTabs} from "../antd/Tabs";
5
5
  import {space, textStyles} from "../design-system";
6
6
  import {$TSFixMe} from "../tsfixme";
7
7
  import {AppIconPicker, AppIconPickerProps} from "./app-icon-picker";
@@ -0,0 +1,42 @@
1
+ import type {Category} from "@fibery/emoji-data";
2
+
3
+ import {css} from "@linaria/core";
4
+ import {space, textStyles, themeVars} from "../../design-system";
5
+ import {useEmojiPickerCtx, useEmojiPickerI18N} from "./root";
6
+ import {IconButton} from "../../button/icon-button";
7
+ import Settings from "../../icons/react/Settings";
8
+
9
+ const categoryLabelCss = css`
10
+ ${textStyles.heading6}
11
+ /* in designs it's 31px but we make it equal to emoji size just to make virtualization easier */
12
+ height: var(--fibery-emoji-container-size, 32px);
13
+ color: ${themeVars.accentTextColor};
14
+ background-color: ${themeVars.actionMenuBg};
15
+ padding: ${space.s12}px ${space.s6}px ${space.s4}px ${space.s6}px;
16
+
17
+ display: flex;
18
+ align-items: center;
19
+ gap: ${space.s4}px;
20
+ `;
21
+
22
+ type EmojiPickerCategoryLabelProps = {
23
+ category: Category;
24
+ };
25
+ export const EmojiPickerCategoryLabel: React.FC<EmojiPickerCategoryLabelProps> = ({category}) => {
26
+ const i18n = useEmojiPickerI18N();
27
+ const emojiPickerCtx = useEmojiPickerCtx();
28
+
29
+ const label = i18n.categories[category.id as keyof (typeof i18n)["categories"]] || category.name;
30
+
31
+ return (
32
+ <div className={categoryLabelCss}>
33
+ <span>{label}</span>
34
+
35
+ {category.id === "custom" && emojiPickerCtx.onCustomCategorySettings && (
36
+ <IconButton onClick={emojiPickerCtx.onCustomCategorySettings}>
37
+ <Settings iconSize={16} />
38
+ </IconButton>
39
+ )}
40
+ </div>
41
+ );
42
+ };
@@ -12,6 +12,7 @@ const emojiPickerCss = css`
12
12
  type EmojiPickerContentSettings = {
13
13
  perLine: number;
14
14
  emojiSize: number;
15
+ emojiContainerSize: number;
15
16
  };
16
17
 
17
18
  const [Provider, useCtx] = createContext<EmojiPickerContentSettings>("EmojiPickerContentSettings");
@@ -28,18 +29,29 @@ export const EmojiPickerContent: React.FC<React.PropsWithChildren<EmojiPickerCon
28
29
  children,
29
30
  }) => {
30
31
  const width = perLine * (emojiSize + emojiPadding * 2) + contentHorizontalPadding * 2 + measureScrollbar();
32
+ const emojiContainerSize = emojiSize + emojiPadding * 2;
31
33
 
32
- const contentSettings = useMemo(
34
+ const contentSettings = useMemo<EmojiPickerContentSettings>(
33
35
  () => ({
36
+ emojiContainerSize,
34
37
  emojiSize,
35
38
  perLine,
36
39
  }),
37
- [emojiSize, perLine]
40
+ [emojiContainerSize, emojiSize, perLine]
38
41
  );
39
42
 
40
43
  return (
41
44
  <Provider value={contentSettings}>
42
- <div style={{"--fibery-emoji-hover-color": emojiHoverColor, width} as CSSProperties} className={emojiPickerCss}>
45
+ <div
46
+ style={
47
+ {
48
+ "--fibery-emoji-container-size": `${emojiContainerSize}px`,
49
+ "--fibery-emoji-hover-color": emojiHoverColor,
50
+ width,
51
+ } as CSSProperties
52
+ }
53
+ className={emojiPickerCss}
54
+ >
43
55
  {children}
44
56
  </div>
45
57
  </Provider>
@@ -4,11 +4,13 @@ import {space} from "../../design-system";
4
4
  const footerCss = css`
5
5
  position: relative;
6
6
  border-top: 1px solid var(--fibery-color-separatorColor);
7
- padding: ${space.s8}px ${space.s12}px;
7
+ padding: ${space.s6}px ${space.s12}px;
8
8
  min-height: 40px;
9
+
10
+ display: flex;
11
+ align-items: center;
9
12
  `;
10
13
 
11
14
  export const EmojiPickerFooter: React.FC<React.PropsWithChildren> = ({children}) => {
12
- // TODO: Preview + ColorPicker + Skin Tone
13
15
  return <div className={footerCss}>{children}</div>;
14
16
  };
@@ -0,0 +1,73 @@
1
+ import type {EmojiItem} from "@fibery/emoji-data";
2
+ import {css} from "@linaria/core";
3
+ import {memo} from "react";
4
+ import {IconButton} from "../../button/icon-button";
5
+ import {themeVars} from "../../design-system";
6
+ import {useEmojiDataStoreSelector} from "../stores/emoji-data-store";
7
+ import {useEmojiPickerContentSettings} from "./content";
8
+ import {Emoji} from "./emoji";
9
+ import {useSetEmojiPreview} from "./preview-provider";
10
+ import {useEmojiPickerCtx} from "./root";
11
+ import {useEmojiSkin} from "./skin-provider";
12
+
13
+ export type EmojiPickerGridItemProps = {
14
+ id: string;
15
+ categoryId: string;
16
+ highlighted?: boolean;
17
+ /** Custom renderer for emoji items */
18
+ renderEmoji?: (args: {emoji: EmojiItem}) => React.ReactNode;
19
+ };
20
+
21
+ export const EmojiPickerGridItem = memo<EmojiPickerGridItemProps>(({id, categoryId, renderEmoji, highlighted}) => {
22
+ const setEmojiPreview = useSetEmojiPreview();
23
+ const skin = useEmojiSkin();
24
+ const emojiPickerCtx = useEmojiPickerCtx();
25
+ const emojiContentSettings = useEmojiPickerContentSettings();
26
+
27
+ const emoji = useEmojiDataStoreSelector((store) => store.get(id, {skin}));
28
+
29
+ if (!emoji) {
30
+ return null;
31
+ }
32
+
33
+ // TODO: title should be shown in preview in footer
34
+ const title = emoji.shortNames ? emoji.shortNames[0] : undefined;
35
+
36
+ return (
37
+ <IconButton
38
+ style={{
39
+ width: emojiContentSettings.emojiContainerSize,
40
+ height: emojiContentSettings.emojiContainerSize,
41
+ }}
42
+ size="big"
43
+ onClick={() => emojiPickerCtx.onEmojiSelect(emoji)}
44
+ onMouseEnter={() => setEmojiPreview({emoji, categoryId})}
45
+ onMouseLeave={() => setEmojiPreview(null)}
46
+ data-highlighted={highlighted}
47
+ className={css`
48
+ display: flex;
49
+ align-items: center;
50
+ justify-content: center;
51
+ &:hover:not(:disabled),
52
+ &:focus-visible,
53
+ &[data-highlighted="true"] {
54
+ background-color: var(--fibery-emoji-hover-color, ${themeVars.badgeBgColor});
55
+ }
56
+ `}
57
+ title={title}
58
+ aria-label={emoji.name}
59
+ >
60
+ {renderEmoji ? (
61
+ renderEmoji({emoji})
62
+ ) : (
63
+ <Emoji
64
+ emoji={id}
65
+ size={emojiContentSettings.emojiSize}
66
+ version={emojiPickerCtx.version}
67
+ skin={skin}
68
+ native={emojiPickerCtx.native}
69
+ />
70
+ )}
71
+ </IconButton>
72
+ );
73
+ });