@atlaskit/emoji 67.2.1 → 67.3.1

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 (99) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/cjs/api/EmojiResource.js +6 -0
  3. package/dist/cjs/components/common/DeletableEmojiTooltipContent.js +34 -0
  4. package/dist/cjs/components/common/DeleteButton.js +2 -1
  5. package/dist/cjs/components/common/Emoji.js +94 -81
  6. package/dist/cjs/components/common/EmojiActions.js +8 -6
  7. package/dist/cjs/components/common/EmojiDeletePreview.js +19 -6
  8. package/dist/cjs/components/common/EmojiErrorMessage.js +13 -5
  9. package/dist/cjs/components/common/EmojiRadioButton.js +1 -0
  10. package/dist/cjs/components/common/EmojiUploadPicker.js +30 -29
  11. package/dist/cjs/components/common/EmojiUploadPreview.js +12 -5
  12. package/dist/cjs/components/common/FileChooser.js +7 -1
  13. package/dist/cjs/components/common/RetryableButton.js +9 -3
  14. package/dist/cjs/components/common/ToolTipContentWithKeymap.js +25 -0
  15. package/dist/cjs/components/common/styles.js +50 -20
  16. package/dist/cjs/components/i18n.js +17 -2
  17. package/dist/cjs/components/picker/CategorySelector.js +2 -1
  18. package/dist/cjs/components/picker/CategoryTracker.js +5 -0
  19. package/dist/cjs/components/picker/EmojiPickerComponent.js +12 -6
  20. package/dist/cjs/components/picker/EmojiPickerList.js +49 -8
  21. package/dist/cjs/components/picker/EmojiPickerListSearch.js +4 -1
  22. package/dist/cjs/components/picker/VirtualList.js +8 -3
  23. package/dist/cjs/{util → hooks}/useInView.js +3 -2
  24. package/dist/cjs/util/browser-support.js +11 -3
  25. package/dist/cjs/util/constants.js +4 -1
  26. package/dist/cjs/util/keymaps.js +55 -0
  27. package/dist/cjs/version.json +1 -1
  28. package/dist/es2019/api/EmojiResource.js +6 -0
  29. package/dist/es2019/components/common/DeletableEmojiTooltipContent.js +27 -0
  30. package/dist/es2019/components/common/DeleteButton.js +2 -1
  31. package/dist/es2019/components/common/Emoji.js +90 -72
  32. package/dist/es2019/components/common/EmojiActions.js +9 -7
  33. package/dist/es2019/components/common/EmojiDeletePreview.js +17 -7
  34. package/dist/es2019/components/common/EmojiErrorMessage.js +10 -4
  35. package/dist/es2019/components/common/EmojiRadioButton.js +1 -0
  36. package/dist/es2019/components/common/EmojiUploadPicker.js +32 -27
  37. package/dist/es2019/components/common/EmojiUploadPreview.js +9 -5
  38. package/dist/es2019/components/common/FileChooser.js +7 -1
  39. package/dist/es2019/components/common/RetryableButton.js +9 -3
  40. package/dist/es2019/components/common/ToolTipContentWithKeymap.js +15 -0
  41. package/dist/es2019/components/common/styles.js +44 -16
  42. package/dist/es2019/components/i18n.js +17 -2
  43. package/dist/es2019/components/picker/CategorySelector.js +2 -1
  44. package/dist/es2019/components/picker/CategoryTracker.js +3 -0
  45. package/dist/es2019/components/picker/EmojiPickerComponent.js +12 -6
  46. package/dist/es2019/components/picker/EmojiPickerList.js +48 -8
  47. package/dist/es2019/components/picker/EmojiPickerListSearch.js +3 -1
  48. package/dist/es2019/components/picker/VirtualList.js +7 -3
  49. package/dist/es2019/{util → hooks}/useInView.js +2 -2
  50. package/dist/es2019/util/browser-support.js +9 -1
  51. package/dist/es2019/util/constants.js +2 -0
  52. package/dist/es2019/util/keymaps.js +43 -0
  53. package/dist/es2019/version.json +1 -1
  54. package/dist/esm/api/EmojiResource.js +6 -0
  55. package/dist/esm/components/common/DeletableEmojiTooltipContent.js +25 -0
  56. package/dist/esm/components/common/DeleteButton.js +2 -1
  57. package/dist/esm/components/common/Emoji.js +94 -82
  58. package/dist/esm/components/common/EmojiActions.js +9 -7
  59. package/dist/esm/components/common/EmojiDeletePreview.js +17 -7
  60. package/dist/esm/components/common/EmojiErrorMessage.js +11 -4
  61. package/dist/esm/components/common/EmojiRadioButton.js +1 -0
  62. package/dist/esm/components/common/EmojiUploadPicker.js +32 -31
  63. package/dist/esm/components/common/EmojiUploadPreview.js +9 -5
  64. package/dist/esm/components/common/FileChooser.js +7 -1
  65. package/dist/esm/components/common/RetryableButton.js +9 -3
  66. package/dist/esm/components/common/ToolTipContentWithKeymap.js +14 -0
  67. package/dist/esm/components/common/styles.js +43 -17
  68. package/dist/esm/components/i18n.js +17 -2
  69. package/dist/esm/components/picker/CategorySelector.js +2 -1
  70. package/dist/esm/components/picker/CategoryTracker.js +5 -0
  71. package/dist/esm/components/picker/EmojiPickerComponent.js +12 -6
  72. package/dist/esm/components/picker/EmojiPickerList.js +49 -8
  73. package/dist/esm/components/picker/EmojiPickerListSearch.js +4 -1
  74. package/dist/esm/components/picker/VirtualList.js +8 -3
  75. package/dist/esm/{util → hooks}/useInView.js +2 -2
  76. package/dist/esm/util/browser-support.js +9 -1
  77. package/dist/esm/util/constants.js +2 -0
  78. package/dist/esm/util/keymaps.js +43 -0
  79. package/dist/esm/version.json +1 -1
  80. package/dist/types/components/common/DeletableEmojiTooltipContent.d.ts +6 -0
  81. package/dist/types/components/common/Emoji.d.ts +6 -0
  82. package/dist/types/components/common/EmojiErrorMessage.d.ts +1 -0
  83. package/dist/types/components/common/FileChooser.d.ts +1 -0
  84. package/dist/types/components/common/RetryableButton.d.ts +2 -1
  85. package/dist/types/components/common/ToolTipContentWithKeymap.d.ts +9 -0
  86. package/dist/types/components/common/styles.d.ts +6 -2
  87. package/dist/types/components/i18n.d.ts +15 -0
  88. package/dist/types/components/picker/CategoryTracker.d.ts +1 -0
  89. package/dist/types/components/picker/EmojiPickerList.d.ts +8 -1
  90. package/dist/types/components/picker/EmojiPickerListSearch.d.ts +1 -0
  91. package/dist/types/components/picker/VirtualList.d.ts +21 -1
  92. package/dist/types/types.d.ts +1 -1
  93. package/dist/types/util/browser-support.d.ts +6 -1
  94. package/dist/types/util/constants.d.ts +3 -1
  95. package/dist/types/util/keymaps.d.ts +14 -0
  96. package/dist/types/util/type-helpers.d.ts +1 -1
  97. package/package.json +2 -2
  98. package/report.api.md +16 -1
  99. /package/dist/types/{util → hooks}/useInView.d.ts +0 -0
