emoji-picker-react 4.0.0-rc.8 → 4.0.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 (117) hide show
  1. package/README.md +176 -43
  2. package/dist/DomUtils/classNames.d.ts +9 -1
  3. package/dist/DomUtils/elementPositionInRow.d.ts +10 -0
  4. package/dist/DomUtils/focusElement.d.ts +5 -0
  5. package/dist/DomUtils/getActiveElement.d.ts +2 -0
  6. package/dist/DomUtils/isDarkTheme.d.ts +1 -0
  7. package/dist/DomUtils/keyboardNavigation.d.ts +8 -0
  8. package/dist/DomUtils/scrollTo.d.ts +3 -1
  9. package/dist/DomUtils/selectors.d.ts +24 -11
  10. package/dist/EmojiPickerReact.d.ts +6 -0
  11. package/dist/components/ErrorBoundary.d.ts +15 -0
  12. package/dist/components/body/{RecentlyUsed.d.ts → Suggested.d.ts} +1 -1
  13. package/dist/components/context/ElementRefContext.d.ts +10 -13
  14. package/dist/components/context/PickerContext.d.ts +3 -3
  15. package/dist/components/emoji/Emoji.d.ts +11 -7
  16. package/dist/components/emoji/ExportedEmoji.d.ts +8 -0
  17. package/dist/components/footer/Preview.d.ts +1 -1
  18. package/dist/config/categoryConfig.d.ts +7 -3
  19. package/dist/config/config.d.ts +18 -15
  20. package/dist/config/useConfig.d.ts +11 -4
  21. package/dist/data/skinToneVariations.d.ts +2 -0
  22. package/dist/dataUtils/emojiSelectors.d.ts +3 -2
  23. package/dist/dataUtils/suggested.d.ts +11 -0
  24. package/dist/emoji-picker-react.cjs.development.css +9 -9
  25. package/dist/emoji-picker-react.cjs.development.js +3774 -3070
  26. package/dist/emoji-picker-react.cjs.development.js.map +1 -1
  27. package/dist/emoji-picker-react.cjs.production.min.js +1 -1
  28. package/dist/emoji-picker-react.cjs.production.min.js.map +1 -1
  29. package/dist/emoji-picker-react.esm.js +3784 -3080
  30. package/dist/emoji-picker-react.esm.js.map +1 -1
  31. package/dist/hooks/preloadEmoji.d.ts +1 -1
  32. package/dist/hooks/useActiveCategoryScrollDetection.d.ts +1 -2
  33. package/dist/hooks/useCloseAllOpenToggles.d.ts +2 -4
  34. package/dist/hooks/useDebouncedState.d.ts +1 -1
  35. package/dist/hooks/useDisallowMouseMove.d.ts +4 -0
  36. package/dist/hooks/useEmojiPreviewEvents.d.ts +3 -0
  37. package/dist/hooks/useFilter.d.ts +3 -3
  38. package/dist/hooks/useFocus.d.ts +3 -0
  39. package/dist/hooks/useInitialLoad.d.ts +1 -1
  40. package/dist/hooks/useIsEmojiHidden.d.ts +7 -1
  41. package/dist/hooks/useKeyboardNavigation.d.ts +1 -0
  42. package/dist/hooks/useMouseDownHandlers.d.ts +3 -2
  43. package/dist/hooks/useOnFocus.d.ts +1 -0
  44. package/dist/hooks/useScrollCategoryIntoView.d.ts +1 -0
  45. package/dist/hooks/useSetVariationPicker.d.ts +2 -0
  46. package/dist/index.d.ts +2 -2
  47. package/dist/types/exposedTypes.d.ts +5 -1
  48. package/package.json +13 -5
  49. package/src/DomUtils/classNames.ts +12 -1
  50. package/src/DomUtils/elementPositionInRow.ts +167 -0
  51. package/src/DomUtils/focusElement.ts +35 -0
  52. package/src/DomUtils/getActiveElement.ts +5 -0
  53. package/src/DomUtils/isDarkTheme.ts +7 -0
  54. package/src/DomUtils/keyboardNavigation.ts +161 -0
  55. package/src/DomUtils/scrollTo.ts +22 -1
  56. package/src/DomUtils/selectors.ts +200 -41
  57. package/src/EmojiPickerReact.css +99 -48
  58. package/src/EmojiPickerReact.tsx +26 -0
  59. package/src/components/ErrorBoundary.tsx +28 -0
  60. package/src/components/body/Body.tsx +3 -2
  61. package/src/components/body/EmojiCategory.css +14 -28
  62. package/src/components/body/EmojiCategory.tsx +5 -4
  63. package/src/components/body/EmojiList.tsx +13 -13
  64. package/src/components/body/EmojiVariationPicker.css +13 -5
  65. package/src/components/body/EmojiVariationPicker.tsx +25 -17
  66. package/src/components/body/Suggested.tsx +54 -0
  67. package/src/components/context/ElementRefContext.tsx +54 -19
  68. package/src/components/context/PickerContext.tsx +36 -33
  69. package/src/components/emoji/Emoji.css +42 -12
  70. package/src/components/emoji/Emoji.tsx +73 -34
  71. package/src/components/emoji/ExportedEmoji.tsx +30 -0
  72. package/src/components/footer/Preview.css +1 -1
  73. package/src/components/footer/Preview.tsx +16 -23
  74. package/src/components/header/Search.css +7 -7
  75. package/src/components/header/Search.tsx +31 -7
  76. package/src/components/header/SkinTonePicker.css +15 -8
  77. package/src/components/header/SkinTonePicker.tsx +23 -5
  78. package/src/components/main/PickerMain.css +0 -2
  79. package/src/components/main/PickerMain.tsx +14 -1
  80. package/src/components/navigation/CategoryNavigation.css +41 -10
  81. package/src/components/navigation/CategoryNavigation.tsx +22 -10
  82. package/src/components/navigation/svg/CategoryNav.svg +343 -379
  83. package/src/config/categoryConfig.ts +40 -14
  84. package/src/config/config.ts +59 -13
  85. package/src/config/useConfig.ts +35 -7
  86. package/src/data/emojis.json +1 -1
  87. package/src/data/skinToneVariations.ts +19 -0
  88. package/src/dataUtils/emojiSelectors.ts +25 -5
  89. package/src/dataUtils/{recentlyUsed.ts → suggested.ts} +20 -18
  90. package/src/hooks/preloadEmoji.ts +13 -2
  91. package/src/hooks/useActiveCategoryScrollDetection.ts +10 -8
  92. package/src/hooks/useCloseAllOpenToggles.ts +15 -4
  93. package/src/hooks/useDebouncedState.ts +10 -7
  94. package/src/hooks/useDisallowMouseMove.ts +47 -0
  95. package/src/hooks/useEmojiPreviewEvents.ts +103 -0
  96. package/src/hooks/useFilter.ts +46 -47
  97. package/src/hooks/useFocus.ts +40 -0
  98. package/src/hooks/useInitialLoad.ts +1 -0
  99. package/src/hooks/useIsEmojiHidden.ts +16 -3
  100. package/src/hooks/useKeyboardNavigation.ts +438 -0
  101. package/src/hooks/useMouseDownHandlers.ts +97 -89
  102. package/src/hooks/useOnFocus.ts +46 -0
  103. package/src/hooks/useOnScroll.ts +2 -2
  104. package/src/hooks/useScrollCategoryIntoView.ts +28 -0
  105. package/src/hooks/useSetVariationPicker.ts +28 -0
  106. package/src/index.tsx +9 -17
  107. package/src/types/exposedTypes.ts +6 -1
  108. package/dist/DomUtils/emojiElementRef.d.ts +0 -10
  109. package/dist/DomUtils/scrollCategoryIntoView.d.ts +0 -1
  110. package/dist/dataUtils/recentlyUsed.d.ts +0 -11
  111. package/dist/hooks/useEmojiElements.d.ts +0 -1
  112. package/dist/hooks/useEmojiMouseEnter.d.ts +0 -3
  113. package/src/DomUtils/emojiElementRef.ts +0 -42
  114. package/src/DomUtils/scrollCategoryIntoView.ts +0 -18
  115. package/src/components/body/RecentlyUsed.tsx +0 -50
  116. package/src/hooks/useEmojiElements.ts +0 -7
  117. package/src/hooks/useEmojiMouseEnter.ts +0 -59
