@atlaskit/smart-card 21.2.0 → 22.1.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 (128) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/dist/cjs/extractors/block/index.js +6 -7
  3. package/dist/cjs/extractors/common/actions/extractPreviewAction.js +16 -19
  4. package/dist/cjs/state/actions/index.js +54 -10
  5. package/dist/cjs/state/analytics/types.js +5 -0
  6. package/dist/cjs/state/analytics/useSmartLinkAnalytics.js +429 -109
  7. package/dist/cjs/state/hooks-external/useSmartLinkActions.js +5 -3
  8. package/dist/cjs/utils/analytics/analytics.js +241 -74
  9. package/dist/cjs/utils/analytics/index.js +32 -5
  10. package/dist/cjs/utils/mocks.js +28 -2
  11. package/dist/cjs/version.json +1 -1
  12. package/dist/cjs/view/BlockCard/actions/PreviewAction.js +2 -2
  13. package/dist/cjs/view/BlockCard/index.js +6 -5
  14. package/dist/cjs/view/CardWithUrl/component.js +27 -5
  15. package/dist/cjs/view/CardWithUrl/loader.js +6 -1
  16. package/dist/cjs/view/EmbedCard/index.js +3 -2
  17. package/dist/cjs/view/EmbedModal/components/analytics/index.js +100 -0
  18. package/dist/cjs/view/EmbedModal/components/analytics/types.js +5 -0
  19. package/dist/cjs/view/EmbedModal/components/size-experiment/index.js +5 -10
  20. package/dist/cjs/view/EmbedModal/index.js +54 -15
  21. package/dist/cjs/view/EmbedModal/types.js +9 -1
  22. package/dist/cjs/view/FlexibleCard/components/blocks/block/index.js +17 -3
  23. package/dist/cjs/view/FlexibleCard/components/blocks/preview-block/index.js +5 -2
  24. package/dist/cjs/view/FlexibleCard/components/common/image-icon/index.js +4 -2
  25. package/dist/cjs/view/FlexibleCard/components/common/loading-skeleton/index.js +1 -2
  26. package/dist/cjs/view/FlexibleCard/components/elements/media/index.js +5 -2
  27. package/dist/cjs/view/HoverCard/components/HoverCardComponent.js +11 -3
  28. package/dist/cjs/view/HoverCard/components/HoverCardContent.js +36 -8
  29. package/dist/cjs/view/HoverCard/components/SnippetOrPreview.js +74 -0
  30. package/dist/cjs/view/HoverCard/index.js +6 -1
  31. package/dist/cjs/view/HoverCard/styled.js +11 -3
  32. package/dist/cjs/view/common/Modal.js +29 -4
  33. package/dist/cjs/view/common/ModalHeader.js +3 -2
  34. package/dist/es2019/extractors/block/index.js +6 -7
  35. package/dist/es2019/extractors/common/actions/extractPreviewAction.js +16 -10
  36. package/dist/es2019/state/actions/index.js +54 -10
  37. package/dist/es2019/state/analytics/types.js +1 -0
  38. package/dist/es2019/state/analytics/useSmartLinkAnalytics.js +424 -41
  39. package/dist/es2019/state/hooks-external/useSmartLinkActions.js +5 -3
  40. package/dist/es2019/utils/analytics/analytics.js +254 -79
  41. package/dist/es2019/utils/analytics/index.js +32 -4
  42. package/dist/es2019/utils/mocks.js +26 -1
  43. package/dist/es2019/version.json +1 -1
  44. package/dist/es2019/view/BlockCard/actions/PreviewAction.js +3 -3
  45. package/dist/es2019/view/BlockCard/index.js +5 -4
  46. package/dist/es2019/view/CardWithUrl/component.js +27 -5
  47. package/dist/es2019/view/CardWithUrl/loader.js +6 -1
  48. package/dist/es2019/view/EmbedCard/index.js +3 -2
  49. package/dist/es2019/view/EmbedModal/components/analytics/index.js +80 -0
  50. package/dist/es2019/view/EmbedModal/components/analytics/types.js +1 -0
  51. package/dist/es2019/view/EmbedModal/components/size-experiment/index.js +5 -9
  52. package/dist/es2019/view/EmbedModal/index.js +46 -13
  53. package/dist/es2019/view/EmbedModal/types.js +6 -1
  54. package/dist/es2019/view/FlexibleCard/components/blocks/block/index.js +17 -7
  55. package/dist/es2019/view/FlexibleCard/components/blocks/preview-block/index.js +4 -1
  56. package/dist/es2019/view/FlexibleCard/components/common/image-icon/index.js +4 -2
  57. package/dist/es2019/view/FlexibleCard/components/common/loading-skeleton/index.js +2 -3
  58. package/dist/es2019/view/FlexibleCard/components/elements/media/index.js +5 -2
  59. package/dist/es2019/view/HoverCard/components/HoverCardComponent.js +11 -3
  60. package/dist/es2019/view/HoverCard/components/HoverCardContent.js +36 -10
  61. package/dist/es2019/view/HoverCard/components/SnippetOrPreview.js +42 -0
  62. package/dist/es2019/view/HoverCard/index.js +6 -1
  63. package/dist/es2019/view/HoverCard/styled.js +8 -0
  64. package/dist/es2019/view/common/Modal.js +27 -6
  65. package/dist/es2019/view/common/ModalHeader.js +3 -2
  66. package/dist/esm/extractors/block/index.js +6 -7
  67. package/dist/esm/extractors/common/actions/extractPreviewAction.js +17 -19
  68. package/dist/esm/state/actions/index.js +54 -10
  69. package/dist/esm/state/analytics/types.js +1 -0
  70. package/dist/esm/state/analytics/useSmartLinkAnalytics.js +428 -111
  71. package/dist/esm/state/hooks-external/useSmartLinkActions.js +5 -3
  72. package/dist/esm/utils/analytics/analytics.js +241 -74
  73. package/dist/esm/utils/analytics/index.js +31 -4
  74. package/dist/esm/utils/mocks.js +25 -0
  75. package/dist/esm/version.json +1 -1
  76. package/dist/esm/view/BlockCard/actions/PreviewAction.js +3 -3
  77. package/dist/esm/view/BlockCard/index.js +5 -4
  78. package/dist/esm/view/CardWithUrl/component.js +27 -5
  79. package/dist/esm/view/CardWithUrl/loader.js +6 -1
  80. package/dist/esm/view/EmbedCard/index.js +3 -2
  81. package/dist/esm/view/EmbedModal/components/analytics/index.js +82 -0
  82. package/dist/esm/view/EmbedModal/components/analytics/types.js +1 -0
  83. package/dist/esm/view/EmbedModal/components/size-experiment/index.js +5 -10
  84. package/dist/esm/view/EmbedModal/index.js +52 -15
  85. package/dist/esm/view/EmbedModal/types.js +6 -1
  86. package/dist/esm/view/FlexibleCard/components/blocks/block/index.js +11 -3
  87. package/dist/esm/view/FlexibleCard/components/blocks/preview-block/index.js +5 -2
  88. package/dist/esm/view/FlexibleCard/components/common/image-icon/index.js +4 -2
  89. package/dist/esm/view/FlexibleCard/components/common/loading-skeleton/index.js +1 -2
  90. package/dist/esm/view/FlexibleCard/components/elements/media/index.js +5 -2
  91. package/dist/esm/view/HoverCard/components/HoverCardComponent.js +11 -3
  92. package/dist/esm/view/HoverCard/components/HoverCardContent.js +36 -10
  93. package/dist/esm/view/HoverCard/components/SnippetOrPreview.js +53 -0
  94. package/dist/esm/view/HoverCard/index.js +6 -1
  95. package/dist/esm/view/HoverCard/styled.js +6 -2
  96. package/dist/esm/view/common/Modal.js +28 -6
  97. package/dist/esm/view/common/ModalHeader.js +3 -2
  98. package/dist/types/extractors/block/types.d.ts +4 -2
  99. package/dist/types/extractors/common/actions/extractPreviewAction.d.ts +7 -5
  100. package/dist/types/state/analytics/types.d.ts +9 -0
  101. package/dist/types/state/analytics/useSmartLinkAnalytics.d.ts +41 -24
  102. package/dist/types/state/hooks/useSmartLink.d.ts +26 -18
  103. package/dist/types/state/hooks-external/useSmartLinkActions.d.ts +6 -2
  104. package/dist/types/utils/analytics/analytics.d.ts +19 -21
  105. package/dist/types/utils/analytics/index.d.ts +2 -2
  106. package/dist/types/utils/analytics/types.d.ts +91 -0
  107. package/dist/types/utils/mocks.d.ts +48 -0
  108. package/dist/types/utils/types.d.ts +3 -1
  109. package/dist/types/view/BlockCard/actions/PreviewAction.d.ts +4 -0
  110. package/dist/types/view/BlockCard/index.d.ts +2 -2
  111. package/dist/types/view/BlockCard/types.d.ts +4 -4
  112. package/dist/types/view/Card/index.d.ts +1 -1
  113. package/dist/types/view/EmbedCard/types.d.ts +2 -2
  114. package/dist/types/view/EmbedModal/components/analytics/index.d.ts +5 -0
  115. package/dist/types/view/EmbedModal/components/analytics/types.d.ts +6 -0
  116. package/dist/types/view/EmbedModal/components/size-experiment/index.d.ts +4 -3
  117. package/dist/types/view/EmbedModal/index.d.ts +3 -1
  118. package/dist/types/view/EmbedModal/types.d.ts +14 -4
  119. package/dist/types/view/FlexibleCard/components/blocks/preview-block/types.d.ts +7 -1
  120. package/dist/types/view/FlexibleCard/components/blocks/types.d.ts +15 -0
  121. package/dist/types/view/FlexibleCard/components/common/image-icon/types.d.ts +1 -0
  122. package/dist/types/view/FlexibleCard/components/elements/media/types.d.ts +5 -0
  123. package/dist/types/view/HoverCard/components/SnippetOrPreview.d.ts +4 -0
  124. package/dist/types/view/HoverCard/styled.d.ts +2 -0
  125. package/dist/types/view/HoverCard/types.d.ts +5 -0
  126. package/dist/types/view/common/Modal.d.ts +6 -8
  127. package/package.json +1 -1
  128. package/report.api.md +324 -108
