@atlaskit/media-card 81.10.19 → 81.11.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 (35) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/cjs/card/card.js +1 -1
  3. package/dist/cjs/card/cardView.js +53 -16
  4. package/dist/cjs/card/fileCard.js +3 -1
  5. package/dist/cjs/card/media-card-analytics-error-boundary.js +1 -1
  6. package/dist/cjs/card/ui/loadingHold/loadingHold.js +34 -0
  7. package/dist/cjs/card/ui/wrapper/imageContainer-compiled.compiled.css +8 -1
  8. package/dist/cjs/card/ui/wrapper/imageContainer-compiled.js +9 -2
  9. package/dist/cjs/inline/loader.js +1 -1
  10. package/dist/cjs/utils/ufoExperiences.js +1 -1
  11. package/dist/es2019/card/card.js +1 -1
  12. package/dist/es2019/card/cardView.js +51 -16
  13. package/dist/es2019/card/fileCard.js +3 -1
  14. package/dist/es2019/card/media-card-analytics-error-boundary.js +1 -1
  15. package/dist/es2019/card/ui/loadingHold/loadingHold.js +29 -0
  16. package/dist/es2019/card/ui/wrapper/imageContainer-compiled.compiled.css +8 -1
  17. package/dist/es2019/card/ui/wrapper/imageContainer-compiled.js +9 -2
  18. package/dist/es2019/inline/loader.js +1 -1
  19. package/dist/es2019/utils/ufoExperiences.js +1 -1
  20. package/dist/esm/card/card.js +1 -1
  21. package/dist/esm/card/cardView.js +53 -16
  22. package/dist/esm/card/fileCard.js +3 -1
  23. package/dist/esm/card/media-card-analytics-error-boundary.js +1 -1
  24. package/dist/esm/card/ui/loadingHold/loadingHold.js +28 -0
  25. package/dist/esm/card/ui/wrapper/imageContainer-compiled.compiled.css +8 -1
  26. package/dist/esm/card/ui/wrapper/imageContainer-compiled.js +9 -2
  27. package/dist/esm/inline/loader.js +1 -1
  28. package/dist/esm/utils/ufoExperiences.js +1 -1
  29. package/dist/types/card/cardView.d.ts +2 -1
  30. package/dist/types/card/fileCard.d.ts +6 -1
  31. package/dist/types/card/ui/loadingHold/loadingHold.d.ts +12 -0
  32. package/dist/types/card/ui/wrapper/imageContainer-compiled.d.ts +6 -1
  33. package/dist/types/card/ui/wrapper/imageContainer.d.ts +1 -0
  34. package/dist/types/types.d.ts +1 -0
  35. package/package.json +5 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @atlaskit/media-card
2
2
 
3
+ ## 81.11.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`2d026706450c6`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/2d026706450c6) -
8
+ Add the `allowAIGeneratedMediaMotion` media plugin option and the `hasLoadingMotion` card prop, so
9
+ media that arrives mid-flight opens the document out to make room for itself and then fades its
10
+ preview in, instead of drawing a loading indicator and snapping into place. Behind
11
+ `aifc_page_create_defer_generated_visuals`.
12
+
3
13
  ## 81.10.19
4
14
 
5
15
  ### Patch Changes
@@ -8,7 +8,7 @@ var _reactIntl = require("react-intl");
8
8
  var _mediaCommon = require("@atlaskit/media-common");
9
9
  var _CardWithPerformanceObserver = require("./CardWithPerformanceObserver");
10
10
  var packageName = "@atlaskit/media-card";