@@ -1,69 +1,102 @@
1
1
  import clsx from 'clsx';
2
2
  import * as React from 'react';
3
3
 
4
+ import { ClassNames } from '../../DomUtils/classNames';
4
5
  import { DataEmoji } from '../../dataUtils/DataTypes';
6
+ import { asEmoji } from '../../dataUtils/asEmoji';
5
7
  import {
8
+ emojiByUnified,
6
9
  emojiHasVariations,
7
10
  emojiName,
8
- emojiUnified,
11
+ emojiNames,
9
12
  emojiUrlByUnified
10
13
  } from '../../dataUtils/emojiSelectors';
11
14
  import { parseNativeEmoji } from '../../dataUtils/parseNativeEmoji';
12
- import { ClassNames } from '../../DomUtils/classNames';
13
- import { EmojiRef } from '../../DomUtils/emojiElementRef';
14
15
  import { EmojiStyle } from '../../types/exposedTypes';
15
16
  import { useEmojisThatFailedToLoadState } from '../context/PickerContext';
16
17
  import './Emoji.css';
17
18
 
18
- type Props = Readonly<{
19
- hidden?: boolean;
19
+ type ClickableEmojiProps = Readonly<
20
+ BaseProps & {
21
+ hidden?: boolean;
22
+ showVariations?: boolean;
23
+ hiddenOnSearch?: boolean;
24
+ emoji: DataEmoji;
25
+ }
26
+ >;
27
+
28
+ type BaseProps = {
29
+ emoji?: DataEmoji;
20
30
  emojiStyle: EmojiStyle;
21
- emoji: DataEmoji;
22
31
  unified: string;
23
- showVariations?: boolean;
24
32
  size?: number;
25
- emojiRef?: EmojiRef;
26
- }>;
33
+ lazyLoad?: boolean;
34
+ };
27
35
 