@@ -5,12 +5,15 @@ import { Component } from 'react';
5
5
  import { jsx } from '@emotion/react';
6
6
  import { FormattedMessage, injectIntl } from 'react-intl-next';
7
7
  import AkButton from '@atlaskit/button/custom-theme-button';
8
+ import FocusLock from 'react-focus-lock';
8
9
  import { messages } from '../i18n';
9
10
  import CachingEmoji from './CachingEmoji';
10
- import EmojiErrorMessage from './EmojiErrorMessage';
11
+ import EmojiErrorMessage, { emojiErrorScreenreaderTestId } from './EmojiErrorMessage';
11
12
  import RetryableButton from './RetryableButton';
12
- import { cancelButton, deleteFooter, deletePreview, deleteText, emojiDeleteErrorMessage, previewButtonGroup } from './styles';
13
+ import { cancelButton, deleteFooter, deletePreview, deleteText, emojiDeleteErrorMessage, headingH5, previewButtonGroup } from './styles';
14
+ import VisuallyHidden from '@atlaskit/visually-hidden';
13
15
  export const emojiDeletePreviewTestId = 'emoji-delete-preview';
16
+ const deleteEmojiLabelId = 'fabric.emoji.delete.label.id';
14
17
  class EmojiDeletePreview extends Component {
15
18
  constructor(props) {
16
19
  super(props);
@@ -63,12 +66,16 @@ class EmojiDeletePreview extends Component {
63
66
  const {
64
67
  formatMessage
65
68
  } = intl;
66
- return jsx("div", {
69
+ return jsx(FocusLock, {
70
+ noFocusGuards: true
71
+ }, jsx("div", {
67
72
  css: deletePreview,
68
73
  "data-testid": emojiDeletePreviewTestId
69
74
  }, jsx("div", {
70
75
  css: deleteText
71
- }, jsx("h5", null, jsx(FormattedMessage, messages.deleteEmojiTitle)), jsx(FormattedMessage, _extends({}, messages.deleteEmojiDescription, {
76
+ }, jsx("h2", {
77
+ css: headingH5
78
+ }, jsx(FormattedMessage, messages.deleteEmojiTitle)), jsx(FormattedMessage, _extends({}, messages.deleteEmojiDescription, {
72
79
  values: {
73
80
  emojiShortName: emoji.shortName
74
81
  }
@@ -82,17 +89,20 @@ class EmojiDeletePreview extends Component {
82
89
  message: formatMessage(messages.deleteEmojiFailed),
83
90
  messageStyles: emojiDeleteErrorMessage,
84
91
  tooltip: true
85
- }) : null : null, jsx(RetryableButton, {
92
+ }) : null : null, jsx(VisuallyHidden, {
93
+ id: deleteEmojiLabelId
94
+ }, formatMessage(messages.deleteEmojiLabel)), jsx(RetryableButton, {
86
95
  label: formatMessage(messages.deleteEmojiLabel),
87
96
  onSubmit: this.onSubmit,
88
97
  appearance: "danger",
89
98
  loading: loading,
90
- error: error
99
+ error: error,
100
+ ariaLabelledBy: `${emojiErrorScreenreaderTestId} ${deleteEmojiLabelId}`
91
101
  }), jsx(AkButton, {
92
102
  appearance: "subtle",
93
103
  onClick: this.onCancel,
94
104
  css: cancelButton
95
- }, jsx(FormattedMessage, messages.cancelLabel)))));
105
+ }, jsx(FormattedMessage, messages.cancelLabel))))));
96
106
  }
97
107
  }
98
108
  export default injectIntl(EmojiDeletePreview);
@@ -4,6 +4,9 @@ import { jsx } from '@emotion/react';
4
4
  import Tooltip from '@atlaskit/tooltip';
5
5
  import ErrorIcon from '@atlaskit/icon/glyph/error';
6
6
  import VisuallyHidden from '@atlaskit/visually-hidden';
7
+ import { FormattedMessage, useIntl } from 'react-intl-next';
8
+ import { messages } from '../i18n';
9
+ export const emojiErrorScreenreaderTestId = 'emoji-error-screenreader-message';
7
10
  export const emojiErrorMessageTestId = 'emoji-error-message';
8
11
  export const emojiErrorMessageTooltipTestId = 'emoji-error-message-tooltip';
9
12
  export const emojiErrorIconTestId = 'emoji-error-icon';
@@ -13,6 +16,9 @@ const EmojiErrorMessage = props => {
13
16
  message,
14
17
  tooltip
15
18
  } = props;
19
+ const {
20
+ formatMessage
21
+ } = useIntl();
16
22
  const visualContent = tooltip ? jsx("div", {
17
23
  css: messageStyles,
18
24
  "data-testid": emojiErrorMessageTestId
@@ -21,18 +27,18 @@ const EmojiErrorMessage = props => {
21
27
  position: "top",
22
28
  testId: emojiErrorMessageTooltipTestId
23
29
  }, jsx(ErrorIcon, {
24
- label: "Error",
30
+ label: formatMessage(messages.error),
25
31
  size: "medium",
26
32
  testId: emojiErrorIconTestId
27
33
  }))) : jsx("div", {
28
34
  css: messageStyles,
29
35
  "data-testid": emojiErrorMessageTestId
30
36
  }, jsx(ErrorIcon, {
31
- label: "Error",
37
+ label: formatMessage(messages.error),
32
38
  size: "small"
33
39
  }), message);
34
40
  return jsx(Fragment, null, jsx(VisuallyHidden, {
35
- role: "alert"
36
- }, message), visualContent);
41
+ id: emojiErrorScreenreaderTestId
42
+ }, jsx(FormattedMessage, messages.error, errMsg => jsx("span", null, errMsg, " ", message, "."))), visualContent);
37
43
  };
