@atlaskit/emoji 67.1.0 → 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 (131) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/cjs/api/EmojiResource.js +29 -24
  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 +60 -24
  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 +13 -7
  23. package/dist/cjs/components/picker/EmojiPickerEmojiRow.js +32 -4
  24. package/dist/cjs/components/picker/EmojiPickerList.js +140 -51
  25. package/dist/cjs/components/picker/EmojiPickerListSearch.js +16 -3
  26. package/dist/cjs/components/picker/EmojiPickerVirtualItems.js +5 -2
  27. package/dist/cjs/components/picker/VirtualList.js +196 -14
  28. package/dist/cjs/components/picker/styles.js +43 -51
  29. package/dist/cjs/context/EmojiPickerListContext.js +33 -0
  30. package/dist/cjs/hooks/useEmojiPickerListContext.js +12 -0
  31. package/dist/cjs/util/constants.js +40 -1
  32. package/dist/cjs/util/shared-styles.js +3 -4
  33. package/dist/cjs/version.json +1 -1
  34. package/dist/es2019/api/EmojiResource.js +29 -24
  35. package/dist/es2019/api/media/TokenManager.js +4 -4
  36. package/dist/es2019/components/common/CachingEmoji.js +10 -3
  37. package/dist/es2019/components/common/Emoji.js +44 -11
  38. package/dist/es2019/components/common/EmojiActions.js +54 -23
  39. package/dist/es2019/components/common/EmojiErrorMessage.js +7 -3
  40. package/dist/es2019/components/common/EmojiPlaceholder.js +1 -0
  41. package/dist/es2019/components/common/EmojiRadioButton.js +54 -0
  42. package/dist/es2019/components/common/EmojiUploadPicker.js +75 -36
  43. package/dist/es2019/components/common/EmojiUploadPreview.js +11 -2
  44. package/dist/es2019/components/common/FileChooser.js +1 -1
  45. package/dist/es2019/components/common/ResourcedEmojiComponent.js +4 -0
  46. package/dist/es2019/components/common/RetryableButton.js +7 -3
  47. package/dist/es2019/components/common/TonePreviewButton.js +34 -0
  48. package/dist/es2019/components/common/ToneSelector.js +55 -21
  49. package/dist/es2019/components/common/styles.js +41 -10
  50. package/dist/es2019/components/i18n.js +44 -4
  51. package/dist/es2019/components/picker/CategorySelector.js +114 -89
  52. package/dist/es2019/components/picker/CategoryTracker.js +0 -24
  53. package/dist/es2019/components/picker/EmojiPickerCategoryHeading.js +2 -2
  54. package/dist/es2019/components/picker/EmojiPickerComponent.js +14 -7
  55. package/dist/es2019/components/picker/EmojiPickerEmojiRow.js +51 -21
  56. package/dist/es2019/components/picker/EmojiPickerList.js +102 -21
  57. package/dist/es2019/components/picker/EmojiPickerListSearch.js +14 -4
  58. package/dist/es2019/components/picker/EmojiPickerVirtualItems.js +4 -1
  59. package/dist/es2019/components/picker/VirtualList.js +193 -12
  60. package/dist/es2019/components/picker/styles.js +20 -28
  61. package/dist/es2019/context/EmojiPickerListContext.js +17 -0
  62. package/dist/es2019/hooks/useEmojiPickerListContext.js +3 -0
  63. package/dist/es2019/util/constants.js +31 -0
  64. package/dist/es2019/util/shared-styles.js +1 -2
  65. package/dist/es2019/version.json +1 -1
  66. package/dist/esm/api/EmojiResource.js +29 -24
  67. package/dist/esm/api/media/TokenManager.js +4 -4
  68. package/dist/esm/components/common/CachingEmoji.js +14 -6
  69. package/dist/esm/components/common/Emoji.js +48 -12
  70. package/dist/esm/components/common/EmojiActions.js +61 -25
  71. package/dist/esm/components/common/EmojiErrorMessage.js +7 -3
  72. package/dist/esm/components/common/EmojiPlaceholder.js +1 -0
  73. package/dist/esm/components/common/EmojiRadioButton.js +52 -0
  74. package/dist/esm/components/common/EmojiUploadPicker.js +77 -36
  75. package/dist/esm/components/common/EmojiUploadPreview.js +11 -2
  76. package/dist/esm/components/common/FileChooser.js +1 -1
  77. package/dist/esm/components/common/ResourcedEmojiComponent.js +4 -0
  78. package/dist/esm/components/common/RetryableButton.js +7 -3
  79. package/dist/esm/components/common/TonePreviewButton.js +33 -0
  80. package/dist/esm/components/common/ToneSelector.js +49 -18
  81. package/dist/esm/components/common/styles.js +40 -12
  82. package/dist/esm/components/i18n.js +44 -4
  83. package/dist/esm/components/picker/CategorySelector.js +114 -95
  84. package/dist/esm/components/picker/CategoryTracker.js +0 -28
  85. package/dist/esm/components/picker/EmojiPickerCategoryHeading.js +2 -2
  86. package/dist/esm/components/picker/EmojiPickerComponent.js +13 -7
  87. package/dist/esm/components/picker/EmojiPickerEmojiRow.js +32 -4
  88. package/dist/esm/components/picker/EmojiPickerList.js +141 -52
  89. package/dist/esm/components/picker/EmojiPickerListSearch.js +17 -4
  90. package/dist/esm/components/picker/EmojiPickerVirtualItems.js +5 -2
  91. package/dist/esm/components/picker/VirtualList.js +195 -12
  92. package/dist/esm/components/picker/styles.js +37 -45
  93. package/dist/esm/context/EmojiPickerListContext.js +21 -0
  94. package/dist/esm/hooks/useEmojiPickerListContext.js +5 -0
  95. package/dist/esm/util/constants.js +31 -0
  96. package/dist/esm/util/shared-styles.js +1 -2
  97. package/dist/esm/version.json +1 -1
  98. package/dist/types/api/EmojiResource.d.ts +2 -0
  99. package/dist/types/components/common/Emoji.d.ts +7 -1
  100. package/dist/types/components/common/EmojiActions.d.ts +3 -2
  101. package/dist/types/components/common/{EmojiButton.d.ts → EmojiRadioButton.d.ts} +3 -4
  102. package/dist/types/components/common/EmojiUploadPicker.d.ts +6 -4
  103. package/dist/types/components/common/RetryableButton.d.ts +1 -0
  104. package/dist/types/components/common/TonePreviewButton.d.ts +14 -0
  105. package/dist/types/components/common/ToneSelector.d.ts +8 -5
  106. package/dist/types/components/common/internal-types.d.ts +9 -0
  107. package/dist/types/components/common/styles.d.ts +2 -1
  108. package/dist/types/components/i18n.d.ts +40 -0
  109. package/dist/types/components/picker/CategorySelector.d.ts +3 -10
  110. package/dist/types/components/picker/CategoryTracker.d.ts +0 -2
  111. package/dist/types/components/picker/EmojiPickerCategoryHeading.d.ts +2 -1
  112. package/dist/types/components/picker/EmojiPickerEmojiRow.d.ts +5 -0
  113. package/dist/types/components/picker/EmojiPickerList.d.ts +10 -5
  114. package/dist/types/components/picker/EmojiPickerListSearch.d.ts +1 -0
  115. package/dist/types/components/picker/EmojiPickerVirtualItems.d.ts +1 -1
  116. package/dist/types/components/picker/VirtualList.d.ts +2 -0
  117. package/dist/types/components/picker/styles.d.ts +1 -1
  118. package/dist/types/context/EmojiPickerListContext.d.ts +10 -0
  119. package/dist/types/hooks/useEmojiPickerListContext.d.ts +1 -0
  120. package/dist/types/util/constants.d.ts +27 -0
  121. package/dist/types/util/shared-styles.d.ts +1 -1
  122. package/dist/types/util/type-helpers.d.ts +1 -1
  123. package/package.json +9 -6
  124. package/report.api.md +52 -1
  125. package/README.md +0 -3
  126. package/dist/es2019/components/common/EmojiButton.js +0 -49
  127. package/dist/esm/components/common/EmojiButton.js +0 -43
  128. /package/dist/cjs/{components/hooks.js → hooks/useIsMounted.js} +0 -0
  129. /package/dist/es2019/{components/hooks.js → hooks/useIsMounted.js} +0 -0
  130. /package/dist/esm/{components/hooks.js → hooks/useIsMounted.js} +0 -0
  131. /package/dist/types/{components/hooks.d.ts → hooks/useIsMounted.d.ts} +0 -0