11
- var packageVersion = "81.10.18";
11
+ var packageVersion = "81.10.19";
12
12
  var Card = exports.Card = (0, _mediaCommon.withMediaAnalyticsContext)({
13
13
  packageVersion: packageVersion,
14
14
  packageName: packageName,
@@ -36,6 +36,7 @@ var _PreviewUnavailable = require("./ui/iconMessage/PreviewUnavailable");
36
36
  var _iconWrapper = require("./ui/iconWrapper/iconWrapper");
37
37
  var _imageRenderer = require("./ui/imageRenderer");
38
38
  var _loadingBar = require("./ui/loadingBar/loadingBar");
39
+ var _loadingHold = require("./ui/loadingHold/loadingHold");
39
40
  var _openMediaViewerButton = _interopRequireDefault(require("./ui/openMediaViewerButton/openMediaViewerButton"));
40
41
  var _playButton = require("./ui/playButton/playButton");
41
42
  var _progressBar = require("./ui/progressBar/progressBar");
@@ -99,6 +100,7 @@ var CardViewBase = exports.CardViewBase = function CardViewBase(_ref) {
99
100
  traceId = _ref.traceId,
100
101
  isAIGenerating = _ref.isAIGenerating,
101
102
  isCWR = _ref.isCWR,
103
+ hasLoadingMotion = _ref.hasLoadingMotion,
102
104
  backgroundColor = _ref.backgroundColor;
103
105
  var intl = (0, _reactIntl.useIntl)();
104
106
  var _useState = (0, _react.useState)(false),
@@ -112,6 +114,18 @@ var CardViewBase = exports.CardViewBase = function CardViewBase(_ref) {
112
114
  var divRef = (0, _react.useRef)(null);
113
115
  var prevCardPreviewRef = (0, _react.useRef)();
114
116
  var breakpoint = (0, _useBreakpoint.useBreakpoint)(dimensions === null || dimensions === void 0 ? void 0 : dimensions.width, divRef);
117
+ var isErrorStatus = status === 'error' || status === 'failed-processing';
118
+ // While the motion owns the loading presentation the card draws nothing of its own — no
119
+ // indicator, progress bar, type icon or background — so the consumer can open space into
120
+ // emptiness and fade the preview in over it. Errors keep their normal treatment, or a
121
+ // failure would be indistinguishable from empty space.
122
+ //
123
+ // Every consumer below derives from this, so gating it here enforces the gate at the
124
+ // component boundary rather than trusting callers to do it. The prop is the left operand
125
+ // because every media card in every product renders this component — reading the gate
126
+ // unconditionally would put all of them in its exposure population. `isErrorStatus` is
127
+ // checked after it, so cards that opted in still count towards that population.
128
+ var suppressLoadingUI = !!hasLoadingMotion && (0, _fg.fg)('aifc_page_create_defer_generated_visuals') && !isErrorStatus;
115
129
  (0, _react.useEffect)(function () {
116
130
  // We should only switch didImageRender to false when cardPreview goes undefined, not when it is changed. as this method could be triggered after onImageLoad callback, falling on a race condition
117
131
  if (prevCardPreviewRef.current && !cardPreview) {
@@ -171,13 +185,15 @@ var CardViewBase = exports.CardViewBase = function CardViewBase(_ref) {
171
185
  switch (status) {
172
186
  case 'uploading':
173
187
  return _objectSpread(_objectSpread({}, defaultConfig), {}, {
174
- renderBlanket: !disableOverlay || mediaType !== 'video',
188
+ renderBlanket: !suppressLoadingUI && (!disableOverlay || mediaType !== 'video'),
175
189
  isFixedBlanket: true,
176
- renderProgressBar: !isAIGenerating
190
+ renderProgressBar: !isAIGenerating && !suppressLoadingUI,
191
+ renderTypeIcon: !suppressLoadingUI && defaultConfig.renderTypeIcon
177
192
  });
178
193
  case 'processing':
179
194
  return _objectSpread(_objectSpread({}, defaultConfig), {}, {
180
- iconMessage: !didImageRender && !isZeroSize ? /*#__PURE__*/_react.default.createElement(_CreatingPreview.CreatingPreview, {
195
+ renderTypeIcon: !suppressLoadingUI && defaultConfig.renderTypeIcon,
196
+ iconMessage: !didImageRender && !isZeroSize && !suppressLoadingUI ? /*#__PURE__*/_react.default.createElement(_CreatingPreview.CreatingPreview, {
181
197
  disableAnimation: disableAnimation
182
198
  }) : undefined
183
199
  });
@@ -249,9 +265,12 @@ var CardViewBase = exports.CardViewBase = function CardViewBase(_ref) {
249
265
 
250
266
  // AI border shown while generating; cleared by the decoration on upload completion.
251
267
  /* eslint-disable @atlaskit/platform/no-preconditioning -- loading-specific gate (cc-maui-phase-2) layered on the MAUI experiment cohort during phased rollout; preconditioning is intentional and will be removed when the existing gates are cleaned up */
252
- var renderAIBorderOverride = !!isAIGenerating && (0, _fg.fg)('cc-maui-phase-2') && (0, _expValEquals.expValEquals)('cc-maui-experiment', 'isEnabled', true);
268
+ var renderAIBorderOverride = !!isAIGenerating && (0, _fg.fg)('cc-maui-phase-2') && (0, _expValEquals.expValEquals)('cc-maui-experiment', 'isEnabled', true) &&
269
+ // Last so the gates above are still evaluated, and their exposures still fire, on
270
+ // surfaces that suppress the overlay.
271
+ !suppressLoadingUI;
253
272
  /* eslint-enable @atlaskit/platform/no-preconditioning */
254
- var shouldDisplayBackground = !cardPreview || !disableOverlay || status === 'error' || status === 'failed-processing';
273
+ var shouldDisplayBackground = status === 'error' || status === 'failed-processing' || !suppressLoadingUI && (!cardPreview || !disableOverlay);
255
274
  var isPlayButtonClickable = shouldRenderPlayButton() && !!disableOverlay;
256
275
  var isTickBoxSelectable = !disableOverlay && !!selectable && !selected;
257
276
  var _ref4 = metadata || {},
@@ -291,6 +310,27 @@ var CardViewBase = exports.CardViewBase = function CardViewBase(_ref) {
291
310
  setDidSvgRender(true);
292
311
  onSvgLoad === null || onSvgLoad === void 0 || onSvgLoad();
293
312
  };
313
+
314
+ // Held back until the preview has rendered, then faded in over the card's surface.
315
+ var mediaMotion;
316
+ if (suppressLoadingUI) {
317
+ mediaMotion = didImageRender || didSvgRender ? 'entering' : 'hidden';
318
+ }
319
+
320
+ // Lazy so the experiment is only evaluated, and exposed, when this is actually rendered.
321
+ var renderLoadingPlaceholder = function renderLoadingPlaceholder() {
322
+ return (0, _expValEquals.expValEquals)('cc-maui-ai-edit-loading-experiment', 'isEnabled', true) ? /*#__PURE__*/_react.default.createElement(_loadingBar.LoadingBar, {
323
+ animationDisabled: disableAnimation,
324
+ testId: "media-card-loading",
325
+ interactionName: "media-card-loading"
326
+ }) : /*#__PURE__*/_react.default.createElement(_iconWrapper.IconWrapper, {
327
+ breakpoint: breakpoint,
328
+ hasTitleBox: hasVisibleTitleBox
329
+ }, /*#__PURE__*/_react.default.createElement(_spinner.default, {
330
+ testId: "media-card-loading",
331
+ interactionName: "media-card-loading"
332
+ }));
333
+ };
294
334
  var contents = /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_imageContainer.ImageContainer, {
295
335
  centerElements: didSvgRender && !(0, _fg.fg)('platform_media_card_image_render') // The whole centerElements styles can go after removing the flag
296
336
  ,
@@ -300,7 +340,8 @@ var CardViewBase = exports.CardViewBase = function CardViewBase(_ref) {
300
340
  progress: progress,
301
341
  mediaCardCursor: mediaCardCursor,
302
342
  selected: selected,
303
- source: cardPreview === null || cardPreview === void 0 ? void 0 : cardPreview.source
343
+ source: cardPreview === null || cardPreview === void 0 ? void 0 : cardPreview.source,
344
+ mediaMotion: mediaMotion
304
345
  }, renderTypeIcon && /*#__PURE__*/_react.default.createElement(_iconWrapper.IconWrapper, {
305
346
  breakpoint: breakpoint,
306
347
  hasTitleBox: hasVisibleTitleBox
@@ -309,17 +350,13 @@ var CardViewBase = exports.CardViewBase = function CardViewBase(_ref) {
309
350
  mediaType: mediaType,
310
351
  mimeType: mimeType,
311
352
  name: name
312
- }), iconMessage), renderSpinner && ((0, _expValEquals.expValEquals)('cc-maui-ai-edit-loading-experiment', 'isEnabled', true) ? /*#__PURE__*/_react.default.createElement(_loadingBar.LoadingBar, {
313
- animationDisabled: disableAnimation,
314
- testId: "media-card-loading",
315
- interactionName: "media-card-loading"
316
- }) : /*#__PURE__*/_react.default.createElement(_iconWrapper.IconWrapper, {
317
- breakpoint: breakpoint,
318
- hasTitleBox: hasVisibleTitleBox
319
- }, /*#__PURE__*/_react.default.createElement(_spinner.default, {
320
- testId: "media-card-loading",
353
+ }), iconMessage), renderSpinner && (suppressLoadingUI ?
354
+ /*#__PURE__*/
355
+ // Nothing is drawn, but the interaction still has to be held for the
356
+ // same window as the indicator it replaces.
357
+ _react.default.createElement(_loadingHold.LoadingHold, {
321
358
  interactionName: "media-card-loading"
322
- }))), renderSvgView && identifier && (0, _mediaClient.isFileIdentifier)(identifier) && /*#__PURE__*/_react.default.createElement(_svgView.SvgView, {
359
+ }) : renderLoadingPlaceholder()), renderSvgView && identifier && (0, _mediaClient.isFileIdentifier)(identifier) && /*#__PURE__*/_react.default.createElement(_svgView.SvgView, {
323
360
  identifier: identifier,
324
361
  resizeMode: resizeMode || 'crop',
325
362
  onError: onSvgError,
@@ -97,6 +97,7 @@ var FileCard = exports.FileCard = function FileCard(_ref) {
97
97
  backgroundColor = _ref.backgroundColor,
98
98
  isAIGenerating = _ref.isAIGenerating,
99
99
  isCWR = _ref.isCWR,
100
+ hasLoadingMotion = _ref.hasLoadingMotion,
100
101
  onPreviewRender = _ref.onPreviewRender,
101
102
  shouldHideTooltip = _ref.shouldHideTooltip,
102
103
  mediaViewerItems = _ref.mediaViewerItems,
@@ -803,7 +804,8 @@ var FileCard = exports.FileCard = function FileCard(_ref) {
803
804
  shouldHideTooltip: shouldHideTooltip,
804
805
  overriddenCreationDate: overridenDate,
805
806
  isAIGenerating: isAIGenerating,
806
- isCWR: isCWR
807
+ isCWR: isCWR,
808
+ hasLoadingMotion: hasLoadingMotion
807
809
  });
808
810
  return isLazyWithOverride ? /*#__PURE__*/_react.default.createElement(_viewportDetector.ViewportDetector, {
809
811
  cardEl: cardElement,
@@ -87,7 +87,7 @@ var WrappedMediaCardAnalyticsErrorBoundary = /*#__PURE__*/function (_React$Compo
87
87
  }(_react.default.Component);
88
88
  (0, _defineProperty2.default)(WrappedMediaCardAnalyticsErrorBoundary, "displayName", 'MediaCardAnalyticsErrorBoundary');
89
89
  var packageName = "@atlaskit/media-card";
90
- var packageVersion = "81.10.18";
90
+ var packageVersion = "81.10.19";
91
91
 
92
92
  // @ts-ignore: [PIT-1685] Fails in post-office due to backwards incompatibility issue with React 18
93
93
  var MediaCardAnalyticsErrorBoundary = (0, _mediaCommon.withMediaAnalyticsContext)({
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.LoadingHold = void 0;
8
+ var _react = require("react");
9
+ var _interactionContext = _interopRequireDefault(require("@atlaskit/interaction-context"));
10
+ /**
11
+ * `useLayoutEffect` in SSR-safe form, matching `LoadingBar` and the DS interaction-tracing
12
+ * convention.
13
+ *
14
+ * @see https://hello.atlassian.net/wiki/spaces/DST/pages/2081696628/DSTDACI-010+-+Interaction+Tracing+hooks+in+DS+components
15
+ */
16
+ var useLayoutEffect = typeof window === 'undefined' ? _react.useEffect : _react.useLayoutEffect;
17
+ /**
18
+ * Holds the in-progress interaction while a card is loading, without drawing anything.
19
+ *
20
+ * `LoadingBar` and `Spinner` each own a hold for as long as they are mounted. Cards that
21
+ * express loading through motion instead still need the interaction held for the same window,
22
+ * so this stands in for them under the same `interactionName` — mounted and unmounted at the
23
+ * same points, so UFO traces stay comparable either way.
24
+ */
25
+ var LoadingHold = exports.LoadingHold = function LoadingHold(_ref) {
26
+ var interactionName = _ref.interactionName;
27
+ var interactionContext = (0, _react.useContext)(_interactionContext.default);
28
+ useLayoutEffect(function () {
29
+ if (interactionContext != null) {
30
+ return interactionContext.hold(interactionName);
31
+ }
32
+ }, [interactionContext, interactionName]);
33
+ return null;
34
+ };
@@ -2,10 +2,17 @@
2
2
  ._1bah1h6o{justify-content:center}
3
3
  ._1bsb1osq{width:100%}
4
4
  ._1e0c1txw{display:flex}
5
+ ._1o511hrf{animation-fill-mode:both}
6
+ ._1pglindk{animation-timing-function:var(--ds-easing-out-practical,cubic-bezier(.4,1,.6,1))}
5
7
  ._1reo15vq{overflow-x:hidden}
6
8
  ._2lx21bp4{flex-direction:column}
7
9
  ._4cvr1h6o{align-items:center}
8
10
  ._4t3i1osq{height:100%}
11
+ ._5sagi9qs{animation-duration:var(--ds-duration-medium,.2s)}
9
12
  ._c71l1osq{max-height:100%}
13
+ ._j7hq1bym{animation-name:var(--ds-keyframe-fade-in,FadeIn0to100)}
14
+ ._ju2515uc{animation-delay:var(--ds-duration-xlong,.4s)}
10
15
  ._kqswh2mm{position:relative}
11
- ._p12f1osq{max-width:100%}
16
+ ._p12f1osq{max-width:100%}
17
+ ._tzy4idpf{opacity:0}
18
+ @media (prefers-reduced-motion:reduce){._1bumglyw{animation:none}}
@@ -13,6 +13,12 @@ var _classnames = require("../../classnames");
13
13
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
14
14
  var imageContainerStyles = null;
15
15
  var imageContainerCenterStyles = null;
16
+
17
+ // Holds the preview back until it has decoded, so it can be faded in rather than appearing the
18
+ // instant it becomes displayable. Nothing is drawn in the meantime — consumers using this
19
+ // motion keep the node out of the layout until the preview is ready.
20
+ var mediaMotionHiddenStyles = null;
21
+ var mediaMotionEnteringStyles = null;
16
22
  var ImageContainer = exports.ImageContainer = function ImageContainer(_ref) {
17
23
  var children = _ref.children,
18
24
  mediaName = _ref.mediaName,
@@ -21,7 +27,8 @@ var ImageContainer = exports.ImageContainer = function ImageContainer(_ref) {
21
27
  selected = _ref.selected,
22
28
  source = _ref.source,
23
29
  centerElements = _ref.centerElements,
24
- mediaCardCursor = _ref.mediaCardCursor;
30
+ mediaCardCursor = _ref.mediaCardCursor,
31
+ mediaMotion = _ref.mediaMotion;
25
32
  return /*#__PURE__*/React.createElement("div", {
26
33
  "data-testid": _classnames.fileCardImageViewSelector
27
34
  /**
@@ -31,7 +38,7 @@ var ImageContainer = exports.ImageContainer = function ImageContainer(_ref) {
31
38
  */
32
39
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
33
40
  ,
34
- className: (0, _runtime.ax)(["_1reo15vq _18m915vq _2rko18qm _1e0c1txw _kqswh2mm _p12f1osq _1bsb1osq _4t3i1osq _c71l1osq", centerElements && "_2lx21bp4 _1bah1h6o _4cvr1h6o", _classnames.fileCardImageViewSelector]),
41
+ className: (0, _runtime.ax)(["_1reo15vq _18m915vq _2rko18qm _1e0c1txw _kqswh2mm _p12f1osq _1bsb1osq _4t3i1osq _c71l1osq", centerElements && "_2lx21bp4 _1bah1h6o _4cvr1h6o", mediaMotion === 'hidden' && "_tzy4idpf", mediaMotion === 'entering' && "_j7hq1bym _5sagi9qs _1pglindk _ju2515uc _1o511hrf _1bumglyw", _classnames.fileCardImageViewSelector]),
35
42
  "data-test-media-name": mediaName,
36
43
  "data-test-status": status,
37
44
  "data-test-progress": progress,
@@ -116,7 +116,7 @@ var MediaInlineCardLoader = exports.default = /*#__PURE__*/function (_React$Pure
116
116
  ErrorBoundary = _this$state.ErrorBoundary;
117
117
  var analyticsContext = {
118
118
  packageVersion: "@atlaskit/media-card",
119
- packageName: "81.10.18",
119
+ packageName: "81.10.19",
120
120
  componentName: 'mediaInlineCard',
121
121
  component: 'mediaInlineCard'
122
122
  };
@@ -23,7 +23,7 @@ var _getMediaGlobalScope = require("./globalScope/getMediaGlobalScope");
23
23
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
24
24
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
25
25
  var packageName = "@atlaskit/media-card";
26
- var packageVersion = "81.10.18";
26
+ var packageVersion = "81.10.19";
27
27
  var SAMPLE_RATE = 0.05;
28
28
 
29
29
  /**
@@ -2,7 +2,7 @@ import { injectIntl } from 'react-intl';
2
2
  import { withMediaAnalyticsContext } from '@atlaskit/media-common';
3
3
  import { CardWithPerformanceObserver } from './CardWithPerformanceObserver';
4
4
  const packageName = "@atlaskit/media-card";
5
- const packageVersion = "81.10.18";
5
+ const packageVersion = "81.10.19";
6
6
  export const Card = withMediaAnalyticsContext({
7
7
  packageVersion,
8
8
  packageName,
@@ -26,6 +26,7 @@ import { PreviewUnavailable } from './ui/iconMessage/PreviewUnavailable';
26
26
  import { IconWrapper } from './ui/iconWrapper/iconWrapper';
27
27
  import { ImageRenderer } from './ui/imageRenderer';
28
28
  import { LoadingBar } from './ui/loadingBar/loadingBar';
29
+ import { LoadingHold } from './ui/loadingHold/loadingHold';
29
30
  import OpenMediaViewerButton from './ui/openMediaViewerButton/openMediaViewerButton';
30
31
  import { PlayButton } from './ui/playButton/playButton';
31
32
  import { ProgressBar } from './ui/progressBar/progressBar';
@@ -84,6 +85,7 @@ export const CardViewBase = ({
84
85
  traceId,
85
86
  isAIGenerating,
86
87
  isCWR,
88
+ hasLoadingMotion,
87
89
  backgroundColor
88
90
  }) => {
89
91
  const intl = useIntl();
@@ -92,6 +94,18 @@ export const CardViewBase = ({
92
94
  const divRef = useRef(null);
93
95
  const prevCardPreviewRef = useRef();
94
96
  const breakpoint = useBreakpoint(dimensions === null || dimensions === void 0 ? void 0 : dimensions.width, divRef);
97
+ const isErrorStatus = status === 'error' || status === 'failed-processing';
98
+ // While the motion owns the loading presentation the card draws nothing of its own — no
99
+ // indicator, progress bar, type icon or background — so the consumer can open space into
100
+ // emptiness and fade the preview in over it. Errors keep their normal treatment, or a
101
+ // failure would be indistinguishable from empty space.
102
+ //
103
+ // Every consumer below derives from this, so gating it here enforces the gate at the
104
+ // component boundary rather than trusting callers to do it. The prop is the left operand
105
+ // because every media card in every product renders this component — reading the gate
106
+ // unconditionally would put all of them in its exposure population. `isErrorStatus` is
107
+ // checked after it, so cards that opted in still count towards that population.
108
+ const suppressLoadingUI = !!hasLoadingMotion && fg('aifc_page_create_defer_generated_visuals') && !isErrorStatus;
95
109
  useEffect(() => {
96
110
  // We should only switch didImageRender to false when cardPreview goes undefined, not when it is changed. as this method could be triggered after onImageLoad callback, falling on a race condition
97
111
  if (prevCardPreviewRef.current && !cardPreview) {
@@ -155,14 +169,16 @@ export const CardViewBase = ({
155
169
  case 'uploading':
156
170
  return {
157
171
  ...defaultConfig,
158
- renderBlanket: !disableOverlay || mediaType !== 'video',
172
+ renderBlanket: !suppressLoadingUI && (!disableOverlay || mediaType !== 'video'),
159
173
  isFixedBlanket: true,
160
- renderProgressBar: !isAIGenerating
174
+ renderProgressBar: !isAIGenerating && !suppressLoadingUI,
175
+ renderTypeIcon: !suppressLoadingUI && defaultConfig.renderTypeIcon
161
176
  };
162
177
  case 'processing':
163
178
  return {
164
179
  ...defaultConfig,
165
- iconMessage: !didImageRender && !isZeroSize ? /*#__PURE__*/React.createElement(CreatingPreview, {
180
+ renderTypeIcon: !suppressLoadingUI && defaultConfig.renderTypeIcon,
181
+ iconMessage: !didImageRender && !isZeroSize && !suppressLoadingUI ? /*#__PURE__*/React.createElement(CreatingPreview, {
166
182
  disableAnimation: disableAnimation
167
183
  }) : undefined
168
184
  };
@@ -238,9 +254,12 @@ export const CardViewBase = ({
238
254
 
239
255
  // AI border shown while generating; cleared by the decoration on upload completion.
240
256
  /* eslint-disable @atlaskit/platform/no-preconditioning -- loading-specific gate (cc-maui-phase-2) layered on the MAUI experiment cohort during phased rollout; preconditioning is intentional and will be removed when the existing gates are cleaned up */
241
- const renderAIBorderOverride = !!isAIGenerating && fg('cc-maui-phase-2') && expValEquals('cc-maui-experiment', 'isEnabled', true);
257
+ const renderAIBorderOverride = !!isAIGenerating && fg('cc-maui-phase-2') && expValEquals('cc-maui-experiment', 'isEnabled', true) &&
258
+ // Last so the gates above are still evaluated, and their exposures still fire, on
259
+ // surfaces that suppress the overlay.
260
+ !suppressLoadingUI;
242
261
  /* eslint-enable @atlaskit/platform/no-preconditioning */
243
- const shouldDisplayBackground = !cardPreview || !disableOverlay || status === 'error' || status === 'failed-processing';
262
+ const shouldDisplayBackground = status === 'error' || status === 'failed-processing' || !suppressLoadingUI && (!cardPreview || !disableOverlay);
244
263
  const isPlayButtonClickable = shouldRenderPlayButton() && !!disableOverlay;
245
264
  const isTickBoxSelectable = !disableOverlay && !!selectable && !selected;
246
265
  const {
@@ -280,6 +299,25 @@ export const CardViewBase = ({
280
299
  setDidSvgRender(true);
281
300
  onSvgLoad === null || onSvgLoad === void 0 ? void 0 : onSvgLoad();
282
301
  };
302
+
303
+ // Held back until the preview has rendered, then faded in over the card's surface.
304
+ let mediaMotion;
305
+ if (suppressLoadingUI) {
306
+ mediaMotion = didImageRender || didSvgRender ? 'entering' : 'hidden';
307
+ }
308
+
309
+ // Lazy so the experiment is only evaluated, and exposed, when this is actually rendered.
310
+ const renderLoadingPlaceholder = () => expValEquals('cc-maui-ai-edit-loading-experiment', 'isEnabled', true) ? /*#__PURE__*/React.createElement(LoadingBar, {
311
+ animationDisabled: disableAnimation,
312
+ testId: "media-card-loading",
313
+ interactionName: "media-card-loading"
314
+ }) : /*#__PURE__*/React.createElement(IconWrapper, {
315
+ breakpoint: breakpoint,
316
+ hasTitleBox: hasVisibleTitleBox
317
+ }, /*#__PURE__*/React.createElement(SpinnerIcon, {
318
+ testId: "media-card-loading",
319
+ interactionName: "media-card-loading"
320
+ }));
283
321
  const contents = /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ImageContainer, {
284
322
  centerElements: didSvgRender && !fg('platform_media_card_image_render') // The whole centerElements styles can go after removing the flag
285
323
  ,
@@ -289,7 +327,8 @@ export const CardViewBase = ({
289
327
  progress: progress,
290
328
  mediaCardCursor: mediaCardCursor,
291
329
  selected: selected,
292
- source: cardPreview === null || cardPreview === void 0 ? void 0 : cardPreview.source
330
+ source: cardPreview === null || cardPreview === void 0 ? void 0 : cardPreview.source,
331
+ mediaMotion: mediaMotion
293
332
  }, renderTypeIcon && /*#__PURE__*/React.createElement(IconWrapper, {
294
333
  breakpoint: breakpoint,
295
334
  hasTitleBox: hasVisibleTitleBox
@@ -298,17 +337,13 @@ export const CardViewBase = ({
298
337
  mediaType: mediaType,
299
338
  mimeType: mimeType,
300
339
  name: name
301
- }), iconMessage), renderSpinner && (expValEquals('cc-maui-ai-edit-loading-experiment', 'isEnabled', true) ? /*#__PURE__*/React.createElement(LoadingBar, {
302
- animationDisabled: disableAnimation,
303
- testId: "media-card-loading",
304
- interactionName: "media-card-loading"
305
- }) : /*#__PURE__*/React.createElement(IconWrapper, {
306
- breakpoint: breakpoint,
307
- hasTitleBox: hasVisibleTitleBox
308
- }, /*#__PURE__*/React.createElement(SpinnerIcon, {
309
- testId: "media-card-loading",
340
+ }), iconMessage), renderSpinner && (suppressLoadingUI ?
341
+ /*#__PURE__*/
342
+ // Nothing is drawn, but the interaction still has to be held for the
343
+ // same window as the indicator it replaces.
344
+ React.createElement(LoadingHold, {
310
345
  interactionName: "media-card-loading"
311
- }))), renderSvgView && identifier && isFileIdentifier(identifier) && /*#__PURE__*/React.createElement(SvgView, {
346
+ }) : renderLoadingPlaceholder()), renderSvgView && identifier && isFileIdentifier(identifier) && /*#__PURE__*/React.createElement(SvgView, {
312
347
  identifier: identifier,
313
348
  resizeMode: resizeMode || 'crop',
314
349
  onError: onSvgError,
@@ -78,6 +78,7 @@ export const FileCard = ({
78
78
  backgroundColor,
79
79
  isAIGenerating,
80
80
  isCWR,
81
+ hasLoadingMotion,
81
82
  onPreviewRender,
82
83
  shouldHideTooltip,
83
84
  mediaViewerItems,
@@ -718,7 +719,8 @@ export const FileCard = ({
718
719
  shouldHideTooltip: shouldHideTooltip,
719
720
  overriddenCreationDate: overridenDate,
720
721
  isAIGenerating: isAIGenerating,
721
- isCWR: isCWR
722
+ isCWR: isCWR,
723
+ hasLoadingMotion: hasLoadingMotion
722
724
  });
723
725
  return isLazyWithOverride ? /*#__PURE__*/React.createElement(ViewportDetector, {
724
726
  cardEl: cardElement,
@@ -66,7 +66,7 @@ class WrappedMediaCardAnalyticsErrorBoundary extends React.Component {
66
66
  }
67
67
  _defineProperty(WrappedMediaCardAnalyticsErrorBoundary, "displayName", 'MediaCardAnalyticsErrorBoundary');
68
68
  const packageName = "@atlaskit/media-card";
69
- const packageVersion = "81.10.18";
69
+ const packageVersion = "81.10.19";
70
70
 
71
71
  // @ts-ignore: [PIT-1685] Fails in post-office due to backwards incompatibility issue with React 18
72
72
  const MediaCardAnalyticsErrorBoundary = withMediaAnalyticsContext({
@@ -0,0 +1,29 @@
1
+ import { useContext, useEffect, useLayoutEffect as useRealLayoutEffect } from 'react';
2
+ import InteractionContext from '@atlaskit/interaction-context';
3
+
4
+ /**
5
+ * `useLayoutEffect` in SSR-safe form, matching `LoadingBar` and the DS interaction-tracing
6
+ * convention.
7
+ *
8
+ * @see https://hello.atlassian.net/wiki/spaces/DST/pages/2081696628/DSTDACI-010+-+Interaction+Tracing+hooks+in+DS+components
9
+ */
10
+ const useLayoutEffect = typeof window === 'undefined' ? useEffect : useRealLayoutEffect;
11
+ /**
12
+ * Holds the in-progress interaction while a card is loading, without drawing anything.
13
+ *
14
+ * `LoadingBar` and `Spinner` each own a hold for as long as they are mounted. Cards that
15
+ * express loading through motion instead still need the interaction held for the same window,
16
+ * so this stands in for them under the same `interactionName` — mounted and unmounted at the
17
+ * same points, so UFO traces stay comparable either way.
18
+ */
19
+ export const LoadingHold = ({
20
+ interactionName
21
+ }) => {
22
+ const interactionContext = useContext(InteractionContext);
23
+ useLayoutEffect(() => {
24
+ if (interactionContext != null) {
25
+ return interactionContext.hold(interactionName);
26
+ }
27
+ }, [interactionContext, interactionName]);
28
+ return null;
29
+ };
@@ -2,10 +2,17 @@
2
2
  ._1bah1h6o{justify-content:center}
3
3
  ._1bsb1osq{width:100%}
4
4
  ._1e0c1txw{display:flex}
5
+ ._1o511hrf{animation-fill-mode:both}
6
+ ._1pglindk{animation-timing-function:var(--ds-easing-out-practical,cubic-bezier(.4,1,.6,1))}
5
7
  ._1reo15vq{overflow-x:hidden}
6
8
  ._2lx21bp4{flex-direction:column}
7
9
  ._4cvr1h6o{align-items:center}
8
10
  ._4t3i1osq{height:100%}
11
+ ._5sagi9qs{animation-duration:var(--ds-duration-medium,.2s)}
9
12
  ._c71l1osq{max-height:100%}
13
+ ._j7hq1bym{animation-name:var(--ds-keyframe-fade-in,FadeIn0to100)}
14
+ ._ju2515uc{animation-delay:var(--ds-duration-xlong,.4s)}
10
15
  ._kqswh2mm{position:relative}
11
- ._p12f1osq{max-width:100%}
16
+ ._p12f1osq{max-width:100%}
17
+ ._tzy4idpf{opacity:0}
18
+ @media (prefers-reduced-motion:reduce){._1bumglyw{animation:none}}
@@ -5,6 +5,12 @@ import { ax, ix } from "@compiled/react/runtime";
5
5
  import { fileCardImageViewSelector } from '../../classnames';
6
6
  const imageContainerStyles = null;
7
7
  const imageContainerCenterStyles = null;
8
+
9
+ // Holds the preview back until it has decoded, so it can be faded in rather than appearing the
10
+ // instant it becomes displayable. Nothing is drawn in the meantime — consumers using this
11
+ // motion keep the node out of the layout until the preview is ready.
12
+ const mediaMotionHiddenStyles = null;
13
+ const mediaMotionEnteringStyles = null;
8
14
  export const ImageContainer = ({
9
15
  children,
10
16
  mediaName,
@@ -13,7 +19,8 @@ export const ImageContainer = ({
13
19
  selected,
14
20
  source,
15
21
  centerElements,
16
- mediaCardCursor
22
+ mediaCardCursor,
23
+ mediaMotion
17
24
  }) => /*#__PURE__*/React.createElement("div", {
18
25
  "data-testid": fileCardImageViewSelector
19
26
  /**
@@ -23,7 +30,7 @@ export const ImageContainer = ({
23
30
  */
24
31
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
25
32
  ,
26
- className: ax(["_1reo15vq _18m915vq _2rko18qm _1e0c1txw _kqswh2mm _p12f1osq _1bsb1osq _4t3i1osq _c71l1osq", centerElements && "_2lx21bp4 _1bah1h6o _4cvr1h6o", fileCardImageViewSelector]),
33
+ className: ax(["_1reo15vq _18m915vq _2rko18qm _1e0c1txw _kqswh2mm _p12f1osq _1bsb1osq _4t3i1osq _c71l1osq", centerElements && "_2lx21bp4 _1bah1h6o _4cvr1h6o", mediaMotion === 'hidden' && "_tzy4idpf", mediaMotion === 'entering' && "_j7hq1bym _5sagi9qs _1pglindk _ju2515uc _1o511hrf _1bumglyw", fileCardImageViewSelector]),
27
34
  "data-test-media-name": mediaName,
28
35
  "data-test-status": status,
29
36
  "data-test-progress": progress,
@@ -37,7 +37,7 @@ export default class MediaInlineCardLoader extends React.PureComponent {
37
37
  } = this.state;
38
38
  const analyticsContext = {
39
39
  packageVersion: "@atlaskit/media-card",
40
- packageName: "81.10.18",
40
+ packageName: "81.10.19",
41
41
  componentName: 'mediaInlineCard',
42
42
  component: 'mediaInlineCard'
43
43
  };
@@ -12,7 +12,7 @@ import { extractErrorInfo } from './analytics/extractErrorInfo';
12
12
  import { getRenderErrorRequestMetadata } from './analytics/getRenderErrorRequestMetadata';
13
13
  import { getMediaGlobalScope } from './globalScope/getMediaGlobalScope';
14
14
  const packageName = "@atlaskit/media-card";
15
- const packageVersion = "81.10.18";
15
+ const packageVersion = "81.10.19";
16
16
  const SAMPLE_RATE = 0.05;
17
17
 
18
18
  /**
@@ -2,7 +2,7 @@ import { injectIntl } from 'react-intl';
2
2
  import { withMediaAnalyticsContext } from '@atlaskit/media-common';
3
3
  import { CardWithPerformanceObserver } from './CardWithPerformanceObserver';
4
4
  var packageName = "@atlaskit/media-card";
5
- var packageVersion = "81.10.18";
5
+ var packageVersion = "81.10.19";
6
6
  export var Card = withMediaAnalyticsContext({
7
7
  packageVersion: packageVersion,
8
8
  packageName: packageName,
@@ -30,6 +30,7 @@ import { PreviewUnavailable } from './ui/iconMessage/PreviewUnavailable';
30
30
  import { IconWrapper } from './ui/iconWrapper/iconWrapper';
31
31
  import { ImageRenderer } from './ui/imageRenderer';
32
32
  import { LoadingBar } from './ui/loadingBar/loadingBar';
33
+ import { LoadingHold } from './ui/loadingHold/loadingHold';
33
34
  import OpenMediaViewerButton from './ui/openMediaViewerButton/openMediaViewerButton';
34
35
  import { PlayButton } from './ui/playButton/playButton';
35
36
  import { ProgressBar } from './ui/progressBar/progressBar';
@@ -90,6 +91,7 @@ export var CardViewBase = function CardViewBase(_ref) {
90
91
  traceId = _ref.traceId,
91
92
  isAIGenerating = _ref.isAIGenerating,
92
93
  isCWR = _ref.isCWR,
94
+ hasLoadingMotion = _ref.hasLoadingMotion,
93
95
  backgroundColor = _ref.backgroundColor;
94
96
  var intl = useIntl();
95
97
  var _useState = useState(false),
@@ -103,6 +105,18 @@ export var CardViewBase = function CardViewBase(_ref) {
103
105
  var divRef = useRef(null);
104
106
  var prevCardPreviewRef = useRef();
105
107
  var breakpoint = useBreakpoint(dimensions === null || dimensions === void 0 ? void 0 : dimensions.width, divRef);
108
+ var isErrorStatus = status === 'error' || status === 'failed-processing';
109
+ // While the motion owns the loading presentation the card draws nothing of its own — no
110
+ // indicator, progress bar, type icon or background — so the consumer can open space into
111
+ // emptiness and fade the preview in over it. Errors keep their normal treatment, or a
112
+ // failure would be indistinguishable from empty space.
113
+ //
114
+ // Every consumer below derives from this, so gating it here enforces the gate at the
115
+ // component boundary rather than trusting callers to do it. The prop is the left operand
116
+ // because every media card in every product renders this component — reading the gate
117
+ // unconditionally would put all of them in its exposure population. `isErrorStatus` is
118
+ // checked after it, so cards that opted in still count towards that population.
119
+ var suppressLoadingUI = !!hasLoadingMotion && fg('aifc_page_create_defer_generated_visuals') && !isErrorStatus;
106
120
  useEffect(function () {
107
121
  // We should only switch didImageRender to false when cardPreview goes undefined, not when it is changed. as this method could be triggered after onImageLoad callback, falling on a race condition
108
122
  if (prevCardPreviewRef.current && !cardPreview) {
@@ -162,13 +176,15 @@ export var CardViewBase = function CardViewBase(_ref) {
162
176
  switch (status) {
163
177
  case 'uploading':
164
178
  return _objectSpread(_objectSpread({}, defaultConfig), {}, {
165
- renderBlanket: !disableOverlay || mediaType !== 'video',
179
+ renderBlanket: !suppressLoadingUI && (!disableOverlay || mediaType !== 'video'),
166
180
  isFixedBlanket: true,
167
- renderProgressBar: !isAIGenerating
181
+ renderProgressBar: !isAIGenerating && !suppressLoadingUI,
182
+ renderTypeIcon: !suppressLoadingUI && defaultConfig.renderTypeIcon
168
183
  });
169
184
  case 'processing':
170
185
  return _objectSpread(_objectSpread({}, defaultConfig), {}, {
171
- iconMessage: !didImageRender && !isZeroSize ? /*#__PURE__*/React.createElement(CreatingPreview, {
186
+ renderTypeIcon: !suppressLoadingUI && defaultConfig.renderTypeIcon,
187
+ iconMessage: !didImageRender && !isZeroSize && !suppressLoadingUI ? /*#__PURE__*/React.createElement(CreatingPreview, {
172
188
  disableAnimation: disableAnimation
173
189
  }) : undefined
174
190
  });
@@ -240,9 +256,12 @@ export var CardViewBase = function CardViewBase(_ref) {
240
256
 
241
257
  // AI border shown while generating; cleared by the decoration on upload completion.
242
258
  /* eslint-disable @atlaskit/platform/no-preconditioning -- loading-specific gate (cc-maui-phase-2) layered on the MAUI experiment cohort during phased rollout; preconditioning is intentional and will be removed when the existing gates are cleaned up */
243
- var renderAIBorderOverride = !!isAIGenerating && fg('cc-maui-phase-2') && expValEquals('cc-maui-experiment', 'isEnabled', true);
259
+ var renderAIBorderOverride = !!isAIGenerating && fg('cc-maui-phase-2') && expValEquals('cc-maui-experiment', 'isEnabled', true) &&
260
+ // Last so the gates above are still evaluated, and their exposures still fire, on
261
+ // surfaces that suppress the overlay.
262
+ !suppressLoadingUI;
244
263
  /* eslint-enable @atlaskit/platform/no-preconditioning */
245
- var shouldDisplayBackground = !cardPreview || !disableOverlay || status === 'error' || status === 'failed-processing';
264
+ var shouldDisplayBackground = status === 'error' || status === 'failed-processing' || !suppressLoadingUI && (!cardPreview || !disableOverlay);
246
265
  var isPlayButtonClickable = shouldRenderPlayButton() && !!disableOverlay;
247
266
  var isTickBoxSelectable = !disableOverlay && !!selectable && !selected;
248
267
  var _ref4 = metadata || {},
@@ -282,6 +301,27 @@ export var CardViewBase = function CardViewBase(_ref) {
282
301
  setDidSvgRender(true);
283
302
  onSvgLoad === null || onSvgLoad === void 0 || onSvgLoad();
284
303
  };
304
+
305
+ // Held back until the preview has rendered, then faded in over the card's surface.
306
+ var mediaMotion;
307
+ if (suppressLoadingUI) {
308
+ mediaMotion = didImageRender || didSvgRender ? 'entering' : 'hidden';
309
+ }
310
+
311
+ // Lazy so the experiment is only evaluated, and exposed, when this is actually rendered.
312
+ var renderLoadingPlaceholder = function renderLoadingPlaceholder() {
313
+ return expValEquals('cc-maui-ai-edit-loading-experiment', 'isEnabled', true) ? /*#__PURE__*/React.createElement(LoadingBar, {
314
+ animationDisabled: disableAnimation,
315
+ testId: "media-card-loading",
316
+ interactionName: "media-card-loading"
317
+ }) : /*#__PURE__*/React.createElement(IconWrapper, {
318
+ breakpoint: breakpoint,
319
+ hasTitleBox: hasVisibleTitleBox
320
+ }, /*#__PURE__*/React.createElement(SpinnerIcon, {
321
+ testId: "media-card-loading",
322
+ interactionName: "media-card-loading"
323
+ }));
324
+ };
285
325
  var contents = /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ImageContainer, {
286
326
  centerElements: didSvgRender && !fg('platform_media_card_image_render') // The whole centerElements styles can go after removing the flag
287
327
  ,
@@ -291,7 +331,8 @@ export var CardViewBase = function CardViewBase(_ref) {
291
331
  progress: progress,
292
332
  mediaCardCursor: mediaCardCursor,
293
333
  selected: selected,
294
- source: cardPreview === null || cardPreview === void 0 ? void 0 : cardPreview.source
334
+ source: cardPreview === null || cardPreview === void 0 ? void 0 : cardPreview.source,
335
+ mediaMotion: mediaMotion
295
336
  }, renderTypeIcon && /*#__PURE__*/React.createElement(IconWrapper, {
296
337
  breakpoint: breakpoint,
297
338
  hasTitleBox: hasVisibleTitleBox
@@ -300,17 +341,13 @@ export var CardViewBase = function CardViewBase(_ref) {
300
341
  mediaType: mediaType,
301
342
  mimeType: mimeType,
302
343
  name: name
303
- }), iconMessage), renderSpinner && (expValEquals('cc-maui-ai-edit-loading-experiment', 'isEnabled', true) ? /*#__PURE__*/React.createElement(LoadingBar, {
304
- animationDisabled: disableAnimation,
305
- testId: "media-card-loading",
306
- interactionName: "media-card-loading"
307
- }) : /*#__PURE__*/React.createElement(IconWrapper, {
308
- breakpoint: breakpoint,
309
- hasTitleBox: hasVisibleTitleBox
310
- }, /*#__PURE__*/React.createElement(SpinnerIcon, {
311
- testId: "media-card-loading",
344
+ }), iconMessage), renderSpinner && (suppressLoadingUI ?
345
+ /*#__PURE__*/
346
+ // Nothing is drawn, but the interaction still has to be held for the
347
+ // same window as the indicator it replaces.
348
+ React.createElement(LoadingHold, {
312
349
  interactionName: "media-card-loading"
313
- }))), renderSvgView && identifier && isFileIdentifier(identifier) && /*#__PURE__*/React.createElement(SvgView, {
350
+ }) : renderLoadingPlaceholder()), renderSvgView && identifier && isFileIdentifier(identifier) && /*#__PURE__*/React.createElement(SvgView, {
314
351
  identifier: identifier,
315
352
  resizeMode: resizeMode || 'crop',
316
353
  onError: onSvgError,
@@ -89,6 +89,7 @@ export var FileCard = function FileCard(_ref) {
89
89
  backgroundColor = _ref.backgroundColor,
90
90
  isAIGenerating = _ref.isAIGenerating,
91
91
  isCWR = _ref.isCWR,
92
+ hasLoadingMotion = _ref.hasLoadingMotion,
92
93
  onPreviewRender = _ref.onPreviewRender,
93
94
  shouldHideTooltip = _ref.shouldHideTooltip,
94
95
  mediaViewerItems = _ref.mediaViewerItems,
@@ -795,7 +796,8 @@ export var FileCard = function FileCard(_ref) {
795
796
  shouldHideTooltip: shouldHideTooltip,
796
797
  overriddenCreationDate: overridenDate,
797
798
  isAIGenerating: isAIGenerating,
798
- isCWR: isCWR
799
+ isCWR: isCWR,
800
+ hasLoadingMotion: hasLoadingMotion
799
801
  });
800
802
  return isLazyWithOverride ? /*#__PURE__*/React.createElement(ViewportDetector, {
801
803
  cardEl: cardElement,
@@ -80,7 +80,7 @@ var WrappedMediaCardAnalyticsErrorBoundary = /*#__PURE__*/function (_React$Compo
80
80
  }(React.Component);
81
81
  _defineProperty(WrappedMediaCardAnalyticsErrorBoundary, "displayName", 'MediaCardAnalyticsErrorBoundary');
82
82
  var packageName = "@atlaskit/media-card";
83
- var packageVersion = "81.10.18";
83
+ var packageVersion = "81.10.19";
84
84
 
85
85
  // @ts-ignore: [PIT-1685] Fails in post-office due to backwards incompatibility issue with React 18
86
86
  var MediaCardAnalyticsErrorBoundary = withMediaAnalyticsContext({
@@ -0,0 +1,28 @@
1
+ import { useContext, useEffect, useLayoutEffect as useRealLayoutEffect } from 'react';
2
+ import InteractionContext from '@atlaskit/interaction-context';
3
+
4
+ /**
5
+ * `useLayoutEffect` in SSR-safe form, matching `LoadingBar` and the DS interaction-tracing
6
+ * convention.
7
+ *
8
+ * @see https://hello.atlassian.net/wiki/spaces/DST/pages/2081696628/DSTDACI-010+-+Interaction+Tracing+hooks+in+DS+components
9
+ */
10
+ var useLayoutEffect = typeof window === 'undefined' ? useEffect : useRealLayoutEffect;
11
+ /**
12
+ * Holds the in-progress interaction while a card is loading, without drawing anything.
13
+ *
14
+ * `LoadingBar` and `Spinner` each own a hold for as long as they are mounted. Cards that
15
+ * express loading through motion instead still need the interaction held for the same window,
16
+ * so this stands in for them under the same `interactionName` — mounted and unmounted at the
17
+ * same points, so UFO traces stay comparable either way.
18
+ */
19
+ export var LoadingHold = function LoadingHold(_ref) {
20
+ var interactionName = _ref.interactionName;
21
+ var interactionContext = useContext(InteractionContext);
22
+ useLayoutEffect(function () {
23
+ if (interactionContext != null) {
24
+ return interactionContext.hold(interactionName);
25
+ }
26
+ }, [interactionContext, interactionName]);
27
+ return null;
28
+ };
@@ -2,10 +2,17 @@
2
2
  ._1bah1h6o{justify-content:center}
3
3
  ._1bsb1osq{width:100%}
4
4
  ._1e0c1txw{display:flex}
5
+ ._1o511hrf{animation-fill-mode:both}
6
+ ._1pglindk{animation-timing-function:var(--ds-easing-out-practical,cubic-bezier(.4,1,.6,1))}
5
7
  ._1reo15vq{overflow-x:hidden}
6
8
  ._2lx21bp4{flex-direction:column}
7
9
  ._4cvr1h6o{align-items:center}
8
10
  ._4t3i1osq{height:100%}
11
+ ._5sagi9qs{animation-duration:var(--ds-duration-medium,.2s)}
9
12
  ._c71l1osq{max-height:100%}
13
+ ._j7hq1bym{animation-name:var(--ds-keyframe-fade-in,FadeIn0to100)}
14
+ ._ju2515uc{animation-delay:var(--ds-duration-xlong,.4s)}
10
15
  ._kqswh2mm{position:relative}
11
- ._p12f1osq{max-width:100%}
16
+ ._p12f1osq{max-width:100%}
17
+ ._tzy4idpf{opacity:0}
18
+ @media (prefers-reduced-motion:reduce){._1bumglyw{animation:none}}
@@ -5,6 +5,12 @@ import { ax, ix } from "@compiled/react/runtime";
5
5
  import { fileCardImageViewSelector } from '../../classnames';
6
6
  var imageContainerStyles = null;
7
7
  var imageContainerCenterStyles = null;
8
+
9
+ // Holds the preview back until it has decoded, so it can be faded in rather than appearing the
10
+ // instant it becomes displayable. Nothing is drawn in the meantime — consumers using this
11
+ // motion keep the node out of the layout until the preview is ready.
12
+ var mediaMotionHiddenStyles = null;
13
+ var mediaMotionEnteringStyles = null;
8
14
  export var ImageContainer = function ImageContainer(_ref) {
9
15
  var children = _ref.children,
10
16
  mediaName = _ref.mediaName,
@@ -13,7 +19,8 @@ export var ImageContainer = function ImageContainer(_ref) {
13
19
  selected = _ref.selected,
14
20
  source = _ref.source,
15
21
  centerElements = _ref.centerElements,
16
- mediaCardCursor = _ref.mediaCardCursor;
22
+ mediaCardCursor = _ref.mediaCardCursor,
23
+ mediaMotion = _ref.mediaMotion;
17
24
  return /*#__PURE__*/React.createElement("div", {
18
25
  "data-testid": fileCardImageViewSelector
19
26
  /**
@@ -23,7 +30,7 @@ export var ImageContainer = function ImageContainer(_ref) {
23
30
  */
24
31
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
25
32
  ,
26
- className: ax(["_1reo15vq _18m915vq _2rko18qm _1e0c1txw _kqswh2mm _p12f1osq _1bsb1osq _4t3i1osq _c71l1osq", centerElements && "_2lx21bp4 _1bah1h6o _4cvr1h6o", fileCardImageViewSelector]),
33
+ className: ax(["_1reo15vq _18m915vq _2rko18qm _1e0c1txw _kqswh2mm _p12f1osq _1bsb1osq _4t3i1osq _c71l1osq", centerElements && "_2lx21bp4 _1bah1h6o _4cvr1h6o", mediaMotion === 'hidden' && "_tzy4idpf", mediaMotion === 'entering' && "_j7hq1bym _5sagi9qs _1pglindk _ju2515uc _1o511hrf _1bumglyw", fileCardImageViewSelector]),
27
34
  "data-test-media-name": mediaName,
28
35
  "data-test-status": status,
29
36
  "data-test-progress": progress,
@@ -101,7 +101,7 @@ var MediaInlineCardLoader = /*#__PURE__*/function (_React$PureComponent) {
101
101
  ErrorBoundary = _this$state.ErrorBoundary;
102
102
  var analyticsContext = {
103
103
  packageVersion: "@atlaskit/media-card",
104
- packageName: "81.10.18",
104
+ packageName: "81.10.19",
105
105
  componentName: 'mediaInlineCard',
106
106
  component: 'mediaInlineCard'
107
107
  };
@@ -16,7 +16,7 @@ import { extractErrorInfo } from './analytics/extractErrorInfo';
16
16
  import { getRenderErrorRequestMetadata } from './analytics/getRenderErrorRequestMetadata';
17
17
  import { getMediaGlobalScope } from './globalScope/getMediaGlobalScope';
18
18
  var packageName = "@atlaskit/media-card";
19
- var packageVersion = "81.10.18";
19
+ var packageVersion = "81.10.19";
20
20
  var SAMPLE_RATE = 0.05;
21
21
 
22
22
  /**
@@ -46,6 +46,7 @@ export interface CardViewProps {
46
46
  overriddenCreationDate?: number;
47
47
  readonly isAIGenerating?: boolean;
48
48
  readonly isCWR?: boolean;
49
+ readonly hasLoadingMotion?: boolean;
49
50
  }
50
51
  export type CardViewBaseProps = CardViewProps & WithAnalyticsEventsProps;
51
52
  type TraceTooltipVariant = 'preview-unavailable' | 'failed-to-load';
@@ -66,6 +67,6 @@ export interface RenderConfigByStatus {
66
67
  customTitleMessage?: MessageDescriptor;
67
68
  traceTooltipVariant?: TraceTooltipVariant;
68
69
  }
69
- export declare const CardViewBase: ({ identifier, innerRef, onImageLoad, onImageError, dimensions, onClick, onMouseEnter, onFocus, testId, metadata, status, selected, selectable, cardPreview, mediaCardCursor, shouldHideTooltip, progress, alt, resizeMode, onDisplayImage, nativeLazyLoad, forceSyncDisplay, actions, disableOverlay, titleBoxBgColor, titleBoxIcon, error, disableAnimation, openMediaViewerButtonRef, shouldOpenMediaViewer, overriddenCreationDate, onSvgError, onSvgLoad, traceId, isAIGenerating, isCWR, backgroundColor, }: CardViewBaseProps) => React.JSX.Element;
70
+ export declare const CardViewBase: ({ identifier, innerRef, onImageLoad, onImageError, dimensions, onClick, onMouseEnter, onFocus, testId, metadata, status, selected, selectable, cardPreview, mediaCardCursor, shouldHideTooltip, progress, alt, resizeMode, onDisplayImage, nativeLazyLoad, forceSyncDisplay, actions, disableOverlay, titleBoxBgColor, titleBoxIcon, error, disableAnimation, openMediaViewerButtonRef, shouldOpenMediaViewer, overriddenCreationDate, onSvgError, onSvgLoad, traceId, isAIGenerating, isCWR, hasLoadingMotion, backgroundColor, }: CardViewBaseProps) => React.JSX.Element;
70
71
  export declare const CardView: React.ForwardRefExoticComponent<Omit<CardViewProps, keyof WithAnalyticsEventsProps> & React.RefAttributes<any>>;
71
72
  export {};
@@ -40,6 +40,11 @@ export interface FileCardProps extends CardEventProps {
40
40
  readonly isAIGenerating?: boolean;
41
41
  /** Marks the card as part of the CWR (create-with-Rovo) infographics flow. */
42
42
  readonly isCWR?: boolean;
43
+ /**
44
+ * Drops the loading indicator and fades the preview in over the card's surface instead.
45
+ * Also behind `aifc_page_create_defer_generated_visuals`.
46
+ */
47
+ readonly hasLoadingMotion?: boolean;
43
48
  /** Callback fired when the media card's image preview has rendered. */
44
49
  readonly onPreviewRender?: (fileId: string) => void;
45
50
  /** Instance of file identifier. */
@@ -93,4 +98,4 @@ export interface FileCardProps extends CardEventProps {
93
98
  */
94
99
  readonly fallbackMediaNameFetcher?: (id: string) => Promise<string>;
95
100
  }
96
- export declare const FileCard: ({ appearance, resizeMode, isLazy, disableOverlay, featureFlags, identifier, ssr, dimensions, originalDimensions, contextId, alt, actions, shouldEnableDownloadButton, useInlinePlayer, shouldOpenMediaViewer, onFullscreenChange, selectable, selected, testId, titleBoxBgColor, titleBoxIcon, backgroundColor, isAIGenerating, isCWR, onPreviewRender, shouldHideTooltip, mediaViewerItems, onClick, onMouseEnter, onFocus, videoControlsWrapperRef, viewerOptions, includeHashForDuplicateFiles, ssrItemDetails, ssrFileState, ssrMediaItem, onError, mediaViewerExtensions, fallbackMediaNameFetcher, }: FileCardProps) => React.JSX.Element;
101
+ export declare const FileCard: ({ appearance, resizeMode, isLazy, disableOverlay, featureFlags, identifier, ssr, dimensions, originalDimensions, contextId, alt, actions, shouldEnableDownloadButton, useInlinePlayer, shouldOpenMediaViewer, onFullscreenChange, selectable, selected, testId, titleBoxBgColor, titleBoxIcon, backgroundColor, isAIGenerating, isCWR, hasLoadingMotion, onPreviewRender, shouldHideTooltip, mediaViewerItems, onClick, onMouseEnter, onFocus, videoControlsWrapperRef, viewerOptions, includeHashForDuplicateFiles, ssrItemDetails, ssrFileState, ssrMediaItem, onError, mediaViewerExtensions, fallbackMediaNameFetcher, }: FileCardProps) => React.JSX.Element;
@@ -0,0 +1,12 @@
1
+ export type LoadingHoldProps = {
2
+ interactionName?: string;
3
+ };
4
+ /**
5
+ * Holds the in-progress interaction while a card is loading, without drawing anything.
6
+ *
7
+ * `LoadingBar` and `Spinner` each own a hold for as long as they are mounted. Cards that
8
+ * express loading through motion instead still need the interaction held for the same window,
9
+ * so this stands in for them under the same `interactionName` — mounted and unmounted at the
10
+ * same points, so UFO traces stay comparable either way.
11
+ */
12
+ export declare const LoadingHold: ({ interactionName }: LoadingHoldProps) => null;
@@ -9,6 +9,11 @@ type ImageContainerProps = {
9
9
  progress?: number;
10
10
  selected?: boolean;
11
11
  source?: string;
12
+ /**
13
+ * Entering motion for the media preview: `hidden` while it is still loading, `entering`
14
+ * once it has rendered. Undefined opts out entirely.
15
+ */
16
+ mediaMotion?: 'hidden' | 'entering';
12
17
  };
13
- export declare const ImageContainer: ({ children, mediaName, status, progress, selected, source, centerElements, mediaCardCursor, }: ImageContainerProps) => JSX.Element;
18
+ export declare const ImageContainer: ({ children, mediaName, status, progress, selected, source, centerElements, mediaCardCursor, mediaMotion, }: ImageContainerProps) => JSX.Element;
14
19
  export {};
@@ -10,6 +10,7 @@ type ImageContainerProps = {
10
10
  progress?: number;
11
11
  selected?: boolean;
12
12
  source?: string;
13
+ mediaMotion?: 'hidden' | 'entering';
13
14
  };
14
15
  export declare const ImageContainer: (props: ImageContainerProps) => React.JSX.Element;
15
16
  export {};
@@ -70,6 +70,7 @@ export interface SharedCardProps {
70
70
  readonly titleBoxIcon?: TitleBoxIcon;
71
71
  readonly isAIGenerating?: boolean;
72
72
  readonly isCWR?: boolean;
73
+ readonly hasLoadingMotion?: boolean;
73
74
  readonly onPreviewRender?: (fileId: string) => void;
74
75
  readonly viewerOptions?: ViewerOptionsProps;
75
76
  /** Sets options for viewer **/
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/media-card",
3
- "version": "81.10.19",
3
+ "version": "81.11.0",
4
4
  "description": "Includes all media card related components, CardView, CardViewSmall, Card...",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
@@ -106,6 +106,10 @@
106
106
  "wait-for-expect": "^1.2.0"
107
107
  },
108
108
  "platform-feature-flags": {
109
+ "aifc_page_create_defer_generated_visuals": {
110
+ "type": "boolean",
111
+ "referenceOnly": true
112
+ },
109
113
  "cc-maui-phase-2": {
110
114
  "type": "boolean"
111
115
  },