@fibery/ui-kit 1.14.0 → 1.15.0

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