@atlaskit/emoji 67.0.7 → 67.2.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 (238) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/cjs/api/EmojiResource.js +41 -25
  3. package/dist/cjs/api/media/TokenManager.js +4 -4
  4. package/dist/cjs/components/common/CachingEmoji.js +14 -6
  5. package/dist/cjs/components/common/Emoji.js +48 -12
  6. package/dist/cjs/components/common/EmojiActions.js +62 -26
  7. package/dist/cjs/components/common/EmojiErrorMessage.js +12 -7
  8. package/dist/cjs/components/common/EmojiPlaceholder.js +1 -0
  9. package/dist/cjs/components/common/{EmojiButton.js → EmojiRadioButton.js} +28 -19
  10. package/dist/cjs/components/common/EmojiUploadPicker.js +80 -37
  11. package/dist/cjs/components/common/EmojiUploadPreview.js +11 -2
  12. package/dist/cjs/components/common/FileChooser.js +2 -2
  13. package/dist/cjs/components/common/ResourcedEmojiComponent.js +4 -0
  14. package/dist/cjs/components/common/RetryableButton.js +7 -3
  15. package/dist/cjs/components/common/TonePreviewButton.js +44 -0
  16. package/dist/cjs/components/common/ToneSelector.js +53 -25
  17. package/dist/cjs/components/common/styles.js +45 -16
  18. package/dist/cjs/components/i18n.js +44 -4
  19. package/dist/cjs/components/picker/CategorySelector.js +112 -90
  20. package/dist/cjs/components/picker/CategoryTracker.js +0 -28
  21. package/dist/cjs/components/picker/EmojiPickerCategoryHeading.js +2 -1
  22. package/dist/cjs/components/picker/EmojiPickerComponent.js +33 -44
  23. package/dist/cjs/components/picker/EmojiPickerEmojiRow.js +32 -4
  24. package/dist/cjs/components/picker/EmojiPickerList.js +154 -88
  25. package/dist/cjs/components/picker/EmojiPickerListSearch.js +66 -117
  26. package/dist/cjs/components/picker/EmojiPickerVirtualItems.js +5 -2
  27. package/dist/cjs/components/picker/VirtualList.js +273 -171
  28. package/dist/cjs/components/picker/styles.js +43 -51
  29. package/dist/cjs/components/typeahead/EmojiTypeAheadComponent.js +0 -10
  30. package/dist/cjs/context/EmojiPickerListContext.js +33 -0
  31. package/dist/cjs/hooks/useEmojiPickerListContext.js +12 -0
  32. package/dist/cjs/hooks/useIsMounted.js +17 -0
  33. package/dist/cjs/i18n/cs.js +35 -34
  34. package/dist/cjs/i18n/da.js +35 -34
  35. package/dist/cjs/i18n/de.js +35 -34
  36. package/dist/cjs/i18n/en.js +35 -34
  37. package/dist/cjs/i18n/en_GB.js +35 -34
  38. package/dist/cjs/i18n/es.js +35 -34
  39. package/dist/cjs/i18n/fi.js +35 -34
  40. package/dist/cjs/i18n/fr.js +35 -34
  41. package/dist/cjs/i18n/hu.js +35 -34
  42. package/dist/cjs/i18n/it.js +35 -34
  43. package/dist/cjs/i18n/ja.js +35 -34
  44. package/dist/cjs/i18n/ko.js +35 -34
  45. package/dist/cjs/i18n/nb.js +35 -34
  46. package/dist/cjs/i18n/nl.js +35 -34
  47. package/dist/cjs/i18n/pl.js +35 -34
  48. package/dist/cjs/i18n/pt_BR.js +35 -34
  49. package/dist/cjs/i18n/ru.js +35 -34
  50. package/dist/cjs/i18n/sv.js +35 -34
  51. package/dist/cjs/i18n/th.js +35 -34
  52. package/dist/cjs/i18n/tr.js +35 -34
  53. package/dist/cjs/i18n/uk.js +35 -34
  54. package/dist/cjs/i18n/vi.js +35 -34
  55. package/dist/cjs/i18n/zh.js +35 -34
  56. package/dist/cjs/i18n/zh_TW.js +35 -34
  57. package/dist/cjs/types.js +7 -1
  58. package/dist/cjs/util/constants.js +43 -2
  59. package/dist/cjs/util/shared-styles.js +3 -4
  60. package/dist/cjs/version.json +1 -1
  61. package/dist/es2019/api/EmojiResource.js +42 -26
  62. package/dist/es2019/api/media/TokenManager.js +4 -4
  63. package/dist/es2019/components/common/CachingEmoji.js +10 -3
  64. package/dist/es2019/components/common/Emoji.js +44 -11
  65. package/dist/es2019/components/common/EmojiActions.js +55 -24
  66. package/dist/es2019/components/common/EmojiErrorMessage.js +7 -3
  67. package/dist/es2019/components/common/EmojiPlaceholder.js +1 -0
  68. package/dist/es2019/components/common/EmojiRadioButton.js +54 -0
  69. package/dist/es2019/components/common/EmojiUploadPicker.js +75 -36
  70. package/dist/es2019/components/common/EmojiUploadPreview.js +11 -2
  71. package/dist/es2019/components/common/FileChooser.js +1 -1
  72. package/dist/es2019/components/common/ResourcedEmojiComponent.js +4 -0
  73. package/dist/es2019/components/common/RetryableButton.js +7 -3
  74. package/dist/es2019/components/common/TonePreviewButton.js +34 -0
  75. package/dist/es2019/components/common/ToneSelector.js +55 -21
  76. package/dist/es2019/components/common/styles.js +41 -10
  77. package/dist/es2019/components/i18n.js +44 -4
  78. package/dist/es2019/components/picker/CategorySelector.js +114 -89
  79. package/dist/es2019/components/picker/CategoryTracker.js +0 -24
  80. package/dist/es2019/components/picker/EmojiPickerCategoryHeading.js +2 -2
  81. package/dist/es2019/components/picker/EmojiPickerComponent.js +36 -46
  82. package/dist/es2019/components/picker/EmojiPickerEmojiRow.js +51 -21
  83. package/dist/es2019/components/picker/EmojiPickerList.js +113 -55
  84. package/dist/es2019/components/picker/EmojiPickerListSearch.js +61 -98
  85. package/dist/es2019/components/picker/EmojiPickerVirtualItems.js +4 -1
  86. package/dist/es2019/components/picker/VirtualList.js +247 -108
  87. package/dist/es2019/components/picker/styles.js +20 -28
  88. package/dist/es2019/components/typeahead/EmojiTypeAheadComponent.js +0 -10
  89. package/dist/es2019/context/EmojiPickerListContext.js +17 -0
  90. package/dist/es2019/hooks/useEmojiPickerListContext.js +3 -0
  91. package/dist/es2019/hooks/useIsMounted.js +11 -0
  92. package/dist/es2019/i18n/cs.js +35 -34
  93. package/dist/es2019/i18n/da.js +35 -34
  94. package/dist/es2019/i18n/de.js +35 -34
  95. package/dist/es2019/i18n/en.js +35 -34
  96. package/dist/es2019/i18n/en_GB.js +35 -34
  97. package/dist/es2019/i18n/es.js +35 -34
  98. package/dist/es2019/i18n/fi.js +35 -34
  99. package/dist/es2019/i18n/fr.js +35 -34
  100. package/dist/es2019/i18n/hu.js +35 -34
  101. package/dist/es2019/i18n/it.js +35 -34
  102. package/dist/es2019/i18n/ja.js +35 -34
  103. package/dist/es2019/i18n/ko.js +35 -34
  104. package/dist/es2019/i18n/nb.js +35 -34
  105. package/dist/es2019/i18n/nl.js +35 -34
  106. package/dist/es2019/i18n/pl.js +35 -34
  107. package/dist/es2019/i18n/pt_BR.js +35 -34
  108. package/dist/es2019/i18n/ru.js +35 -34
  109. package/dist/es2019/i18n/sv.js +35 -34
  110. package/dist/es2019/i18n/th.js +35 -34
  111. package/dist/es2019/i18n/tr.js +35 -34
  112. package/dist/es2019/i18n/uk.js +35 -34
  113. package/dist/es2019/i18n/vi.js +35 -34
  114. package/dist/es2019/i18n/zh.js +35 -34
  115. package/dist/es2019/i18n/zh_TW.js +35 -34
  116. package/dist/es2019/types.js +5 -0
  117. package/dist/es2019/util/constants.js +32 -0
  118. package/dist/es2019/util/shared-styles.js +1 -2
  119. package/dist/es2019/version.json +1 -1
  120. package/dist/esm/api/EmojiResource.js +42 -26
  121. package/dist/esm/api/media/TokenManager.js +4 -4
  122. package/dist/esm/components/common/CachingEmoji.js +14 -6
  123. package/dist/esm/components/common/Emoji.js +48 -12
  124. package/dist/esm/components/common/EmojiActions.js +62 -26
  125. package/dist/esm/components/common/EmojiErrorMessage.js +7 -3
  126. package/dist/esm/components/common/EmojiPlaceholder.js +1 -0
  127. package/dist/esm/components/common/EmojiRadioButton.js +52 -0
  128. package/dist/esm/components/common/EmojiUploadPicker.js +77 -36
  129. package/dist/esm/components/common/EmojiUploadPreview.js +11 -2
  130. package/dist/esm/components/common/FileChooser.js +1 -1
  131. package/dist/esm/components/common/ResourcedEmojiComponent.js +4 -0
  132. package/dist/esm/components/common/RetryableButton.js +7 -3
  133. package/dist/esm/components/common/TonePreviewButton.js +33 -0
  134. package/dist/esm/components/common/ToneSelector.js +49 -18
  135. package/dist/esm/components/common/styles.js +40 -12
  136. package/dist/esm/components/i18n.js +44 -4
  137. package/dist/esm/components/picker/CategorySelector.js +114 -95
  138. package/dist/esm/components/picker/CategoryTracker.js +0 -28
  139. package/dist/esm/components/picker/EmojiPickerCategoryHeading.js +2 -2
  140. package/dist/esm/components/picker/EmojiPickerComponent.js +35 -46
  141. package/dist/esm/components/picker/EmojiPickerEmojiRow.js +32 -4
  142. package/dist/esm/components/picker/EmojiPickerList.js +156 -86
  143. package/dist/esm/components/picker/EmojiPickerListSearch.js +64 -117
  144. package/dist/esm/components/picker/EmojiPickerVirtualItems.js +5 -2
  145. package/dist/esm/components/picker/VirtualList.js +274 -172
  146. package/dist/esm/components/picker/styles.js +37 -45
  147. package/dist/esm/components/typeahead/EmojiTypeAheadComponent.js +0 -10
  148. package/dist/esm/context/EmojiPickerListContext.js +21 -0
  149. package/dist/esm/hooks/useEmojiPickerListContext.js +5 -0
  150. package/dist/esm/hooks/useIsMounted.js +11 -0
  151. package/dist/esm/i18n/cs.js +35 -34
  152. package/dist/esm/i18n/da.js +35 -34
  153. package/dist/esm/i18n/de.js +35 -34
  154. package/dist/esm/i18n/en.js +35 -34
  155. package/dist/esm/i18n/en_GB.js +35 -34
  156. package/dist/esm/i18n/es.js +35 -34
  157. package/dist/esm/i18n/fi.js +35 -34
  158. package/dist/esm/i18n/fr.js +35 -34
  159. package/dist/esm/i18n/hu.js +35 -34
  160. package/dist/esm/i18n/it.js +35 -34
  161. package/dist/esm/i18n/ja.js +35 -34
  162. package/dist/esm/i18n/ko.js +35 -34
  163. package/dist/esm/i18n/nb.js +35 -34
  164. package/dist/esm/i18n/nl.js +35 -34
  165. package/dist/esm/i18n/pl.js +35 -34
  166. package/dist/esm/i18n/pt_BR.js +35 -34
  167. package/dist/esm/i18n/ru.js +35 -34
  168. package/dist/esm/i18n/sv.js +35 -34
  169. package/dist/esm/i18n/th.js +35 -34
  170. package/dist/esm/i18n/tr.js +35 -34
  171. package/dist/esm/i18n/uk.js +35 -34
  172. package/dist/esm/i18n/vi.js +35 -34
  173. package/dist/esm/i18n/zh.js +35 -34
  174. package/dist/esm/i18n/zh_TW.js +35 -34
  175. package/dist/esm/types.js +5 -0
  176. package/dist/esm/util/constants.js +32 -0
  177. package/dist/esm/util/shared-styles.js +1 -2
  178. package/dist/esm/version.json +1 -1
  179. package/dist/types/api/EmojiResource.d.ts +2 -0
  180. package/dist/types/components/common/Emoji.d.ts +7 -1
  181. package/dist/types/components/common/EmojiActions.d.ts +4 -3
  182. package/dist/types/components/common/{EmojiButton.d.ts → EmojiRadioButton.d.ts} +3 -4
  183. package/dist/types/components/common/EmojiUploadPicker.d.ts +6 -4
  184. package/dist/types/components/common/RetryableButton.d.ts +1 -0
  185. package/dist/types/components/common/TonePreviewButton.d.ts +14 -0
  186. package/dist/types/components/common/ToneSelector.d.ts +8 -5
  187. package/dist/types/components/common/internal-types.d.ts +9 -0
  188. package/dist/types/components/common/styles.d.ts +2 -1
  189. package/dist/types/components/i18n.d.ts +40 -0
  190. package/dist/types/components/picker/CategorySelector.d.ts +3 -10
  191. package/dist/types/components/picker/CategoryTracker.d.ts +0 -2
  192. package/dist/types/components/picker/EmojiPickerCategoryHeading.d.ts +2 -1
  193. package/dist/types/components/picker/EmojiPickerEmojiRow.d.ts +5 -0
  194. package/dist/types/components/picker/EmojiPickerList.d.ts +14 -7
  195. package/dist/types/components/picker/EmojiPickerListSearch.d.ts +4 -8
  196. package/dist/types/components/picker/EmojiPickerVirtualItems.d.ts +1 -1
  197. package/dist/types/components/picker/VirtualList.d.ts +7 -24
  198. package/dist/types/components/picker/styles.d.ts +1 -1
  199. package/dist/types/context/EmojiPickerListContext.d.ts +10 -0
  200. package/dist/types/hooks/useEmojiPickerListContext.d.ts +1 -0
  201. package/dist/types/hooks/useIsMounted.d.ts +1 -0
  202. package/dist/types/i18n/cs.d.ts +34 -34
  203. package/dist/types/i18n/da.d.ts +34 -34
  204. package/dist/types/i18n/de.d.ts +34 -34
  205. package/dist/types/i18n/en.d.ts +34 -34
  206. package/dist/types/i18n/en_GB.d.ts +34 -34
  207. package/dist/types/i18n/es.d.ts +34 -34
  208. package/dist/types/i18n/fi.d.ts +34 -34
  209. package/dist/types/i18n/fr.d.ts +34 -34
  210. package/dist/types/i18n/hu.d.ts +34 -34
  211. package/dist/types/i18n/it.d.ts +34 -34
  212. package/dist/types/i18n/ja.d.ts +34 -34
  213. package/dist/types/i18n/ko.d.ts +34 -34
  214. package/dist/types/i18n/nb.d.ts +34 -34
  215. package/dist/types/i18n/nl.d.ts +34 -34
  216. package/dist/types/i18n/pl.d.ts +34 -34
  217. package/dist/types/i18n/pt_BR.d.ts +34 -34
  218. package/dist/types/i18n/ru.d.ts +34 -34
  219. package/dist/types/i18n/sv.d.ts +34 -34
  220. package/dist/types/i18n/th.d.ts +34 -34
  221. package/dist/types/i18n/tr.d.ts +34 -34
  222. package/dist/types/i18n/uk.d.ts +34 -34
  223. package/dist/types/i18n/vi.d.ts +34 -34
  224. package/dist/types/i18n/zh.d.ts +34 -34
  225. package/dist/types/i18n/zh_TW.d.ts +34 -34
  226. package/dist/types/types.d.ts +5 -0
  227. package/dist/types/util/constants.d.ts +28 -0
  228. package/dist/types/util/shared-styles.d.ts +1 -1
  229. package/dist/types/util/type-helpers.d.ts +1 -1
  230. package/package.json +12 -8
  231. package/report.api.md +62 -1
  232. package/README.md +0 -3
  233. package/dist/cjs/components/hooks.js +0 -14
  234. package/dist/es2019/components/common/EmojiButton.js +0 -49
  235. package/dist/es2019/components/hooks.js +0 -8
  236. package/dist/esm/components/common/EmojiButton.js +0 -43
  237. package/dist/esm/components/hooks.js +0 -8
  238. package/dist/types/components/hooks.d.ts +0 -1
