@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.
- package/CHANGELOG.md +23 -0
- package/dist/cjs/api/EmojiResource.js +29 -24
- package/dist/cjs/api/media/TokenManager.js +4 -4
- package/dist/cjs/components/common/CachingEmoji.js +14 -6
- package/dist/cjs/components/common/Emoji.js +48 -12
- package/dist/cjs/components/common/EmojiActions.js +60 -24
- package/dist/cjs/components/common/EmojiErrorMessage.js +12 -7
- package/dist/cjs/components/common/EmojiPlaceholder.js +1 -0
- package/dist/cjs/components/common/{EmojiButton.js → EmojiRadioButton.js} +28 -19
- package/dist/cjs/components/common/EmojiUploadPicker.js +80 -37
- package/dist/cjs/components/common/EmojiUploadPreview.js +11 -2
- package/dist/cjs/components/common/FileChooser.js +2 -2
- package/dist/cjs/components/common/ResourcedEmojiComponent.js +4 -0
- package/dist/cjs/components/common/RetryableButton.js +7 -3
- package/dist/cjs/components/common/TonePreviewButton.js +44 -0
- package/dist/cjs/components/common/ToneSelector.js +53 -25
- package/dist/cjs/components/common/styles.js +45 -16
- package/dist/cjs/components/i18n.js +44 -4
- package/dist/cjs/components/picker/CategorySelector.js +112 -90
- package/dist/cjs/components/picker/CategoryTracker.js +0 -28
- package/dist/cjs/components/picker/EmojiPickerCategoryHeading.js +2 -1
- package/dist/cjs/components/picker/EmojiPickerComponent.js +13 -7
- package/dist/cjs/components/picker/EmojiPickerEmojiRow.js +32 -4
- package/dist/cjs/components/picker/EmojiPickerList.js +140 -51
- package/dist/cjs/components/picker/EmojiPickerListSearch.js +16 -3
- package/dist/cjs/components/picker/EmojiPickerVirtualItems.js +5 -2
- package/dist/cjs/components/picker/VirtualList.js +196 -14
- package/dist/cjs/components/picker/styles.js +43 -51
- package/dist/cjs/context/EmojiPickerListContext.js +33 -0
- package/dist/cjs/hooks/useEmojiPickerListContext.js +12 -0
- package/dist/cjs/util/constants.js +40 -1
- package/dist/cjs/util/shared-styles.js +3 -4
- package/dist/cjs/version.json +1 -1
- package/dist/es2019/api/EmojiResource.js +29 -24
- package/dist/es2019/api/media/TokenManager.js +4 -4
- package/dist/es2019/components/common/CachingEmoji.js +10 -3
- package/dist/es2019/components/common/Emoji.js +44 -11
- package/dist/es2019/components/common/EmojiActions.js +54 -23
- package/dist/es2019/components/common/EmojiErrorMessage.js +7 -3
- package/dist/es2019/components/common/EmojiPlaceholder.js +1 -0
- package/dist/es2019/components/common/EmojiRadioButton.js +54 -0
- package/dist/es2019/components/common/EmojiUploadPicker.js +75 -36
- package/dist/es2019/components/common/EmojiUploadPreview.js +11 -2
- package/dist/es2019/components/common/FileChooser.js +1 -1
- package/dist/es2019/components/common/ResourcedEmojiComponent.js +4 -0
- package/dist/es2019/components/common/RetryableButton.js +7 -3
- package/dist/es2019/components/common/TonePreviewButton.js +34 -0
- package/dist/es2019/components/common/ToneSelector.js +55 -21
- package/dist/es2019/components/common/styles.js +41 -10
- package/dist/es2019/components/i18n.js +44 -4
- package/dist/es2019/components/picker/CategorySelector.js +114 -89
- package/dist/es2019/components/picker/CategoryTracker.js +0 -24
- package/dist/es2019/components/picker/EmojiPickerCategoryHeading.js +2 -2
- package/dist/es2019/components/picker/EmojiPickerComponent.js +14 -7
- package/dist/es2019/components/picker/EmojiPickerEmojiRow.js +51 -21
- package/dist/es2019/components/picker/EmojiPickerList.js +102 -21
- package/dist/es2019/components/picker/EmojiPickerListSearch.js +14 -4
- package/dist/es2019/components/picker/EmojiPickerVirtualItems.js +4 -1
- package/dist/es2019/components/picker/VirtualList.js +193 -12
- package/dist/es2019/components/picker/styles.js +20 -28
- package/dist/es2019/context/EmojiPickerListContext.js +17 -0
- package/dist/es2019/hooks/useEmojiPickerListContext.js +3 -0
- package/dist/es2019/util/constants.js +31 -0
- package/dist/es2019/util/shared-styles.js +1 -2
- package/dist/es2019/version.json +1 -1
- package/dist/esm/api/EmojiResource.js +29 -24
- package/dist/esm/api/media/TokenManager.js +4 -4
- package/dist/esm/components/common/CachingEmoji.js +14 -6
- package/dist/esm/components/common/Emoji.js +48 -12
- package/dist/esm/components/common/EmojiActions.js +61 -25
- package/dist/esm/components/common/EmojiErrorMessage.js +7 -3
- package/dist/esm/components/common/EmojiPlaceholder.js +1 -0
- package/dist/esm/components/common/EmojiRadioButton.js +52 -0
- package/dist/esm/components/common/EmojiUploadPicker.js +77 -36
- package/dist/esm/components/common/EmojiUploadPreview.js +11 -2
- package/dist/esm/components/common/FileChooser.js +1 -1
- package/dist/esm/components/common/ResourcedEmojiComponent.js +4 -0
- package/dist/esm/components/common/RetryableButton.js +7 -3
- package/dist/esm/components/common/TonePreviewButton.js +33 -0
- package/dist/esm/components/common/ToneSelector.js +49 -18
- package/dist/esm/components/common/styles.js +40 -12
- package/dist/esm/components/i18n.js +44 -4
- package/dist/esm/components/picker/CategorySelector.js +114 -95
- package/dist/esm/components/picker/CategoryTracker.js +0 -28
- package/dist/esm/components/picker/EmojiPickerCategoryHeading.js +2 -2
- package/dist/esm/components/picker/EmojiPickerComponent.js +13 -7
- package/dist/esm/components/picker/EmojiPickerEmojiRow.js +32 -4
- package/dist/esm/components/picker/EmojiPickerList.js +141 -52
- package/dist/esm/components/picker/EmojiPickerListSearch.js +17 -4
- package/dist/esm/components/picker/EmojiPickerVirtualItems.js +5 -2
- package/dist/esm/components/picker/VirtualList.js +195 -12
- package/dist/esm/components/picker/styles.js +37 -45
- package/dist/esm/context/EmojiPickerListContext.js +21 -0
- package/dist/esm/hooks/useEmojiPickerListContext.js +5 -0
- package/dist/esm/util/constants.js +31 -0
- package/dist/esm/util/shared-styles.js +1 -2
- package/dist/esm/version.json +1 -1
- package/dist/types/api/EmojiResource.d.ts +2 -0
- package/dist/types/components/common/Emoji.d.ts +7 -1
- package/dist/types/components/common/EmojiActions.d.ts +3 -2
- package/dist/types/components/common/{EmojiButton.d.ts → EmojiRadioButton.d.ts} +3 -4
- package/dist/types/components/common/EmojiUploadPicker.d.ts +6 -4
- package/dist/types/components/common/RetryableButton.d.ts +1 -0
- package/dist/types/components/common/TonePreviewButton.d.ts +14 -0
- package/dist/types/components/common/ToneSelector.d.ts +8 -5
- package/dist/types/components/common/internal-types.d.ts +9 -0
- package/dist/types/components/common/styles.d.ts +2 -1
- package/dist/types/components/i18n.d.ts +40 -0
- package/dist/types/components/picker/CategorySelector.d.ts +3 -10
- package/dist/types/components/picker/CategoryTracker.d.ts +0 -2
- package/dist/types/components/picker/EmojiPickerCategoryHeading.d.ts +2 -1
- package/dist/types/components/picker/EmojiPickerEmojiRow.d.ts +5 -0
- package/dist/types/components/picker/EmojiPickerList.d.ts +10 -5
- package/dist/types/components/picker/EmojiPickerListSearch.d.ts +1 -0
- package/dist/types/components/picker/EmojiPickerVirtualItems.d.ts +1 -1
- package/dist/types/components/picker/VirtualList.d.ts +2 -0
- package/dist/types/components/picker/styles.d.ts +1 -1
- package/dist/types/context/EmojiPickerListContext.d.ts +10 -0
- package/dist/types/hooks/useEmojiPickerListContext.d.ts +1 -0
- package/dist/types/util/constants.d.ts +27 -0
- package/dist/types/util/shared-styles.d.ts +1 -1
- package/dist/types/util/type-helpers.d.ts +1 -1
- package/package.json +9 -6
- package/report.api.md +52 -1
- package/README.md +0 -3
- package/dist/es2019/components/common/EmojiButton.js +0 -49
- package/dist/esm/components/common/EmojiButton.js +0 -43
- /package/dist/cjs/{components/hooks.js → hooks/useIsMounted.js} +0 -0
- /package/dist/es2019/{components/hooks.js → hooks/useIsMounted.js} +0 -0
- /package/dist/esm/{components/hooks.js → hooks/useIsMounted.js} +0 -0
- /package/dist/types/{components/hooks.d.ts → hooks/useIsMounted.d.ts} +0 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/** @jsx jsx */
|
|
2
|
-
import
|
|
2
|
+
import { Fragment, useEffect, useState, useRef, memo, useCallback, useMemo } 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';
|
|
6
6
|
import CrossIcon from '@atlaskit/icon/glyph/cross';
|
|
7
7
|
import AkButton from '@atlaskit/button/standard-button';
|
|
8
|
+
import FocusLock from 'react-focus-lock';
|
|
8
9
|
import * as ImageUtil from '../../util/image';
|
|
9
10
|
import debug from '../../util/logger';
|
|
10
11
|
import { messages } from '../i18n';
|
|
@@ -14,6 +15,8 @@ import FileChooser from './FileChooser';
|
|
|
14
15
|
import { UploadStatus } from './internal-types';
|
|
15
16
|
import { closeEmojiUploadButton, emojiChooseFileErrorMessage, emojiUpload, emojiUploadBottom, emojiUploadTop, uploadChooseFileBrowse, uploadChooseFileEmojiName, uploadChooseFileMessage, uploadChooseFileRow } from './styles';
|
|
16
17
|
export const uploadEmojiNameInputTestId = 'upload-emoji-name-input';
|
|
18
|
+
export const uploadEmojiComponentTestId = 'upload-emoji-component';
|
|
19
|
+
export const cancelEmojiUploadPickerTestId = 'cancel-emoji-upload-picker';
|
|
17
20
|
const disallowedReplacementsMap = new Map([[':', ''], ['!', ''], ['@', ''], ['#', ''], ['%', ''], ['^', ''], ['&', ''], ['*', ''], ['(', ''], [')', ''], [' ', '_']]);
|
|
18
21
|
const sanitizeName = name => {
|
|
19
22
|
// prevent / replace certain characters, allow others
|
|
@@ -27,7 +30,7 @@ const toEmojiName = uploadName => {
|
|
|
27
30
|
const name = uploadName.split('_').join(' ');
|
|
28
31
|
return `${name.substr(0, 1).toLocaleUpperCase()}${name.substr(1)}`;
|
|
29
32
|
};
|
|
30
|
-
const ChooseEmojiFile = props => {
|
|
33
|
+
const ChooseEmojiFile = /*#__PURE__*/memo(props => {
|
|
31
34
|
const {
|
|
32
35
|
name = '',
|
|
33
36
|
onChooseFile,
|
|
@@ -42,13 +45,31 @@ const ChooseEmojiFile = props => {
|
|
|
42
45
|
} = intl;
|
|
43
46
|
const disableChooser = !name;
|
|
44
47
|
const fileChooserButtonDescriptionId = 'choose.emoji.file.button.screen.reader.description.id';
|
|
45
|
-
const
|
|
48
|
+
const inputRef = useRef(null);
|
|
49
|
+
const onKeyDownHandler = useCallback(event => {
|
|
46
50
|
if (event.key === 'Escape') {
|
|
47
51
|
onUploadCancelled();
|
|
48
52
|
}
|
|
49
|
-
};
|
|
53
|
+
}, [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
|
+
}
|
|
60
|
+
}, []);
|
|
61
|
+
|
|
62
|
+
// make sure input has focus after update
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
window.requestAnimationFrame(setInputFocus);
|
|
65
|
+
}, [setInputFocus]);
|
|
66
|
+
const cancelLabel = formatMessage(messages.cancelLabel);
|
|
67
|
+
const emojiPlaceholder = formatMessage(messages.emojiPlaceholder);
|
|
68
|
+
const emojiNameAriaLabel = formatMessage(messages.emojiNameAriaLabel);
|
|
69
|
+
const 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", {
|
|
@@ -57,34 +78,37 @@ const ChooseEmojiFile = props => {
|
|
|
57
78
|
css: closeEmojiUploadButton
|
|
58
79
|
}, jsx(AkButton, {
|
|
59
80
|
onClick: onUploadCancelled,
|
|
60
|
-
"aria-
|
|
81
|
+
"aria-label": cancelLabel,
|
|
61
82
|
appearance: "subtle",
|
|
62
83
|
spacing: "none",
|
|
63
|
-
shouldFitContainer: true
|
|
84
|
+
shouldFitContainer: true,
|
|
85
|
+
testId: cancelEmojiUploadPickerTestId
|
|
64
86
|
}, jsx(CrossIcon, {
|
|
65
87
|
size: "small",
|
|
66
|
-
label:
|
|
88
|
+
label: cancelLabel
|
|
67
89
|
})))), jsx("div", {
|
|
68
90
|
css: uploadChooseFileRow
|
|
69
91
|
}, jsx("span", {
|
|
70
92
|
css: uploadChooseFileEmojiName
|
|
71
93
|
}, jsx(TextField, {
|
|
72
|
-
placeholder:
|
|
73
|
-
"aria-label":
|
|
94
|
+
placeholder: emojiPlaceholder,
|
|
95
|
+
"aria-label": emojiNameAriaLabel,
|
|
74
96
|
maxLength: maxNameLength,
|
|
75
97
|
onChange: onNameChange,
|
|
76
98
|
onKeyDown: onKeyDownHandler,
|
|
77
99
|
value: name,
|
|
78
100
|
isCompact: true,
|
|
79
101
|
autoFocus: true,
|
|
80
|
-
testId: uploadEmojiNameInputTestId
|
|
102
|
+
testId: uploadEmojiNameInputTestId,
|
|
103
|
+
ref: inputRef,
|
|
104
|
+
id: "new-emoji-name-input"
|
|
81
105
|
})), jsx("span", {
|
|
82
106
|
css: uploadChooseFileBrowse
|
|
83
107
|
}, jsx(FormattedMessage, messages.emojiChooseFileScreenReaderDescription, screenReaderDescription => jsx(Fragment, null, jsx("span", {
|
|
84
108
|
hidden: true,
|
|
85
109
|
id: fileChooserButtonDescriptionId
|
|
86
110
|
}, screenReaderDescription), jsx(FileChooser, {
|
|
87
|
-
label:
|
|
111
|
+
label: emojiChooseFileTitle,
|
|
88
112
|
onChange: onChooseFile,
|
|
89
113
|
onClick: onClick,
|
|
90
114
|
accept: "image/png,image/jpeg,image/gif",
|
|
@@ -96,8 +120,9 @@ const ChooseEmojiFile = props => {
|
|
|
96
120
|
messageStyles: emojiChooseFileErrorMessage,
|
|
97
121
|
message: errorMessage
|
|
98
122
|
})));
|
|
99
|
-
};
|
|
123
|
+
});
|
|
100
124
|
const EmojiUploadPicker = props => {
|
|
125
|
+
var _document$activeEleme2;
|
|
101
126
|
const {
|
|
102
127
|
errorMessage,
|
|
103
128
|
initialUploadName,
|
|
@@ -111,6 +136,8 @@ const EmojiUploadPicker = props => {
|
|
|
111
136
|
const [name, setName] = useState(initialUploadName && sanitizeName(initialUploadName));
|
|
112
137
|
const [filename, setFilename] = useState();
|
|
113
138
|
const [previewImage, setPreviewImage] = useState();
|
|
139
|
+
// 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 : '');
|
|
114
141
|
useEffect(() => {
|
|
115
142
|
if (errorMessage) {
|
|
116
143
|
setUploadStatus(UploadStatus.Error);
|
|
@@ -126,13 +153,18 @@ const EmojiUploadPicker = props => {
|
|
|
126
153
|
setName(sanitizeName(initialUploadName));
|
|
127
154
|
}
|
|
128
155
|
}, [initialUploadName]);
|
|
129
|
-
const
|
|
156
|
+
const clearUploadPicker = useCallback(() => {
|
|
157
|
+
setName(undefined);
|
|
158
|
+
setPreviewImage(undefined);
|
|
159
|
+
setUploadStatus(UploadStatus.Waiting);
|
|
160
|
+
}, []);
|
|
161
|
+
const onNameChange = useCallback(event => {
|
|
130
162
|
let newName = sanitizeName(event.target.value);
|
|
131
163
|
if (name !== newName) {
|
|
132
164
|
setName(newName);
|
|
133
165
|
}
|
|
134
|
-
};
|
|
135
|
-
const onAddEmoji = () => {
|
|
166
|
+
}, [name]);
|
|
167
|
+
const onAddEmoji = useCallback(() => {
|
|
136
168
|
if (uploadStatus === UploadStatus.Uploading) {
|
|
137
169
|
return;
|
|
138
170
|
}
|
|
@@ -164,13 +196,16 @@ const EmojiUploadPicker = props => {
|
|
|
164
196
|
});
|
|
165
197
|
});
|
|
166
198
|
}
|
|
167
|
-
};
|
|
168
|
-
const
|
|
199
|
+
}, [clearUploadPicker, filename, name, onUploadEmoji, previewImage, uploadStatus]);
|
|
200
|
+
const cancelChooseFile = useCallback(() => {
|
|
201
|
+
setPreviewImage(undefined);
|
|
202
|
+
}, []);
|
|
203
|
+
const errorOnUpload = useCallback(event => {
|
|
169
204
|
debug('File load error: ', event);
|
|
170
205
|
setChooseEmojiErrorMessage(messages.emojiUploadFailed);
|
|
171
206
|
cancelChooseFile();
|
|
172
|
-
};
|
|
173
|
-
const onFileLoad = file => async f => {
|
|
207
|
+
}, [cancelChooseFile]);
|
|
208
|
+
const onFileLoad = useCallback(file => async f => {
|
|
174
209
|
try {
|
|
175
210
|
setFilename(file.name);
|
|
176
211
|
await ImageUtil.parseImage(f.target.result);
|
|
@@ -179,11 +214,8 @@ const EmojiUploadPicker = props => {
|
|
|
179
214
|
setChooseEmojiErrorMessage(messages.emojiInvalidImage);
|
|
180
215
|
cancelChooseFile();
|
|
181
216
|
}
|
|
182
|
-
};
|
|
183
|
-
const
|
|
184
|
-
setPreviewImage(undefined);
|
|
185
|
-
};
|
|
186
|
-
const onChooseFile = event => {
|
|
217
|
+
}, [cancelChooseFile]);
|
|
218
|
+
const onChooseFile = useCallback(event => {
|
|
187
219
|
const files = event.target.files;
|
|
188
220
|
if (files.length) {
|
|
189
221
|
const reader = new FileReader();
|
|
@@ -200,17 +232,23 @@ const EmojiUploadPicker = props => {
|
|
|
200
232
|
} else {
|
|
201
233
|
cancelChooseFile();
|
|
202
234
|
}
|
|
203
|
-
};
|
|
204
|
-
const
|
|
205
|
-
setName(undefined);
|
|
206
|
-
setPreviewImage(undefined);
|
|
207
|
-
setUploadStatus(UploadStatus.Waiting);
|
|
208
|
-
};
|
|
209
|
-
const cancelUpload = () => {
|
|
235
|
+
}, [cancelChooseFile, errorOnUpload, onFileLoad]);
|
|
236
|
+
const cancelUpload = useCallback(() => {
|
|
210
237
|
clearUploadPicker();
|
|
211
238
|
onUploadCancelled();
|
|
212
|
-
|
|
213
|
-
|
|
239
|
+
|
|
240
|
+
// using setTimeout here to allow the UI to update before setting focus
|
|
241
|
+
setTimeout(lastFocus => {
|
|
242
|
+
if (lastFocus) {
|
|
243
|
+
var _document$getElementB;
|
|
244
|
+
(_document$getElementB = document.getElementById(lastFocus)) === null || _document$getElementB === void 0 ? void 0 : _document$getElementB.focus();
|
|
245
|
+
}
|
|
246
|
+
}, 0, lastFocusedElementId.current);
|
|
247
|
+
}, [clearUploadPicker, onUploadCancelled]);
|
|
248
|
+
const chooseErrorMessage = useMemo(() => chooseEmojiErrorMessage ? jsx(FormattedMessage, chooseEmojiErrorMessage) : undefined, [chooseEmojiErrorMessage]);
|
|
249
|
+
return jsx(FocusLock, {
|
|
250
|
+
noFocusGuards: true
|
|
251
|
+
}, name && previewImage ? jsx(EmojiUploadPreview, {
|
|
214
252
|
errorMessage: errorMessage,
|
|
215
253
|
name: name,
|
|
216
254
|
onAddEmoji: onAddEmoji,
|
|
@@ -223,8 +261,9 @@ const EmojiUploadPicker = props => {
|
|
|
223
261
|
onClick: onFileChooserClicked,
|
|
224
262
|
onNameChange: onNameChange,
|
|
225
263
|
onUploadCancelled: cancelUpload,
|
|
226
|
-
errorMessage:
|
|
264
|
+
errorMessage: chooseErrorMessage,
|
|
227
265
|
intl: intl
|
|
228
266
|
}));
|
|
229
267
|
};
|
|
230
|
-
|
|
268
|
+
const EmojiUploadPickerComponent = injectIntl( /*#__PURE__*/memo(EmojiUploadPicker));
|
|
269
|
+
export default EmojiUploadPickerComponent;
|
|
@@ -11,8 +11,10 @@ import EmojiErrorMessage 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
|
+
import VisuallyHidden from '@atlaskit/visually-hidden';
|
|
14
15
|
export const uploadPreviewTestId = 'upload-preview';
|
|
15
16
|
export const cancelUploadButtonTestId = 'cancel-upload-button';
|
|
17
|
+
const addEmojiButtonDescriptionId = 'add.emoji.button.screen.reader.description.id';
|
|
16
18
|
class EmojiUploadPreview extends PureComponent {
|
|
17
19
|
render() {
|
|
18
20
|
const {
|
|
@@ -64,12 +66,19 @@ class EmojiUploadPreview extends PureComponent {
|
|
|
64
66
|
messageStyles: emojiPreviewErrorMessage,
|
|
65
67
|
message: errorMessage,
|
|
66
68
|
tooltip: true
|
|
67
|
-
}) : null, jsx(
|
|
69
|
+
}) : null, !errorMessage && jsx(VisuallyHidden, {
|
|
70
|
+
id: addEmojiButtonDescriptionId
|
|
71
|
+
}, jsx(FormattedMessage, _extends({}, messages.emojiPreview, {
|
|
72
|
+
values: {
|
|
73
|
+
emoji: name
|
|
74
|
+
}
|
|
75
|
+
}))), jsx(RetryableButton, {
|
|
68
76
|
label: formatMessage(messages.addEmojiLabel),
|
|
69
77
|
onSubmit: onAddEmoji,
|
|
70
78
|
appearance: "primary",
|
|
71
79
|
loading: uploading,
|
|
72
|
-
error: !!errorMessage
|
|
80
|
+
error: !!errorMessage,
|
|
81
|
+
ariaDescribedby: addEmojiButtonDescriptionId
|
|
73
82
|
}), jsx(AkButton, {
|
|
74
83
|
onClick: onUploadCancelled,
|
|
75
84
|
appearance: "subtle",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useRef } from 'react';
|
|
2
|
-
import AkButton from '@atlaskit/button/
|
|
2
|
+
import AkButton from '@atlaskit/button/standard-button';
|
|
3
3
|
export const chooseFileButtonTestId = 'choose-file-button';
|
|
4
4
|
export const fileUploadInputTestId = 'file-upload';
|
|
5
5
|
const FileChooser = props => {
|
|
@@ -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
|
-
|
|
2
|
-
import
|
|
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
|
-
|
|
17
|
-
|
|
22
|
+
onToneSelected,
|
|
23
|
+
onToneClose,
|
|
24
|
+
selectedTone,
|
|
25
|
+
isVisible
|
|
18
26
|
} = props;
|
|
19
27
|
const isMounted = useRef(false);
|
|
20
|
-
const
|
|
28
|
+
const selectedToneRadioRef = useRef(null);
|
|
29
|
+
const {
|
|
30
|
+
formatMessage
|
|
31
|
+
} = useIntl();
|
|
21
32
|
const emojiToneCollection = useMemo(() => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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 (
|
|
31
|
-
|
|
53
|
+
if (isVisible) {
|
|
54
|
+
var _selectedToneRadioRef;
|
|
55
|
+
(_selectedToneRadioRef = selectedToneRadioRef.current) === null || _selectedToneRadioRef === void 0 ? void 0 : _selectedToneRadioRef.focus();
|
|
32
56
|
}
|
|
33
|
-
}, [
|
|
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
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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:
|
|
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:
|
|
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
|
|
124
|
-
|
|
125
|
-
|
|
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:
|
|
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:
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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
|
});
|