38
44
  export default EmojiErrorMessage;
@@ -21,6 +21,7 @@ const handleKeyPress = (props, event) => {
21
21
  onSelected
22
22
  } = props;
23
23
  event.preventDefault();
24
+ event.stopPropagation();
24
25
  if (onSelected) {
25
26
  onSelected();
26
27
  }
@@ -1,5 +1,5 @@
1
1
  /** @jsx jsx */
2
- import { Fragment, useEffect, useState, useRef, memo, useCallback, useMemo } from 'react';
2
+ import { Fragment, useEffect, useLayoutEffect, useState, useRef, memo, useCallback } from 'react';
3
3
  import { jsx } from '@emotion/react';
4
4
  import { FormattedMessage, injectIntl } from 'react-intl-next';
5
5
  import TextField from '@atlaskit/textfield';
@@ -9,14 +9,15 @@ import FocusLock from 'react-focus-lock';
9
9
  import * as ImageUtil from '../../util/image';
10
10
  import debug from '../../util/logger';
11
11
  import { messages } from '../i18n';
12
- import EmojiErrorMessage from './EmojiErrorMessage';
12
+ import EmojiErrorMessage, { emojiErrorScreenreaderTestId } from './EmojiErrorMessage';
13
13
  import EmojiUploadPreview from './EmojiUploadPreview';
14
14
  import FileChooser from './FileChooser';
15
15
  import { UploadStatus } from './internal-types';
16
- import { closeEmojiUploadButton, emojiChooseFileErrorMessage, emojiUpload, emojiUploadBottom, emojiUploadTop, uploadChooseFileBrowse, uploadChooseFileEmojiName, uploadChooseFileMessage, uploadChooseFileRow } from './styles';
16
+ import { closeEmojiUploadButton, emojiChooseFileErrorMessage, emojiUpload, emojiUploadBottom, emojiUploadTop, headingH5, requiredSymbol, uploadChooseFileBrowse, uploadChooseFileEmojiName, uploadChooseFileMessage, uploadChooseFileRow } from './styles';
17
17
  export const uploadEmojiNameInputTestId = 'upload-emoji-name-input';
18
18
  export const uploadEmojiComponentTestId = 'upload-emoji-component';
19
19
  export const cancelEmojiUploadPickerTestId = 'cancel-emoji-upload-picker';
20
+ const addCustomEmojiChooseFileScreenreaderId = 'fabric.emoji.choose.file.label.id';
20
21
  const disallowedReplacementsMap = new Map([[':', ''], ['!', ''], ['@', ''], ['#', ''], ['%', ''], ['^', ''], ['&', ''], ['*', ''], ['(', ''], [')', ''], [' ', '_']]);
21
22
  const sanitizeName = name => {
22
23
  // prevent / replace certain characters, allow others
@@ -51,18 +52,12 @@ const ChooseEmojiFile = /*#__PURE__*/memo(props => {
51
52
  onUploadCancelled();
52
53
  }
53
54
  }, [onUploadCancelled]);
