@atlaskit/reactions 21.8.1 → 22.0.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 +26 -0
- package/dist/cjs/MockReactionsClient.js +24 -16
- package/dist/cjs/analytics/analytics.js +9 -1
- package/dist/cjs/analytics/ufo.js +24 -2
- package/dist/cjs/components/Counter/Counter.js +16 -20
- package/dist/cjs/components/Reaction/Reaction.js +52 -18
- package/dist/cjs/components/Reaction/styles.js +8 -1
- package/dist/cjs/components/ReactionDialog/ReactionView.js +121 -0
- package/dist/cjs/components/ReactionDialog/ReactionsDialog.js +187 -0
- package/dist/cjs/components/ReactionDialog/ReactionsList.js +104 -0
- package/dist/cjs/components/ReactionDialog/index.js +13 -0
- package/dist/cjs/components/ReactionDialog/styles.js +202 -0
- package/dist/cjs/components/ReactionPicker/ReactionPicker.js +15 -20
- package/dist/cjs/components/ReactionTooltip/ReactionTooltip.js +26 -12
- package/dist/cjs/components/ReactionTooltip/styles.js +11 -2
- package/dist/cjs/components/Reactions/Reactions.js +166 -21
- package/dist/cjs/components/Reactions/styles.js +11 -6
- package/dist/cjs/components/Trigger/Trigger.js +1 -2
- package/dist/cjs/components/index.js +9 -1
- package/dist/cjs/containers/ConnectedReactionPicker/ConnectedReactionPicker.js +6 -2
- package/dist/cjs/containers/ConnectedReactionsView/ConnectedReactionsView.js +9 -4
- package/dist/cjs/shared/constants.js +62 -10
- package/dist/cjs/shared/i18n.js +40 -0
- package/dist/cjs/shared/utils.js +60 -2
- package/dist/cjs/types/reaction.js +13 -1
- package/dist/cjs/version.json +1 -1
- package/dist/es2019/MockReactionsClient.js +22 -14
- package/dist/es2019/analytics/analytics.js +3 -0
- package/dist/es2019/analytics/ufo.js +19 -0
- package/dist/es2019/components/Counter/Counter.js +16 -15
- package/dist/es2019/components/Reaction/Reaction.js +43 -18
- package/dist/es2019/components/Reaction/styles.js +9 -2
- package/dist/es2019/components/ReactionDialog/ReactionView.js +69 -0
- package/dist/es2019/components/ReactionDialog/ReactionsDialog.js +145 -0
- package/dist/es2019/components/ReactionDialog/ReactionsList.js +69 -0
- package/dist/es2019/components/ReactionDialog/index.js +1 -0
- package/dist/es2019/components/ReactionDialog/styles.js +169 -0
- package/dist/es2019/components/ReactionPicker/ReactionPicker.js +12 -20
- package/dist/es2019/components/ReactionTooltip/ReactionTooltip.js +22 -12
- package/dist/es2019/components/ReactionTooltip/styles.js +9 -1
- package/dist/es2019/components/Reactions/Reactions.js +146 -22
- package/dist/es2019/components/Reactions/styles.js +9 -5
- package/dist/es2019/components/Trigger/Trigger.js +1 -2
- package/dist/es2019/components/index.js +2 -1
- package/dist/es2019/containers/ConnectedReactionPicker/ConnectedReactionPicker.js +8 -2
- package/dist/es2019/containers/ConnectedReactionsView/ConnectedReactionsView.js +5 -4
- package/dist/es2019/shared/constants.js +55 -6
- package/dist/es2019/shared/i18n.js +43 -0
- package/dist/es2019/shared/utils.js +51 -0
- package/dist/es2019/types/reaction.js +13 -1
- package/dist/es2019/version.json +1 -1
- package/dist/esm/MockReactionsClient.js +24 -13
- package/dist/esm/analytics/analytics.js +5 -0
- package/dist/esm/analytics/ufo.js +19 -0
- package/dist/esm/components/Counter/Counter.js +17 -17
- package/dist/esm/components/Reaction/Reaction.js +51 -19
- package/dist/esm/components/Reaction/styles.js +9 -2
- package/dist/esm/components/ReactionDialog/ReactionView.js +98 -0
- package/dist/esm/components/ReactionDialog/ReactionsDialog.js +161 -0
- package/dist/esm/components/ReactionDialog/ReactionsList.js +79 -0
- package/dist/esm/components/ReactionDialog/index.js +1 -0
- package/dist/esm/components/ReactionDialog/styles.js +177 -0
- package/dist/esm/components/ReactionPicker/ReactionPicker.js +15 -20
- package/dist/esm/components/ReactionTooltip/ReactionTooltip.js +26 -12
- package/dist/esm/components/ReactionTooltip/styles.js +9 -1
- package/dist/esm/components/Reactions/Reactions.js +158 -22
- package/dist/esm/components/Reactions/styles.js +9 -5
- package/dist/esm/components/Trigger/Trigger.js +1 -2
- package/dist/esm/components/index.js +2 -1
- package/dist/esm/containers/ConnectedReactionPicker/ConnectedReactionPicker.js +6 -2
- package/dist/esm/containers/ConnectedReactionsView/ConnectedReactionsView.js +8 -4
- package/dist/esm/shared/constants.js +57 -6
- package/dist/esm/shared/i18n.js +40 -0
- package/dist/esm/shared/utils.js +53 -0
- package/dist/esm/types/reaction.js +13 -1
- package/dist/esm/version.json +1 -1
- package/dist/types/MockReactionsClient.d.ts +7 -3
- package/dist/types/analytics/analytics.d.ts +10 -0
- package/dist/types/analytics/ufo.d.ts +18 -2
- package/dist/types/components/Counter/Counter.d.ts +0 -1
- package/dist/types/components/Reaction/Reaction.d.ts +11 -1
- package/dist/types/components/ReactionDialog/ReactionView.d.ts +19 -0
- package/dist/types/components/ReactionDialog/ReactionsDialog.d.ts +32 -0
- package/dist/types/components/ReactionDialog/ReactionsList.d.ts +23 -0
- package/dist/types/components/ReactionDialog/index.d.ts +1 -0
- package/dist/types/components/ReactionDialog/styles.d.ts +11 -0
- package/dist/types/components/ReactionPicker/ReactionPicker.d.ts +5 -0
- package/dist/types/components/ReactionTooltip/ReactionTooltip.d.ts +12 -0
- package/dist/types/components/ReactionTooltip/styles.d.ts +1 -0
- package/dist/types/components/Reactions/Reactions.d.ts +45 -6
- package/dist/types/components/Reactions/styles.d.ts +1 -0
- package/dist/types/components/index.d.ts +1 -0
- package/dist/types/containers/ConnectedReactionPicker/ConnectedReactionPicker.d.ts +0 -4
- package/dist/types/containers/ConnectedReactionsView/ConnectedReactionsView.d.ts +2 -11
- package/dist/types/index.d.ts +1 -1
- package/dist/types/shared/constants.d.ts +11 -5
- package/dist/types/shared/i18n.d.ts +40 -0
- package/dist/types/shared/utils.d.ts +7 -0
- package/dist/types/types/User.d.ts +10 -0
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/types/reaction.d.ts +15 -2
- package/docs/0-intro.tsx +3 -0
- package/docs/5-graphql-support.tsx +153 -0
- package/package.json +8 -6
package/dist/cjs/version.json
CHANGED
|
@@ -2,44 +2,52 @@ import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
|
2
2
|
import { constants } from './shared';
|
|
3
3
|
export const containerAri = 'ari:cloud:owner:demo-cloud-id:container/1';
|
|
4
4
|
export const ari = 'ari:cloud:owner:demo-cloud-id:item/1';
|
|
5
|
-
export const getReactionSummary = (shortName, count, reacted) => {
|
|
5
|
+
export const getReactionSummary = (shortName, count, reacted, extendedReactions) => {
|
|
6
|
+
const getReactionsByShortName = extendedReactions ? constants.ExtendedReactionsByShortName.get(shortName) : constants.DefaultReactionsByShortName.get(shortName);
|
|
6
7
|
return {
|
|
7
8
|
ari,
|
|
8
9
|
containerAri,
|
|
9
|
-
emojiId:
|
|
10
|
+
emojiId: getReactionsByShortName.id,
|
|
10
11
|
count,
|
|
11
12
|
reacted
|
|
12
13
|
};
|
|
13
14
|
};
|
|
14
15
|
export const getUser = (id, displayName) => ({
|
|
15
16
|
id,
|
|
16
|
-
displayName
|
|
17
|
+
displayName,
|
|
18
|
+
profilePicture: {
|
|
19
|
+
path: 'https://pbs.twimg.com/profile_images/803832195970433027/aaoG6PJI_400x400.jpg'
|
|
20
|
+
}
|
|
17
21
|
});
|
|
18
22
|
|
|
19
23
|
const getReactionKey = (containerAri, ari) => {
|
|
20
24
|
return `${containerAri}|${ari}`;
|
|
21
25
|
};
|
|
22
26
|
|
|
23
|
-
const defaultUsers = [getUser('oscar', 'Oscar Wallhult'), getUser('julien', 'Julien Michel Hoarau'), getUser('craig', 'Craig Petchell'), getUser('jerome', 'Jerome Touffe-Blin'), getUser('esoares', 'Eduardo Soares'), getUser('lpereira', 'Luiz Pereira'), getUser('pcurren', 'Paul Curren'), getUser('ttjandra', 'Tara Tjandra'), getUser('severington', 'Ste Everington'), getUser('sguillope', 'Sylvain Guillope'), getUser('alunnon', 'Alex Lunnon')];
|
|
24
|
-
export const
|
|
27
|
+
const defaultUsers = [getUser('oscar', 'Oscar Wallhult'), getUser('julien', 'Julien Michel Hoarau'), getUser('craig', 'Craig Petchell'), getUser('jerome', 'Jerome Touffe-Blin'), getUser('esoares', 'Eduardo Soares'), getUser('lpereira', 'Luiz Pereira'), getUser('pcurren', 'Paul Curren'), getUser('ttjandra', 'Tara Tjandra'), getUser('severington', 'Ste Everington'), getUser('sguillope', 'Sylvain Guillope'), getUser('alunnon', 'Alex Lunnon'), getUser('bsmith', 'Bob Smith'), getUser('jdoe', 'Jane Doe'), getUser('mhomes', 'Mary Homes'), getUser('ckent', 'Clark Kent')];
|
|
28
|
+
export const simpleMockData = {
|
|
25
29
|
[getReactionKey(containerAri, ari)]: [getReactionSummary(':fire:', 1, true), getReactionSummary(':thumbsup:', 999, false), getReactionSummary(':astonished:', 9, false), getReactionSummary(':heart:', 99, false)]
|
|
26
30
|
};
|
|
31
|
+
const extendedMockData = {
|
|
32
|
+
[getReactionKey(containerAri, ari)]: [getReactionSummary(':fire:', 1, true, true), getReactionSummary(':thumbsup:', 999, false, true), getReactionSummary(':astonished:', 9, false, true), getReactionSummary(':heart:', 99, false, true), getReactionSummary(':thinking:', 10, false, true), getReactionSummary(':clap:', 99, false, true), getReactionSummary(':thumbsdown:', 2, false, true), getReactionSummary(':bulb:', 16, false, true), getReactionSummary(':star:', 9999, false, true), getReactionSummary(':green_heart:', 9, false, true), getReactionSummary(':blue_heart:', 8392, false, true), getReactionSummary(':broken_heart:', 1, false, true), getReactionSummary(':grinning:', 10601, false, true), getReactionSummary(':slight_smile:', 99, false, true)]
|
|
33
|
+
};
|
|
27
34
|
/**
|
|
28
35
|
* Mocked version of the client to fetch user information
|
|
29
36
|
*/
|
|
30
37
|
|
|
31
38
|
export class MockReactionsClient {
|
|
32
|
-
constructor(delay = 0) {
|
|
39
|
+
constructor(delay = 0, showExtendedReactions = false) {
|
|
33
40
|
_defineProperty(this, "delayPromise", () => new Promise(resolve => window.setTimeout(resolve, this.delay)));
|
|
34
41
|
|
|
35
42
|
this.delay = delay;
|
|
43
|
+
this.mockData = showExtendedReactions ? extendedMockData : simpleMockData;
|
|
36
44
|
}
|
|
37
45
|
|
|
38
46
|
async getReactions(containerAri, aris) {
|
|
39
47
|
await this.delayPromise();
|
|
40
48
|
return aris.reduce((results, ari) => {
|
|
41
49
|
const reactionKey = getReactionKey(containerAri, ari);
|
|
42
|
-
results[ari] = mockData[reactionKey] || [];
|
|
50
|
+
results[ari] = this.mockData[reactionKey] || [];
|
|
43
51
|
return results;
|
|
44
52
|
}, {});
|
|
45
53
|
}
|
|
@@ -47,7 +55,7 @@ export class MockReactionsClient {
|
|
|
47
55
|
async getDetailedReaction(containerAri, ari, emojiId) {
|
|
48
56
|
await this.delayPromise();
|
|
49
57
|
const reactionKey = `${containerAri}|${ari}`;
|
|
50
|
-
const reactionsMockData = mockData[reactionKey];
|
|
58
|
+
const reactionsMockData = this.mockData[reactionKey];
|
|
51
59
|
|
|
52
60
|
if (reactionsMockData) {
|
|
53
61
|
const reaction = reactionsMockData.find(reaction_1 => reaction_1.emojiId === emojiId);
|
|
@@ -74,10 +82,10 @@ export class MockReactionsClient {
|
|
|
74
82
|
await this.delayPromise();
|
|
75
83
|
const reactionKey = getReactionKey(containerAri, ari);
|
|
76
84
|
let found = false;
|
|
77
|
-
const reactionsMockData = mockData[reactionKey];
|
|
85
|
+
const reactionsMockData = this.mockData[reactionKey];
|
|
78
86
|
|
|
79
87
|
if (reactionsMockData) {
|
|
80
|
-
mockData[reactionKey] = reactionsMockData.map(reaction => {
|
|
88
|
+
this.mockData[reactionKey] = reactionsMockData.map(reaction => {
|
|
81
89
|
if (reaction.emojiId === emojiId) {
|
|
82
90
|
found = true;
|
|
83
91
|
return { ...reaction,
|
|
@@ -91,7 +99,7 @@ export class MockReactionsClient {
|
|
|
91
99
|
}
|
|
92
100
|
|
|
93
101
|
if (!found) {
|
|
94
|
-
mockData[reactionKey] = [...(reactionsMockData ? reactionsMockData : []), {
|
|
102
|
+
this.mockData[reactionKey] = [...(reactionsMockData ? reactionsMockData : []), {
|
|
95
103
|
containerAri,
|
|
96
104
|
ari,
|
|
97
105
|
emojiId,
|
|
@@ -100,13 +108,13 @@ export class MockReactionsClient {
|
|
|
100
108
|
}];
|
|
101
109
|
}
|
|
102
110
|
|
|
103
|
-
return mockData[reactionKey];
|
|
111
|
+
return this.mockData[reactionKey];
|
|
104
112
|
}
|
|
105
113
|
|
|
106
114
|
async deleteReaction(containerAri, ari, emojiId) {
|
|
107
115
|
await this.delayPromise();
|
|
108
116
|
const reactionKey = getReactionKey(containerAri, ari);
|
|
109
|
-
mockData[reactionKey] = mockData[reactionKey].map(reaction => {
|
|
117
|
+
this.mockData[reactionKey] = this.mockData[reactionKey].map(reaction => {
|
|
110
118
|
if (reaction.emojiId === emojiId) {
|
|
111
119
|
if (reaction.count === 1) {
|
|
112
120
|
return undefined;
|
|
@@ -120,7 +128,7 @@ export class MockReactionsClient {
|
|
|
120
128
|
|
|
121
129
|
return reaction;
|
|
122
130
|
}).filter(reaction_1 => !!reaction_1);
|
|
123
|
-
return mockData[reactionKey];
|
|
131
|
+
return this.mockData[reactionKey];
|
|
124
132
|
}
|
|
125
133
|
|
|
126
134
|
}
|
|
@@ -58,6 +58,9 @@ export const createReactionSelectionEvent = (source, emojiId, reaction, startTim
|
|
|
58
58
|
export const createReactionHoveredEvent = startTime => createPayload('hovered', 'existingReaction', UI_EVENT_TYPE)({
|
|
59
59
|
duration: calculateDuration(startTime)
|
|
60
60
|
});
|
|
61
|
+
export const createReactionFocusedEvent = startTime => createPayload('focused', 'existingReaction', UI_EVENT_TYPE)({
|
|
62
|
+
duration: calculateDuration(startTime)
|
|
63
|
+
});
|
|
61
64
|
export const createReactionClickedEvent = (added, emojiId) => createPayload('clicked', 'existingReaction', UI_EVENT_TYPE)({
|
|
62
65
|
added,
|
|
63
66
|
emojiId
|
|
@@ -33,6 +33,9 @@ export let ExperienceName;
|
|
|
33
33
|
ExperienceName["REACTION_ADDED"] = "reaction-added";
|
|
34
34
|
ExperienceName["REACTION_REMOVED"] = "reaction-removed";
|
|
35
35
|
ExperienceName["REACTION_DETAILS_FETCHED"] = "reaction-details-fetched";
|
|
36
|
+
ExperienceName["REACTION_DIALOG_OPENED"] = "reaction-dialog-opened";
|
|
37
|
+
ExperienceName["REACTION_DIALOG_CLOSED"] = "reaction-dialog-closed";
|
|
38
|
+
ExperienceName["REACTION_DIALOG_SELECTED_REACTION_CHANGED"] = "reaction-dialog-selected-reaction-changed";
|
|
36
39
|
})(ExperienceName || (ExperienceName = {}));
|
|
37
40
|
|
|
38
41
|
export let ComponentName;
|
|
@@ -44,6 +47,7 @@ export let ComponentName;
|
|
|
44
47
|
ComponentName["PICKER_RENDERED"] = "reactions-picker";
|
|
45
48
|
ComponentName["REACTIONS"] = "reactions-list";
|
|
46
49
|
ComponentName["REACTION_ITEM"] = "reaction-item";
|
|
50
|
+
ComponentName["REACTION_DIALOG"] = "reaction-dialog";
|
|
47
51
|
})(ComponentName || (ComponentName = {}));
|
|
48
52
|
|
|
49
53
|
export const PickerRender = new UFOExperience(ExperienceName.PICKER_OPENED, createExperienceConfig(ComponentName.PICKER_RENDERED, ExperienceTypes.Experience, ExperiencePerformanceTypes.InlineResult));
|
|
@@ -57,6 +61,21 @@ export const ReactionsRendered = new ConcurrentExperience(ExperienceName.REACTIO
|
|
|
57
61
|
*/
|
|
58
62
|
|
|
59
63
|
export const ReactionsAdd = new ConcurrentExperience(ExperienceName.REACTION_ADDED, createExperienceConfig(ComponentName.REACTIONS, ExperienceTypes.Experience, ExperiencePerformanceTypes.InlineResult));
|
|
64
|
+
/**
|
|
65
|
+
* Expeirence when a reaction dialog is opened
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
export const ReactionDialogOpened = new UFOExperience(ExperienceName.REACTION_DIALOG_OPENED, createExperienceConfig(ComponentName.REACTION_DIALOG, ExperienceTypes.Experience, ExperiencePerformanceTypes.InlineResult));
|
|
69
|
+
/**
|
|
70
|
+
* Experience when a reaction dialog is closed
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
export const ReactionDialogClosed = new UFOExperience(ExperienceName.REACTION_DIALOG_CLOSED, createExperienceConfig(ComponentName.REACTION_DIALOG, ExperienceTypes.Experience, ExperiencePerformanceTypes.InlineResult));
|
|
74
|
+
/**
|
|
75
|
+
* Experience when a reaction changed/fetched from inside the modal dialog
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
export const ReactionDialogSelectedReactionChanged = new UFOExperience(ExperienceName.REACTION_DIALOG_SELECTED_REACTION_CHANGED, createExperienceConfig(ComponentName.REACTION_DIALOG, ExperienceTypes.Experience, ExperiencePerformanceTypes.InlineResult));
|
|
60
79
|
/**
|
|
61
80
|
* Experience when a reaction details gets fetched
|
|
62
81
|
*/
|
|
@@ -4,7 +4,7 @@ import { jsx, css } from '@emotion/react';
|
|
|
4
4
|
import { SlideIn, ExitingPersistence, mediumDurationMs } from '@atlaskit/motion'; // eslint-disable-next-line @atlaskit/design-system/no-banned-imports
|
|
5
5
|
|
|
6
6
|
import usePreviousValue from '@atlaskit/ds-lib/use-previous-value';
|
|
7
|
-
import {
|
|
7
|
+
import { utils } from '../../shared';
|
|
8
8
|
import * as styles from './styles';
|
|
9
9
|
/**
|
|
10
10
|
* Test id for component top level div
|
|
@@ -21,30 +21,31 @@ export const RENDER_COUNTER_TESTID = 'counter-container';
|
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
export const RENDER_LABEL_TESTID = 'counter_label_wrapper';
|
|
24
|
-
|
|
25
|
-
// Check if reached limit
|
|
26
|
-
if (limit && value >= limit) {
|
|
27
|
-
return overLimitLabel || '';
|
|
28
|
-
} else if (value === 0) {
|
|
29
|
-
return '';
|
|
30
|
-
} else {
|
|
31
|
-
return value.toString();
|
|
32
|
-
}
|
|
33
|
-
};
|
|
24
|
+
|
|
34
25
|
/**
|
|
35
26
|
* Display reaction count next to the emoji button
|
|
36
27
|
*/
|
|
37
|
-
|
|
38
28
|
export const Counter = ({
|
|
39
29
|
highlight = false,
|
|
40
|
-
limit
|
|
41
|
-
overLimitLabel
|
|
30
|
+
limit,
|
|
31
|
+
overLimitLabel,
|
|
42
32
|
className,
|
|
43
33
|
value,
|
|
44
34
|
animationDuration = mediumDurationMs
|
|
45
35
|
}) => {
|
|
36
|
+
const getLabel = value => {
|
|
37
|
+
// Check if reached limit
|
|
38
|
+
if (limit && overLimitLabel && value >= limit) {
|
|
39
|
+
return overLimitLabel || '';
|
|
40
|
+
} else if (value === 0) {
|
|
41
|
+
return '';
|
|
42
|
+
} else {
|
|
43
|
+
return utils.formatLargeNumber(value);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
46
47
|
const previousValue = usePreviousValue(value);
|
|
47
|
-
const label = getLabel(value
|
|
48
|
+
const label = getLabel(value);
|
|
48
49
|
const increase = previousValue ? previousValue < value : false;
|
|
49
50
|
return jsx("div", {
|
|
50
51
|
className: className,
|
|
@@ -10,10 +10,10 @@ import { FlashAnimation } from '../FlashAnimation';
|
|
|
10
10
|
import { ReactionTooltip } from '../ReactionTooltip';
|
|
11
11
|
import { i18n, utils } from '../../shared';
|
|
12
12
|
import * as styles from './styles';
|
|
13
|
+
|
|
13
14
|
/**
|
|
14
15
|
* Test id for Reaction item wrapper div
|
|
15
16
|
*/
|
|
16
|
-
|
|
17
17
|
export const RENDER_REACTION_TESTID = 'render_reaction_wrapper';
|
|
18
18
|
|
|
19
19
|
/**
|
|
@@ -23,19 +23,25 @@ export const Reaction = ({
|
|
|
23
23
|
emojiProvider,
|
|
24
24
|
onClick,
|
|
25
25
|
reaction,
|
|
26
|
-
onMouseEnter,
|
|
26
|
+
onMouseEnter = () => {},
|
|
27
|
+
onFocused = () => {},
|
|
27
28
|
className,
|
|
28
|
-
flash = false
|
|
29
|
+
flash = false,
|
|
30
|
+
handleUserListClick = () => {},
|
|
31
|
+
allowUserDialog
|
|
29
32
|
}) => {
|
|
30
|
-
const
|
|
31
|
-
id: reaction.emojiId,
|
|
32
|
-
shortName: ''
|
|
33
|
-
};
|
|
33
|
+
const intl = useIntl();
|
|
34
34
|
const hoverStart = useRef();
|
|
35
|
+
const focusStart = useRef();
|
|
35
36
|
const {
|
|
36
37
|
createAnalyticsEvent
|
|
37
38
|
} = useAnalyticsEvents();
|
|
38
|
-
const [emojiName, setEmojiName] = useState();
|
|
39
|
+
const [emojiName, setEmojiName] = useState();
|
|
40
|
+
const [isTooltipEnabled, setIsTooltipEnabled] = useState(true);
|
|
41
|
+
const emojiId = {
|
|
42
|
+
id: reaction.emojiId,
|
|
43
|
+
shortName: ''
|
|
44
|
+
}; // TODO: Extract the flow to a custom hook to retrieve emoji detailed description from an id using a custom hook. This will benefit a better optimization instead of the emojiProvider resolving everytime.
|
|
39
45
|
// Also optimize in future version to fetch in batch several emojiIds
|
|
40
46
|
|
|
41
47
|
useEffect(() => {
|
|
@@ -48,7 +54,7 @@ export const Reaction = ({
|
|
|
48
54
|
}
|
|
49
55
|
})();
|
|
50
56
|
}, [emojiProvider, reaction.emojiId]);
|
|
51
|
-
const
|
|
57
|
+
const handleClick = useCallback(event => {
|
|
52
58
|
event.preventDefault();
|
|
53
59
|
|
|
54
60
|
if (utils.isLeftClick(event)) {
|
|
@@ -62,21 +68,38 @@ export const Reaction = ({
|
|
|
62
68
|
}, [createAnalyticsEvent, reaction, onClick]);
|
|
63
69
|
const handleMouseEnter = useCallback(event => {
|
|
64
70
|
event.preventDefault();
|
|
71
|
+
setIsTooltipEnabled(true);
|
|
65
72
|
|
|
66
73
|
if (!reaction.users || !reaction.users.length) {
|
|
67
|
-
|
|
74
|
+
focusStart.current = Date.now();
|
|
68
75
|
}
|
|
69
76
|
|
|
70
|
-
Analytics.createAndFireSafe(createAnalyticsEvent, Analytics.createReactionHoveredEvent,
|
|
77
|
+
Analytics.createAndFireSafe(createAnalyticsEvent, Analytics.createReactionHoveredEvent, focusStart.current);
|
|
78
|
+
onMouseEnter(reaction.emojiId, event);
|
|
79
|
+
}, [createAnalyticsEvent, reaction, onMouseEnter]);
|
|
80
|
+
const handleFocused = useCallback(event => {
|
|
81
|
+
event.preventDefault();
|
|
82
|
+
setIsTooltipEnabled(true);
|
|
71
83
|
|
|
72
|
-
if (
|
|
73
|
-
|
|
84
|
+
if (!reaction.users || !reaction.users.length) {
|
|
85
|
+
hoverStart.current = Date.now();
|
|
74
86
|
}
|
|
75
|
-
|
|
76
|
-
|
|
87
|
+
|
|
88
|
+
Analytics.createAndFireSafe(createAnalyticsEvent, Analytics.createReactionFocusedEvent, hoverStart.current);
|
|
89
|
+
onFocused(reaction.emojiId, event);
|
|
90
|
+
}, [createAnalyticsEvent, reaction, onFocused]);
|
|
91
|
+
|
|
92
|
+
const handleOpenReactionsDialog = emojiId => {
|
|
93
|
+
handleUserListClick(emojiId);
|
|
94
|
+
setIsTooltipEnabled(false);
|
|
95
|
+
};
|
|
96
|
+
|
|
77
97
|
return jsx(ReactionTooltip, {
|
|
78
98
|
emojiName: emojiName,
|
|
79
|
-
reaction: reaction
|
|
99
|
+
reaction: reaction,
|
|
100
|
+
handleUserListClick: handleOpenReactionsDialog,
|
|
101
|
+
allowUserDialog: allowUserDialog,
|
|
102
|
+
isEnabled: isTooltipEnabled
|
|
80
103
|
}, jsx("button", {
|
|
81
104
|
className: className,
|
|
82
105
|
css: [styles.reactionStyle, reaction.reacted && styles.reactedStyle],
|
|
@@ -86,8 +109,10 @@ export const Reaction = ({
|
|
|
86
109
|
type: "button",
|
|
87
110
|
"data-emoji-id": reaction.emojiId,
|
|
88
111
|
"data-testid": RENDER_REACTION_TESTID,
|
|
89
|
-
|
|
90
|
-
onMouseEnter: handleMouseEnter
|
|
112
|
+
onClick: handleClick,
|
|
113
|
+
onMouseEnter: handleMouseEnter,
|
|
114
|
+
onFocus: handleFocused,
|
|
115
|
+
"data-emoji-button-id": reaction.emojiId
|
|
91
116
|
}, jsx(FlashAnimation, {
|
|
92
117
|
flash: flash,
|
|
93
118
|
css: styles.flashStyle
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** @jsx jsx */
|
|
2
2
|
import { css } from '@emotion/react';
|
|
3
|
-
import { B50, B75, B300, N20, N40, N400 } from '@atlaskit/theme/colors';
|
|
3
|
+
import { B50, B75, B300, N20, N40, N400, B100 } from '@atlaskit/theme/colors';
|
|
4
4
|
import { token } from '@atlaskit/tokens';
|
|
5
5
|
/**
|
|
6
6
|
* Default styling px height for an emoji reaction
|
|
@@ -19,7 +19,6 @@ export const emojiStyle = css({
|
|
|
19
19
|
padding: '4px 4px 4px 8px'
|
|
20
20
|
});
|
|
21
21
|
export const reactionStyle = css({
|
|
22
|
-
outline: 'none',
|
|
23
22
|
display: 'flex',
|
|
24
23
|
flexDirection: 'row',
|
|
25
24
|
alignItems: 'flex-start',
|
|
@@ -36,6 +35,13 @@ export const reactionStyle = css({
|
|
|
36
35
|
transition: '200ms ease-in-out',
|
|
37
36
|
'&:hover': {
|
|
38
37
|
background: `${token('color.background.neutral.subtle.hovered', N20)}`
|
|
38
|
+
},
|
|
39
|
+
'&:focus': {
|
|
40
|
+
boxShadow: `0 0 0 2px ${token('color.border.focused', B100)}`,
|
|
41
|
+
// background, box-shadow
|
|
42
|
+
transitionDuration: '0s, 0.2s',
|
|
43
|
+
// disabling browser focus outline
|
|
44
|
+
outline: 'none'
|
|
39
45
|
}
|
|
40
46
|
});
|
|
41
47
|
export const reactedStyle = css({
|
|
@@ -50,6 +56,7 @@ export const flashHeight = akHeight - 2; // height without the 1px border
|
|
|
50
56
|
export const flashStyle = css({
|
|
51
57
|
display: 'flex',
|
|
52
58
|
flexDirection: 'row',
|
|
59
|
+
alignItems: 'center',
|
|
53
60
|
borderRadius: '10px',
|
|
54
61
|
height: `${flashHeight}px`
|
|
55
62
|
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import _extends from "@babel/runtime/helpers/extends";
|
|
2
|
+
|
|
3
|
+
/** @jsx jsx */
|
|
4
|
+
import { useEffect, useState, useMemo } from 'react';
|
|
5
|
+
import { useIntl } from 'react-intl-next';
|
|
6
|
+
import { jsx } from '@emotion/react';
|
|
7
|
+
import { ResourcedEmoji } from '@atlaskit/emoji/element';
|
|
8
|
+
import Avatar from '@atlaskit/avatar/Avatar';
|
|
9
|
+
import Spinner from '@atlaskit/spinner';
|
|
10
|
+
import { useTabPanel } from '@atlaskit/tabs';
|
|
11
|
+
import { i18n } from '../../shared';
|
|
12
|
+
import { reactionViewStyle, userListStyle, userStyle, centerSpinner } from './styles';
|
|
13
|
+
export const ReactionView = ({
|
|
14
|
+
selectedEmojiId,
|
|
15
|
+
emojiProvider,
|
|
16
|
+
reaction
|
|
17
|
+
}) => {
|
|
18
|
+
const intl = useIntl();
|
|
19
|
+
const [emojiName, setEmojiName] = useState('');
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
(async () => {
|
|
22
|
+
const provider = await emojiProvider;
|
|
23
|
+
const emoji = await provider.findByEmojiId({
|
|
24
|
+
shortName: '',
|
|
25
|
+
id: selectedEmojiId
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
if (emoji && emoji.name) {
|
|
29
|
+
setEmojiName(emoji.name);
|
|
30
|
+
}
|
|
31
|
+
})();
|
|
32
|
+
}, [emojiProvider, selectedEmojiId]);
|
|
33
|
+
const alphabeticalNames = useMemo(() => {
|
|
34
|
+
var _reactionObj$users;
|
|
35
|
+
|
|
36
|
+
const reactionObj = reaction;
|
|
37
|
+
return ((_reactionObj$users = reactionObj.users) === null || _reactionObj$users === void 0 ? void 0 : _reactionObj$users.sort((a, b) => a.displayName.localeCompare(b.displayName))) || [];
|
|
38
|
+
}, [reaction]);
|
|
39
|
+
const tabPanelAttributes = useTabPanel();
|
|
40
|
+
return jsx("div", _extends({
|
|
41
|
+
css: reactionViewStyle
|
|
42
|
+
}, tabPanelAttributes), jsx("p", null, jsx(ResourcedEmoji, {
|
|
43
|
+
emojiProvider: emojiProvider,
|
|
44
|
+
emojiId: {
|
|
45
|
+
id: selectedEmojiId,
|
|
46
|
+
shortName: ''
|
|
47
|
+
},
|
|
48
|
+
fitToHeight: 24
|
|
49
|
+
}), intl.formatMessage(i18n.messages.emojiName, {
|
|
50
|
+
emojiName
|
|
51
|
+
})), alphabeticalNames.length === 0 ? jsx("div", {
|
|
52
|
+
css: centerSpinner
|
|
53
|
+
}, jsx(Spinner, {
|
|
54
|
+
size: "large"
|
|
55
|
+
})) : jsx("ul", {
|
|
56
|
+
css: userListStyle
|
|
57
|
+
}, alphabeticalNames.map(user => {
|
|
58
|
+
var _user$profilePicture;
|
|
59
|
+
|
|
60
|
+
const profile = (_user$profilePicture = user.profilePicture) === null || _user$profilePicture === void 0 ? void 0 : _user$profilePicture.path;
|
|
61
|
+
return jsx("li", {
|
|
62
|
+
css: userStyle,
|
|
63
|
+
key: user.id
|
|
64
|
+
}, jsx(Avatar, {
|
|
65
|
+
size: "large",
|
|
66
|
+
src: profile
|
|
67
|
+
}), jsx("span", null, user.displayName));
|
|
68
|
+
})));
|
|
69
|
+
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/** @jsx jsx */
|
|
2
|
+
import { useEffect, useState, useRef, useCallback, useMemo } from 'react';
|
|
3
|
+
import { useIntl } from 'react-intl-next';
|
|
4
|
+
import { jsx } from '@emotion/react';
|
|
5
|
+
import Button from '@atlaskit/button/standard-button';
|
|
6
|
+
import Modal, { ModalBody, ModalFooter, ModalHeader, ModalTitle } from '@atlaskit/modal-dialog';
|
|
7
|
+
import { constants, i18n } from '../../shared';
|
|
8
|
+
import { ReactionsList } from './ReactionsList';
|
|
9
|
+
import { containerStyle, titleStyle } from './styles';
|
|
10
|
+
/**
|
|
11
|
+
* Test id for the Reactions modal dialog
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export const RENDER_MODAL_TESTID = 'render-reactions-modal';
|
|
15
|
+
|
|
16
|
+
const getDimensions = container => {
|
|
17
|
+
return {
|
|
18
|
+
clientWidth: container === null || container === void 0 ? void 0 : container.clientWidth,
|
|
19
|
+
scrollWidth: container === null || container === void 0 ? void 0 : container.scrollWidth,
|
|
20
|
+
scrollLeft: container === null || container === void 0 ? void 0 : container.scrollLeft
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const ReactionsDialog = ({
|
|
25
|
+
reactions = [],
|
|
26
|
+
handleCloseReactionsDialog = () => {},
|
|
27
|
+
emojiProvider,
|
|
28
|
+
selectedEmojiId,
|
|
29
|
+
handleSelectReaction = () => {}
|
|
30
|
+
}) => {
|
|
31
|
+
const [elementToScroll, setElementToScroll] = useState();
|
|
32
|
+
const [reactionsContainerRef, setReactionsContainerRef] = useState(null);
|
|
33
|
+
const reactionElementsRef = useRef();
|
|
34
|
+
const observerRef = useRef();
|
|
35
|
+
const intl = useIntl();
|
|
36
|
+
const isSelectedEmojiViewed = useRef(false);
|
|
37
|
+
const totalReactionsCount = useMemo(() => {
|
|
38
|
+
return reactions.reduce((accum, current) => {
|
|
39
|
+
return accum += current === null || current === void 0 ? void 0 : current.count;
|
|
40
|
+
}, 0);
|
|
41
|
+
}, [reactions]);
|
|
42
|
+
const sortedReactions = useMemo(() => {
|
|
43
|
+
return reactions.sort((a, b) => (b === null || b === void 0 ? void 0 : b.count) - (a === null || a === void 0 ? void 0 : a.count));
|
|
44
|
+
}, [reactions]);
|
|
45
|
+
/* set Reactions Border Width , 9 Number of reactions to display*/
|
|
46
|
+
|
|
47
|
+
const reactionsBorderWidth = useMemo(() => {
|
|
48
|
+
return Math.ceil(reactions.length / constants.NUMBER_OF_REACTIONS_TO_DISPLAY) * 100;
|
|
49
|
+
}, [reactions]);
|
|
50
|
+
/* Callback from IntersectionObserver to set/unset classNames based on visibility to toggle styles*/
|
|
51
|
+
|
|
52
|
+
const handleNavigation = useCallback(entries => {
|
|
53
|
+
entries.forEach((entry, index) => {
|
|
54
|
+
var _dataset;
|
|
55
|
+
|
|
56
|
+
const element = entry.target;
|
|
57
|
+
const emojiElement = element === null || element === void 0 ? void 0 : element.querySelector('[data-emoji-id]');
|
|
58
|
+
const emojiId = emojiElement === null || emojiElement === void 0 ? void 0 : (_dataset = emojiElement.dataset) === null || _dataset === void 0 ? void 0 : _dataset.emojiId;
|
|
59
|
+
|
|
60
|
+
if (entry.intersectionRatio < 1) {
|
|
61
|
+
element.classList.add('disabled');
|
|
62
|
+
/*Check if selectedEmoji (passed as props based on what user selects) is out of viewport */
|
|
63
|
+
|
|
64
|
+
if (emojiId === selectedEmojiId && !isSelectedEmojiViewed.current) {
|
|
65
|
+
setElementToScroll(emojiElement !== null && emojiElement !== void 0 ? emojiElement : undefined);
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
if (emojiId === selectedEmojiId && !isSelectedEmojiViewed.current) {
|
|
69
|
+
isSelectedEmojiViewed.current = true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
element.classList.remove('disabled');
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}, [selectedEmojiId]);
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (elementToScroll && !isSelectedEmojiViewed.current && reactionsContainerRef) {
|
|
78
|
+
isSelectedEmojiViewed.current = true;
|
|
79
|
+
const parentElement = elementToScroll.closest('.reaction-elements');
|
|
80
|
+
const reactionsList = document.querySelector('#reactions-dialog-tabs-list');
|
|
81
|
+
const {
|
|
82
|
+
clientWidth
|
|
83
|
+
} = getDimensions(reactionsList);
|
|
84
|
+
const offsetLeft = parentElement === null || parentElement === void 0 ? void 0 : parentElement.offsetLeft;
|
|
85
|
+
/* which means emoji is not in viewport so scroll to it*/
|
|
86
|
+
|
|
87
|
+
if (reactionsList && offsetLeft > clientWidth) {
|
|
88
|
+
const scrollBy = Math.trunc(offsetLeft / clientWidth) * clientWidth;
|
|
89
|
+
reactionsList.scrollLeft += scrollBy;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}, [elementToScroll, reactionsContainerRef]);
|
|
93
|
+
/* Set up InterSectionObserver to observer reaction elements on navigating*/
|
|
94
|
+
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (reactionsContainerRef) {
|
|
97
|
+
const options = {
|
|
98
|
+
root: reactionsContainerRef,
|
|
99
|
+
rootMargin: '0px',
|
|
100
|
+
threshold: 1.0
|
|
101
|
+
};
|
|
102
|
+
observerRef.current = new IntersectionObserver(handleNavigation, options);
|
|
103
|
+
reactionElementsRef.current = reactionsContainerRef.querySelectorAll('.reaction-elements');
|
|
104
|
+
reactionElementsRef.current && reactionElementsRef.current.length > 0 && reactionElementsRef.current.forEach(child => {
|
|
105
|
+
var _observerRef$current;
|
|
106
|
+
|
|
107
|
+
observerRef === null || observerRef === void 0 ? void 0 : (_observerRef$current = observerRef.current) === null || _observerRef$current === void 0 ? void 0 : _observerRef$current.observe(child);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return () => {
|
|
112
|
+
if (observerRef.current) {
|
|
113
|
+
observerRef.current.disconnect();
|
|
114
|
+
observerRef.current = undefined;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}, [reactionsContainerRef, reactions, handleNavigation, selectedEmojiId]);
|
|
118
|
+
const setRef = useCallback(node => {
|
|
119
|
+
if (!reactionsContainerRef) {
|
|
120
|
+
setReactionsContainerRef(node);
|
|
121
|
+
} // eslint-disable-next-line react-hooks/exhaustive-deps
|
|
122
|
+
|
|
123
|
+
}, []);
|
|
124
|
+
return jsx(Modal, {
|
|
125
|
+
onClose: handleCloseReactionsDialog,
|
|
126
|
+
height: 600,
|
|
127
|
+
testId: RENDER_MODAL_TESTID
|
|
128
|
+
}, jsx(ModalHeader, null, jsx("div", {
|
|
129
|
+
css: titleStyle
|
|
130
|
+
}, jsx(ModalTitle, null, intl.formatMessage(i18n.messages.reactionsCount, {
|
|
131
|
+
count: totalReactionsCount
|
|
132
|
+
})))), jsx(ModalBody, null, jsx("div", {
|
|
133
|
+
css: containerStyle(reactionsBorderWidth),
|
|
134
|
+
ref: setRef
|
|
135
|
+
}, jsx(ReactionsList, {
|
|
136
|
+
initialEmojiId: selectedEmojiId,
|
|
137
|
+
reactions: sortedReactions,
|
|
138
|
+
emojiProvider: emojiProvider,
|
|
139
|
+
onReactionChanged: handleSelectReaction
|
|
140
|
+
}))), jsx(ModalFooter, null, jsx(Button, {
|
|
141
|
+
appearance: "primary",
|
|
142
|
+
onClick: handleCloseReactionsDialog,
|
|
143
|
+
autoFocus: true
|
|
144
|
+
}, intl.formatMessage(i18n.messages.closeReactionsDialog))));
|
|
145
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/** @jsx jsx */
|
|
2
|
+
import { useCallback, useState } from 'react';
|
|
3
|
+
import { jsx } from '@emotion/react';
|
|
4
|
+
import { ResourcedEmoji } from '@atlaskit/emoji/element';
|
|
5
|
+
import Tabs, { Tab, TabList } from '@atlaskit/tabs';
|
|
6
|
+
import { useThemeObserver } from '@atlaskit/tokens';
|
|
7
|
+
import { Counter } from '../Counter';
|
|
8
|
+
import { counterStyle, customTabWrapper, customTabListStyles } from './styles';
|
|
9
|
+
import { ReactionView } from './ReactionView';
|
|
10
|
+
export const ReactionsList = ({
|
|
11
|
+
reactions,
|
|
12
|
+
initialEmojiId,
|
|
13
|
+
emojiProvider,
|
|
14
|
+
onReactionChanged
|
|
15
|
+
}) => {
|
|
16
|
+
const [selectedEmoji, setSelectedEmoji] = useState(() => {
|
|
17
|
+
// Calculate this only on initialize the List of Tabs and each Reactions View collection
|
|
18
|
+
return {
|
|
19
|
+
index: reactions.findIndex(reaction => reaction.emojiId === initialEmojiId),
|
|
20
|
+
id: initialEmojiId
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
const theme = useThemeObserver();
|
|
24
|
+
const onTabChange = useCallback((index, analyticsEvent) => {
|
|
25
|
+
if (index === selectedEmoji.index) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const emojiId = reactions[index].emojiId;
|
|
30
|
+
setSelectedEmoji({
|
|
31
|
+
index,
|
|
32
|
+
id: emojiId
|
|
33
|
+
});
|
|
34
|
+
onReactionChanged(emojiId, analyticsEvent);
|
|
35
|
+
}, [selectedEmoji.index, reactions, onReactionChanged]);
|
|
36
|
+
return jsx(Tabs, {
|
|
37
|
+
id: "reactions-dialog-tabs",
|
|
38
|
+
onChange: onTabChange,
|
|
39
|
+
selected: selectedEmoji.index
|
|
40
|
+
}, jsx("div", {
|
|
41
|
+
css: customTabListStyles,
|
|
42
|
+
id: "reactions-dialog-tabs-list"
|
|
43
|
+
}, jsx(TabList, null, reactions.map(reaction => {
|
|
44
|
+
const emojiId = {
|
|
45
|
+
id: reaction.emojiId,
|
|
46
|
+
shortName: ''
|
|
47
|
+
};
|
|
48
|
+
return jsx("div", {
|
|
49
|
+
css: customTabWrapper((emojiId === null || emojiId === void 0 ? void 0 : emojiId.id) === selectedEmoji.id, selectedEmoji.id, theme),
|
|
50
|
+
className: "reaction-elements",
|
|
51
|
+
key: reaction.emojiId,
|
|
52
|
+
"data-testid": emojiId === null || emojiId === void 0 ? void 0 : emojiId.id
|
|
53
|
+
}, jsx(Tab, null, jsx(ResourcedEmoji, {
|
|
54
|
+
emojiProvider: emojiProvider,
|
|
55
|
+
emojiId: emojiId,
|
|
56
|
+
fitToHeight: 16,
|
|
57
|
+
showTooltip: true
|
|
58
|
+
}), jsx("div", {
|
|
59
|
+
css: counterStyle((emojiId === null || emojiId === void 0 ? void 0 : emojiId.id) === selectedEmoji.id)
|
|
60
|
+
}, jsx(Counter, {
|
|
61
|
+
value: reaction.count
|
|
62
|
+
}))));
|
|
63
|
+
}))), reactions.map(reaction => jsx(ReactionView, {
|
|
64
|
+
key: reaction.emojiId,
|
|
65
|
+
reaction: reaction,
|
|
66
|
+
selectedEmojiId: selectedEmoji.id,
|
|
67
|
+
emojiProvider: emojiProvider
|
|
68
|
+
})));
|
|
69
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ReactionsDialog } from './ReactionsDialog';
|