@@ -61,7 +61,16 @@ export function CardWithUrlContent({
61
61
  }, [state.details, url]);
62
62
  const handleClickWrapper = useCallback(event => {
63
63
  const isModifierKeyPressed = isSpecialEvent(event);
64
- analytics.ui.cardClickedEvent(id, isFlexibleUi ? 'flexible' : appearance, state.status, definitionId, extensionKey, isModifierKeyPressed, undefined, product, subproduct);
64
+ analytics.ui.cardClickedEvent({
65
+ id,
66
+ display: isFlexibleUi ? 'flexible' : appearance,
67
+ status: state.status,
68
+ definitionId,
69
+ extensionKey,
70
+ isModifierKeyPressed,
71
+ destinationProduct: product,
72
+ destinationSubproduct: subproduct
73
+ });
65
74
 
66
75
  if (onClick) {
67
76
  onClick(event);
@@ -81,7 +90,14 @@ export function CardWithUrlContent({
81
90
 
82
91
  if (state.status !== 'pending') {
83
92
  measure.create(id, state.status);
84
- analytics.operational.instrument(id, state.status, definitionId, extensionKey, resourceType, state.error);
93
+ analytics.operational.instrument({
94
+ id,
95
+ status: state.status,
96
+ definitionId,
97
+ extensionKey,
98
+ resourceType,
99
+ error: state.error
100
+ });
85
101
  }
86
102
  }, [id, appearance, state.status, state.error, definitionId, extensionKey, resourceType, analytics.operational]); // NB: once the smart-card has rendered into an end state, we capture
87
103
  // this as a successful render. These can be one of:
@@ -90,7 +106,13 @@ export function CardWithUrlContent({
90
106
 
91
107
  useEffect(() => {
92
108
  if (isFinalState(state.status)) {
93
- analytics.ui.renderSuccessEvent(appearance, state.status, id, definitionId, extensionKey);
109
+ analytics.ui.renderSuccessEvent({
110
+ display: appearance,
111
+ status: state.status,
112
+ id,
113
+ definitionId,
114
+ extensionKey
115
+ });
94
116
  }
95
117
  }, [appearance, state.status, url, definitionId, extensionKey, analytics.ui, id]);
96
118
 
@@ -154,7 +176,7 @@ export function CardWithUrlContent({
154
176
  handleErrorRetry: handleRetry,
155
177
  handleInvoke: handleInvoke,
156
178
  handleFrameClick: handleClickWrapper,
157
- handleAnalytics: dispatchAnalytics,
179
+ analytics: analytics,
158
180
  isSelected: isSelected,
159
181
  onResolve: onResolve,
160
182
  onError: onError,
@@ -172,7 +194,7 @@ export function CardWithUrlContent({
172
194
  handleErrorRetry: handleRetry,
173
195
  handleFrameClick: handleClickWrapper,
174
196
  handleInvoke: handleInvoke,
175
- handleAnalytics: dispatchAnalytics,
197
+ analytics: analytics,
176
198
  isSelected: isSelected,
177
199
  isFrameVisible: isFrameVisible,
178
200
  platform: platform,
@@ -68,7 +68,12 @@ export function CardWithURLRenderer(props) {
68
68
  const errorInfo = {
69
69
  componentStack
70
70
  };
71
- analytics.ui.renderFailedEvent(appearance, id, error, errorInfo);
71
+ analytics.ui.renderFailedEvent({
72
+ display: appearance,
73
+ id,
74
+ error,
75
+ errorInfo
76
+ });
72
77
  }
73
78
  }, [analytics.ui, appearance, id]);
74
79
 
@@ -22,7 +22,7 @@ export const EmbedCard = /*#__PURE__*/React.forwardRef(({
22
22
  handleAuthorize,
23
23
  handleErrorRetry,
24
24
  handleFrameClick,
25
- handleAnalytics,
25
+ analytics,
26
26
  handleInvoke,
27
27
  showActions,
28
28
  isSelected,
@@ -79,7 +79,8 @@ export const EmbedCard = /*#__PURE__*/React.forwardRef(({
79
79
  }
80
80
 
81
81
  const resolvedBlockViewProps = extractBlockProps(data, meta, {
82
- handleAnalytics,
82
+ analytics,
83
+ origin: 'smartLinkEmbed',
83
84
  handleInvoke,
84
85
  extensionKey: getExtensionKey(details)
85
86
  });
@@ -0,0 +1,80 @@
1
+ import _extends from "@babel/runtime/helpers/extends";
2
+ import React, { useCallback } from 'react';
3
+ import { EmbedModalSize } from '../../types';
4
+
5
+ const getResizeFrom = size => size === EmbedModalSize.Small ? EmbedModalSize.Large : EmbedModalSize.Small;
6
+
7
+ const withAnalytics = Component => props => {
8
+ const {
9
+ analytics,
10
+ onClose,
11
+ onOpen,
12
+ onOpenFailed,
13
+ onResize,
14
+ origin
15
+ } = props;
16
+ const handleOnOpen = useCallback(context => {
17
+ analytics.screen.modalViewedEvent({
18
+ name: 'embedPreviewModal',
19
+ attributes: {
20
+ origin,
21
+ size: context.size
22
+ }
23
+ });
24
+ analytics.ui.renderSuccessEvent({
25
+ status: 'resolved',
26
+ display: 'preview'
27
+ });
28
+
29
+ if (onOpen) {
30
+ onOpen(context);
31
+ }
32
+ }, [analytics.screen, analytics.ui, onOpen, origin]);
33
+ const handleOnOpenFailed = useCallback((error, errorInfo) => {
34
+ analytics.ui.renderFailedEvent({
35
+ display: 'preview',
36
+ error,
37
+ errorInfo
38
+ });
39
+
40
+ if (onOpenFailed) {
41
+ onOpenFailed(error, errorInfo);
42
+ }
43
+ }, [analytics.ui, onOpenFailed]);
44
+ const handleOnClose = useCallback(context => {
45
+ analytics.ui.modalClosedEvent({
46
+ actionSubjectId: 'embedPreview',
47
+ attributes: {
48
+ origin,
49
+ previewTime: context.duration,
50
+ size: context.size
51
+ }
52
+ });
53
+
54
+ if (onClose) {
55
+ onClose(context);
56
+ }
57
+ }, [analytics.ui, onClose, origin]);
58
+ const handleOnResize = useCallback(context => {
59
+ analytics.ui.buttonClickedEvent({
60
+ actionSubjectId: 'embedPreviewResize',
61
+ attributes: {
62
+ newSize: context.size,
63
+ origin,
64
+ previousSize: getResizeFrom(context.size)
65
+ }
66
+ });
67
+
68
+ if (onResize) {
69
+ onResize(context);
70
+ }
71
+ }, [analytics.ui, onResize, origin]);
72
+ return /*#__PURE__*/React.createElement(Component, _extends({}, props, {
73
+ onClose: handleOnClose,
74
+ onOpen: handleOnOpen,
75
+ onOpenFailed: handleOnOpenFailed,
76
+ onResize: handleOnResize
77
+ }));
78
+ };
79
+
80
+ export default withAnalytics;
@@ -1,28 +1,24 @@
1
1
  import _extends from "@babel/runtime/helpers/extends";
2
2
  import React from 'react';
3
3
  import Modal from '../../../common/Modal';
4
- import { MAX_MODAL_SIZE, MIN_MODAL_SIZE } from '../../constants';
4
+ import { EmbedModalSize } from '../../types';
5
5
 
6
6
  const withSizeExperiment = Component => props => {
7
7
  const {
8
- featureFlags = {},
9
- closeLabel = 'Close Preview'
8
+ featureFlags = {}
10
9
  } = props;
11
10
  const {
12
- embedModalSize
11
+ embedModalSize: size
13
12
  } = featureFlags; // Experiment
14
13
 
15
- if (embedModalSize === 'large' || embedModalSize === 'small') {
16
- const size = embedModalSize === 'large' ? MAX_MODAL_SIZE : MIN_MODAL_SIZE;
14
+ if (size === EmbedModalSize.Large || size === EmbedModalSize.Small) {
17
15
  return /*#__PURE__*/React.createElement(Component, _extends({}, props, {
18
16
  size: size
19
17
  }));
20
18
  } // Control
21
19
 
22
20
 
23
- return /*#__PURE__*/React.createElement(Modal, _extends({}, props, {
24
- closeLabel: closeLabel
25
- }));
21
+ return /*#__PURE__*/React.createElement(Modal, props);
26
22
  };
27
23
 
28
24
  export default withSizeExperiment;
@@ -1,10 +1,16 @@
1
- import React, { useCallback, useState } from 'react';
1
+ import React, { useCallback, useRef, useState } from 'react';
2
2
  import ModalDialog, { ModalBody, ModalTransition } from '@atlaskit/modal-dialog';
3
3
  import LinkInfo from './components/link-info';
4
4
  import { MAX_MODAL_SIZE, MIN_MODAL_SIZE } from './constants';
5
5
  import EmbedContent from './components/embed-content';
6
+ import { EmbedModalSize } from './types';
6
7
  import withErrorBoundary from './components/error-boundary';
7
8
  import withSizeExperiment from './components/size-experiment';
9
+ import withAnalytics from './components/analytics';
10
+
11
+ const toSize = width => width === MAX_MODAL_SIZE ? EmbedModalSize.Large : EmbedModalSize.Small;
12
+
13
+ const toWidth = size => size === EmbedModalSize.Large ? MAX_MODAL_SIZE : MIN_MODAL_SIZE;
8
14
 
9
15
  const EmbedModal = ({
10
16
  download: downloadUrl,
@@ -14,37 +20,64 @@ const EmbedModal = ({
14
20
  onClose,
15
21
  onDownloadActionClick,
16
22
  onOpen,
23
+ onResize,
17
24
  onViewActionClick,
18
25
  providerName,
19
26
  showModal,
20
- size: defaultSize = MIN_MODAL_SIZE,
27
+ size = EmbedModalSize.Small,
21
28
  src,
22
29
  testId = 'smart-embed-preview-modal',
23
30
  title,
24
31
  url
25
32
  }) => {
33
+ const defaultWidth = toWidth(size);
26
34
  const [isOpen, setIsOpen] = useState(showModal);
27
- const [size, setSize] = useState(defaultSize);
35
+ const [width, setWidth] = useState(defaultWidth);
36
+ const openAt = useRef();
37
+ const handleOnOpenComplete = useCallback(() => {
38
+ openAt.current = Date.now();
39
+
40
+ if (onOpen) {
41
+ onOpen({
42
+ size
43
+ });
44
+ }
45
+ }, [onOpen, size]);
28
46
  const handleOnClose = useCallback(() => setIsOpen(false), []);
29
- const handleOnSizeClick = useCallback(() => {
30
- const newSize = size === MIN_MODAL_SIZE ? MAX_MODAL_SIZE : MIN_MODAL_SIZE;
31
- setSize(newSize);
32
- }, [size]);
47
+ const handleOnCloseComplete = useCallback(() => {
48
+ if (onClose) {
49
+ const duration = openAt.current ? Date.now() - openAt.current : undefined;
50
+ onClose({
51
+ duration,
52
+ size: toSize(width)
53
+ });
54
+ }
55
+ }, [onClose, width]);
56
+ const handleOnResizeClick = useCallback(() => {
57
+ const newWidth = width === MIN_MODAL_SIZE ? MAX_MODAL_SIZE : MIN_MODAL_SIZE;
58
+ setWidth(newWidth);
59
+
60
+ if (onResize) {
61
+ onResize({
62
+ size: toSize(newWidth)
63
+ });
64
+ }
65
+ }, [onResize, width]);
33
66
  return /*#__PURE__*/React.createElement(ModalTransition, null, isOpen && /*#__PURE__*/React.createElement(ModalDialog, {
34
67
  height: "100%",
35
68
  onClose: handleOnClose,
36
- onCloseComplete: onClose,
37
- onOpenComplete: onOpen,
69
+ onCloseComplete: handleOnCloseComplete,
70
+ onOpenComplete: handleOnOpenComplete,
38
71
  testId: testId,
39
- width: size
72
+ width: width
40
73
  }, /*#__PURE__*/React.createElement(LinkInfo, {
41
74
  downloadUrl: downloadUrl,
42
75
  icon: icon,
43
76
  providerName: providerName,
44
77
  onViewButtonClick: onViewActionClick,
45
78
  onDownloadButtonClick: onDownloadActionClick,
46
- onResizeButtonClick: handleOnSizeClick,
47
- size: size,
79
+ onResizeButtonClick: handleOnResizeClick,
80
+ size: width,
48
81
  title: title,
49
82
  testId: testId,
50
83
  url: url
@@ -56,4 +89,4 @@ const EmbedModal = ({
56
89
  }))));
57
90
  };
58
91
 
59
- export default withSizeExperiment(withErrorBoundary(EmbedModal));
92
+ export default withSizeExperiment(withAnalytics(withErrorBoundary(EmbedModal)));
@@ -1 +1,6 @@
1
- export {};
1
+ export let EmbedModalSize;
2
+
3
+ (function (EmbedModalSize) {
4
+ EmbedModalSize["Large"] = "large";
5
+ EmbedModalSize["Small"] = "small";
6
+ })(EmbedModalSize || (EmbedModalSize = {}));
@@ -1,5 +1,5 @@
1
1
  /** @jsx jsx */
2
- import React from 'react';
2
+ import React, { useEffect } from 'react';
3
3
  import { css, jsx } from '@emotion/core';
4
4
  import { ElementName, SmartLinkDirection, SmartLinkSize } from '../../../../../constants';
5
5
  import { getBaseStyles, getGapSize, highlightRemoveStyles, renderChildren } from '../utils';
@@ -37,11 +37,21 @@ const Block = ({
37
37
  direction = SmartLinkDirection.Horizontal,
38
38
  size = SmartLinkSize.Medium,
39
39
  testId = 'smart-block',
40
- overrideCss
41
- }) => jsx("div", {
42
- css: [getBlockStyles(direction, size), overrideCss],
43
- "data-smart-block": true,
44
- "data-testid": testId
45
- }, renderChildren(children, size));
40
+ overrideCss,
41
+ blockRef,
42
+ onRender,
43
+ onTransitionEnd
44
+ }) => {
45
+ useEffect(() => {
46
+ onRender && onRender();
47
+ }, [onRender]);
48
+ return jsx("div", {
49
+ css: [getBlockStyles(direction, size), overrideCss],
50
+ "data-smart-block": true,
51
+ "data-testid": testId,
52
+ onTransitionEnd: onTransitionEnd,
53
+ ref: blockRef
54
+ }, renderChildren(children, size));
55
+ };
46
56
 
47
57
  export default Block;
@@ -13,6 +13,7 @@ import { Preview } from '../../elements';
13
13
  const PreviewBlock = ({
14
14
  status = SmartLinkStatus.Fallback,
15
15
  testId = 'smart-block-preview',
16
+ onError,
16
17
  ...blockProps
17
18
  }) => {
18
19
  if (status !== SmartLinkStatus.Resolved) {
@@ -21,7 +22,9 @@ const PreviewBlock = ({
21
22
 
22
23
  return /*#__PURE__*/React.createElement(Block, _extends({}, blockProps, {
23
24
  testId: `${testId}-resolved-view`
24
- }), /*#__PURE__*/React.createElement(Preview, null));
25
+ }), /*#__PURE__*/React.createElement(Preview, {
26
+ onError: onError
27
+ }));
25
28
  };
26
29
 
27
30
  export default PreviewBlock;
@@ -7,7 +7,8 @@ import LoadingSkeleton from '../loading-skeleton';
7
7
  const ImageIcon = ({
8
8
  defaultIcon,
9
9
  testId,
10
- url
10
+ url,
11
+ onError
11
12
  }) => jsx(ImageLoader, {
12
13
  src: url,
13
14
  loading: jsx(LoadingSkeleton, {
@@ -17,7 +18,8 @@ const ImageIcon = ({
17
18
  src: url,
18
19
  "data-testid": `${testId}-image`
19
20
  }),
20
- errored: defaultIcon
21
+ errored: defaultIcon,
22
+ onError: onError
21
23
  });
22
24
 
23
25
  export default ImageIcon;
@@ -8,10 +8,9 @@ const LoadingSkeleton = ({
8
8
  width,
9
9
  height
10
10
  }) => {
11
- const animationWidth = width ? `${width}rem` : '20px';
12
11
  const animationNameStyles = keyframes`
13
- 0% { background-position: -${animationWidth} 0; }
14
- 100% { background-position: ${animationWidth} 0; }
12
+ 0% { background-position: 50% 0; }
13
+ 100% { background-position: -50% 0; }
15
14
  `;
16
15
  const styles = css`
17
16
  width: ${width}rem;
@@ -59,7 +59,8 @@ const Media = ({
59
59
  overrideCss,
60
60
  testId = 'smart-element-media',
61
61
  type,
62
- url
62
+ url,
63
+ onError
63
64
  }) => {
64
65
  if (!type || !url) {
65
66
  return null;
@@ -72,7 +73,9 @@ const Media = ({
72
73
  "data-testid": testId
73
74
  }, jsx(ImageIcon, {
74
75
  testId: `${testId}-image`,
75
- url: url
76
+ url: url,
77
+ onError: onError,
78
+ defaultIcon: null
76
79
  }));
77
80
  };
78
81
 
@@ -43,7 +43,11 @@ export const HoverCardComponent = ({
43
43
  //Check its previously open to avoid firing events when moving between the child and hover card components
44
44
  if (isOpen === true && cardOpenTime.current) {
45
45
  const hoverTime = Date.now() - cardOpenTime.current;
46
- analytics.ui.hoverCardDismissedEvent('card', hoverTime, 'mouse_hover');
46
+ analytics.ui.hoverCardDismissedEvent({
47
+ previewDisplay: 'card',
48
+ hoverTime,
49
+ previewInvokeMethod: 'mouse_hover'
50
+ });
47
51
  }
48
52
 
49
53
  setIsOpen(false);
@@ -66,7 +70,10 @@ export const HoverCardComponent = ({
66
70
  //Check if its previously closed to avoid firing events when moving between the child and hover card components
67
71
  if (isOpen === false) {
68
72
  cardOpenTime.current = Date.now();
69
- analytics.ui.hoverCardViewedEvent(hoverDisplay, invokeMethod);
73
+ analytics.ui.hoverCardViewedEvent({
74
+ previewDisplay: hoverDisplay,
75
+ previewInvokeMethod: invokeMethod
76
+ });
70
77
  } //If these are undefined then popupOffset is undefined and we fallback to default bottom-start placement
71
78
 
72
79
 
@@ -84,7 +91,8 @@ export const HoverCardComponent = ({
84
91
  const linkActions = useSmartLinkActions({
85
92
  url,
86
93
  appearance: SMART_CARD_ANALYTICS_DISPLAY,
87
- analyticsHandler
94
+ analyticsHandler,
95
+ origin: 'smartLinkPreviewHoverCard'
88
96
  });
89
97
  const onActionClick = useCallback(actionId => {
90
98
  if (actionId === 'preview-content') {
@@ -1,17 +1,17 @@
1
1
  import ShortcutIcon from '@atlaskit/icon/glyph/shortcut';
2
2
  import { extractType } from '@atlaskit/linking-common/extractors';
3
- import React, { useCallback, useEffect, useMemo } from 'react';
3
+ import React, { useCallback, useEffect, useMemo, useRef } from 'react';
4
4
  import { ActionName, SmartLinkPosition, SmartLinkSize } from '../../../constants';
5
- import extractPreview from '../../../extractors/flexible/extract-preview';
6
5
  import { extractMetadata } from '../../../extractors/hover/extractMetadata';
7
6
  import { useSmartCardActions } from '../../../state/actions';
8
7
  import { getExtensionKey } from '../../../state/helpers';
9
8
  import { isSpecialEvent } from '../../../utils';
10
9
  import FlexibleCard from '../../FlexibleCard';
11
- import { FooterBlock, MetadataBlock, PreviewBlock, SnippetBlock, TitleBlock } from '../../FlexibleCard/components/blocks';
12
- import { flexibleUiOptions, footerBlockCss, metadataBlockCss, titleBlockCss } from '../styled';
10
+ import { FooterBlock, MetadataBlock, SnippetBlock, TitleBlock } from '../../FlexibleCard/components/blocks';
11
+ import { flexibleUiOptions, footerBlockCss, metadataBlockCss, titleBlockCss, hiddenSnippetStyles } from '../styled';
13
12
  import { getSimulatedMetadata, SMART_CARD_ANALYTICS_DISPLAY } from '../utils';
14
13
  import HoverCardLoadingView from './HoverCardLoadingView';
14
+ import SnippetOrPreview from './SnippetOrPreview';
15
15
  export const getOpenAction = (url, analytics, onActionClick) => ({
16
16
  name: ActionName.CustomAction,
17
17
  icon: /*#__PURE__*/React.createElement(ShortcutIcon, {
@@ -25,7 +25,9 @@ export const getOpenAction = (url, analytics, onActionClick) => ({
25
25
  }
26
26
 
27
27
  window.open(url, '_blank');
28
- analytics.ui.hoverCardOpenLinkClickedEvent('card');
28
+ analytics.ui.hoverCardOpenLinkClickedEvent({
29
+ previewDisplay: 'card'
30
+ });
29
31
  },
30
32
  testId: 'hover-card-open-button'
31
33
  });
@@ -61,13 +63,22 @@ const HoverCardContent = ({
61
63
  useEffect(() => {
62
64
  // Since hover preview only render on resolved status,
63
65
  // there is no need to check for statuses.
64
- analytics.ui.renderSuccessEvent(SMART_CARD_ANALYTICS_DISPLAY, cardState.status);
66
+ analytics.ui.renderSuccessEvent({
67
+ display: SMART_CARD_ANALYTICS_DISPLAY,
68
+ status: cardState.status
69
+ });
65
70
  }, [analytics.ui, cardState.status]);
66
71
  const extensionKey = useMemo(() => getExtensionKey(cardState.details), [cardState.details]);
67
72
  const onClick = useCallback(event => {
68
73
  const isModifierKeyPressed = isSpecialEvent(event);
69
- analytics.ui.cardClickedEvent(undefined, SMART_CARD_ANALYTICS_DISPLAY, cardState.status, undefined, undefined, isModifierKeyPressed, undefined, undefined, undefined, 'titleGoToLink');
70
- }, [cardState.status, analytics.ui]);
74
+ analytics.ui.cardClickedEvent({
75
+ id,
76
+ display: SMART_CARD_ANALYTICS_DISPLAY,
77
+ status: cardState.status,
78
+ isModifierKeyPressed,
79
+ actionSubjectId: 'titleGoToLink'
80
+ });
81
+ }, [cardState.status, analytics.ui, id]);
71
82
  const titleActions = useMemo(() => [getOpenAction(url, analytics, onActionClick)], [url, analytics, onActionClick]);
72
83
  const footerActions = useMemo(() => toFooterActions(cardActions, onActionClick), [cardActions, onActionClick]);
73
84
  const data = (_cardState$details = cardState.details) === null || _cardState$details === void 0 ? void 0 : _cardState$details.data;
@@ -79,7 +90,17 @@ const HoverCardContent = ({
79
90
  subtitle
80
91
  } = extractMetadata(getSimulatedMetadata(extensionKey, types));
81
92
  const titleMaxLines = subtitle && subtitle.length > 0 ? 1 : 2;
82
- const body = data && extractPreview(data) ? /*#__PURE__*/React.createElement(PreviewBlock, null) : /*#__PURE__*/React.createElement(SnippetBlock, null);
93
+ const snippetHeight = React.useRef(0);
94
+ const snippetBlockRef = useRef(null);
95
+ const onSnippetRender = useCallback(() => {
96
+ var _snippetBlockRef$curr, _snippetBlockRef$curr2;
97
+
98
+ snippetHeight.current = (_snippetBlockRef$curr = (_snippetBlockRef$curr2 = snippetBlockRef.current) === null || _snippetBlockRef$curr2 === void 0 ? void 0 : _snippetBlockRef$curr2.getBoundingClientRect().height) !== null && _snippetBlockRef$curr !== void 0 ? _snippetBlockRef$curr : 0;
99
+ }, []);
100
+ const body = SnippetOrPreview({
101
+ data: data,
102
+ snippetHeight: snippetHeight.current
103
+ });
83
104
  const titleBlockProps = {
84
105
  actions: titleActions,
85
106
  maxLines: titleMaxLines,
@@ -112,7 +133,12 @@ const HoverCardContent = ({
112
133
  size: SmartLinkSize.Large,
113
134
  overrideCss: metadataBlockCss,
114
135
  maxLines: 1
115
- }), body, /*#__PURE__*/React.createElement(FooterBlock, {
136
+ }), body, /*#__PURE__*/React.createElement(SnippetBlock, {
137
+ testId: 'hidden-snippet',
138
+ onRender: onSnippetRender,
139
+ blockRef: snippetBlockRef,
140
+ overrideCss: hiddenSnippetStyles
141
+ }), /*#__PURE__*/React.createElement(FooterBlock, {
116
142
  actions: footerActions,
117
143
  size: SmartLinkSize.Large,
118
144
  overrideCss: footerBlockCss
@@ -0,0 +1,42 @@
1
+ import React, { useCallback, useState, useRef } from 'react';
2
+ import { css } from '@emotion/core';
3
+ import extractPreview from '../../../extractors/flexible/extract-preview';
4
+ import { PreviewBlock, SnippetBlock } from '../../FlexibleCard/components/blocks';
5
+ import { getTransitionStyles } from '../styled';
6
+
7
+ const SnippetOrPreview = ({
8
+ data,
9
+ snippetHeight
10
+ }) => {
11
+ const transitionStarted = useRef(false);
12
+ const previewBlockRef = useRef(null);
13
+ const [showSnippet, setShowSnippet] = useState(false);
14
+ const [previewCss, setPreviewCss] = useState(); // Set Preview to a fixed height to enable transitions
15
+
16
+ const onPreviewRender = useCallback(() => {
17
+ var _previewBlockRef$curr;
18
+
19
+ previewBlockRef.current && setPreviewCss(css`
20
+ height: ${(_previewBlockRef$curr = previewBlockRef.current) === null || _previewBlockRef$curr === void 0 ? void 0 : _previewBlockRef$curr.getBoundingClientRect().height}px;
21
+ `);
22
+ }, []); // On error set Preview to Snippet height with transition
23
+
24
+ const onPreviewError = useCallback(() => {
25
+ if (transitionStarted.current === false) {
26
+ setPreviewCss(getTransitionStyles(snippetHeight));
27
+ transitionStarted.current = true;
28
+ }
29
+ }, [snippetHeight]);
30
+ const onPreviewTransitionEnd = useCallback(() => {
31
+ setShowSnippet(true);
32
+ }, []);
33
+ return !showSnippet && data && extractPreview(data) ? /*#__PURE__*/React.createElement(PreviewBlock, {
34
+ onError: onPreviewError,
35
+ overrideCss: previewCss,
36
+ onTransitionEnd: onPreviewTransitionEnd,
37
+ blockRef: previewBlockRef,
38
+ onRender: onPreviewRender
39
+ }) : /*#__PURE__*/React.createElement(SnippetBlock, null);
40
+ };
41
+
42
+ export default SnippetOrPreview;
@@ -22,7 +22,12 @@ const HoverCardWithErrorBoundary = props => {
22
22
  }, [createAnalyticsEvent]);
23
23
  const analytics = useSmartLinkAnalytics(url, analyticsHandler, id);
24
24
  const onError = useCallback((error, info) => {
25
- analytics.ui.renderFailedEvent(SMART_CARD_ANALYTICS_DISPLAY, id, error, info);
25
+ analytics.ui.renderFailedEvent({
26
+ display: SMART_CARD_ANALYTICS_DISPLAY,
27
+ id,
28
+ error,
29
+ errorInfo: info
30
+ });
26
31
  }, [analytics.ui, id]);
27
32
  return jsx(ErrorBoundary, {
28
33
  fallback: children,
@@ -83,4 +83,12 @@ export const skeletonContainer = css`
83
83
  flex-direction: column;
84
84
  gap: 0.625rem;
85
85
  align-items: center;
86
+ `;
87
+ export const hiddenSnippetStyles = css`
88
+ visibility: hidden;
89
+ position: absolute;
90
+ `;
91
+ export const getTransitionStyles = snippetHeight => css`
92
+ transition: height 300ms ease-in-out;
93
+ height: ${snippetHeight}px;
86
94
  `;