28
- export function Emoji({
36
+ export function ClickableEmoji({
29
37
  emoji,
30
38
  unified,
31
39
  hidden,
40
+ hiddenOnSearch,
32
41
  emojiStyle,
33
42
  showVariations = true,
34
43
  size,
35
- emojiRef
36
- }: Props) {
44
+ lazyLoad
45
+ }: ClickableEmojiProps) {
37
46
  const hasVariations = emojiHasVariations(emoji);
38
47
 
39
- const style = {} as React.CSSProperties;
40
- if (size) {
41
- style.width = style.height = style.fontSize = `${size}px`;
42
- }
43
-
44
- const base = {
45
- style,
46
- unified
47
- };
48
-
49
48
  return (
50
49
  <button
51
- className={clsx('epr-emoji', {
50
+ className={clsx(ClassNames.emoji, {
52
51
  [ClassNames.hidden]: hidden,
53
- [ClassNames.visible]: !hidden,
54
- 'epr-emoji-has-variations': hasVariations && showVariations
52
+ [ClassNames.hiddenOnSearch]: hiddenOnSearch,
53
+ [ClassNames.visible]: !hidden && !hiddenOnSearch,
54
+ [ClassNames.emojiHasVariatios]: hasVariations && showVariations
55
55
  })}
56
56
  data-unified={unified}
57
- data-original={emojiUnified(emoji)}
58
57
  // @ts-ignore - let's ignore the fact this is not a real react ref, ok?
59
- ref={emojiRef}
58
+ aria-label={emojiName(emoji)}
59
+ data-full-name={emojiNames(emoji)}
60
60
  >
61
+ <ViewOnlyEmoji
62
+ unified={unified}
63
+ emoji={emoji}
64
+ size={size}
65
+ emojiStyle={emojiStyle}
66
+ lazyLoad={lazyLoad}
67
+ />
68
+ </button>
69
+ );
70
+ }
71
+
72
+ export function ViewOnlyEmoji({
73
+ emoji,
74
+ unified,
75
+ emojiStyle,
76
+ size,
77
+ lazyLoad
78
+ }: BaseProps) {
79
+ const style = {} as React.CSSProperties;
80
+ if (size) {
81
+ style.width = style.height = style.fontSize = `${size}px`;
82
+ }
83
+
84
+ const emojiToRender = emoji ? emoji : emojiByUnified(unified);
85
+
86
+ return (
87
+ <>
61
88
  {emojiStyle === EmojiStyle.NATIVE ? (
62
- <NativeEmoji {...base} />
89
+ <NativeEmoji unified={unified} style={style} />
63
90
  ) : (
64
- <EmojiImg {...base} emoji={emoji} emojiStyle={emojiStyle} />
91
+ <EmojiImg
92
+ unified={unified}
93
+ style={style}
94
+ emoji={asEmoji(emojiToRender)}
95
+ emojiStyle={emojiStyle}
96
+ lazyLoad={lazyLoad}
97
+ />
65
98
  )}
66
- </button>
99
+ </>
67
100
  );
68
101
  }
69
102
 
@@ -75,7 +108,11 @@ function NativeEmoji({
75
108
  style: React.CSSProperties;
76
109
  }) {
77
110
  return (
78
- <span className="epr-emoji-native" data-unified={unified} style={style}>
111
+ <span
112
+ className={clsx(ClassNames.external, 'epr-emoji-native')}
113
+ data-unified={unified}
114
+ style={style}
115
+ >
79
116
  {parseNativeEmoji(unified)}
80
117
  </span>
81
118
  );
@@ -85,12 +122,14 @@ function EmojiImg({
85
122
  emoji,
86
123
  unified,
87
124
  emojiStyle,
88
- style
125
+ style,
126
+ lazyLoad = false
89
127
  }: {
90
128
  emoji: DataEmoji;
91
129
  unified: string;
92
130
  emojiStyle: EmojiStyle;
93
131
  style: React.CSSProperties;
132
+ lazyLoad?: boolean;
94
133
  }) {
95
134
  const [, setEmojisThatFailedToLoad] = useEmojisThatFailedToLoadState();
96
135
 
@@ -98,8 +137,8 @@ function EmojiImg({
98
137
  <img
99
138
  src={emojiUrlByUnified(emojiStyle, unified)}
100
139
  alt={emojiName(emoji)}
101
- className="epr-emoji-img"
102
- loading="lazy"
140
+ className={clsx(ClassNames.external, 'epr-emoji-img')}
141
+ loading={lazyLoad ? 'lazy' : 'eager'}
103
142
  onError={onError}
104
143
  style={style}
105
144
  />
@@ -0,0 +1,30 @@
1
+ import * as React from 'react';
2
+
3
+ import { EmojiStyle } from '../../types/exposedTypes';
4
+
5
+ import { ViewOnlyEmoji } from './Emoji';
6
+
7
+ export function ExportedEmoji({
8
+ unified,
9
+ size = 32,
10
+ emojiStyle = EmojiStyle.APPLE,
11
+ lazyLoad = false
12
+ }: {
13
+ unified: string;
14
+ emojiStyle?: EmojiStyle;
15
+ size?: number;
16
+ lazyLoad?: boolean;
17
+ }) {
18
+ if (!unified) {
19
+ return null;
20
+ }
21
+
22
+ return (
23
+ <ViewOnlyEmoji
24
+ unified={unified}
25
+ size={size}
26
+ emojiStyle={emojiStyle}
27
+ lazyLoad={lazyLoad}
28
+ />
29
+ );
30
+ }
@@ -1,5 +1,5 @@
1
1
  .EmojiPickerReact .epr-preview {
2
- padding: 10px;
2
+ padding: 0 var(--epr-horizontal-padding);
3
3
  z-index: 1;
4
4
  align-items: center;
5
5
  border-top: 1px solid var(--epr-preview-border-color);
@@ -1,37 +1,34 @@
1
1
  import * as React from 'react';
2
2
  import { useState } from 'react';
3
3
 
4
- import {
5
- useEmojiStyleConfig,
6
- useShowPreviewConfig
7
- } from '../../config/useConfig';
4
+ import { useEmojiStyleConfig, usePreviewConfig } from '../../config/useConfig';
8
5
  import { asEmoji } from '../../dataUtils/asEmoji';
9
6
  import {
10
7
  emojiByUnified,
11
8
  emojiName,
12
9
  emojiUnified
13
10
  } from '../../dataUtils/emojiSelectors';
14
- import { useEmojiMouseEnter } from '../../hooks/useEmojiMouseEnter';
11
+ import { useEmojiPreviewEvents } from '../../hooks/useEmojiPreviewEvents';
15
12
  import Flex from '../Layout/Flex';
16
- import { Emoji } from '../emoji/Emoji';
17
- import './Preview.css';
18
13
  import { useEmojiVariationPickerState } from '../context/PickerContext';
14
+ import { ViewOnlyEmoji } from '../emoji/Emoji';
15
+ import './Preview.css';
19
16
 
20
17
  export function Preview() {
21
- const showPreview = useShowPreviewConfig();
22
- const [hoveredEmoji, setHoveredEmoji] = useState<HoveredEmoji>(null);
18
+ const previewConfig = usePreviewConfig();
19
+ const [previewEmoji, setPreviewEmoji] = useState<PreviewEmoji>(null);
23
20
  const emojiStyle = useEmojiStyleConfig();
24
21
  const [variationPickerEmoji] = useEmojiVariationPickerState();
25
22
 
26
- useEmojiMouseEnter(showPreview, setHoveredEmoji);
23
+ useEmojiPreviewEvents(previewConfig.showPreview, setPreviewEmoji);
27
24
 
28
- if (!showPreview) {
25
+ if (!previewConfig.showPreview) {
29
26
  return null;
30
27
  }
31
28
 
32
- const emoji = emojiByUnified(hoveredEmoji?.originalUnified);
29
+ const emoji = emojiByUnified(previewEmoji?.originalUnified);
33
30
 
34
- const show = emoji != null && hoveredEmoji != null;
31
+ const show = emoji != null && previewEmoji != null;
35
32
 
36
33
  return (
37
34
  <Flex className="epr-preview">
@@ -41,31 +38,27 @@ export function Preview() {
41
38
 
42
39
  function PreviewContent() {
43
40
  const defaultEmoji = asEmoji(
44
- variationPickerEmoji ?? emojiByUnified('1f60a')
41
+ variationPickerEmoji ?? emojiByUnified(previewConfig.defaultEmoji)
45
42
  );
46
43
  const defaultText = variationPickerEmoji
47
44
  ? emojiName(variationPickerEmoji)
48
- : "What's your mood?";
45
+ : previewConfig.defaultCaption;
49
46
 
50
47
  return (
51
48
  <>
52
49
  <div>
53
50
  {show ? (
54
- <Emoji
55
- unified={hoveredEmoji?.unified as string}
51
+ <ViewOnlyEmoji
52
+ unified={previewEmoji?.unified as string}
56
53
  emoji={asEmoji(emoji)}
57
- showVariations={false}
58
54
  emojiStyle={emojiStyle}
59
- hidden={false}
60
55
  size={45}
61
56
  />
62
57
  ) : defaultEmoji ? (
63
- <Emoji
58
+ <ViewOnlyEmoji
64
59
  unified={emojiUnified(defaultEmoji)}
65
60
  emoji={defaultEmoji}
66
- showVariations={false}
67
61
  emojiStyle={emojiStyle}
68
- hidden={false}
69
62
  size={45}
70
63
  />
71
64
  ) : null}
@@ -82,7 +75,7 @@ export function Preview() {
82
75
  }
83
76
  }
84
77
 
85
- export type HoveredEmoji = null | {
78
+ export type PreviewEmoji = null | {
86
79
  unified: string;
87
80
  originalUnified: string;
88
81
  };
@@ -7,12 +7,12 @@
7
7
  .EmojiPickerReact .epr-search-container input.epr-search {
8
8
  outline: none;
9
9
  transition: all 0.2s ease-in-out;
10
- color: var(--epr-search-bar-text-color);
10
+ color: var(--epr-search-input-text-color);
11
11
  border-radius: var(--epr-search-input-border-radius);
12
12
  padding: var(--epr-search-input-padding);
13
13
  height: var(--epr-search-input-height);
14
- background-color: var(--epr-search-bar-bg-color);
15
- border: 1px solid var(--epr-search-bar-bg-color);
14
+ background-color: var(--epr-search-input-bg-color);
15
+ border: 1px solid var(--epr-search-input-bg-color);
16
16
  width: 100%;
17
17
  }
18
18
 
@@ -24,7 +24,7 @@
24
24
  background-repeat: no-repeat;
25
25
  position: absolute;
26
26
  top: 50%;
27
- right: 10px;
27
+ right: var(--epr-search-bar-inner-padding);
28
28
  transform: translateY(-50%);
29
29
  background-size: 20px;
30
30
  }
@@ -48,7 +48,7 @@
48
48
  content: '';
49
49
  position: absolute;
50
50
  top: 50%;
51
- left: 10px;
51
+ left: var(--epr-search-bar-inner-padding);
52
52
  transform: translateY(-50%);
53
53
  width: 20px;
54
54
  height: 20px;
@@ -63,10 +63,10 @@
63
63
  }
64
64
 
65
65
  .EmojiPickerReact .epr-search-container input.epr-search::placeholder {
66
- color: var(--epr-search-bar-placeholder-color);
66
+ color: var(--epr-search-input-placeholder-color);
67
67
  }
68
68
 
69
69
  .EmojiPickerReact .epr-search-container input.epr-search:focus {
70
- background-color: var(--epr-search-bar-bg-color-active);
70
+ background-color: var(--epr-search-input-bg-color-active);
71
71
  border: 1px solid var(--epr-search-border-color);
72
72
  }
@@ -1,30 +1,43 @@
1
+ import clsx from 'clsx';
1
2
  import * as React from 'react';
3
+ import { useState } from 'react';
2
4
 
3
5
  import {
4
6
  useAutoFocusSearchConfig,
5
7
  useSearchPlaceHolderConfig
6
8
  } from '../../config/useConfig';
7
9
  import { useCloseAllOpenToggles } from '../../hooks/useCloseAllOpenToggles';
8
- import { useFilter } from '../../hooks/useFilter';
10
+ import { useClearSearch, useFilter } from '../../hooks/useFilter';
9
11
  import Relative from '../Layout/Relative';
12
+ import { useSearchInputRef } from '../context/ElementRefContext';
13
+
10
14
  import './Search.css';
11
15
 
12
16
  export function Search() {
13
- const { closeAllOpenToggles } = useCloseAllOpenToggles();
14
-
17
+ const [inc, setInc] = useState(0);
18
+ const closeAllOpenToggles = useCloseAllOpenToggles();
19
+ const SearchInputRef = useSearchInputRef();
20
+ const clearSearch = useClearSearch();
15
21
  const placeholder = useSearchPlaceHolderConfig();
16
22
  const autoFocus = useAutoFocusSearchConfig();
17
- const { onChange, searchTerm, clearSearch, SearchInputRef } = useFilter();
23
+ const { onChange } = useFilter();
24
+
25
+ const input = SearchInputRef?.current;
26
+ const value = input?.value;
18
27
 
19
28
  return (
20
29
  <Relative className="epr-search-container">
30
+ <CssSearch value={value} />
21
31
  <input
32
+ // eslint-disable-next-line jsx-a11y/no-autofocus
22
33
  autoFocus={autoFocus}
34
+ aria-label={'Type to search for an emoji'}
23
35
  onFocus={closeAllOpenToggles}
24
36
  className="epr-search"
25
37
  type="text"
26
38
  placeholder={placeholder}
27
39
  onChange={event => {
40
+ setInc(inc + 1);
28
41
  setTimeout(() => {
29
42
  onChange(event.target.value);
30
43
  });
@@ -32,9 +45,20 @@ export function Search() {
32
45
  ref={SearchInputRef}
33
46
  />
34
47
  <div className="epr-icn-search" />
35
- {searchTerm ? (
36
- <button className="epr-btn-clear-search" onClick={clearSearch} />
37
- ) : null}
48
+ <button
49
+ className={clsx('epr-btn-clear-search', 'epr-visible-on-search-only')}
50
+ onClick={clearSearch}
51
+ />
38
52
  </Relative>
39
53
  );
40
54
  }
55
+
56
+ function CssSearch({ value }: { value: undefined | string }) {
57
+ return value ? (
58
+ <style>{`
59
+ .EmojiPickerReact button.epr-emoji:not([data-full-name*="${value}"]) {
60
+ display: none;
61
+ }
62
+ `}</style>
63
+ ) : null;
64
+ }
@@ -1,5 +1,8 @@
1
1
  .EmojiPickerReact .epr-skin-tones {
2
- /* margin: 0 5px 0 15px; */
2
+ --epr-skin-tone-size: 16px;
3
+ }
4
+
5
+ .EmojiPickerReact .epr-skin-tones {
3
6
  display: flex;
4
7
  align-items: center;
5
8
  justify-content: flex-end;
@@ -13,8 +16,8 @@
13
16
 
14
17
  .EmojiPickerReact .epr-skin-tone-select {
15
18
  position: relative;
16
- width: 16px;
17
- height: 16px;
19
+ width: var(--epr-skin-tone-size);
20
+ height: var(--epr-skin-tone-size);
18
21
  }
19
22
 
20
23
  .EmojiPickerReact .epr-skin-tones.open .epr-tone {
@@ -26,7 +29,7 @@
26
29
  opacity: 0;
27
30
  }
28
31
 
29
- .EmojiPickerReact .epr-skin-tones .epr-tone.active {
32
+ .EmojiPickerReact .epr-skin-tones .epr-tone.epr-active {
30
33
  z-index: 1;
31
34
  }
32
35
 
@@ -34,8 +37,8 @@
34
37
  display: block;
35
38
  cursor: pointer;
36
39
  border-radius: 4px;
37
- height: 100%;
38
- width: 100%;
40
+ height: var(--epr-skin-tone-size);
41
+ width: var(--epr-skin-tone-size);
39
42
  position: absolute;
40
43
  right: 0;
41
44
  transition: transform 0.3s ease-in-out, opacity 0.5s ease-in-out;
@@ -47,10 +50,14 @@
47
50
  box-shadow: 0 0 0 5px var(--epr-active-skin-hover-color);
48
51
  }
49
52
 
53
+ .EmojiPickerReact .epr-skin-tones .epr-tone:focus {
54
+ box-shadow: 0 0 0 5px var(--epr-focus-bg-color);
55
+ }
56
+
50
57
  .EmojiPickerReact
51
58
  .epr-skin-tones.open
52
59
  .epr-skin-tone-select
53
- .epr-tone.active:after {
60
+ .epr-tone.epr-active:after {
54
61
  content: '';
55
62
  position: absolute;
56
63
  top: -2px;
@@ -61,7 +68,7 @@
61
68
  border: 1px solid var(--epr-active-skin-tone-indicator-border-color);
62
69
  }
63
70
 
64
- .EmojiPickerReact .epr-skin-tones .epr-tone.active {
71
+ .EmojiPickerReact .epr-skin-tones .epr-tone.epr-active {
65
72
  opacity: 1;
66
73
  z-index: 1;
67
74
  }
@@ -1,10 +1,16 @@
1
1
  import clsx from 'clsx';
2
2
  import * as React from 'react';
3
3
 
4
+ import { ClassNames } from '../../DomUtils/classNames';
4
5
  import { useSkinTonesDisabledConfig } from '../../config/useConfig';
5
- import skinToneVariations from '../../data/skinToneVariations';
6
+ import skinToneVariations, {
7
+ skinTonesNamed
8
+ } from '../../data/skinToneVariations';
6
9
  import { useCloseAllOpenToggles } from '../../hooks/useCloseAllOpenToggles';
10
+ import { useFocusSearchInput } from '../../hooks/useFocus';
11
+ import { SkinTones } from '../../types/exposedTypes';
7
12
  import Relative from '../Layout/Relative';
13
+ import { useSkinTonePickerRef } from '../context/ElementRefContext';
8
14
  import {
9
15
  useActiveSkinToneState,
10
16
  useSkinToneFanOpenState
@@ -12,10 +18,12 @@ import {
12
18
  import './SkinTonePicker.css';
13
19
 
14
20
  export function SkinTonePicker() {
21
+ const SkinTonePickerRef = useSkinTonePickerRef();
15
22
  const isDisabled = useSkinTonesDisabledConfig();
16
23
  const [isOpen, setIsOpen] = useSkinToneFanOpenState();
17
24
  const [activeSkinTone, setActiveSkinTone] = useActiveSkinToneState();
18
- const { closeAllOpenToggles } = useCloseAllOpenToggles();
25
+ const closeAllOpenToggles = useCloseAllOpenToggles();
26
+ const focusSearchInput = useFocusSearchInput();
19
27
 
20
28
  if (isDisabled) {
21
29
  return null;
@@ -27,7 +35,7 @@ export function SkinTonePicker() {
27
35
  open: isOpen
28
36
  })}
29
37
  >
30
- <div className="epr-skin-tone-select" onClick={() => setIsOpen(!isOpen)}>
38
+ <div className="epr-skin-tone-select" ref={SkinTonePickerRef}>
31
39
  {skinToneVariations.map((skinToneVariation, i) => {
32
40
  const active = skinToneVariation === activeSkinTone;
33
41
  return (
@@ -39,13 +47,23 @@ export function SkinTonePicker() {
39
47
  )
40
48
  }}
41
49
  onClick={() => {
42
- isOpen && setActiveSkinTone(skinToneVariation);
50
+ if (isOpen) {
51
+ setActiveSkinTone(skinToneVariation);
52
+ focusSearchInput();
53
+ } else {
54
+ setIsOpen(true);
55
+ }
43
56
  closeAllOpenToggles();
44
57
  }}
45
58
  key={skinToneVariation}
46
59
  className={clsx(`epr-tone-${skinToneVariation}`, 'epr-tone', {
47
- active
60
+ [ClassNames.active]: active
48
61
  })}
62
+ tabIndex={isOpen ? 0 : -1}
63
+ aria-pressed={active}
64
+ aria-label={`Skin tone ${
65
+ skinTonesNamed[skinToneVariation as SkinTones]
66
+ }`}
49
67
  ></button>
50
68
  );
51
69
  })}
@@ -6,6 +6,4 @@ aside.EmojiPickerReact.epr-main {
6
6
  border-style: solid;
7
7
  border-radius: var(--epr-picker-border-radius);
8
8
  border-color: var(--epr-picker-border-color);
9
- width: var(--epr-picker-width);
10
- height: var(--epr-picker-height);
11
9
  }
@@ -1,9 +1,11 @@
1
1
  import clsx from 'clsx';
2
2
  import * as React from 'react';
3
3
 
4
- import { useThemeConfig } from '../../config/useConfig';
5
4
  import { ClassNames } from '../../DomUtils/classNames';
5
+ import { usePickerSizeConfig, useThemeConfig } from '../../config/useConfig';
6
6
  import useIsSearchMode from '../../hooks/useIsSearchMode';
7
+ import { useKeyboardNavigation } from '../../hooks/useKeyboardNavigation';
8
+ import { useOnFocus } from '../../hooks/useOnFocus';
7
9
  import { Theme } from '../../types/exposedTypes';
8
10
  import { usePickerMainRef } from '../context/ElementRefContext';
9
11
  import { PickerContextProvider } from '../context/PickerContext';
@@ -29,6 +31,16 @@ function PickerRootElement({ children }: RootProps) {
29
31
  const theme = useThemeConfig();
30
32
  const searchModeActive = useIsSearchMode();
31
33
  const PickerMainRef = usePickerMainRef();
34
+ const { height, width } = usePickerSizeConfig();
35
+
36
+ useKeyboardNavigation();
37
+ useOnFocus();
38
+
39
+ const style = {
40
+ height: `${height}px`,
41
+ width: `${width}px`
42
+ };
43
+
32
44
  return (
33
45
  <aside
34
46
  className={clsx('EmojiPickerReact', 'epr-main', {
@@ -36,6 +48,7 @@ function PickerRootElement({ children }: RootProps) {
36
48
  'epr-dark-theme': theme === Theme.DARK
37
49
  })}
38
50
  ref={PickerMainRef}
51
+ style={style}
39
52
  >
40
53
  {children}
41
54
  </aside>
@@ -15,24 +15,52 @@
15
15
  outline: none;
16
16
  }
17
17
 
18
- .EmojiPickerReact.epr-search-active .epr-category-nav > button.epr-cat-btn,
19
- .EmojiPickerReact.epr-search-active
20
- .epr-category-nav
21
- > button.epr-cat-btn:hover,
22
- .EmojiPickerReact.epr-search-active
23
- .epr-category-nav
24
- > button.epr-cat-btn.active {
18
+ .EmojiPickerReact .epr-category-nav > button.epr-cat-btn:focus:before {
19
+ content: '';
20
+ position: absolute;
21
+ top: -2px;
22
+ left: -2px;
23
+ right: -2px;
24
+ bottom: -2px;
25
+ border: 2px solid var(--epr-category-icon-active-color);
26
+ border-radius: 50%;
27
+ }
28
+
29
+ aside.EmojiPickerReact.epr-main:has(input:not(:placeholder-shown))
30
+ .epr-category-nav {
25
31
  opacity: 0.3;
26
32
  cursor: default;
27
33
  pointer-events: none;
28
34
  }
29
35
 
36
+ .EmojiPickerReact.epr-search-active .epr-category-nav {
37
+ opacity: 0.3;
38
+ cursor: default;
39
+ pointer-events: none;
40
+ }
41
+ aside.EmojiPickerReact.epr-main:has(input:not(:placeholder-shown))
42
+ .epr-category-nav {
43
+ opacity: 0.3;
44
+ cursor: default;
45
+ pointer-events: none;
46
+ }
47
+ .EmojiPickerReact.epr-search-active .epr-category-nav {
48
+ opacity: 0.3;
49
+ cursor: default;
50
+ pointer-events: none;
51
+ }
52
+ aside.EmojiPickerReact.epr-main:has(input:not(:placeholder-shown))
53
+ .epr-category-nav
54
+ > button.epr-cat-btn:hover {
55
+ opacity: 1;
56
+ background-position-y: var(--epr-category-navigation-button-size);
57
+ }
30
58
  .EmojiPickerReact:not(.epr-search-active)
31
59
  .epr-category-nav
32
60
  > button.epr-cat-btn:hover,
33
61
  .EmojiPickerReact:not(.epr-search-active)
34
62
  .epr-category-nav
35
- > button.epr-cat-btn.active {
63
+ > button.epr-cat-btn.epr-active {
36
64
  opacity: 1;
37
65
  background-position-y: var(--epr-category-navigation-button-size);
38
66
  }
@@ -45,15 +73,18 @@
45
73
  background-position-y: calc(var(--epr-category-navigation-button-size) * 2);
46
74
  }
47
75
 
76
+ aside.EmojiPickerReact.epr-main:has(input:placeholder-shown) .epr-category-nav {
77
+ background-position-y: calc(var(--epr-category-navigation-button-size) * 3);
78
+ }
48
79
  .EmojiPickerReact.epr-dark-theme:not(.epr-search-active)
49
80
  .epr-category-nav
50
81
  > button.epr-cat-btn:hover,
51
82
  .EmojiPickerReact.epr-dark-theme:not(.epr-search-active)
52
83
  .epr-category-nav
53
- > button.epr-cat-btn.active {
84
+ > button.epr-cat-btn.epr-active {
54
85
  background-position-y: calc(var(--epr-category-navigation-button-size) * 3);
55
86
  }
56
- .EmojiPickerReact button.epr-cat-btn.epr-icn-recently_used {
87
+ .EmojiPickerReact button.epr-cat-btn.epr-icn-suggested {
57
88
  background-position-x: calc(var(--epr-category-navigation-button-size) * -8);
58
89
  }
59
90
  .EmojiPickerReact button.epr-cat-btn.epr-icn-activities {