@@ -101,6 +101,10 @@ export const ResourcedEmojiComponent = props => {
101
101
  }
102
102
  fetchOrGetEmoji(resolvedEmojiProvider, emojiId, optimistic);
103
103
  }, [resolvedEmojiProvider, emojiId, optimistic, fetchOrGetEmoji]);
104
+
105
+ /**
106
+ * Setting resolved emoji provider for optimistic rendering
107
+ */
104
108
  useEffect(() => {
105
109
  Promise.resolve(emojiProvider).then(emojiProvider => {
106
110
  setResolvedEmojiProvider(emojiProvider);
@@ -21,20 +21,24 @@ const RetryButton = props => {
21
21
  css: uploadRetryButton,
22
22
  appearance: "warning",
23
23
  onClick: onSubmit,
24
- testId: retryUploadButtonTestId
24
+ testId: retryUploadButtonTestId,
25
+ autoFocus: true
25
26
  }, retryLabel));
26
27
  };
27
28
  const UploadButton = props => {
28
29
  const {
29
30
  appearance,
30
31
  onSubmit,
31
- label
32
+ label,
33
+ ariaDescribedby
32
34
  } = props;
33
35
  return jsx(AkButton, {
34
36
  css: uploadEmojiButton,
35
37
  appearance: appearance,
36
38
  onClick: onSubmit,
37
- testId: uploadEmojiButtonTestId
39
+ testId: uploadEmojiButtonTestId,
40
+ "aria-describedby": ariaDescribedby,
41
+ autoFocus: true
38
42
  }, label);
39
43
  };
40
44
  const RetryableButton = props => {
@@ -0,0 +1,34 @@
1
+ /** @jsx jsx */
2
+ import { forwardRef, memo } from 'react';
3
+ import { jsx } from '@emotion/react';
4
+ import { emojiButton, hidden } from './styles';
5
+ import Emoji from './Emoji';
6
+ export const tonePreviewTestId = 'tone-preview';
7
+ export const TonePreviewButton = /*#__PURE__*/forwardRef((props, ref) => {
8
+ const {
9
+ emoji,
10
+ selectOnHover,
11
+ ariaLabelText,
12
+ ariaExpanded,
13
+ onSelected,
14
+ isVisible = true
15
+ } = props;
16
+ return jsx("button", {
17
+ ref: ref,
18
+ css: [emojiButton, !isVisible && hidden],
19
+ onClick: onSelected,
20
+ "aria-label": ariaLabelText,
21
+ "aria-expanded": ariaExpanded,
22
+ "aria-controls": "emoji-picker-tone-selector",
23
+ style: {
24
+ overflow: 'hidden'
25
+ },
26
+ "data-testid": tonePreviewTestId
27
+ }, jsx(Emoji, {
28
+ emoji: emoji,
29
+ selectOnHover: selectOnHover,
30
+ shouldBeInteractive: false,
31
+ "aria-hidden": true
32
+ }));
33
+ });
34
+ export default /*#__PURE__*/memo(TonePreviewButton);
@@ -1,8 +1,14 @@
1
- import React, { useEffect, useMemo, useRef } from 'react';
2
- import EmojiButton from './EmojiButton';
1
+ /** @jsx jsx */
2
+ import { jsx } from '@emotion/react';
3
+ import { memo, useEffect, useMemo, useRef } from 'react';
3
4
  import { withAnalyticsEvents } from '@atlaskit/analytics-next';
4
5
  import { createAndFireEventInElementsChannel, toneSelectedEvent, toneSelectorOpenedEvent } from '../../util/analytics';
5
6
  import { setSkinToneAriaLabelText } from './setSkinToneAriaLabelText';
7
+ import EmojiRadioButton from './EmojiRadioButton';
8
+ import { useIntl } from 'react-intl-next';
9
+ import { messages } from '../i18n';
10
+ import { hidden } from './styles';
11
+ export const toneSelectorTestId = 'tone-selector';
6
12
  const extractAllTones = emoji => {
7
13
  if (emoji.skinVariations) {
8
14
  return [emoji, ...emoji.skinVariations];
@@ -13,30 +19,52 @@ export const ToneSelectorInternal = props => {
13
19
  const {
14
20
  createAnalyticsEvent,
15
21
  emoji,
16
- previewEmojiId,
17
- onToneSelected
22
+ onToneSelected,
23
+ onToneClose,
24
+ selectedTone,
25
+ isVisible
18
26
  } = props;
19
27
  const isMounted = useRef(false);
20
- const firstToneButtonRef = useRef(null);
28
+ const selectedToneRadioRef = useRef(null);
29
+ const {
30
+ formatMessage
31
+ } = useIntl();
21
32
  const emojiToneCollection = useMemo(() => {
22
- return extractAllTones(emoji).map((tone, index) => ({
23
- ...tone,
24
- focused: tone.id !== previewEmojiId,
25
- label: setSkinToneAriaLabelText(tone.name),
26
- toneId: index
27
- }));
28
- }, [emoji, previewEmojiId]);
33
+ var selectedToneIndex = -1;
34
+ const toneColletion = extractAllTones(emoji).map((tone, index) => {
35
+ const isSelected = index === selectedTone;
36
+ if (isSelected) {
37
+ selectedToneIndex = index;
38
+ }
39
+ return {
40
+ ...tone,
41
+ isSelected: isSelected,
42
+ label: setSkinToneAriaLabelText(tone.name),
43
+ toneIndex: index
44
+ };
45
+ });
46
+
47
+ // push description of selected tone to the end of the array
48
+ // so that it gets rendered last/rightmost
49
+ toneColletion.push(toneColletion.splice(selectedToneIndex, 1)[0]);
50
+ return toneColletion;
51
+ }, [emoji, selectedTone]);
29
52
  useEffect(() => {
30
- if (firstToneButtonRef.current) {
31
- firstToneButtonRef.current.focus();
53
+ if (isVisible) {
54
+ var _selectedToneRadioRef;
55
+ (_selectedToneRadioRef = selectedToneRadioRef.current) === null || _selectedToneRadioRef === void 0 ? void 0 : _selectedToneRadioRef.focus();
32
56
  }
33
- }, [firstToneButtonRef]);
57
+ }, [isVisible, selectedToneRadioRef]);
34
58
  const fireAnalyticsEvent = event => {
35
59
  if (createAnalyticsEvent) {
36
60
  createAndFireEventInElementsChannel(event)(createAnalyticsEvent);
37
61
  }
38
62
  };
39
63
  const onToneSelectedHandler = toneValue => () => {
64
+ if (selectedTone === toneValue && onToneClose) {
65
+ onToneClose();
66
+ return;
67
+ }
40
68
  onToneSelected(toneValue);
41
69
  const toneList = ['default', 'light', 'mediumLight', 'medium', 'mediumDark', 'dark'];
42
70
  fireAnalyticsEvent(toneSelectedEvent({
@@ -47,17 +75,23 @@ export const ToneSelectorInternal = props => {
47
75
  fireAnalyticsEvent(toneSelectorOpenedEvent({}));
48
76
  }
49
77
  isMounted.current = true;
50
- return /*#__PURE__*/React.createElement("div", null, emojiToneCollection.map(tone => {
51
- return /*#__PURE__*/React.createElement(EmojiButton, {
52
- ref: tone.focused ? firstToneButtonRef : null,
53
- shouldHideButton: tone.id === previewEmojiId,
78
+ return jsx("div", {
79
+ role: "radiogroup",
80
+ "data-testid": toneSelectorTestId,
81
+ id: "emoji-picker-tone-selector",
82
+ "aria-label": formatMessage(messages.emojiSelectSkinToneListAriaLabelText),
83
+ css: !isVisible && hidden
84
+ }, emojiToneCollection.map(tone => {
85
+ return jsx(EmojiRadioButton, {
86
+ ref: tone.isSelected ? selectedToneRadioRef : null,
87
+ defaultChecked: tone.isSelected,
54
88
  ariaLabelText: tone.label,
55
89
  key: `${tone.id}`,
56
- onSelected: onToneSelectedHandler(tone.toneId),
57
90
  emoji: tone,
91
+ onSelected: onToneSelectedHandler(tone.toneIndex),
58
92
  selectOnHover: true
59
93
  });
60
94
  }));
61
95
  };
62
96
  const ToneSelector = withAnalyticsEvents()(ToneSelectorInternal);
63
- export default ToneSelector;
97
+ export default /*#__PURE__*/memo(ToneSelector);
@@ -1,8 +1,7 @@
1
1
  import { css, keyframes } from '@emotion/react';
2
- import { borderRadius } from '@atlaskit/theme/constants';
3
2
  import { defaultEmojiHeight } from '../../util/constants';
4
3
  import { akEmojiSelectedBackgroundColor } from '../../util/shared-styles';
5
- import { N20, N200, N20A, N300, N900, R300, R400 } from '@atlaskit/theme/colors';
4
+ import { B100, N20, N200, N20A, N300, N900, R300, R400 } from '@atlaskit/theme/colors';
6
5
  export const commonSelectedStyles = 'emoji-common-selected';
7
6
  export const selectOnHoverStyles = 'emoji-common-select-on-hover';
8
7
  export const emojiSprite = 'emoji-common-emoji-sprite';
@@ -30,7 +29,7 @@ export const emojiToneSelectorContainer = css({
30
29
  padding: '10px 10px 11px 0'
31
30
  });
32
31
  export const emojiStyles = css({
33
- borderRadius: `${borderRadius()}px`,
32
+ borderRadius: "var(--ds-radius-100, 3px)",
34
33
  backgroundColor: 'transparent',
35
34
  display: 'inline-block',
36
35
  verticalAlign: 'middle',
@@ -47,6 +46,11 @@ export const emojiStyles = css({
47
46
  },
48
47
  img: {
49
48
  display: 'block'
49
+ },
50
+ '&:focus': {
51
+ boxShadow: `0 0 0 2px ${`var(--ds-border-focused, ${B100})`}`,
52
+ transitionDuration: '0s, 0.2s',
53
+ outline: 'none'
50
54
  }
51
55
  });
52
56
  export const emojiContainer = css({
@@ -62,6 +66,11 @@ export const emojiContainer = css({
62
66
  minHeight: `${defaultEmojiHeight}px`,
63
67
  minWidth: `${defaultEmojiHeight}px`,
64
68
  verticalAlign: 'middle'
69
+ },
70
+ '&:focus': {
71
+ boxShadow: `0 0 0 2px ${`var(--ds-border-focused, ${B100})`}`,
72
+ transitionDuration: '0s, 0.2s',
73
+ outline: 'none'
65
74
  }
66
75
  });
67
76
  export const placeholder = 'emoji-common-placeholder';
@@ -70,7 +79,7 @@ export const placeholderContainer = css({
70
79
  margin: '-1px 0',
71
80
  display: 'inline-block',
72
81
  background: "var(--ds-border, #f7f7f7)",
73
- borderRadius: `${borderRadius()}px`,
82
+ borderRadius: "var(--ds-radius-100, 3px)",
74
83
  overflow: 'hidden',
75
84
  verticalAlign: 'middle',
76
85
  whiteSpace: 'nowrap',
@@ -95,18 +104,25 @@ export const placeholderContainerAnimated = css({
95
104
  animation: `${easeSweep} 1s cubic-bezier(0.4, 0.0, 0.2, 1) infinite`
96
105
  }
97
106
  });
107
+ export const hidden = css({
108
+ opacity: 0,
109
+ visibility: 'hidden',
110
+ display: 'none'
111
+ });
98
112
  export const emojiButton = css({
99
113
  backgroundColor: 'transparent',
100
114
  border: '0',
115
+ borderRadius: "var(--ds-radius-100, 3px)",
101
116
  cursor: 'pointer',
102
117
  padding: 0,
118
+ position: 'relative',
119
+ display: 'inline-block',
103
120
  /* Firefox */
104
121
  ['&::-moz-focus-inner']: {
105
122
  border: '0 none',
106
123
  padding: 0
107
124
  },
108
125
  '&>span': {
109
- borderRadius: `${borderRadius()}px`,
110
126
  padding: '6px',
111
127
  // Scale sprite to fit regardless of default emoji size
112
128
  [`&>.${emojiSprite}`]: {
@@ -118,11 +134,26 @@ export const emojiButton = css({
118
134
  height: '24px',
119
135
  width: '24px'
120
136
  }
137
+ },
138
+ '&:focus': {
139
+ boxShadow: `0 0 0 2px ${`var(--ds-border-focused, ${B100})`}`,
140
+ transitionDuration: '0s, 0.2s',
141
+ outline: 'none'
121
142
  }
122
143
  });
123
- export const hiddenToneButton = css({
124
- // Hide currently selected tone that rendered in the ToneSelector to avoid duplication
125
- display: 'none'
144
+ export const emojiRadio = css({
145
+ opacity: 0,
146
+ position: 'absolute',
147
+ top: '-10px',
148
+ left: '-10px',
149
+ '+span': {
150
+ borderRadius: "var(--ds-radius-100, 3px)"
151
+ },
152
+ '&:focus + span': {
153
+ boxShadow: `0 0 0 2px ${`var(--ds-border-focused, ${B100})`}`,
154
+ transitionDuration: '0s, 0.2s',
155
+ outline: 'none'
156
+ }
126
157
  });
127
158
 
128
159
  // Emoji Preview
@@ -197,7 +228,7 @@ export const previewImg = css({
197
228
 
198
229
  export const emojiScrollable = css({
199
230
  border: `1px solid ${"var(--ds-border, #fff)"}`,
200
- borderRadius: `${borderRadius()}px`,
231
+ borderRadius: "var(--ds-radius-100, 3px)",
201
232
  display: 'block',
202
233
  margin: '0',
203
234
  overflowX: 'hidden',
@@ -264,7 +295,7 @@ export const uploadPreview = css({
264
295
  justifyContent: 'space-between',
265
296
  alignItems: 'center',
266
297
  background: `var(--ds-background-neutral, ${N20})`,
267
- borderRadius: `${borderRadius()}px`,
298
+ borderRadius: "var(--ds-radius-100, 3px)",
268
299
  padding: '10px'
269
300
  });
270
301
  export const uploadPreviewText = css({
@@ -22,8 +22,8 @@ export const messages = defineMessages({
22
22
  },
23
23
  emojiPlaceholder: {
24
24
  id: 'fabric.emoji.placeholder',
25
- defaultMessage: 'Emoji name',
26
- description: 'Placeholder for emoji'
25
+ defaultMessage: 'e.g. hello',
26
+ description: 'Placeholder for emoji that provides an example for emoji name'
27
27
  },
28
28
  emojiNameAriaLabel: {
29
29
  id: 'fabric.emoji.name.ariaLabel',
@@ -42,9 +42,14 @@ export const messages = defineMessages({
42
42
  },
43
43
  emojiSelectSkinToneButtonAriaLabelText: {
44
44
  id: 'fabric.emoji.select.skin.tone.ariaLabel',
45
- defaultMessage: 'Select skin tone, {selectedTone}',
45
+ defaultMessage: 'Choose your skin tone, {selectedTone} selected',
46
46
  description: 'Message indicating the purpose of the skin tone selection button and the selected tone'
47
47
  },
48
+ emojiSelectSkinToneListAriaLabelText: {
49
+ id: 'fabric.emoji.select.skin.list.ariaLabel',
50
+ defaultMessage: 'Skin tone selector',
51
+ description: 'Message indicating the purpose of the skin tone list and make user to choose one tone'
52
+ },
48
53
  emojiImageRequirements: {
49
54
  id: 'fabric.emoji.image.requirements',
50
55
  defaultMessage: 'JPG, PNG or GIF. Max size 1 MB.',
@@ -82,9 +87,24 @@ export const messages = defineMessages({
82
87
  },
83
88
  searchLabel: {
84
89
  id: 'fabric.emoji.search.label',
85
- defaultMessage: 'Search emoji',
90
+ defaultMessage: 'Emoji name',
86
91
  description: 'verb - button label to search'
87
92
  },
93
+ searchResultsStatus: {
94
+ id: 'fabric.emoji.search.status',
95
+ defaultMessage: 'Found {count} emojis',
96
+ description: 'search results status for screenreader to read out'
97
+ },
98
+ searchResultsStatusSeeAll: {
99
+ id: 'fabric.emoji.search.status',
100
+ defaultMessage: 'Seeing all emojis',
101
+ description: 'search results status when no search query for screenreader to read out'
102
+ },
103
+ categoriesSelectorLabel: {
104
+ id: 'fabric.emoji.categories.label',
105
+ defaultMessage: 'Choose a emoji category',
106
+ description: 'Aria label for Emoji categories selector at the top of emoji picker'
107
+ },
88
108
  categoriesSearchResults: {
89
109
  id: 'fabric.emoji.categories.search.results',
90
110
  defaultMessage: 'Search results',
@@ -169,5 +189,25 @@ export const messages = defineMessages({
169
189
  id: 'fabric.emoji.error.image.too.big',
170
190
  defaultMessage: 'Selected image is more than 1 MB',
171
191
  description: 'Error message for image too big, beyond the size limit'
192
+ },
193
+ emojiPickerTitle: {
194
+ id: 'fabric.emoji.picker',
195
+ defaultMessage: 'Emoji picker',
196
+ description: 'Aria label for emoji picker'
197
+ },
198
+ emojiPickerListPanel: {
199
+ id: 'fabric.emoji.pickerlist.tabpanel',
200
+ defaultMessage: 'Emojis actions and list panel',
201
+ description: 'Aria lable for tabpanel of emoji picker, which shows emojis actions and list'
202
+ },
203
+ emojiPickerGrid: {
204
+ id: 'fabric.emoji.pickerlist.grid',
205
+ defaultMessage: '{showSearchResults, select, true{Search results} other{Emojis}}',
206
+ description: `Aria label for emoji picker grid, showSearchResults is a boolean variable, message will be "Entering Emojis table", or "Leaving Emojis"`
207
+ },
208
+ emojiButtonRoleDescription: {
209
+ id: 'fabric.emoji.emojipicker.emoi.roledescription',
210
+ defaultMessage: 'emoji button',
211
+ description: `Aria roledescription for emoji button, used in emoji picker.`
172
212
  }
173
213
  });
@@ -1,13 +1,14 @@
1
- import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
1
  /** @jsx jsx */
3
- import React from 'react';
2
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
4
3
  import { jsx } from '@emotion/react';
5
- import { PureComponent } from 'react';
6
- import { injectIntl } from 'react-intl-next';
7
- import { defaultCategories } from '../../util/constants';
4
+ import { useIntl } from 'react-intl-next';
5
+ import Tooltip from '@atlaskit/tooltip';
6
+ import { CATEGORYSELECTOR_KEYBOARD_KEYS_SUPPORTED, defaultCategories, KeyboardKeys } from '../../util/constants';
8
7
  import { messages } from '../i18n';
9
8
  import { CategoryDescriptionMap } from './categories';
10
- import { active, categorySelector, disable, categoryStyles } from './styles';
9
+ import { active, categorySelector, disable, categoryStyles, categorySelectorTablist } from './styles';
10
+ import { usePrevious } from '../../hooks/usePrevious';
11
+ import { RENDER_EMOJI_PICKER_LIST_TESTID } from './EmojiPickerList';
11
12
  export const sortCategories = (c1, c2) => CategoryDescriptionMap[c1].order - CategoryDescriptionMap[c2].order;
12
13
  const addNewCategories = (oldCategories, newCategories) => {
13
14
  if (!newCategories) {
@@ -17,90 +18,114 @@ const addNewCategories = (oldCategories, newCategories) => {
17
18
  };
18
19
  export const categorySelectorComponentTestId = 'category-selector-component';
19
20
  export const categorySelectorCategoryTestId = categoryId => `category-selector-${categoryId}`;
20
- class CategorySelector extends PureComponent {
21
- constructor(props) {
22
- super(props);
23
- _defineProperty(this, "onClick", event => {
24
- const {
25
- onCategorySelected,
26
- disableCategories
27
- } = this.props;
28
- if (disableCategories) {
29
- event.preventDefault();
30
- return;
31
- }
32
- const categoryId = event.currentTarget.getAttribute('data-category-id');
33
- if (onCategorySelected) {
34
- onCategorySelected(categoryId);
35
- }
36
- });
37
- const {
38
- dynamicCategories
39
- } = props;
40
- let categories = defaultCategories;
41
- if (dynamicCategories) {
42
- categories = addNewCategories(categories, dynamicCategories);
21
+ const CategorySelector = props => {
22
+ const {
23
+ disableCategories,
24
+ dynamicCategories,
25
+ activeCategoryId,
26
+ onCategorySelected
27
+ } = props;
28
+ const [categories, setCategories] = useState(addNewCategories(defaultCategories, dynamicCategories));
29
+ const [currentFocus, setCurrentFocus] = useState(0);
30
+ const categoryRef = useRef(null);
31
+ const prevDynamicCategories = usePrevious(dynamicCategories);
32
+ const {
33
+ formatMessage
34
+ } = useIntl();
35
+ const updateCategories = useCallback(() => {
36
+ const newCategories = addNewCategories(defaultCategories, dynamicCategories);
37
+ setCategories(newCategories);
38
+ }, [dynamicCategories]);
39
+ useEffect(() => {
40
+ if (prevDynamicCategories !== dynamicCategories) {
41
+ updateCategories();
43
42
  }
44
- this.state = {
45
- categories
46
- };
47
- }
48
- UNSAFE_componentWillUpdate(nextProps) {
49
- if (this.props.dynamicCategories !== nextProps.dynamicCategories) {
50
- this.setState({
51
- categories: addNewCategories(defaultCategories, nextProps.dynamicCategories)
52
- });
43
+ }, [prevDynamicCategories, dynamicCategories, updateCategories]);
44
+ const focusCategory = useCallback(index => {
45
+ var _categoryRef$current;
46
+ const categoryToFocus = (_categoryRef$current = categoryRef.current) === null || _categoryRef$current === void 0 ? void 0 : _categoryRef$current.querySelector(`[data-focus-index="${index}"]`);
47
+ categoryToFocus && categoryToFocus.focus();
48
+ setCurrentFocus(index);
49
+ }, [categoryRef, setCurrentFocus]);
50
+ const handleKeyDown = e => {
51
+ if (!CATEGORYSELECTOR_KEYBOARD_KEYS_SUPPORTED.includes(e.key)) {
52
+ return;
53
53
  }
54
- }
55
- render() {
56
- const {
57
- disableCategories,
58
- intl
59
- } = this.props;
60
- const {
61
- categories
62
- } = this.state;
63
- let categoriesSection;
64
- if (categories) {
65
- const {
66
- formatMessage
67
- } = intl;
68
- categoriesSection = jsx("ul", {
69
- "data-testid": categorySelectorComponentTestId
70
- }, categories.map(categoryId => {
71
- const category = CategoryDescriptionMap[categoryId];
72
- const categoryClasses = [categoryStyles];
73
- if (categoryId === this.props.activeCategoryId) {
74
- categoryClasses.push(active);
75
- }
76
- if (disableCategories) {
77
- categoryClasses.push(disable);
78
- }
79
- const Icon = category.icon;
80
- const categoryName = formatMessage(messages[category.name]);
81
- return jsx("li", {
82
- key: category.id
83
- }, jsx("button", {
84
- "aria-label": categoryName,
85
- "data-category-id": category.id,
86
- disabled: disableCategories,
87
- css: categoryClasses,
88
- onClick: this.onClick,
89
- title: categoryName,
90
- type: "button",
91
- "data-testid": categorySelectorCategoryTestId(categoryId)
92
- }, jsx(Icon, {
93
- label: categoryName
94
- })));
95
- }));
54
+ e.preventDefault();
55
+ const lastCategoryIndex = categories.length - 1;
56
+ switch (e.key) {
57
+ // navigate to the right category
58
+ case KeyboardKeys.ArrowRight:
59
+ focusCategory(currentFocus === lastCategoryIndex ? 0 : currentFocus + 1);
60
+ break;
61
+ // navigate to the left category
62
+ case KeyboardKeys.ArrowLeft:
63
+ focusCategory(currentFocus === 0 ? lastCategoryIndex : currentFocus - 1);
64
+ break;
65
+ // navigate to the first category
66
+ case KeyboardKeys.Home:
67
+ focusCategory(0);
68
+ break;
69
+ // navigate to the last category
70
+ case KeyboardKeys.End:
71
+ focusCategory(lastCategoryIndex);
72
+ break;
73
+ }
74
+ };
75
+ const handleClick = (categoryId, index) => event => {
76
+ if (disableCategories) {
77
+ event.preventDefault();
78
+ return;
96
79
  }
97
- return jsx("div", {
98
- css: categorySelector
99
- }, categoriesSection);
80
+ if (onCategorySelected) {
81
+ onCategorySelected(categoryId);
82
+ }
83
+ setCurrentFocus(index);
84
+ };
85
+ let categoriesSection;
86
+ if (categories) {
87
+ categoriesSection = jsx("div", {
88
+ role: "tablist",
89
+ "aria-label": formatMessage(messages.categoriesSelectorLabel),
90
+ "data-testid": categorySelectorComponentTestId,
91
+ ref: categoryRef,
92
+ css: categorySelectorTablist
93
+ }, categories.map((categoryId, index) => {
94
+ const category = CategoryDescriptionMap[categoryId];
95
+ const categoryClasses = [categoryStyles];
96
+ if (categoryId === activeCategoryId) {
97
+ categoryClasses.push(active);
98
+ }
99
+ if (disableCategories) {
100
+ categoryClasses.push(disable);
101
+ }
102
+ const Icon = category.icon;
103
+ const categoryName = formatMessage(messages[category.name]);
104
+ return jsx(Tooltip, {
105
+ content: categoryName,
106
+ position: "bottom",
107
+ key: category.id
108
+ }, jsx("button", {
109
+ type: "button",
110
+ id: `category-selector-${category.id}`,
111
+ "data-focus-index": index,
112
+ "aria-label": categoryName,
113
+ "aria-controls": currentFocus === index ? RENDER_EMOJI_PICKER_LIST_TESTID : undefined,
114
+ "aria-selected": currentFocus === index ? true : false,
115
+ css: categoryClasses,
116
+ disabled: disableCategories,
117
+ onClick: handleClick(categoryId, index),
118
+ "data-testid": categorySelectorCategoryTestId(categoryId),
119
+ tabIndex: currentFocus === index ? 0 : -1,
120
+ onKeyDown: handleKeyDown,
121
+ role: "tab"
122
+ }, jsx(Icon, {
123
+ label: categoryName
124
+ })));
125
+ }));
100
126
  }
101
- }
102
- _defineProperty(CategorySelector, "defaultProps", {
103
- onCategorySelected: () => {},
104
- dynamicCategories: []
105
- });
106
- export default injectIntl(CategorySelector);
127
+ return jsx("div", {
128
+ css: categorySelector
129
+ }, categoriesSection);
130
+ };
131
+ export default CategorySelector;
@@ -21,28 +21,4 @@ export default class CategoryTracker {
21
21
  getRow(category) {
22
22
  return this.categoryToRow.get(category);
23
23
  }
24
- findNearestCategoryAbove(startIndex, list) {
25
- const rows = Array.from(this.rowToCategory.keys()).sort((a, b) => a - b);
26
- if (rows.length === 0) {
27
- return;
28
- }
29
-
30
- // Return first category if list not yet rendered
31
- // or the top row is above the first category
32
- if (!list || rows[0] > startIndex) {
33
- return this.rowToCategory.get(rows[0]);
34
- }
35
- let bounds = [0, rows.length - 1];
36
- let index = Math.floor(rows.length / 2);
37
- while (rows[index] !== startIndex && bounds[0] < bounds[1]) {
38
- if (rows[index] > startIndex) {
39
- bounds[1] = Math.max(index - 1, 0);
40
- } else {
41
- bounds[0] = index + 1;
42
- }
43
- index = Math.floor((bounds[0] + bounds[1]) / 2);
44
- }
45
- const headerRow = rows[rows[index] > startIndex ? Math.max(index - 1, 0) : index];
46
- return this.rowToCategory.get(headerRow);
47
- }
48
24
  }
@@ -5,7 +5,6 @@ import { FormattedMessage } from 'react-intl-next';
5
5
  import { isMessagesKey } from '../../util/type-helpers';
6
6
  import { messages } from '../i18n';
7
7
  import { emojiCategoryTitle } from './styles';
8
-
9
8
  /**
10
9
  * Test id for wrapper Emoji Picker List div
11
10
  */
@@ -18,7 +17,8 @@ const EmojiPickerCategoryHeading = ({
18
17
  id: id,
19
18
  "data-category-id": id,
20
19
  className: className,
21
- "data-testid": RENDER_EMOJI_PICKER_CATEGORY_HEADING_TESTID
20
+ "data-testid": RENDER_EMOJI_PICKER_CATEGORY_HEADING_TESTID,
21
+ role: "rowheader"
22
22
  }, jsx("div", {
23
23
  css: emojiCategoryTitle
24
24
  }, isMessagesKey(title) ? jsx(FormattedMessage, messages[title]) : title));