@@ -2,12 +2,13 @@ import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
2
2
  import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
3
3
  import _regeneratorRuntime from "@babel/runtime/regenerator";
4
4
  /** @jsx jsx */
5
- import React, { Fragment, useEffect, useState } from 'react';
5
+ import { Fragment, useEffect, useState, useRef, memo, useCallback, useMemo } from 'react';
6
6
  import { jsx } from '@emotion/react';
7
7
  import { FormattedMessage, injectIntl } from 'react-intl-next';
8
8
  import TextField from '@atlaskit/textfield';
9
9
  import CrossIcon from '@atlaskit/icon/glyph/cross';
10
10
  import AkButton from '@atlaskit/button/standard-button';
11
+ import FocusLock from 'react-focus-lock';
11
12
  import * as ImageUtil from '../../util/image';
12
13
  import debug from '../../util/logger';
13
14
  import { messages } from '../i18n';
@@ -17,6 +18,8 @@ import FileChooser from './FileChooser';
17
18
  import { UploadStatus } from './internal-types';
18
19
  import { closeEmojiUploadButton, emojiChooseFileErrorMessage, emojiUpload, emojiUploadBottom, emojiUploadTop, uploadChooseFileBrowse, uploadChooseFileEmojiName, uploadChooseFileMessage, uploadChooseFileRow } from './styles';
