emoji-picker-react 4.5.7 → 4.5.9

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.
@@ -5,6 +5,7 @@ export declare function useFilter(): {
5
5
  onChange: (inputValue: string) => void;
6
6
  searchTerm: string;
7
7
  SearchInputRef: import("../components/context/ElementRefContext").ElementRef<HTMLInputElement>;
8
+ statusSearchResults: string;
8
9
  };
9
10
  export declare function useIsEmojiFiltered(): (unified: string) => boolean;
10
11
  export declare type FilterDict = Record<string, DataEmoji>;
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.5.7",
2
+ "version": "4.5.9",
3
3
  "license": "MIT",
4
4
  "main": "dist/index.js",
5
5
  "typings": "dist/index.d.ts",
@@ -26,6 +26,8 @@ function EmojiPicker(props: PickerProps) {
26
26
 
27
27
  // eslint-disable-next-line complexity
28
28
  export default React.memo(EmojiPicker, (prev, next) => {
29
+ const prevCustomEmojis = prev.customEmojis ?? [];
30
+ const nextCustomEmojis = next.customEmojis ?? [];
29
31
  return (
30
32
  prev.emojiVersion === next.emojiVersion &&
31
33
  prev.searchPlaceHolder === next.searchPlaceHolder &&
@@ -40,6 +42,7 @@ export default React.memo(EmojiPicker, (prev, next) => {
40
42
  prev.height === next.height &&
41
43
  prev.width === next.width &&
42
44
  prev.searchDisabled === next.searchDisabled &&
43
- prev.skinTonePickerLocation === next.skinTonePickerLocation
45
+ prev.skinTonePickerLocation === next.skinTonePickerLocation &&
46
+ prevCustomEmojis.length === nextCustomEmojis.length
44
47
  );
45
48
  });
@@ -18,6 +18,8 @@
18
18
  display: flex;
19
19
  align-items: center;
20
20
  font-weight: bold;
21
+ font-size: 16px;
22
+ margin: 0;
21
23
  text-transform: capitalize;
22
24
  backdrop-filter: blur(3px);
23
25
  padding: var(--epr-category-label-padding);
@@ -33,7 +33,7 @@ export function EmojiCategory({
33
33
  data-name={category}
34
34
  aria-label={categoryName}
35
35
  >
36
- <div className={ClassNames.label}>{categoryName}</div>
36
+ <h2 className={ClassNames.label}>{categoryName}</h2>
37
37
  <div className={ClassNames.categoryContent}>{children}</div>
38
38
  </li>
39
39
  );
@@ -17,7 +17,8 @@ const ConfigContext = React.createContext<PickerConfigInternal>(
17
17
  );
18
18
 
19
19
  export function PickerConfigProvider({ children, ...config }: Props) {
20
- const [mergedConfig] = React.useState(() => mergeConfig(config));
20
+ const mergedConfig = useSetConfig(config);
21
+
21
22
  return (
22
23
  <ConfigContext.Provider value={mergedConfig}>
23
24
  {children}
@@ -25,6 +26,22 @@ export function PickerConfigProvider({ children, ...config }: Props) {
25
26
  );
26
27
  }
27
28
 
29
+ export function useSetConfig(config: PickerConfig) {
30
+ const [mergedConfig, setMergedConfig] = React.useState(() =>
31
+ mergeConfig(config)
32
+ );
33
+
34
+ React.useEffect(() => {
35
+ if (config.customEmojis?.length !== mergedConfig.customEmojis?.length) {
36
+ setMergedConfig(mergeConfig(config));
37
+ }
38
+ // not gonna...
39
+ // eslint-disable-next-line react-hooks/exhaustive-deps
40
+ }, [config.customEmojis?.length]);
41
+
42
+ return mergedConfig;
43
+ }
44
+
28
45
  export function usePickerConfig() {
29
46
  return React.useContext(ConfigContext);
30
47
  }
@@ -4,6 +4,16 @@
4
4
  min-width: 0;
5
5
  }
6
6
 
7
+ .EmojiPickerReact .epr-search-container .epr-status-visually-hidden {
8
+ clip: rect(0 0 0 0);
9
+ clip-path: inset(50%);
10
+ height: 1px;
11
+ overflow: hidden;
12
+ position: absolute;
13
+ white-space: nowrap;
14
+ width: 1px;
15
+ }
16
+
7
17
  .EmojiPickerReact .epr-search-container input.epr-search {
8
18
  outline: none;
9
19
  transition: all 0.2s ease-in-out;
@@ -47,7 +47,7 @@ export function Search() {
47
47
  const clearSearch = useClearSearch();
48
48
  const placeholder = useSearchPlaceHolderConfig();
49
49
  const autoFocus = useAutoFocusSearchConfig();
50
- const { onChange } = useFilter();
50
+ const { statusSearchResults, searchTerm, onChange } = useFilter();
51
51
 
52
52
  const input = SearchInputRef?.current;
53
53
  const value = input?.value;
@@ -62,6 +62,7 @@ export function Search() {
62
62
  onFocus={closeAllOpenToggles}
63
63
  className="epr-search"
64
64
  type="text"
65
+ aria-controls='epr-search-id'
65
66
  placeholder={placeholder}
66
67
  onChange={event => {
67
68
  setInc(inc + 1);
@@ -71,6 +72,17 @@ export function Search() {
71
72
  }}
72
73
  ref={SearchInputRef}
73
74
  />
75
+ {searchTerm ? (
76
+ <div
77
+ role='status'
78
+ className={clsx('epr-status-search-results', 'epr-status-visually-hidden')}
79
+ aria-live='polite'
80
+ id='epr-search-id'
81
+ aria-atomic='true'
82
+ >
83
+ {statusSearchResults}
84
+ </div>
85
+ ) : null}
74
86
  <div className="epr-icn-search" />
75
87
  <Button
76
88
  className={clsx('epr-btn-clear-search', 'epr-visible-on-search-only')}
@@ -27,9 +27,16 @@ export function CategoryNavigation() {
27
27
  const hideCustomCategory = useShouldHideCustomEmojis();
28
28
 
29
29
  return (
30
- <div className="epr-category-nav" ref={CategoryNavigationRef}>
30
+ <div
31
+ className='epr-category-nav'
32
+ role='tablist'
33
+ aria-label='Category navigation'
34
+ id='epr-category-nav-id'
35
+ ref={CategoryNavigationRef}
36
+ >
31
37
  {categoriesConfig.map(categoryConfig => {
32
38
  const category = categoryFromCategoryConfig(categoryConfig);
39
+ const isActiveCategory = category === activeCategory;
33
40
 
34
41
  if (isCustomCategory(categoryConfig) && hideCustomCategory) {
35
42
  return null;
@@ -37,9 +44,9 @@ export function CategoryNavigation() {
37
44
 
38
45
  return (
39
46
  <Button
40
- tabIndex={isSearchMode ? -1 : 0}
47
+ tabIndex={(isSearchMode || isActiveCategory) ? -1 : 0}
41
48
  className={clsx('epr-cat-btn', `epr-icn-${category}`, {
42
- [ClassNames.active]: category === activeCategory
49
+ [ClassNames.active]: isActiveCategory
43
50
  })}
44
51
  key={category}
45
52
  onClick={() => {
@@ -47,6 +54,9 @@ export function CategoryNavigation() {
47
54
  scrollCategoryIntoView(category);
48
55
  }}
49
56
  aria-label={categoryNameFromCategoryConfig(categoryConfig)}
57
+ aria-selected={isActiveCategory}
58
+ role='tab'
59
+ aria-controls='epr-category-nav-id'
50
60
  />
51
61
  );
52
62
  })}
@@ -22,6 +22,13 @@ import { CustomEmoji } from './customEmojiConfig';
22
22
  const KNOWN_FAILING_EMOJIS = ['2640-fe0f', '2642-fe0f', '2695-fe0f'];
23
23
 
24
24
  export const DEFAULT_SEARCH_PLACEHOLDER = 'Search';
25
+ export const SEARCH_RESULTS_NO_RESULTS_FOUND = 'No results found';
26
+ export const SEARCH_RESULTS_SUFFIX =
27
+ ' found. Use up and down arrow keys to navigate.';
28
+ export const SEARCH_RESULTS_ONE_RESULT_FOUND =
29
+ '1 result' + SEARCH_RESULTS_SUFFIX;
30
+ export const SEARCH_RESULTS_MULTIPLE_RESULTS_FOUND =
31
+ '%n results' + SEARCH_RESULTS_SUFFIX;
25
32
 
26
33
  export function mergeConfig(
27
34
  userConfig: PickerConfig = {}
@@ -11,6 +11,9 @@ import {
11
11
  import { CategoriesConfig } from './categoryConfig';
12
12
  import {
13
13
  DEFAULT_SEARCH_PLACEHOLDER,
14
+ SEARCH_RESULTS_NO_RESULTS_FOUND,
15
+ SEARCH_RESULTS_ONE_RESULT_FOUND,
16
+ SEARCH_RESULTS_MULTIPLE_RESULTS_FOUND,
14
17
  PickerDimensions,
15
18
  PreviewConfig
16
19
  } from './config';
@@ -126,3 +129,16 @@ function getDimension(dimensionConfig: PickerDimensions): PickerDimensions {
126
129
  ? `${dimensionConfig}px`
127
130
  : dimensionConfig;
128
131
  }
132
+
133
+ export function useSearchResultsConfig(searchResultsCount: number): string {
134
+ const hasResults = searchResultsCount > 0;
135
+ const isPlural = searchResultsCount > 1;
136
+
137
+ if (hasResults) {
138
+ return isPlural ?
139
+ SEARCH_RESULTS_MULTIPLE_RESULTS_FOUND.replace('%n', searchResultsCount.toString())
140
+ : SEARCH_RESULTS_ONE_RESULT_FOUND;
141
+ }
142
+
143
+ return SEARCH_RESULTS_NO_RESULTS_FOUND;
144
+ }
@@ -12,6 +12,7 @@ import { DataEmoji } from '../dataUtils/DataTypes';
12
12
  import { emojiNames } from '../dataUtils/emojiSelectors';
13
13
 
14
14
  import { useFocusSearchInput } from './useFocus';
15
+ import { useSearchResultsConfig } from '../config/useConfig';
15
16
 
16
17
  function useSetFilterRef() {
17
18
  const filterRef = useFilterRef();
@@ -63,11 +64,13 @@ export function useFilter() {
63
64
  const applySearch = useApplySearch();
64
65
 
65
66
  const [searchTerm] = useSearchTermState();
67
+ const statusSearchResults = getStatusSearchResults(filterRef.current, searchTerm);
66
68
 
67
69
  return {
68
70
  onChange,
69
71
  searchTerm,
70
72
  SearchInputRef,
73
+ statusSearchResults,
71
74
  };
72
75
 
73
76
  function onChange(inputValue: string) {
@@ -183,3 +186,10 @@ export function getNormalizedSearchTerm(str: string): string {
183
186
 
184
187
  return str.trim().toLowerCase();
185
188
  }
189
+
190
+ function getStatusSearchResults(filterState: FilterState, searchTerm: string): string {
191
+ if (!filterState?.[searchTerm]) return '';
192
+
193
+ const searchResultsCount = Object.entries(filterState?.[searchTerm])?.length || 0;
194
+ return useSearchResultsConfig(searchResultsCount);
195
+ }