@capillarytech/creatives-library 9.0.15-alpha.1 → 9.0.15-alpha.3

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.
@@ -206,6 +206,7 @@ export const Rcs = (props) => {
206
206
  showTestAndPreviewSlidebox: propsShowTestAndPreviewSlidebox,
207
207
  handleTestAndPreview: propsHandleTestAndPreview,
208
208
  handleCloseTestAndPreview: propsHandleCloseTestAndPreview,
209
+ handleClose,
209
210
  } = props || {};
210
211
  const { formatMessage } = intl;
211
212
  const { TextArea } = CapInput;
@@ -621,31 +622,43 @@ export const Rcs = (props) => {
621
622
  <CapRow gutter={16} className="rcs-carousel-dimension-row">
622
623
  <CapColumn span={12}>
623
624
  <CapHeading type="h4" className="rcs-carousel-dimension-label">Card height</CapHeading>
624
- <CapSelect
625
- id="rcs-carousel-height-select"
626
- value={selectedCarouselHeight}
627
- onChange={(val) => {
628
- // Like rich-card dimension changes: clear media so user re-uploads matching new dimensions.
629
- resetCarouselMediaForAllCards();
630
- setSelectedCarouselHeight(val);
631
- }}
632
- options={CAROUSEL_HEIGHT_OPTIONS}
633
- disabled={isEditFlow || !isFullMode}
634
- />
625
+ {isEditFlow ? (
626
+ <CapHeading type="h5" className="rcs-creative-name-value">
627
+ {CAROUSEL_HEIGHT_OPTIONS.find((option) => option.value === selectedCarouselHeight)?.label || '-'}
628
+ </CapHeading>
629
+ ) : (
630
+ <CapSelect
631
+ id="rcs-carousel-height-select"
632
+ value={selectedCarouselHeight}
633
+ onChange={(val) => {
634
+ // Like rich-card dimension changes: clear media so user re-uploads matching new dimensions.
635
+ resetCarouselMediaForAllCards();
636
+ setSelectedCarouselHeight(val);
637
+ }}
638
+ options={CAROUSEL_HEIGHT_OPTIONS}
639
+ disabled={!isFullMode}
640
+ />
641
+ )}
635
642
  </CapColumn>
636
643
  <CapColumn span={12}>
637
644
  <CapHeading type="h4" className="rcs-carousel-dimension-label">Card width</CapHeading>
638
- <CapSelect
639
- id="rcs-carousel-width-select"
640
- value={selectedCarouselWidth}
641
- onChange={(val) => {
642
- // Like rich-card dimension changes: clear media so user re-uploads matching new dimensions.
643
- resetCarouselMediaForAllCards();
644
- setSelectedCarouselWidth(val);
645
- }}
646
- options={CAROUSEL_WIDTH_OPTIONS}
647
- disabled={isEditFlow || !isFullMode}
648
- />
645
+ {isEditFlow ? (
646
+ <CapHeading type="h5" className="rcs-creative-name-value">
647
+ {CAROUSEL_WIDTH_OPTIONS.find((option) => option.value === selectedCarouselWidth)?.label || '-'}
648
+ </CapHeading>
649
+ ) : (
650
+ <CapSelect
651
+ id="rcs-carousel-width-select"
652
+ value={selectedCarouselWidth}
653
+ onChange={(val) => {
654
+ // Like rich-card dimension changes: clear media so user re-uploads matching new dimensions.
655
+ resetCarouselMediaForAllCards();
656
+ setSelectedCarouselWidth(val);
657
+ }}
658
+ options={CAROUSEL_WIDTH_OPTIONS}
659
+ disabled={!isFullMode}
660
+ />
661
+ )}
649
662
  </CapColumn>
650
663
  </CapRow>
651
664
  {!!selectedCarousel && (
@@ -672,7 +685,7 @@ export const Rcs = (props) => {
672
685
  </CapHeading>
673
686
  </CapRow>
674
687
  )}
675
- description={(
688
+ description={!isEditFlow && (
676
689
  <CapLabel type="label3">{formatMessage(messages.btnDesc)}</CapLabel>
677
690
  )}
678
691
  />
@@ -771,7 +784,7 @@ export const Rcs = (props) => {
771
784
  // Default: IMAGE
772
785
  return (
773
786
  <>
774
- <CapHeading type="h4" className="rcs-image-dimensions-label">Upload Image</CapHeading>
787
+ <CapHeading type="h4" className="rcs-image-dimensions-label">{formatMessage(messages.mediaImage)}</CapHeading>
775
788
  <CapImageUpload
776
789
  allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
777
790
  imgWidth={RCS_CAROUSEL_IMAGE_DIMENSIONS?.[dimKey]?.width}
@@ -838,41 +851,50 @@ export const Rcs = (props) => {
838
851
  className="rcs-carousel-card"
839
852
  >
840
853
  {/* Media selection should be at top of card */}
841
- <CapRow className="rcs-carousel-media-selection">
842
- <CapColumn className="rcs-carousel-media-selection-heading">
854
+ {isEditFlow ? (
855
+ <div className="rcs-creative-name-readonly">
843
856
  <CapHeading type="h4">{formatMessage(messages.mediaTypeLabel)}</CapHeading>
844
- </CapColumn>
845
- <CapColumn>
846
- <CapRadioGroup
847
- id={`rcs-carousel-media-radio-${index}`}
848
- options={mediaRadioOptions}
849
- value={card.mediaType}
850
- onChange={({ target: { value } }) => {
851
- // Reset media fields when switching type
852
- if (value === RCS_MEDIA_TYPES.IMAGE) {
853
- // Switching to IMAGE: clear video + thumbnail uploads so they don't auto-restore.
854
- clearCarouselCardMedia(index, { clearImage: false, clearVideo: true, clearThumb: true });
855
- handleCarouselValueChange(index, [
856
- { fieldName: 'mediaType', value },
857
- { fieldName: 'videoAsset', value: {} },
858
- { fieldName: 'thumbnailSrc', value: '' },
859
- ]);
860
- } else {
861
- // Switching to VIDEO: clear image upload so it doesn't auto-restore.
862
- clearCarouselCardMedia(index, { clearImage: true, clearVideo: false, clearThumb: false });
863
- handleCarouselValueChange(index, [
864
- { fieldName: 'mediaType', value },
865
- { fieldName: 'imageSrc', value: '' },
866
- ]);
867
- }
868
- }}
869
- disabled={isEditFlow || !isFullMode}
870
- className="rcs-radio"
871
- />
872
- </CapColumn>
873
- </CapRow>
857
+ <CapHeading type="h5" className="rcs-creative-name-value">
858
+ {mediaRadioOptions.find((option) => option.value === card.mediaType)?.label || '-'}
859
+ </CapHeading>
860
+ </div>
861
+ ) : (
862
+ <CapRow className="rcs-carousel-media-selection">
863
+ <CapColumn className="rcs-carousel-media-selection-heading">
864
+ <CapHeading type="h4">{formatMessage(messages.mediaTypeLabel)}</CapHeading>
865
+ </CapColumn>
866
+ <CapColumn>
867
+ <CapRadioGroup
868
+ id={`rcs-carousel-media-radio-${index}`}
869
+ options={mediaRadioOptions}
870
+ value={card.mediaType}
871
+ onChange={({ target: { value } }) => {
872
+ // Reset media fields when switching type
873
+ if (value === RCS_MEDIA_TYPES.IMAGE) {
874
+ // Switching to IMAGE: clear video + thumbnail uploads so they don't auto-restore.
875
+ clearCarouselCardMedia(index, { clearImage: false, clearVideo: true, clearThumb: true });
876
+ handleCarouselValueChange(index, [
877
+ { fieldName: 'mediaType', value },
878
+ { fieldName: 'videoAsset', value: {} },
879
+ { fieldName: 'thumbnailSrc', value: '' },
880
+ ]);
881
+ } else {
882
+ // Switching to VIDEO: clear image upload so it doesn't auto-restore.
883
+ clearCarouselCardMedia(index, { clearImage: true, clearVideo: false, clearThumb: false });
884
+ handleCarouselValueChange(index, [
885
+ { fieldName: 'mediaType', value },
886
+ { fieldName: 'imageSrc', value: '' },
887
+ ]);
888
+ }
889
+ }}
890
+ disabled={!isFullMode}
891
+ className="rcs-radio"
892
+ />
893
+ </CapColumn>
894
+ </CapRow>
895
+ )}
874
896
 
875
- <CapRow className="rcs-carousel-media-upload">
897
+ <CapRow className={`rcs-carousel-media-upload ${isEditFlow ? 'rcs-carousel-media-upload--readonly-above' : ''}`}>
876
898
  {renderCarouselCardMedia(index)}
877
899
  </CapRow>
878
900
 
@@ -1015,27 +1037,41 @@ export const Rcs = (props) => {
1015
1037
  const renderCarouselSection = () => {
1016
1038
  if (!isCarouselType) return null;
1017
1039
 
1018
- const operations = (
1040
+ const isMaxCarouselCardsReached = MAX_RCS_CAROUSEL_ALLOWED === (carouselData?.length || 0);
1041
+ const addCardTooltipTitle = isMaxCarouselCardsReached
1042
+ ? formatMessage(messages.rcsCarouselMaxCardTooltip, { max: MAX_RCS_CAROUSEL_ALLOWED })
1043
+ // "Set up the first card" only makes sense while creating a new template —
1044
+ // in edit flow the cards already exist, so don't show it there.
1045
+ : (!isEditFlow && checkDisableAddCarouselButton() ? formatMessage(messages.rcsCarouselAddCardDisabledTooltip) : '');
1046
+
1047
+ // No "+" in edit flow (cards can't be added there) — and with no "+", the
1048
+ // divider before it has nothing to separate, so skip both together instead
1049
+ // of rendering a dangling divider.
1050
+ const operations = isEditFlow ? null : (
1019
1051
  <>
1020
1052
  <CapDivider type="vertical" />
1021
- <CapButton
1022
- onClick={addCarouselCard}
1023
- type="flat"
1024
- className="add-carousel-content-button"
1025
- disabled={
1026
- isEditFlow ||
1027
- !isFullMode ||
1028
- MAX_RCS_CAROUSEL_ALLOWED === (carouselData?.length || 0) ||
1029
- checkDisableAddCarouselButton()
1030
- }
1031
- >
1032
- <CapIcon type="plus" />
1033
- </CapButton>
1053
+ <CapTooltip title={addCardTooltipTitle}>
1054
+ <span className="button-disabled-tooltip-wrapper">
1055
+ <CapButton
1056
+ onClick={addCarouselCard}
1057
+ type="flat"
1058
+ className="add-carousel-content-button"
1059
+ disabled={
1060
+ isEditFlow ||
1061
+ !isFullMode ||
1062
+ isMaxCarouselCardsReached ||
1063
+ checkDisableAddCarouselButton()
1064
+ }
1065
+ >
1066
+ <CapIcon type="plus" />
1067
+ </CapButton>
1068
+ </span>
1069
+ </CapTooltip>
1034
1070
  </>
1035
1071
  );
1036
1072
 
1037
1073
  return (
1038
- <CapRow className="rcs-carousel-section">
1074
+ <CapRow className={`rcs-carousel-section ${isEditFlow ? 'rcs-carousel-section--readonly-above' : ''}`}>
1039
1075
  {renderCarouselDimensionSelection()}
1040
1076
  <CapRow className="rcs-carousel-tab">
1041
1077
  <CapTab
@@ -1617,11 +1653,19 @@ export const Rcs = (props) => {
1617
1653
 
1618
1654
  setTemplateType(contentType.carousel);
1619
1655
  setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
1620
- const cardSettings = rcsContent?.cardSettings || {};
1656
+ // Library/campaign payloads can store cardSettings/cardContent at the flat top-level
1657
+ // `rcsContent` instead of nested under versions.base.content.RCS (same fallback already
1658
+ // used for the rich_card media lookup below, line ~1793). Without it, cardWidth/height
1659
+ // silently fall back to SMALL/MEDIUM, mismatching the dimensions the thumbnail was
1660
+ // actually uploaded at and causing object-fit:cover to crop the preview image.
1661
+ const cardSettings = rcsContent?.cardSettings
1662
+ || get(details, 'rcsContent.cardSettings', {});
1621
1663
  const cardWidth = cardSettings?.cardWidth || SMALL;
1622
1664
  setSelectedCarouselWidth(cardWidth);
1623
1665
 
1624
- const cards = Array.isArray(rcsContent?.cardContent) ? rcsContent.cardContent : [];
1666
+ const cards = Array.isArray(rcsContent?.cardContent)
1667
+ ? rcsContent.cardContent
1668
+ : get(details, 'rcsContent.cardContent', []);
1625
1669
  const firstHeight = cards?.[0]?.media?.height || MEDIUM;
1626
1670
  setSelectedCarouselHeight(firstHeight);
1627
1671
  setSelectedCarousel(`${firstHeight}_${cardWidth}`);
@@ -2502,10 +2546,12 @@ const onTitleAddVar = () => {
2502
2546
  </CapRow>
2503
2547
  }
2504
2548
  description={
2505
- <CapLabel type="label3">{formatMessage(messages.btnDesc)}</CapLabel>
2549
+ !isEditFlow && (
2550
+ <CapLabel type="label3">{formatMessage(messages.btnDesc)}</CapLabel>
2551
+ )
2506
2552
  }
2507
2553
  />
2508
-
2554
+
2509
2555
  <CapActionButton
2510
2556
  buttonType={buttonType}
2511
2557
  updateButtonChange={updateButtonChange}
@@ -4101,17 +4147,29 @@ const onTitleAddVar = () => {
4101
4147
  />
4102
4148
  )
4103
4149
  )}
4104
- {renderLabel('templateTypeLabel')}
4105
- <ConfigProvider theme={{ components: { Radio: { radioSize: 20, dotSize: 8 } } }}>
4106
-
4107
- <CapRadioGroup
4108
- id="select-rcs-template-type"
4109
- options={TEMPLATE_TYPE_OPTIONS}
4110
- onChange={onTemplateTypeChange}
4111
- value={templateType}
4112
- disabled={(isEditFlow || !isFullMode)}
4113
- />
4114
- </ConfigProvider>
4150
+ {isEditFlow ? (
4151
+ <div className="rcs-creative-name-readonly">
4152
+ <CapHeading type="h4">
4153
+ {formatMessage(messages.templateTypeLabel)}
4154
+ </CapHeading>
4155
+ <CapHeading type="h5" className="rcs-creative-name-value">
4156
+ {TEMPLATE_TYPE_OPTIONS.find((option) => option.value === templateType)?.label || '-'}
4157
+ </CapHeading>
4158
+ </div>
4159
+ ) : (
4160
+ <>
4161
+ {renderLabel('templateTypeLabel')}
4162
+ <ConfigProvider theme={{ components: { Radio: { radioSize: 20, dotSize: 8 } } }}>
4163
+ <CapRadioGroup
4164
+ id="select-rcs-template-type"
4165
+ options={TEMPLATE_TYPE_OPTIONS}
4166
+ onChange={onTemplateTypeChange}
4167
+ value={templateType}
4168
+ disabled={!isFullMode}
4169
+ />
4170
+ </ConfigProvider>
4171
+ </>
4172
+ )}
4115
4173
 
4116
4174
  {templateType === contentType.carousel ? (
4117
4175
  renderCarouselSection()
@@ -98,6 +98,7 @@
98
98
  .rcs-form-section-heading {
99
99
  display: flex;
100
100
  margin-top: $CAP_SPACE_20;
101
+ margin-bottom: $CAP_SPACE_08;
101
102
  }
102
103
 
103
104
  .rcs-image-upload--top-spacing {
@@ -219,6 +220,7 @@
219
220
  }
220
221
  .rcs-image-dimensions-label{
221
222
  margin-top: 1.25rem;
223
+ margin-bottom: $CAP_SPACE_08;
222
224
  }
223
225
  .rcs-template-title-input {
224
226
  .rcs-edit-disabled {
@@ -229,12 +231,14 @@
229
231
  }
230
232
  .rcs-template-title-label {
231
233
  margin-top: 1.25rem;
234
+ margin-bottom: $CAP_SPACE_08;
232
235
  }
233
236
  #rcs-template-message-label .ant-btn.cap-button-v2 {
234
237
  height: 32px;
235
238
  }
236
239
  #rcs-template-message-label {
237
240
  margin-top: 1.25rem;
241
+ margin-bottom: $CAP_SPACE_08;
238
242
  }
239
243
  .rcs-container-image {
240
244
  .image-container {
@@ -256,6 +260,17 @@
256
260
  margin-top: 1.25rem;
257
261
  }
258
262
 
263
+ // Edit mode: the readonly Template Type block right above (.rcs-creative-name-readonly)
264
+ // already has its own margin-bottom — stacking these two top-margins on top of that
265
+ // (flex prevents them from collapsing into it) left an oversized gap before Card height.
266
+ .rcs-carousel-section--readonly-above {
267
+ margin-top: 0;
268
+
269
+ .rcs-carousel-dimension-section {
270
+ margin-top: 0;
271
+ }
272
+ }
273
+
259
274
  .rcs-carousel-selected-dimension {
260
275
  display: block;
261
276
  margin-top: 8px;
@@ -264,7 +279,7 @@
264
279
 
265
280
  .rcs-carousel-dimension-label {
266
281
  display: inline-block;
267
- margin-bottom: 0.375rem;
282
+ margin-bottom: $CAP_SPACE_08;
268
283
  }
269
284
 
270
285
  #rcs-carousel-height-select,
@@ -274,6 +289,112 @@
274
289
 
275
290
  .rcs-carousel-tab {
276
291
  margin-top: 0.75rem;
292
+
293
+ // Tab list (.ant-tabs-nav-wrap) defaults to flex:auto and stretches to fill the row,
294
+ // pushing tabBarExtraContent (the "+" add-card button) to the far right edge. Size it
295
+ // to its own content instead so "+" sits right next to the last tab (Figma reference).
296
+ .ant-tabs-nav-wrap {
297
+ flex: none;
298
+ }
299
+
300
+ // .ant-tabs-nav already centers its direct children (antd default), but antd
301
+ // also gives .ant-tabs-nav-wrap AND .ant-tabs-nav-operations (an always-present,
302
+ // just visually-hidden, third sibling for the overflow "more" dropdown)
303
+ // `align-self: stretch` — forcing them to the FULL bar height, while
304
+ // .ant-tabs-extra-content has no such override and centers at its own natural
305
+ // (shorter) height. So a divider centered inside the stretched tab list and a
306
+ // divider centered inside the un-stretched extra-content land at different Y
307
+ // positions even though each is individually "centered" — in its own box.
308
+ // Cancel the stretch so every direct child centers the same way.
309
+ .ant-tabs-nav {
310
+ align-items: center;
311
+
312
+ > .ant-tabs-nav-wrap,
313
+ > .ant-tabs-nav-operations {
314
+ align-self: auto;
315
+ }
316
+ }
317
+
318
+ // antd ALWAYS renders `.ant-tabs-nav-operations` as a flex sibling between the
319
+ // tab list and the right extra-content (it holds the overflow "more" dropdown).
320
+ // With no overflow it's only `visibility:hidden`, so it's invisible but STILL
321
+ // occupies a flex slot with real width — that phantom slot is the extra gap
322
+ // showing up before the "|" and "+". Collapse it to zero width when hidden so
323
+ // the divider sits the same distance from "2" as the 1|2 divider does from "1".
324
+ .ant-tabs-nav-operations.ant-tabs-nav-operations-hidden {
325
+ width: 0;
326
+ padding: 0;
327
+ margin: 0;
328
+ border: none;
329
+ }
330
+
331
+ // .ant-tabs-extra-content itself is only a flex ITEM (antd sets `flex: none`
332
+ // on it), not a flex CONTAINER — its own children (the CapDivider and the
333
+ // "+" button's tooltip-wrapper span) fall back to inline layout, where
334
+ // CapDivider's antd default `vertical-align: middle` aligns it to the
335
+ // surrounding line box rather than to the button, which is what was making
336
+ // the "|" and "+" drift out of alignment with each other. Make it an
337
+ // explicit flex row so both children center against each other directly.
338
+ .ant-tabs-extra-content {
339
+ display: flex;
340
+ align-items: center;
341
+ }
342
+
343
+ // Vertical divider between the tabs and "+": antd default (0.9em) reads too short.
344
+ // Also pin its own margin explicitly (antd's default didn't match the gap
345
+ // used for the divider between tab numbers below), which was making this
346
+ // divider float away from "2" instead of sitting the same distance from it
347
+ // as the 1|2 divider sits from "1".
348
+ // 12px each side = the same visual gap the 1|2 divider has (that one uses a
349
+ // 24px margin-left with the divider pseudo offset to its center, i.e. ~12px
350
+ // of clear space on each side). Keep these equal so all three gaps match.
351
+ .ant-tabs-extra-content .ant-divider-vertical {
352
+ height: $CAP_SPACE_24;
353
+ margin: 0 $CAP_SPACE_12;
354
+ }
355
+
356
+ // Same divider treatment between each numbered tab (1 | 2 | ...), not just
357
+ // before the "+" button (Figma reference). antd's own `.ant-tabs-tab` rule
358
+ // already applies `margin: 0 0 0 var(--ant-tabs-horizontal-item-gutter)`
359
+ // (~32px) to every tab — our earlier `margin-right` was ADDING to that
360
+ // instead of replacing it, stacking both gaps into one huge gutter before
361
+ // "2". Zero out antd's own margin first so this is the only gap in play.
362
+ .ant-tabs-tab {
363
+ margin: 0 !important;
364
+ }
365
+
366
+ // `:not(:last-of-type)` matched more than intended: antd renders
367
+ // `.ant-tabs-ink-bar` as another <div> sibling AFTER the last tab inside the
368
+ // same nav list, so that ink-bar div — not tab "2" — was `:last-of-type`,
369
+ // leaving tab "2" wrongly matched and given its own extra divider on top of
370
+ // the real one before "+". Target adjacency to a PRECEDING tab instead
371
+ // (`.ant-tabs-tab + .ant-tabs-tab`), which only ever matches actual tabs
372
+ // regardless of what non-tab siblings antd adds elsewhere in the list.
373
+ .ant-tabs-tab + .ant-tabs-tab {
374
+ position: relative;
375
+ margin-left: $CAP_SPACE_24 !important;
376
+
377
+ &::before {
378
+ content: '';
379
+ position: absolute;
380
+ top: 50%;
381
+ left: -$CAP_SPACE_12;
382
+ transform: translateY(-50%);
383
+ width: 1px;
384
+ height: $CAP_SPACE_24;
385
+ background-color: $CAP_G07;
386
+ }
387
+ }
388
+
389
+ // The "+" wrapper reuses .button-disabled-tooltip-wrapper from CapActionButton
390
+ // (display: inline-block; margin-top: 1.25rem) — that margin was meant for a
391
+ // different context and pushes the "+" button below the tab row/divider baseline
392
+ // here. Re-align it inline with the tabs and vertical divider.
393
+ .button-disabled-tooltip-wrapper {
394
+ display: inline-flex;
395
+ align-items: center;
396
+ margin-top: 0;
397
+ }
277
398
  }
278
399
 
279
400
  .rcs-carousel-card {
@@ -330,6 +451,13 @@
330
451
 
331
452
  .rcs-carousel-card-row {
332
453
  margin-bottom: $CAP_SPACE_12;
454
+
455
+ // Card title / Card body text headers sit directly above their input —
456
+ // CapHeader itself has no bottom margin, so without this the input hugged
457
+ // the header text with no gap.
458
+ .cap-header-v2 {
459
+ margin-bottom: $CAP_SPACE_08;
460
+ }
333
461
  }
334
462
 
335
463
  .rcs-carousel-media-selection {
@@ -338,9 +466,28 @@
338
466
  align-items: center;
339
467
  }
340
468
 
469
+ .rcs-carousel-media-selection-heading {
470
+ margin-bottom: $CAP_SPACE_08;
471
+ }
472
+
341
473
  // Carousel upload previews: keep media preview compact (CapImageUpload uses height=400 by default)
342
474
  .rcs-carousel-media-upload {
343
475
  padding-top: $CAP_SPACE_20;
476
+
477
+ // Edit mode: the readonly Media type block right above (.rcs-creative-name-readonly)
478
+ // already has its own margin-bottom, plus .rcs-image-dimensions-label's own
479
+ // margin-top — stacking this padding-top on top of both left an oversized gap.
480
+ &.rcs-carousel-media-upload--readonly-above {
481
+ padding-top: 0;
482
+
483
+ // Only the first heading (Upload Video, or Image) sits right below the
484
+ // readonly block above — the video variant's second heading (Upload
485
+ // Thumbnail) still needs its own margin-top to separate it from the
486
+ // video upload widget above it.
487
+ > .rcs-image-dimensions-label:first-child {
488
+ margin-top: 0;
489
+ }
490
+ }
344
491
  .image-container {
345
492
  background-image: none;
346
493
  border: 0;
@@ -385,8 +532,15 @@
385
532
  }
386
533
  }
387
534
 
535
+ // The divider's own margin already provides the gap before "+" — this button's
536
+ // extra left padding was stacking on top of that margin, making the gap after
537
+ // the divider visibly bigger than the gap before it (between "2" and the
538
+ // divider). Drop the left padding so both sides of the divider match; keep a
539
+ // little on the right as breathing room since nothing follows "+".
388
540
  .add-carousel-content-button {
389
- padding: 0 $CAP_SPACE_08;
541
+ padding: 0 $CAP_SPACE_08 0 0;
542
+ display: inline-flex;
543
+ align-items: center;
390
544
  }
391
545
  }
392
546
  .rcs-fallback-preview > .cap-slide-box-v2-container.size-r{
@@ -206,6 +206,14 @@ export default defineMessages({
206
206
  id: `${prefix}.rcsCarouselMinCardDeleteTooltip`,
207
207
  defaultMessage: 'At least 1 card needs to be added in the carousel template',
208
208
  },
209
+ rcsCarouselAddCardDisabledTooltip: {
210
+ id: `${prefix}.rcsCarouselAddCardDisabledTooltip`,
211
+ defaultMessage: 'Please set-up the first card before creating the next ones',
212
+ },
213
+ rcsCarouselMaxCardTooltip: {
214
+ id: `${prefix}.rcsCarouselMaxCardTooltip`,
215
+ defaultMessage: 'RCS carousel cannot contain more than {max} content cards.',
216
+ },
209
217
  templateNamePlaceholder: {
210
218
  id: `${prefix}.templateNamePlaceholder`,
211
219
  defaultMessage: 'Enter template name',
@@ -244,15 +252,15 @@ export default defineMessages({
244
252
  },
245
253
  text_message: {
246
254
  id: `${prefix}.text_message`,
247
- defaultMessage: 'text message',
255
+ defaultMessage: 'Text message',
248
256
  },
249
257
  richCard: {
250
258
  id: `${prefix}.richCard`,
251
- defaultMessage: 'rich card',
259
+ defaultMessage: 'Rich card',
252
260
  },
253
261
  carousel: {
254
262
  id: `${prefix}.carousel`,
255
- defaultMessage: 'carousel',
263
+ defaultMessage: 'Carousel',
256
264
  },
257
265
  rcsDoneBtnToolTip: {
258
266
  id: `${prefix}.rcsDoneBtnToolTip`,