@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.
Files changed (104) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/cjs/MockReactionsClient.js +24 -16
  3. package/dist/cjs/analytics/analytics.js +9 -1
  4. package/dist/cjs/analytics/ufo.js +24 -2
  5. package/dist/cjs/components/Counter/Counter.js +16 -20
  6. package/dist/cjs/components/Reaction/Reaction.js +52 -18
  7. package/dist/cjs/components/Reaction/styles.js +8 -1
  8. package/dist/cjs/components/ReactionDialog/ReactionView.js +121 -0
  9. package/dist/cjs/components/ReactionDialog/ReactionsDialog.js +187 -0
  10. package/dist/cjs/components/ReactionDialog/ReactionsList.js +104 -0
  11. package/dist/cjs/components/ReactionDialog/index.js +13 -0
  12. package/dist/cjs/components/ReactionDialog/styles.js +202 -0
  13. package/dist/cjs/components/ReactionPicker/ReactionPicker.js +15 -20
  14. package/dist/cjs/components/ReactionTooltip/ReactionTooltip.js +26 -12
  15. package/dist/cjs/components/ReactionTooltip/styles.js +11 -2
  16. package/dist/cjs/components/Reactions/Reactions.js +166 -21
  17. package/dist/cjs/components/Reactions/styles.js +11 -6
  18. package/dist/cjs/components/Trigger/Trigger.js +1 -2
  19. package/dist/cjs/components/index.js +9 -1
  20. package/dist/cjs/containers/ConnectedReactionPicker/ConnectedReactionPicker.js +6 -2
  21. package/dist/cjs/containers/ConnectedReactionsView/ConnectedReactionsView.js +9 -4
  22. package/dist/cjs/shared/constants.js +62 -10
  23. package/dist/cjs/shared/i18n.js +40 -0
  24. package/dist/cjs/shared/utils.js +60 -2
  25. package/dist/cjs/types/reaction.js +13 -1
  26. package/dist/cjs/version.json +1 -1
  27. package/dist/es2019/MockReactionsClient.js +22 -14
  28. package/dist/es2019/analytics/analytics.js +3 -0
  29. package/dist/es2019/analytics/ufo.js +19 -0
  30. package/dist/es2019/components/Counter/Counter.js +16 -15
  31. package/dist/es2019/components/Reaction/Reaction.js +43 -18
  32. package/dist/es2019/components/Reaction/styles.js +9 -2
  33. package/dist/es2019/components/ReactionDialog/ReactionView.js +69 -0
  34. package/dist/es2019/components/ReactionDialog/ReactionsDialog.js +145 -0
  35. package/dist/es2019/components/ReactionDialog/ReactionsList.js +69 -0
  36. package/dist/es2019/components/ReactionDialog/index.js +1 -0
  37. package/dist/es2019/components/ReactionDialog/styles.js +169 -0
  38. package/dist/es2019/components/ReactionPicker/ReactionPicker.js +12 -20
  39. package/dist/es2019/components/ReactionTooltip/ReactionTooltip.js +22 -12
  40. package/dist/es2019/components/ReactionTooltip/styles.js +9 -1
  41. package/dist/es2019/components/Reactions/Reactions.js +146 -22
  42. package/dist/es2019/components/Reactions/styles.js +9 -5
  43. package/dist/es2019/components/Trigger/Trigger.js +1 -2
  44. package/dist/es2019/components/index.js +2 -1
  45. package/dist/es2019/containers/ConnectedReactionPicker/ConnectedReactionPicker.js +8 -2
  46. package/dist/es2019/containers/ConnectedReactionsView/ConnectedReactionsView.js +5 -4
  47. package/dist/es2019/shared/constants.js +55 -6
  48. package/dist/es2019/shared/i18n.js +43 -0
  49. package/dist/es2019/shared/utils.js +51 -0
  50. package/dist/es2019/types/reaction.js +13 -1
  51. package/dist/es2019/version.json +1 -1
  52. package/dist/esm/MockReactionsClient.js +24 -13
  53. package/dist/esm/analytics/analytics.js +5 -0
  54. package/dist/esm/analytics/ufo.js +19 -0
  55. package/dist/esm/components/Counter/Counter.js +17 -17
  56. package/dist/esm/components/Reaction/Reaction.js +51 -19
  57. package/dist/esm/components/Reaction/styles.js +9 -2
  58. package/dist/esm/components/ReactionDialog/ReactionView.js +98 -0
  59. package/dist/esm/components/ReactionDialog/ReactionsDialog.js +161 -0
  60. package/dist/esm/components/ReactionDialog/ReactionsList.js +79 -0
  61. package/dist/esm/components/ReactionDialog/index.js +1 -0
  62. package/dist/esm/components/ReactionDialog/styles.js +177 -0
  63. package/dist/esm/components/ReactionPicker/ReactionPicker.js +15 -20
  64. package/dist/esm/components/ReactionTooltip/ReactionTooltip.js +26 -12
  65. package/dist/esm/components/ReactionTooltip/styles.js +9 -1
  66. package/dist/esm/components/Reactions/Reactions.js +158 -22
  67. package/dist/esm/components/Reactions/styles.js +9 -5
  68. package/dist/esm/components/Trigger/Trigger.js +1 -2
  69. package/dist/esm/components/index.js +2 -1
  70. package/dist/esm/containers/ConnectedReactionPicker/ConnectedReactionPicker.js +6 -2
  71. package/dist/esm/containers/ConnectedReactionsView/ConnectedReactionsView.js +8 -4
  72. package/dist/esm/shared/constants.js +57 -6
  73. package/dist/esm/shared/i18n.js +40 -0
  74. package/dist/esm/shared/utils.js +53 -0
  75. package/dist/esm/types/reaction.js +13 -1
  76. package/dist/esm/version.json +1 -1
  77. package/dist/types/MockReactionsClient.d.ts +7 -3
  78. package/dist/types/analytics/analytics.d.ts +10 -0
  79. package/dist/types/analytics/ufo.d.ts +18 -2
  80. package/dist/types/components/Counter/Counter.d.ts +0 -1
  81. package/dist/types/components/Reaction/Reaction.d.ts +11 -1
  82. package/dist/types/components/ReactionDialog/ReactionView.d.ts +19 -0
  83. package/dist/types/components/ReactionDialog/ReactionsDialog.d.ts +32 -0
  84. package/dist/types/components/ReactionDialog/ReactionsList.d.ts +23 -0
  85. package/dist/types/components/ReactionDialog/index.d.ts +1 -0
  86. package/dist/types/components/ReactionDialog/styles.d.ts +11 -0
  87. package/dist/types/components/ReactionPicker/ReactionPicker.d.ts +5 -0
  88. package/dist/types/components/ReactionTooltip/ReactionTooltip.d.ts +12 -0
  89. package/dist/types/components/ReactionTooltip/styles.d.ts +1 -0
  90. package/dist/types/components/Reactions/Reactions.d.ts +45 -6
  91. package/dist/types/components/Reactions/styles.d.ts +1 -0
  92. package/dist/types/components/index.d.ts +1 -0
  93. package/dist/types/containers/ConnectedReactionPicker/ConnectedReactionPicker.d.ts +0 -4
  94. package/dist/types/containers/ConnectedReactionsView/ConnectedReactionsView.d.ts +2 -11
  95. package/dist/types/index.d.ts +1 -1
  96. package/dist/types/shared/constants.d.ts +11 -5
  97. package/dist/types/shared/i18n.d.ts +40 -0
  98. package/dist/types/shared/utils.d.ts +7 -0
  99. package/dist/types/types/User.d.ts +10 -0
  100. package/dist/types/types/index.d.ts +1 -1
  101. package/dist/types/types/reaction.d.ts +15 -2
  102. package/docs/0-intro.tsx +3 -0
  103. package/docs/5-graphql-support.tsx +153 -0
  104. package/package.json +8 -6
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "name": "@atlaskit/reactions",
3
- "version": "21.8.1"
3
+ "version": "22.0.0"
4
4
  }
@@ -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: constants.DefaultReactionsByShortName.get(shortName).id,
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 mockData = {
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 { constants } from '../../shared';
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
- export const getLabel = (value, overLimitLabel, limit) => {
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 = constants.DEFAULT_REACTION_TOP_LIMIT,
41
- overLimitLabel = constants.DEFAULT_OVER_THE_LIMIT_REACTION_LABEL,
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, overLimitLabel, limit);
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 emojiId = {
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(); // 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
+ 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 handleMouseUp = useCallback(event => {
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
- hoverStart.current = Date.now();
74
+ focusStart.current = Date.now();
68
75
  }
69
76
 
70
- Analytics.createAndFireSafe(createAnalyticsEvent, Analytics.createReactionHoveredEvent, hoverStart.current);
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 (onMouseEnter) {
73
- onMouseEnter(reaction, event);
84
+ if (!reaction.users || !reaction.users.length) {
85
+ hoverStart.current = Date.now();
74
86
  }
75
- }, [createAnalyticsEvent, reaction, onMouseEnter]);
76
- const intl = useIntl();
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
- onMouseUp: handleMouseUp,
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';