19
20
  export var uploadEmojiNameInputTestId = 'upload-emoji-name-input';
21
+ export var uploadEmojiComponentTestId = 'upload-emoji-component';
22
+ export var cancelEmojiUploadPickerTestId = 'cancel-emoji-upload-picker';
20
23
  var disallowedReplacementsMap = new Map([[':', ''], ['!', ''], ['@', ''], ['#', ''], ['%', ''], ['^', ''], ['&', ''], ['*', ''], ['(', ''], [')', ''], [' ', '_']]);
21
24
  var sanitizeName = function sanitizeName(name) {
22
25
  // prevent / replace certain characters, allow others
@@ -30,7 +33,7 @@ var toEmojiName = function toEmojiName(uploadName) {
30
33
  var name = uploadName.split('_').join(' ');
31
34
  return "".concat(name.substr(0, 1).toLocaleUpperCase()).concat(name.substr(1));
32
35
  };
33
- var ChooseEmojiFile = function ChooseEmojiFile(props) {
36
+ var ChooseEmojiFile = /*#__PURE__*/memo(function (props) {
34
37
  var _props$name = props.name,
35
38
  name = _props$name === void 0 ? '' : _props$name,
36
39
  onChooseFile = props.onChooseFile,
@@ -42,13 +45,31 @@ var ChooseEmojiFile = function ChooseEmojiFile(props) {
42
45
  var formatMessage = intl.formatMessage;
43
46
  var disableChooser = !name;
44
47
  var fileChooserButtonDescriptionId = 'choose.emoji.file.button.screen.reader.description.id';
45
- var onKeyDownHandler = function onKeyDownHandler(event) {
48
+ var inputRef = useRef(null);
49
+ var onKeyDownHandler = useCallback(function (event) {
46
50
  if (event.key === 'Escape') {
47
51
  onUploadCancelled();
48
52
  }
49
- };
53
+ }, [onUploadCancelled]);
54
+ var setInputFocus = useCallback(function () {
55
+ var _inputRef$current, _document$activeEleme, _inputRef$current2;
56
+ (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.focus();
57
+ if (((_document$activeEleme = document.activeElement) === null || _document$activeEleme === void 0 ? void 0 : _document$activeEleme.id) !== ((_inputRef$current2 = inputRef.current) === null || _inputRef$current2 === void 0 ? void 0 : _inputRef$current2.id)) {
58
+ setInputFocus();
59
+ }
60
+ }, []);
61
+
62
+ // make sure input has focus after update
63
+ useEffect(function () {
64
+ window.requestAnimationFrame(setInputFocus);
65
+ }, [setInputFocus]);
66
+ var cancelLabel = formatMessage(messages.cancelLabel);
67
+ var emojiPlaceholder = formatMessage(messages.emojiPlaceholder);
68
+ var emojiNameAriaLabel = formatMessage(messages.emojiNameAriaLabel);
69
+ var emojiChooseFileTitle = formatMessage(messages.emojiChooseFileTitle);
50
70
  return jsx("div", {
51
- css: emojiUpload
71
+ css: emojiUpload,
72
+ "data-testid": uploadEmojiComponentTestId
52
73
  }, jsx("div", {
53
74
  css: emojiUploadTop
54
75
  }, jsx("span", {
@@ -59,27 +80,30 @@ var ChooseEmojiFile = function ChooseEmojiFile(props) {
59
80
  css: closeEmojiUploadButton
60
81
  }, jsx(AkButton, {
61
82
  onClick: onUploadCancelled,
62
- "aria-describedby": formatMessage(messages.cancelLabel),
83
+ "aria-label": cancelLabel,
63
84
  appearance: "subtle",
64
85
  spacing: "none",
65
- shouldFitContainer: true
86
+ shouldFitContainer: true,
87
+ testId: cancelEmojiUploadPickerTestId
66
88
  }, jsx(CrossIcon, {
67
89
  size: "small",
68
- label: formatMessage(messages.cancelLabel)
90
+ label: cancelLabel
69
91
  })))), jsx("div", {
70
92
  css: uploadChooseFileRow
71
93
  }, jsx("span", {
72
94
  css: uploadChooseFileEmojiName
73
95
  }, jsx(TextField, {
74
- placeholder: formatMessage(messages.emojiPlaceholder),
75
- "aria-label": formatMessage(messages.emojiNameAriaLabel),
96
+ placeholder: emojiPlaceholder,
97
+ "aria-label": emojiNameAriaLabel,
76
98
  maxLength: maxNameLength,
77
99
  onChange: onNameChange,
78
100
  onKeyDown: onKeyDownHandler,
79
101
  value: name,
80
102
  isCompact: true,
81
103
  autoFocus: true,
82
- testId: uploadEmojiNameInputTestId
104
+ testId: uploadEmojiNameInputTestId,
105
+ ref: inputRef,
106
+ id: "new-emoji-name-input"
83
107
  })), jsx("span", {
84
108
  css: uploadChooseFileBrowse
85
109
  }, jsx(FormattedMessage, messages.emojiChooseFileScreenReaderDescription, function (screenReaderDescription) {
@@ -87,7 +111,7 @@ var ChooseEmojiFile = function ChooseEmojiFile(props) {
87
111
  hidden: true,
88
112
  id: fileChooserButtonDescriptionId
89
113
  }, screenReaderDescription), jsx(FileChooser, {
90
- label: formatMessage(messages.emojiChooseFileTitle),
114
+ label: emojiChooseFileTitle,
91
115
  onChange: onChooseFile,
92
116
  onClick: onClick,
93
117
  accept: "image/png,image/jpeg,image/gif",
@@ -100,8 +124,9 @@ var ChooseEmojiFile = function ChooseEmojiFile(props) {
100
124
  messageStyles: emojiChooseFileErrorMessage,
101
125
  message: errorMessage
102
126
  })));
103
- };
127
+ });
104
128
  var EmojiUploadPicker = function EmojiUploadPicker(props) {
129
+ var _document$activeEleme2;
105
130
  var errorMessage = props.errorMessage,
106
131
  initialUploadName = props.initialUploadName,
107
132
  onUploadEmoji = props.onUploadEmoji,
@@ -128,6 +153,8 @@ var EmojiUploadPicker = function EmojiUploadPicker(props) {
128
153
  _useState10 = _slicedToArray(_useState9, 2),
129
154
  previewImage = _useState10[0],
130
155
  setPreviewImage = _useState10[1];
156
+ // document is undefined during ssr rendering and throws an error
157
+ var lastFocusedElementId = useRef(typeof document !== 'undefined' ? (_document$activeEleme2 = document.activeElement) === null || _document$activeEleme2 === void 0 ? void 0 : _document$activeEleme2.id : '');
131
158
  useEffect(function () {
132
159
  if (errorMessage) {
133
160
  setUploadStatus(UploadStatus.Error);
@@ -143,13 +170,18 @@ var EmojiUploadPicker = function EmojiUploadPicker(props) {
143
170
  setName(sanitizeName(initialUploadName));
144
171
  }
145
172
  }, [initialUploadName]);
146
- var onNameChange = function onNameChange(event) {
173
+ var clearUploadPicker = useCallback(function () {
174
+ setName(undefined);
175
+ setPreviewImage(undefined);
176
+ setUploadStatus(UploadStatus.Waiting);
177
+ }, []);
178
+ var onNameChange = useCallback(function (event) {
147
179
  var newName = sanitizeName(event.target.value);
148
180
  if (name !== newName) {
149
181
  setName(newName);
150
182
  }
151
- };
152
- var onAddEmoji = function onAddEmoji() {
183
+ }, [name]);
184
+ var onAddEmoji = useCallback(function () {
153
185
  if (uploadStatus === UploadStatus.Uploading) {
154
186
  return;
155
187
  }
@@ -179,13 +211,16 @@ var EmojiUploadPicker = function EmojiUploadPicker(props) {
179
211
  });
180
212
  });
181
213
  }
182
- };
183
- var errorOnUpload = function errorOnUpload(event) {
214
+ }, [clearUploadPicker, filename, name, onUploadEmoji, previewImage, uploadStatus]);
215
+ var cancelChooseFile = useCallback(function () {
216
+ setPreviewImage(undefined);
217
+ }, []);
218
+ var errorOnUpload = useCallback(function (event) {
184
219
  debug('File load error: ', event);
185
220
  setChooseEmojiErrorMessage(messages.emojiUploadFailed);
186
221
  cancelChooseFile();
187
- };
188
- var onFileLoad = function onFileLoad(file) {
222
+ }, [cancelChooseFile]);
223
+ var onFileLoad = useCallback(function (file) {
189
224
  return /*#__PURE__*/function () {
190
225
  var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(f) {
191
226
  return _regeneratorRuntime.wrap(function _callee$(_context) {
@@ -216,11 +251,8 @@ var EmojiUploadPicker = function EmojiUploadPicker(props) {
216
251
  return _ref.apply(this, arguments);
217
252
  };
218
253
  }();
219
- };
220
- var cancelChooseFile = function cancelChooseFile() {
221
- setPreviewImage(undefined);
222
- };
223
- var onChooseFile = function onChooseFile(event) {
254
+ }, [cancelChooseFile]);
255
+ var onChooseFile = useCallback(function (event) {
224
256
  var files = event.target.files;
225
257
  if (files.length) {
226
258
  var reader = new FileReader();
@@ -237,17 +269,25 @@ var EmojiUploadPicker = function EmojiUploadPicker(props) {
237
269
  } else {
238
270
  cancelChooseFile();
239
271
  }
240
- };
241
- var clearUploadPicker = function clearUploadPicker() {
242
- setName(undefined);
243
- setPreviewImage(undefined);
244
- setUploadStatus(UploadStatus.Waiting);
245
- };
246
- var cancelUpload = function cancelUpload() {
272
+ }, [cancelChooseFile, errorOnUpload, onFileLoad]);
273
+ var cancelUpload = useCallback(function () {
247
274
  clearUploadPicker();
248
275
  onUploadCancelled();
249
- };
250
- return jsx(React.Fragment, null, name && previewImage ? jsx(EmojiUploadPreview, {
276
+
277
+ // using setTimeout here to allow the UI to update before setting focus
278
+ setTimeout(function (lastFocus) {
279
+ if (lastFocus) {
280
+ var _document$getElementB;
281
+ (_document$getElementB = document.getElementById(lastFocus)) === null || _document$getElementB === void 0 ? void 0 : _document$getElementB.focus();
282
+ }
283
+ }, 0, lastFocusedElementId.current);
284
+ }, [clearUploadPicker, onUploadCancelled]);
285
+ var chooseErrorMessage = useMemo(function () {
286
+ return chooseEmojiErrorMessage ? jsx(FormattedMessage, chooseEmojiErrorMessage) : undefined;
287
+ }, [chooseEmojiErrorMessage]);
288
+ return jsx(FocusLock, {
289
+ noFocusGuards: true
290
+ }, name && previewImage ? jsx(EmojiUploadPreview, {
251
291
  errorMessage: errorMessage,
252
292
  name: name,
253
293
  onAddEmoji: onAddEmoji,
@@ -260,8 +300,9 @@ var EmojiUploadPicker = function EmojiUploadPicker(props) {
260
300
  onClick: onFileChooserClicked,
261
301
  onNameChange: onNameChange,
262
302
  onUploadCancelled: cancelUpload,
263
- errorMessage: chooseEmojiErrorMessage ? jsx(FormattedMessage, chooseEmojiErrorMessage) : undefined,
303
+ errorMessage: chooseErrorMessage,
264
304
  intl: intl
265
305
  }));
266
306
  };
267
- export default injectIntl(EmojiUploadPicker);
307
+ var EmojiUploadPickerComponent = injectIntl( /*#__PURE__*/memo(EmojiUploadPicker));
308
+ export default EmojiUploadPickerComponent;
@@ -18,8 +18,10 @@ import EmojiErrorMessage from './EmojiErrorMessage';
18
18
  import { UploadStatus } from './internal-types';
19
19
  import RetryableButton from './RetryableButton';
20
20
  import { bigEmojiPreview, cancelButton, emojiPreviewErrorMessage, uploadAddRow, uploadPreview, uploadPreviewFooter, uploadPreviewText } from './styles';
21
+ import VisuallyHidden from '@atlaskit/visually-hidden';
21
22
  export var uploadPreviewTestId = 'upload-preview';
22
23
  export var cancelUploadButtonTestId = 'cancel-upload-button';
24
+ var addEmojiButtonDescriptionId = 'add.emoji.button.screen.reader.description.id';
23
25
  var EmojiUploadPreview = /*#__PURE__*/function (_PureComponent) {
24
26
  _inherits(EmojiUploadPreview, _PureComponent);
25
27
  var _super = _createSuper(EmojiUploadPreview);
@@ -76,12 +78,19 @@ var EmojiUploadPreview = /*#__PURE__*/function (_PureComponent) {
76
78
  messageStyles: emojiPreviewErrorMessage,
77
79
  message: errorMessage,
78
80
  tooltip: true
79
- }) : null, jsx(RetryableButton, {
81
+ }) : null, !errorMessage && jsx(VisuallyHidden, {
82
+ id: addEmojiButtonDescriptionId
83
+ }, jsx(FormattedMessage, _extends({}, messages.emojiPreview, {
84
+ values: {
85
+ emoji: name
86
+ }
87
+ }))), jsx(RetryableButton, {
80
88
  label: formatMessage(messages.addEmojiLabel),
81
89
  onSubmit: onAddEmoji,
82
90
  appearance: "primary",
83
91
  loading: uploading,
84
- error: !!errorMessage
92
+ error: !!errorMessage,
93
+ ariaDescribedby: addEmojiButtonDescriptionId
85
94
  }), jsx(AkButton, {
86
95
  onClick: onUploadCancelled,
87
96
  appearance: "subtle",
@@ -1,5 +1,5 @@
1
1
  import React, { useRef } from 'react';
2
- import AkButton from '@atlaskit/button/custom-theme-button';
2
+ import AkButton from '@atlaskit/button/standard-button';
3
3
  export var chooseFileButtonTestId = 'choose-file-button';
4
4
  export var fileUploadInputTestId = 'file-upload';
5
5
  var FileChooser = function FileChooser(props) {
@@ -139,6 +139,10 @@ export var ResourcedEmojiComponent = function ResourcedEmojiComponent(props) {
139
139
  }
140
140
  fetchOrGetEmoji(resolvedEmojiProvider, emojiId, optimistic);
141
141
  }, [resolvedEmojiProvider, emojiId, optimistic, fetchOrGetEmoji]);
142
+
143
+ /**
144
+ * Setting resolved emoji provider for optimistic rendering
145
+ */
142
146
  useEffect(function () {
143
147
  Promise.resolve(emojiProvider).then(function (emojiProvider) {
144
148
  setResolvedEmojiProvider(emojiProvider);
@@ -20,19 +20,23 @@ var RetryButton = function RetryButton(props) {
20
20
  css: uploadRetryButton,
21
21
  appearance: "warning",
22
22
  onClick: onSubmit,
23
- testId: retryUploadButtonTestId
23
+ testId: retryUploadButtonTestId,
24
+ autoFocus: true
24
25
  }, retryLabel);
25
26
  });
26
27
  };
27
28
  var UploadButton = function UploadButton(props) {
28
29
  var appearance = props.appearance,
29
30
  onSubmit = props.onSubmit,
30
- label = props.label;
31
+ label = props.label,
32
+ ariaDescribedby = props.ariaDescribedby;
31
33
  return jsx(AkButton, {
32
34
  css: uploadEmojiButton,
33
35
  appearance: appearance,
34
36
  onClick: onSubmit,
35
- testId: uploadEmojiButtonTestId
37
+ testId: uploadEmojiButtonTestId,
38
+ "aria-describedby": ariaDescribedby,
39
+ autoFocus: true
36
40
  }, label);
37
41
  };
38
42
  var RetryableButton = function RetryableButton(props) {
@@ -0,0 +1,33 @@
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 var tonePreviewTestId = 'tone-preview';
7
+ export var TonePreviewButton = /*#__PURE__*/forwardRef(function (props, ref) {
8
+ var emoji = props.emoji,
9
+ selectOnHover = props.selectOnHover,
10
+ ariaLabelText = props.ariaLabelText,
11
+ ariaExpanded = props.ariaExpanded,
12
+ onSelected = props.onSelected,
13
+ _props$isVisible = props.isVisible,
14
+ isVisible = _props$isVisible === void 0 ? true : _props$isVisible;
15
+ return jsx("button", {
16
+ ref: ref,
17
+ css: [emojiButton, !isVisible && hidden],
18
+ onClick: onSelected,
19
+ "aria-label": ariaLabelText,
20
+ "aria-expanded": ariaExpanded,
21
+ "aria-controls": "emoji-picker-tone-selector",
22
+ style: {
23
+ overflow: 'hidden'
24
+ },
25
+ "data-testid": tonePreviewTestId
26
+ }, jsx(Emoji, {
27
+ emoji: emoji,
28
+ selectOnHover: selectOnHover,
29
+ shouldBeInteractive: false,
30
+ "aria-hidden": true
31
+ }));
32
+ });
33
+ export default /*#__PURE__*/memo(TonePreviewButton);
@@ -2,11 +2,17 @@ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
3
3
  function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
4
4
  function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
5
- import React, { useEffect, useMemo, useRef } from 'react';
6
- import EmojiButton from './EmojiButton';
5
+ /** @jsx jsx */
6
+ import { jsx } from '@emotion/react';
7
+ import { memo, useEffect, useMemo, useRef } from 'react';
7
8
  import { withAnalyticsEvents } from '@atlaskit/analytics-next';
8
9
  import { createAndFireEventInElementsChannel, toneSelectedEvent, toneSelectorOpenedEvent } from '../../util/analytics';
9
10
  import { setSkinToneAriaLabelText } from './setSkinToneAriaLabelText';
11
+ import EmojiRadioButton from './EmojiRadioButton';
12
+ import { useIntl } from 'react-intl-next';
13
+ import { messages } from '../i18n';
14
+ import { hidden } from './styles';
15
+ export var toneSelectorTestId = 'tone-selector';
10
16
  var extractAllTones = function extractAllTones(emoji) {
11
17
  if (emoji.skinVariations) {
12
18
  return [emoji].concat(_toConsumableArray(emoji.skinVariations));
@@ -16,24 +22,39 @@ var extractAllTones = function extractAllTones(emoji) {
16
22
  export var ToneSelectorInternal = function ToneSelectorInternal(props) {
17
23
  var createAnalyticsEvent = props.createAnalyticsEvent,
18
24
  emoji = props.emoji,
19
- previewEmojiId = props.previewEmojiId,
20
- onToneSelected = props.onToneSelected;
25
+ onToneSelected = props.onToneSelected,
26
+ onToneClose = props.onToneClose,
27
+ selectedTone = props.selectedTone,
28
+ isVisible = props.isVisible;
21
29
  var isMounted = useRef(false);
22
- var firstToneButtonRef = useRef(null);
30
+ var selectedToneRadioRef = useRef(null);
31
+ var _useIntl = useIntl(),
32
+ formatMessage = _useIntl.formatMessage;
23
33
  var emojiToneCollection = useMemo(function () {
24
- return extractAllTones(emoji).map(function (tone, index) {
34
+ var selectedToneIndex = -1;
35
+ var toneColletion = extractAllTones(emoji).map(function (tone, index) {
36
+ var isSelected = index === selectedTone;
37
+ if (isSelected) {
38
+ selectedToneIndex = index;
39
+ }
25
40
  return _objectSpread(_objectSpread({}, tone), {}, {
26
- focused: tone.id !== previewEmojiId,
41
+ isSelected: isSelected,
27
42
  label: setSkinToneAriaLabelText(tone.name),
28
- toneId: index
43
+ toneIndex: index
29
44
  });
30
45
  });
31
- }, [emoji, previewEmojiId]);
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]);
32
52
  useEffect(function () {
33
- if (firstToneButtonRef.current) {
34
- firstToneButtonRef.current.focus();
53
+ if (isVisible) {
54
+ var _selectedToneRadioRef;
55
+ (_selectedToneRadioRef = selectedToneRadioRef.current) === null || _selectedToneRadioRef === void 0 ? void 0 : _selectedToneRadioRef.focus();
35
56
  }
36
- }, [firstToneButtonRef]);
57
+ }, [isVisible, selectedToneRadioRef]);
37
58
  var fireAnalyticsEvent = function fireAnalyticsEvent(event) {
38
59
  if (createAnalyticsEvent) {
39
60
  createAndFireEventInElementsChannel(event)(createAnalyticsEvent);
@@ -41,6 +62,10 @@ export var ToneSelectorInternal = function ToneSelectorInternal(props) {
41
62
  };
42
63
  var onToneSelectedHandler = function onToneSelectedHandler(toneValue) {
43
64
  return function () {
65
+ if (selectedTone === toneValue && onToneClose) {
66
+ onToneClose();
67
+ return;
68
+ }
44
69
  onToneSelected(toneValue);
45
70
  var toneList = ['default', 'light', 'mediumLight', 'medium', 'mediumDark', 'dark'];
46
71
  fireAnalyticsEvent(toneSelectedEvent({
@@ -52,17 +77,23 @@ export var ToneSelectorInternal = function ToneSelectorInternal(props) {
52
77
  fireAnalyticsEvent(toneSelectorOpenedEvent({}));
53
78
  }
54
79
  isMounted.current = true;
55
- return /*#__PURE__*/React.createElement("div", null, emojiToneCollection.map(function (tone) {
56
- return /*#__PURE__*/React.createElement(EmojiButton, {
57
- ref: tone.focused ? firstToneButtonRef : null,
58
- shouldHideButton: tone.id === previewEmojiId,
80
+ return jsx("div", {
81
+ role: "radiogroup",
82
+ "data-testid": toneSelectorTestId,
83
+ id: "emoji-picker-tone-selector",
84
+ "aria-label": formatMessage(messages.emojiSelectSkinToneListAriaLabelText),
85
+ css: !isVisible && hidden
86
+ }, emojiToneCollection.map(function (tone) {
87
+ return jsx(EmojiRadioButton, {
88
+ ref: tone.isSelected ? selectedToneRadioRef : null,
89
+ defaultChecked: tone.isSelected,
59
90
  ariaLabelText: tone.label,
60
91
  key: "".concat(tone.id),
61
- onSelected: onToneSelectedHandler(tone.toneId),
62
92
  emoji: tone,
93
+ onSelected: onToneSelectedHandler(tone.toneIndex),
63
94
  selectOnHover: true
64
95
  });
65
96
  }));
66
97
  };
67
98
  var ToneSelector = withAnalyticsEvents()(ToneSelectorInternal);
68
- export default ToneSelector;
99
+ export default /*#__PURE__*/memo(ToneSelector);
@@ -2,10 +2,9 @@ import _taggedTemplateLiteral from "@babel/runtime/helpers/taggedTemplateLiteral
2
2
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
3
3
  var _css, _css2, _templateObject, _span, _css3, _css6, _input;
4
4
  import { css, keyframes } from '@emotion/react';
5
- import { borderRadius } from '@atlaskit/theme/constants';
6
5
  import { defaultEmojiHeight } from '../../util/constants';
7
6
  import { akEmojiSelectedBackgroundColor } from '../../util/shared-styles';
8
- import { N20, N200, N20A, N300, N900, R300, R400 } from '@atlaskit/theme/colors';
7
+ import { B100, N20, N200, N20A, N300, N900, R300, R400 } from '@atlaskit/theme/colors';
9
8
  export var commonSelectedStyles = 'emoji-common-selected';
10
9
  export var selectOnHoverStyles = 'emoji-common-select-on-hover';
11
10
  export var emojiSprite = 'emoji-common-emoji-sprite';
@@ -33,7 +32,7 @@ export var emojiToneSelectorContainer = css({
33
32
  padding: '10px 10px 11px 0'
34
33
  });
35
34
  export var emojiStyles = css((_css = {
36
- borderRadius: "".concat(borderRadius(), "px"),
35
+ borderRadius: "var(--ds-radius-100, 3px)",
37
36
  backgroundColor: 'transparent',
38
37
  display: 'inline-block',
39
38
  verticalAlign: 'middle',
@@ -48,6 +47,10 @@ export var emojiStyles = css((_css = {
48
47
  visibility: 'visible'
49
48
  }), _defineProperty(_css, "img", {
50
49
  display: 'block'
50
+ }), _defineProperty(_css, '&:focus', {
51
+ boxShadow: "0 0 0 2px ".concat("var(--ds-border-focused, ".concat(B100, ")")),
52
+ transitionDuration: '0s, 0.2s',
53
+ outline: 'none'
51
54
  }), _css));
52
55
  export var emojiContainer = css((_css2 = {
53
56
  display: 'inline-block',
@@ -61,6 +64,10 @@ export var emojiContainer = css((_css2 = {
61
64
  minHeight: "".concat(defaultEmojiHeight, "px"),
62
65
  minWidth: "".concat(defaultEmojiHeight, "px"),
63
66
  verticalAlign: 'middle'
67
+ }), _defineProperty(_css2, '&:focus', {
68
+ boxShadow: "0 0 0 2px ".concat("var(--ds-border-focused, ".concat(B100, ")")),
69
+ transitionDuration: '0s, 0.2s',
70
+ outline: 'none'
64
71
  }), _css2));
65
72
  export var placeholder = 'emoji-common-placeholder';
66
73
  export var placeholderContainer = css({
@@ -68,7 +75,7 @@ export var placeholderContainer = css({
68
75
  margin: '-1px 0',
69
76
  display: 'inline-block',
70
77
  background: "var(--ds-border, #f7f7f7)",
71
- borderRadius: "".concat(borderRadius(), "px"),
78
+ borderRadius: "var(--ds-radius-100, 3px)",
72
79
  overflow: 'hidden',
73
80
  verticalAlign: 'middle',
74
81
  whiteSpace: 'nowrap',
@@ -86,16 +93,23 @@ export var placeholderContainerAnimated = css({
86
93
  animation: "".concat(easeSweep, " 1s cubic-bezier(0.4, 0.0, 0.2, 1) infinite")
87
94
  }
88
95
  });
96
+ export var hidden = css({
97
+ opacity: 0,
98
+ visibility: 'hidden',
99
+ display: 'none'
100
+ });
89
101
  export var emojiButton = css((_css3 = {
90
102
  backgroundColor: 'transparent',
91
103
  border: '0',
104
+ borderRadius: "var(--ds-radius-100, 3px)",
92
105
  cursor: 'pointer',
93
- padding: 0
106
+ padding: 0,
107
+ position: 'relative',
108
+ display: 'inline-block'
94
109
  }, _defineProperty(_css3, '&::-moz-focus-inner', {
95
110
  border: '0 none',
96
111
  padding: 0
97
112
  }), _defineProperty(_css3, '&>span', (_span = {
98
- borderRadius: "".concat(borderRadius(), "px"),
99
113
  padding: '6px'
100
114
  }, _defineProperty(_span, "&>.".concat(emojiSprite), {
101
115
  height: '24px',
@@ -103,10 +117,24 @@ export var emojiButton = css((_css3 = {
103
117
  }), _defineProperty(_span, "&>img", {
104
118
  height: '24px',
105
119
  width: '24px'
106
- }), _span)), _css3));
107
- export var hiddenToneButton = css({
108
- // Hide currently selected tone that rendered in the ToneSelector to avoid duplication
109
- display: 'none'
120
+ }), _span)), _defineProperty(_css3, '&:focus', {
121
+ boxShadow: "0 0 0 2px ".concat("var(--ds-border-focused, ".concat(B100, ")")),
122
+ transitionDuration: '0s, 0.2s',
123
+ outline: 'none'
124
+ }), _css3));
125
+ export var emojiRadio = css({
126
+ opacity: 0,
127
+ position: 'absolute',
128
+ top: '-10px',
129
+ left: '-10px',
130
+ '+span': {
131
+ borderRadius: "var(--ds-radius-100, 3px)"
132
+ },
133
+ '&:focus + span': {
134
+ boxShadow: "0 0 0 2px ".concat("var(--ds-border-focused, ".concat(B100, ")")),
135
+ transitionDuration: '0s, 0.2s',
136
+ outline: 'none'
137
+ }
110
138
  });
111
139
 
112
140
  // Emoji Preview
@@ -177,7 +205,7 @@ export var previewImg = css((_css6 = {
177
205
 
178
206
  export var emojiScrollable = css({
179
207
  border: "1px solid ".concat("var(--ds-border, #fff)"),
180
- borderRadius: "".concat(borderRadius(), "px"),
208
+ borderRadius: "var(--ds-radius-100, 3px)",
181
209
  display: 'block',
182
210
  margin: '0',
183
211
  overflowX: 'hidden',
@@ -242,7 +270,7 @@ export var uploadPreview = css({
242
270
  justifyContent: 'space-between',
243
271
  alignItems: 'center',
244
272
  background: "var(--ds-background-neutral, ".concat(N20, ")"),
245
- borderRadius: "".concat(borderRadius(), "px"),
273
+ borderRadius: "var(--ds-radius-100, 3px)",
246
274
  padding: '10px'
247
275
  });
248
276
  export var uploadPreviewText = css({
@@ -22,8 +22,8 @@ export var 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 var 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 var 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 var 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
  });