54
- const setInputFocus = useCallback(() => {
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
- }
55
+ useLayoutEffect(() => {
56
+ requestAnimationFrame(() => {
57
+ var _inputRef$current;
58
+ (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.focus();
59
+ });
60
60
  }, []);
61
-
62
- // make sure input has focus after update
63
- useEffect(() => {
64
- window.requestAnimationFrame(setInputFocus);
65
- }, [setInputFocus]);
66
61
  const cancelLabel = formatMessage(messages.cancelLabel);
67
62
  const emojiPlaceholder = formatMessage(messages.emojiPlaceholder);
68
63
  const emojiNameAriaLabel = formatMessage(messages.emojiNameAriaLabel);
@@ -72,9 +67,12 @@ const ChooseEmojiFile = /*#__PURE__*/memo(props => {
72
67
  "data-testid": uploadEmojiComponentTestId
73
68
  }, jsx("div", {
74
69
  css: emojiUploadTop
75
- }, jsx("span", {
76
- css: uploadChooseFileMessage
77
- }, jsx(FormattedMessage, messages.addCustomEmojiLabel, message => jsx("h5", null, message))), jsx("div", {
70
+ }, jsx("h2", {
71
+ css: [uploadChooseFileMessage, headingH5]
72
+ }, jsx(FormattedMessage, messages.addCustomEmojiLabel), jsx("span", {
73
+ "aria-hidden": "true",
74
+ css: requiredSymbol
75
+ }, "*")), jsx("div", {
78
76
  css: closeEmojiUploadButton
79
77
  }, jsx(AkButton, {
80
78
  onClick: onUploadCancelled,
@@ -101,18 +99,23 @@ const ChooseEmojiFile = /*#__PURE__*/memo(props => {
101
99
  autoFocus: true,
102
100
  testId: uploadEmojiNameInputTestId,
103
101
  ref: inputRef,
104
- id: "new-emoji-name-input"
102
+ id: "new-emoji-name-input",
103
+ "aria-required": true
105
104
  })), jsx("span", {
106
105
  css: uploadChooseFileBrowse
107
106
  }, jsx(FormattedMessage, messages.emojiChooseFileScreenReaderDescription, screenReaderDescription => jsx(Fragment, null, jsx("span", {
108
107
  hidden: true,
109
108
  id: fileChooserButtonDescriptionId
110
- }, screenReaderDescription), jsx(FileChooser, {
109
+ }, screenReaderDescription), jsx("span", {
110
+ hidden: true,
111
+ id: addCustomEmojiChooseFileScreenreaderId
112
+ }, emojiChooseFileTitle), jsx(FileChooser, {
111
113
  label: emojiChooseFileTitle,
112
114
  onChange: onChooseFile,
113
115
  onClick: onClick,
114
116
  accept: "image/png,image/jpeg,image/gif",
115
117
  ariaDescribedBy: fileChooserButtonDescriptionId,
118
+ ariaLabelledBy: `${emojiErrorScreenreaderTestId} ${addCustomEmojiChooseFileScreenreaderId}`,
116
119
  isDisabled: disableChooser
117
120
  }))))), jsx("div", {
118
121
  css: emojiUploadBottom
@@ -122,7 +125,7 @@ const ChooseEmojiFile = /*#__PURE__*/memo(props => {
122
125
  })));
123
126
  });
124
127
  const EmojiUploadPicker = props => {
125
- var _document$activeEleme2;
128
+ var _document$activeEleme;
126
129
  const {
127
130
  errorMessage,
128
131
  initialUploadName,
@@ -137,7 +140,7 @@ const EmojiUploadPicker = props => {
137
140
  const [filename, setFilename] = useState();
138
141
  const [previewImage, setPreviewImage] = useState();
139
142
  // document is undefined during ssr rendering and throws an error
140
- const lastFocusedElementId = useRef(typeof document !== 'undefined' ? (_document$activeEleme2 = document.activeElement) === null || _document$activeEleme2 === void 0 ? void 0 : _document$activeEleme2.id : '');
143
+ const lastFocusedElementId = useRef(typeof document !== 'undefined' ? (_document$activeEleme = document.activeElement) === null || _document$activeEleme === void 0 ? void 0 : _document$activeEleme.id : '');
141
144
  useEffect(() => {
142
145
  if (errorMessage) {
143
146
  setUploadStatus(UploadStatus.Error);
@@ -202,7 +205,7 @@ const EmojiUploadPicker = props => {
202
205
  }, []);
203
206
  const errorOnUpload = useCallback(event => {
204
207
  debug('File load error: ', event);
205
- setChooseEmojiErrorMessage(messages.emojiUploadFailed);
208
+ setChooseEmojiErrorMessage(jsx(FormattedMessage, messages.emojiUploadFailed));
206
209
  cancelChooseFile();
207
210
  }, [cancelChooseFile]);
208
211
  const onFileLoad = useCallback(file => async f => {
@@ -211,7 +214,7 @@ const EmojiUploadPicker = props => {
211
214
  await ImageUtil.parseImage(f.target.result);
212
215
  setPreviewImage(f.target.result);
213
216
  } catch {
214
- setChooseEmojiErrorMessage(messages.emojiInvalidImage);
217
+ setChooseEmojiErrorMessage(jsx(FormattedMessage, messages.emojiInvalidImage));
215
218
  cancelChooseFile();
216
219
  }
217
220
  }, [cancelChooseFile]);
@@ -221,7 +224,7 @@ const EmojiUploadPicker = props => {
221
224
  const reader = new FileReader();
222
225
  const file = files[0];
223
226
  if (ImageUtil.hasFileExceededSize(file)) {
224
- setChooseEmojiErrorMessage(messages.emojiImageTooBig);
227
+ setChooseEmojiErrorMessage(jsx(FormattedMessage, messages.emojiImageTooBig));
225
228
  cancelChooseFile();
226
229
  return;
227
230
  }
@@ -245,7 +248,9 @@ const EmojiUploadPicker = props => {
245
248
  }
246
249
  }, 0, lastFocusedElementId.current);
247
250
  }, [clearUploadPicker, onUploadCancelled]);
248
- const chooseErrorMessage = useMemo(() => chooseEmojiErrorMessage ? jsx(FormattedMessage, chooseEmojiErrorMessage) : undefined, [chooseEmojiErrorMessage]);
251
+ const onChooseFileClicked = () => {
252
+ onFileChooserClicked && onFileChooserClicked();
253
+ };
249
254
  return jsx(FocusLock, {
250
255
  noFocusGuards: true
251
256
  }, name && previewImage ? jsx(EmojiUploadPreview, {
@@ -258,10 +263,10 @@ const EmojiUploadPicker = props => {
258
263
  }) : jsx(ChooseEmojiFile, {
259
264
  name: name,
260
265
  onChooseFile: onChooseFile,
261
- onClick: onFileChooserClicked,
266
+ onClick: onChooseFileClicked,
262
267
  onNameChange: onNameChange,
263
268
  onUploadCancelled: cancelUpload,
264
- errorMessage: chooseErrorMessage,
269
+ errorMessage: chooseEmojiErrorMessage,
265
270
  intl: intl
266
271
  }));
267
272
  };
@@ -7,14 +7,15 @@ import { FormattedMessage, injectIntl } from 'react-intl-next';
7
7
  import { customCategory } from '../../util/constants';
8
8
  import { messages } from '../i18n';
9
9
  import Emoji from './Emoji';
10
- import EmojiErrorMessage from './EmojiErrorMessage';
10
+ import EmojiErrorMessage, { emojiErrorScreenreaderTestId } from './EmojiErrorMessage';
11
11
  import { UploadStatus } from './internal-types';
12
12
  import RetryableButton from './RetryableButton';
13
13
  import { bigEmojiPreview, cancelButton, emojiPreviewErrorMessage, uploadAddRow, uploadPreview, uploadPreviewFooter, uploadPreviewText } from './styles';
14
14
  import VisuallyHidden from '@atlaskit/visually-hidden';
15
15
  export const uploadPreviewTestId = 'upload-preview';
16
16
  export const cancelUploadButtonTestId = 'cancel-upload-button';
17
- const addEmojiButtonDescriptionId = 'add.emoji.button.screen.reader.description.id';
17
+ const addEmojiPreviewDescriptionId = 'fabric.emoji.preview.description.id';
18
+ const addEmojiButtonLabelId = 'fabric.emoji.add.label.id';
18
19
  class EmojiUploadPreview extends PureComponent {
19
20
  render() {
20
21
  const {
@@ -67,18 +68,21 @@ class EmojiUploadPreview extends PureComponent {
67
68
  message: errorMessage,
68
69
  tooltip: true
69
70
  }) : null, !errorMessage && jsx(VisuallyHidden, {
70
- id: addEmojiButtonDescriptionId
71
+ id: addEmojiPreviewDescriptionId
71
72
  }, jsx(FormattedMessage, _extends({}, messages.emojiPreview, {
72
73
  values: {
73
74
  emoji: name
74
75
  }
75
- }))), jsx(RetryableButton, {
76
+ }))), jsx(VisuallyHidden, {
77
+ id: addEmojiButtonLabelId
78
+ }, errorMessage ? formatMessage(messages.retryLabel) : formatMessage(messages.addEmojiLabel)), jsx(RetryableButton, {
76
79
  label: formatMessage(messages.addEmojiLabel),
77
80
  onSubmit: onAddEmoji,
78
81
  appearance: "primary",
79
82
  loading: uploading,
80
83
  error: !!errorMessage,
81
- ariaDescribedby: addEmojiButtonDescriptionId
84
+ ariaDescribedBy: addEmojiPreviewDescriptionId,
85
+ ariaLabelledBy: `${emojiErrorScreenreaderTestId} ${addEmojiButtonLabelId}`
82
86
  }), jsx(AkButton, {
83
87
  onClick: onUploadCancelled,
84
88
  appearance: "subtle",
@@ -6,13 +6,16 @@ const FileChooser = props => {
6
6
  const {
7
7
  accept,
8
8
  ariaDescribedBy,
9
+ ariaLabelledBy,
9
10
  isDisabled,
10
11
  label,
11
12
  onChange,
12
13
  onClick
13
14
  } = props;
14
15
  const filePickerRef = useRef(null);
16
+ const fileButtonRef = useRef(null);
15
17
  const handleOnChooseFile = () => {
18
+ var _fileButtonRef$curren;
16
19
  if (!filePickerRef.current) {
17
20
  return;
18
21
  }
@@ -20,12 +23,15 @@ const FileChooser = props => {
20
23
  onClick();
21
24
  }
22
25
  filePickerRef.current.click();
26
+ (_fileButtonRef$curren = fileButtonRef.current) === null || _fileButtonRef$curren === void 0 ? void 0 : _fileButtonRef$curren.focus();
23
27
  };
24
28
  return /*#__PURE__*/React.createElement("span", null, /*#__PURE__*/React.createElement(AkButton, {
25
29
  onClick: handleOnChooseFile,
26
30
  isDisabled: isDisabled,
27
31
  "aria-describedby": ariaDescribedBy,
28
- testId: chooseFileButtonTestId
32
+ "aria-labelledby": ariaLabelledBy,
33
+ testId: chooseFileButtonTestId,
34
+ ref: fileButtonRef
29
35
  }, label), /*#__PURE__*/React.createElement("input", {
30
36
  className: "emojiUploadFileInput",
31
37
  ref: filePickerRef,
@@ -15,13 +15,17 @@ const LoadingSpinner = () => {
15
15
  };
16
16
  const RetryButton = props => {
17
17
  const {
18
- onSubmit
18
+ onSubmit,
19
+ ariaLabelledBy,
20
+ ariaDescribedBy
19
21
  } = props;
20
22
  return jsx(FormattedMessage, messages.retryLabel, retryLabel => jsx(AkButton, {
21
23
  css: uploadRetryButton,
22
24
  appearance: "warning",
23
25
  onClick: onSubmit,
24
26
  testId: retryUploadButtonTestId,
27
+ "aria-describedby": ariaDescribedBy,
28
+ "aria-labelledby": ariaLabelledBy,
25
29
  autoFocus: true
26
30
  }, retryLabel));
27
31
  };
@@ -30,14 +34,16 @@ const UploadButton = props => {
30
34
  appearance,
31
35
  onSubmit,
32
36
  label,
33
- ariaDescribedby
37
+ ariaLabelledBy,
38
+ ariaDescribedBy
34
39
  } = props;
35
40
  return jsx(AkButton, {
36
41
  css: uploadEmojiButton,
37
42
  appearance: appearance,
38
43
  onClick: onSubmit,
39
44
  testId: uploadEmojiButtonTestId,
40
- "aria-describedby": ariaDescribedby,
45
+ "aria-describedby": ariaDescribedBy,
46
+ "aria-labelledby": ariaLabelledBy,
41
47
  autoFocus: true
42
48
  }, label);
43
49
  };
@@ -0,0 +1,15 @@
1
+ /** @jsx jsx */
2
+ import React, { Fragment } from 'react';
3
+ import { jsx } from '@emotion/react';
4
+ import { formatShortcut } from '../../util/keymaps';
5
+ import { tooltipShortcutStyle } from './styles';
6
+ export const ToolTipContentWithKeymap = /*#__PURE__*/React.memo(({
7
+ description,
8
+ shortcutOverride,
9
+ keymap
10
+ }) => {
11
+ const shortcut = shortcutOverride || keymap && formatShortcut(keymap);
12
+ return shortcut || description ? jsx(Fragment, null, description, shortcut && description && '\u00A0', shortcut && jsx("span", {
13
+ css: tooltipShortcutStyle
14
+ }, shortcut)) : null;
15
+ });
@@ -1,7 +1,8 @@
1
1
  import { css, keyframes } from '@emotion/react';
2
2
  import { defaultEmojiHeight } from '../../util/constants';
3
3
  import { akEmojiSelectedBackgroundColor } from '../../util/shared-styles';
4
- import { B100, N20, N200, N20A, N300, N900, R300, R400 } from '@atlaskit/theme/colors';
4
+ import { B100, N20, N200, N20A, N300, N400, N900, R300, R400 } from '@atlaskit/theme/colors';
5
+ import { fontFamily as getFontFamily, gridSize as getGridSize } from '@atlaskit/theme/constants';
5
6
  export const commonSelectedStyles = 'emoji-common-selected';
6
7
  export const selectOnHoverStyles = 'emoji-common-select-on-hover';
7
8
  export const emojiSprite = 'emoji-common-emoji-sprite';
@@ -9,18 +10,15 @@ export const emojiNodeStyles = 'emoji-common-node';
9
10
  export const emojiImage = 'emoji-common-emoji-image';
10
11
  export const emojiDeleteButton = 'emoji-common-deleteButton';
11
12
  export const emojiMainStyle = 'emoji-common-main-styles';
13
+ export const deletableEmoji = 'emoji-common-deletable';
12
14
  export const deleteButton = css({
13
15
  // hide by default
14
16
  visibility: 'hidden',
15
17
  display: 'flex',
16
- height: '0px',
17
- // 40px emoji width with 2px left offset
18
- width: '38px',
19
- alignItems: 'flex-end',
20
- justifyContent: 'flex-end',
21
- // vertically align button and prevent emoji offset
22
- paddingTop: '4px',
23
- marginBottom: '-4px'
18
+ position: 'absolute',
19
+ top: '-8px',
20
+ right: '-8px',
21
+ zIndex: 1
24
22
  });
25
23
  export const emojiToneSelectorContainer = css({
26
24
  flex: 1,
@@ -28,7 +26,7 @@ export const emojiToneSelectorContainer = css({
28
26
  justifyContent: 'flex-end',
29
27
  padding: '10px 10px 11px 0'
30
28
  });
31
- export const emojiStyles = css({
29
+ export const emojiImageContainer = css({
32
30
  borderRadius: "var(--ds-radius-100, 3px)",
33
31
  backgroundColor: 'transparent',
34
32
  display: 'inline-block',
@@ -37,6 +35,9 @@ export const emojiStyles = css({
37
35
  // headings. Smaller headings get a slight increase in height, cannot add more negative margin
38
36
  // as a "selected" emoji (e.g. in the editor) will not look good.
39
37
  margin: '-1px 0',
38
+ img: {
39
+ display: 'block'
40
+ },
40
41
  [`&.${commonSelectedStyles},&.${selectOnHoverStyles}:hover`]: {
41
42
  backgroundColor: akEmojiSelectedBackgroundColor
42
43
  },
@@ -44,8 +45,12 @@ export const emojiStyles = css({
44
45
  // show delete button on hover
45
46
  visibility: 'visible'
46
47
  },
47
- img: {
48
- display: 'block'
48
+ [`&.${deletableEmoji}`]: {
49
+ position: 'relative'
50
+ },
51
+ // show delete button on focus
52
+ [`&.${deletableEmoji}:focus-within .${emojiDeleteButton}`]: {
53
+ visibility: 'visible'
49
54
  },
50
55
  '&:focus': {
51
56
  boxShadow: `0 0 0 2px ${`var(--ds-border-focused, ${B100})`}`,
@@ -53,7 +58,7 @@ export const emojiStyles = css({
53
58
  outline: 'none'
54
59
  }
55
60
  });
56
- export const emojiContainer = css({
61
+ export const emojiSpriteContainer = css({
57
62
  display: 'inline-block',
58
63
  // Ensure along with vertical align middle, we don't increase the line height for h1..h6, and p
59
64
  margin: '-1px 0',
@@ -206,13 +211,13 @@ export const previewImg = css({
206
211
  display: 'inline-block',
207
212
  flex: 'initial',
208
213
  width: '32px',
209
- [`& .${emojiSprite}, > span`]: {
214
+ [`& .${emojiSprite}, span[role="img"]`]: {
210
215
  width: '32px',
211
216
  height: '32px',
212
217
  padding: 0,
213
218
  maxHeight: 'inherit'
214
219
  },
215
- [`& > span > img`]: {
220
+ [`& span[role="img"] > img`]: {
216
221
  position: 'relative',
217
222
  left: '50%',
218
223
  top: '50%',
@@ -253,7 +258,9 @@ export const emojiUploadTop = css({
253
258
  fontSize: '12px'
254
259
  });
255
260
  export const uploadChooseFileMessage = css({
256
- color: `var(--ds-text-subtle, ${N300})`
261
+ '&&': {
262
+ color: `var(--ds-text-subtle, ${N300})`
263
+ }
257
264
  });
258
265
  export const closeEmojiUploadButton = css({
259
266
  display: 'flex'
@@ -346,6 +353,19 @@ export const deleteText = css({
346
353
  lineHeight: '16px'
347
354
  }
348
355
  });
356
+ export const headingH5 = css({
357
+ '&&': {
358
+ fontSize: "var(--ds-font-size-075, 12px)",
359
+ fontWeight: "var(--ds-font-weight-semibold, 600)",
360
+ letterSpacing: '-0.003em',
361
+ lineHeight: "var(--ds-font-lineHeight-100, 16px)"
362
+ }
363
+ });
364
+ export const requiredSymbol = css({
365
+ paddingLeft: `${getGridSize() / 4}px`,
366
+ color: `var(--ds-text-danger, ${R400})`,
367
+ fontFamily: getFontFamily()
368
+ });
349
369
  export const previewButtonGroup = css({
350
370
  display: 'flex'
351
371
  });
@@ -416,4 +436,12 @@ export const emojiActionsWrapper = css({
416
436
  display: 'flex',
417
437
  justifyContent: 'flex-end',
418
438
  alignItems: 'center'
439
+ });
440
+ export const tooltipShortcutStyle = css({
441
+ borderRadius: '3px',
442
+ backgroundColor: `var(--ds-background-inverse-subtle, ${N400})`,
443
+ padding: '0 2px',
444
+ /* TODO: fix in develop: https://atlassian.slack.com/archives/CFG3PSQ9E/p1647395052443259?thread_ts=1647394572.556029&cid=CFG3PSQ9E */
445
+ /* stylelint-disable-next-line */
446
+ label: 'tooltip-shortcut'
419
447
  });
@@ -1,5 +1,15 @@
1
1
  import { defineMessages } from 'react-intl-next';
2
2
  export const messages = defineMessages({
3
+ deleteEmojiTooltip: {
4
+ id: 'fabric.emoji.delete.tooltip',
5
+ defaultMessage: 'Delete',
6
+ description: 'Tooltip content for delete emoji when focus on deletable emoji'
7
+ },
8
+ deleteEmojiTooltipForScreenreader: {
9
+ id: 'fabric.emoji.delete.screenreader.tooltip',
10
+ defaultMessage: 'To delete {shortName} emoji, press Backspace',
11
+ description: 'Tooltip content for delete emoji when focus on deletable emoji'
12
+ },
3
13
  deleteEmojiTitle: {
4
14
  id: 'fabric.emoji.delete.title',
5
15
  defaultMessage: 'Remove emoji',
@@ -37,7 +47,7 @@ export const messages = defineMessages({
37
47
  },
38
48
  emojiChooseFileScreenReaderDescription: {
39
49
  id: 'fabric.emoji.choose.file.screenReaderDescription',
40
- defaultMessage: 'Choose a file for the emoji. JPG, PNG or GIF. Max size 1 MB.',
50
+ defaultMessage: 'Choose a file for the emoji. JPG, PNG or GIF. Max size 1 MB',
41
51
  description: 'Message indicating the purpose of choosing the file and requirements for the file'
42
52
  },
43
53
  emojiSelectSkinToneButtonAriaLabelText: {
@@ -102,7 +112,7 @@ export const messages = defineMessages({
102
112
  },
103
113
  categoriesSelectorLabel: {
104
114
  id: 'fabric.emoji.categories.label',
105
- defaultMessage: 'Choose a emoji category',
115
+ defaultMessage: 'Choose an emoji category',
106
116
  description: 'Aria label for Emoji categories selector at the top of emoji picker'
107
117
  },
108
118
  categoriesSearchResults: {
@@ -209,5 +219,10 @@ export const messages = defineMessages({
209
219
  id: 'fabric.emoji.emojipicker.emoi.roledescription',
210
220
  defaultMessage: 'emoji button',
211
221
  description: `Aria roledescription for emoji button, used in emoji picker.`
222
+ },
223
+ error: {
224
+ id: 'fabric.emoji.emojipicker.error',
225
+ defaultMessage: 'Error!',
226
+ description: `Aria label for error icon, apperaed in emoji uploader screens and delete emoji screens of emoji picker`
212
227
  }
213
228
  });
@@ -52,6 +52,7 @@ const CategorySelector = props => {
52
52
  return;
53
53
  }
54
54
  e.preventDefault();
55
+ e.stopPropagation();
55
56
  const lastCategoryIndex = categories.length - 1;
56
57
  switch (e.key) {
57
58
  // navigate to the right category
@@ -111,7 +112,7 @@ const CategorySelector = props => {
111
112
  "data-focus-index": index,
112
113
  "aria-label": categoryName,
113
114
  "aria-controls": currentFocus === index ? RENDER_EMOJI_PICKER_LIST_TESTID : undefined,
114
- "aria-selected": currentFocus === index ? true : false,
115
+ "aria-selected": categoryId === activeCategoryId,
115
116
  css: categoryClasses,
116
117
  disabled: disableCategories,
117
118
  onClick: handleClick(categoryId, index),
@@ -21,4 +21,7 @@ export default class CategoryTracker {
21
21
  getRow(category) {
22
22
  return this.categoryToRow.get(category);
23
23
  }
24
+ getFirstCategory() {
25
+ return this.rowToCategory.get(0);
26
+ }
24
27
  }
@@ -111,8 +111,6 @@ const EmojiPickerComponent = ({
111
111
  if (emojiToRender && !containsEmojiId(emojiToRender, selectedEmoji)) {
112
112
  batchedUpdates(() => {
113
113
  setSelectedEmoji(undefined);
114
- // Only enable categories for full emoji list (non-search)
115
- setActiveCategory(null);
116
114
  });
117
115
  }
118
116
  batchedUpdates(() => {
@@ -276,12 +274,12 @@ const EmojiPickerComponent = ({
276
274
  });
277
275
  fireAnalytics(uploadBeginButton());
278
276
  }, [emojiProvider, fireAnalytics]);
279
- const scrollToUploadedEmoji = useCallback(() => {
277
+ const scrollToUploadedEmoji = useCallback(emojiDescription => {
280
278
  if (emojiPickerList.current) {
281
279
  // Wait a tick to ensure repaint and updated height for picker list
282
280
  window.setTimeout(() => {
283
281
  var _emojiPickerList$curr2;
284
- (_emojiPickerList$curr2 = emojiPickerList.current) === null || _emojiPickerList$curr2 === void 0 ? void 0 : _emojiPickerList$curr2.scrollToRecentlyUploaded();
282
+ (_emojiPickerList$curr2 = emojiPickerList.current) === null || _emojiPickerList$curr2 === void 0 ? void 0 : _emojiPickerList$curr2.scrollToRecentlyUploaded(emojiDescription);
285
283
  }, 0);
286
284
  }
287
285
  }, [emojiPickerList]);
@@ -298,7 +296,7 @@ const EmojiPickerComponent = ({
298
296
  setSelectedEmoji(emojiDescription);
299
297
  setUploading(false);
300
298
  });
301
- scrollToUploadedEmoji();
299
+ scrollToUploadedEmoji(emojiDescription);
302
300
  };
303
301
  uploadEmoji(upload, emojiProvider, errorSetter, onSuccess, fireAnalytics, retry);
304
302
  }, [emojiProvider, fireAnalytics, scrollToUploadedEmoji]);
@@ -348,6 +346,11 @@ const EmojiPickerComponent = ({
348
346
  fireAnalytics(openedPickerEvent());
349
347
  isMounting.current = false;
350
348
  }
349
+
350
+ // stop all key propagation to other event listeners
351
+ const suppressKeyPress = e => {
352
+ e.stopPropagation();
353
+ };
351
354
  useEffect(() => {
352
355
  // componentDidMount logic
353
356
  if (!isMounted) {
@@ -402,7 +405,10 @@ const EmojiPickerComponent = ({
402
405
  "data-emoji-picker-container": true,
403
406
  role: "dialog",
404
407
  "aria-label": formatMessage(messages.emojiPickerTitle),
405
- "aria-modal": true
408
+ "aria-modal": true,
409
+ onKeyPress: suppressKeyPress,
410
+ onKeyUp: suppressKeyPress,
411
+ onKeyDown: suppressKeyPress
406
412
  }, jsx(CategorySelector, {
407
413
  activeCategoryId: activeCategory,
408
414
  dynamicCategories: dynamicCategories,