@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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "9.0.15-alpha.1",
4
+ "version": "9.0.15-alpha.3",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
@@ -39,6 +39,6 @@ export const RCS_CTA_URL_TYPE = {
39
39
  DYNAMIC: 'DYNAMIC',
40
40
  };
41
41
 
42
- export const BTN_MAX_LENGTH = 25;
42
+ export const BTN_MAX_LENGTH = 20;
43
43
  export const PHONE_NUMBER_MAX_LENGTH = 15;
44
44
  export const URL_MAX_LENGTH = 2000;
@@ -97,7 +97,7 @@ export const CapActionButton = (props) => {
97
97
  const { value, id } = target;
98
98
  let errorMessage = '';
99
99
  if (value.length > BTN_MAX_LENGTH) {
100
- errorMessage = formatMessage(messages.ctaButtonTextLengthError);
100
+ errorMessage = formatMessage(messages.ctaButtonTextLengthError, { maxLength: BTN_MAX_LENGTH });
101
101
  } if (!isValidText(value)) {
102
102
  errorMessage = formatMessage(messages.ctaButtonErrorMessage);
103
103
  }
@@ -406,12 +406,13 @@ export const CapActionButton = (props) => {
406
406
  className="cap-rcs-saved-cta margin-t-12"
407
407
  align="middle"
408
408
  type="flex"
409
+ gutter={9}
409
410
  >
410
411
  <CapColumn
411
412
  span={1}
412
413
  className={`${ctaIsPhone ? 'whatsapp-saved-cta-phone-icon' : ''}`}
413
414
  >
414
- <CapIcon size="s" type={ctaIsPhone ? 'call' : 'launch'} />
415
+ <CapIcon size="s" type={ctaIsPhone ? 'call' : (ctaIsReply ? 'small-link' : 'launch')} />
415
416
  </CapColumn>
416
417
  <CapColumn span={6}>
417
418
  <CapLabel
@@ -421,45 +422,43 @@ export const CapActionButton = (props) => {
421
422
  {text}
422
423
  </CapLabel>
423
424
  </CapColumn>
424
- {ctaIsPhone && (
425
- <CapColumn span={10} align="left">
426
- <CapLabel className="phone">+{phoneNumber}</CapLabel>
427
- </CapColumn>
428
- )}
429
- {!ctaIsPhone && !ctaIsReply && (
430
- <>
431
- <CapColumn span={3}>
432
- <CapLabel className="url-type" type="label2">
433
- {(cta.urlType || RCS_CTA_URL_TYPE.STATIC) === RCS_CTA_URL_TYPE.DYNAMIC
434
- ? formatMessage(messages.ctaWebsiteTypeDynamic)
435
- : formatMessage(messages.ctaWebsiteTypeStatic)}
436
- </CapLabel>
437
- </CapColumn>
438
- <CapRow type='flex' className="rcs-url-preview-wrapper">
439
- <CapTooltip title={url} placement="top">
440
- <CapColumn span={14} className="margin-r-0">
425
+ <CapColumn flex={1} className="rcs-saved-cta-value-column">
426
+ <div className="rcs-saved-cta-value-actions">
427
+ {ctaIsPhone && (
428
+ <CapLabel className="phone">+{phoneNumber}</CapLabel>
429
+ )}
430
+ {!ctaIsPhone && !ctaIsReply && (
431
+ <>
432
+ <CapLabel className="url-type" type="label2">
433
+ {(cta.urlType || RCS_CTA_URL_TYPE.STATIC) === RCS_CTA_URL_TYPE.DYNAMIC
434
+ ? formatMessage(messages.ctaWebsiteTypeDynamic)
435
+ : formatMessage(messages.ctaWebsiteTypeStatic)}
436
+ </CapLabel>
437
+ <CapTooltip title={url} placement="top">
441
438
  <CapLabel className="url">{url}</CapLabel>
442
- </CapColumn>
443
- </CapTooltip>
444
- </CapRow>
445
- </>
446
- )}
447
- {(isFullMode && !isEditFlow) && (
448
- <>
449
- <CapColumn
450
- span={1}
451
- className="rcs-saved-cta-edit-icon"
452
- onClick={() => editCta(index)}
453
- >
454
- <CapIcon size="s" type="edit" />
455
- </CapColumn>
456
- {!(isHostIcs && index === 0) && !hideDeleteForSuggestionIndex(index) && !cannotDeleteSavedMandatory(index) && (
457
- <CapColumn className="rcs-saved-cta-delete-icon" span={1} onClick={() => deleteButtonHandler(index)}>
458
- <CapIcon size="s" type="delete" />
459
- </CapColumn>
439
+ </CapTooltip>
440
+ </>
460
441
  )}
461
- </>
462
- )}
442
+ {(isFullMode && !isEditFlow) && (
443
+ <div className="rcs-saved-cta-action-icons">
444
+ <CapIcon
445
+ size="s"
446
+ type="edit"
447
+ className="rcs-saved-cta-edit-icon"
448
+ onClick={() => editCta(index)}
449
+ />
450
+ {!(isHostIcs && index === 0) && !hideDeleteForSuggestionIndex(index) && !cannotDeleteSavedMandatory(index) && (
451
+ <CapIcon
452
+ size="s"
453
+ type="delete"
454
+ className="rcs-saved-cta-delete-icon"
455
+ onClick={() => deleteButtonHandler(index)}
456
+ />
457
+ )}
458
+ </div>
459
+ )}
460
+ </div>
461
+ </CapColumn>
463
462
  </CapRow>
464
463
  );
465
464
  }
@@ -23,13 +23,36 @@
23
23
  padding: 0.625rem;
24
24
  border-radius: $CAP_SPACE_04;
25
25
  margin-bottom: $CAP_SPACE_12;
26
+ flex-wrap: nowrap;
26
27
 
27
- div:not(:last-child) {
28
- margin-right: 0.5625rem;
28
+ // Icon/text columns keep a fixed span; this column takes whatever space
29
+ // is left (flex: 1, set via CapColumn's `flex` prop) instead of a fixed
30
+ // span that has to add up to exactly 24 alongside its siblings — with a
31
+ // fixed span, the gutter/margin between columns had nowhere to come from
32
+ // and pushed this column's content past the card's right border.
33
+ .rcs-saved-cta-value-column {
34
+ min-width: 0;
29
35
  }
30
36
 
31
- .rcs-url-preview-wrapper {
32
- max-width: 5.714rem;
37
+ // Value text and edit/delete icons share one flex row, right-aligned as a
38
+ // unit — keeps the value flush against the icons regardless of its length,
39
+ // instead of the old fixed-span column + absolutely-positioned icon combo
40
+ // (which drifted out of alignment with variable-length URLs/numbers).
41
+ .rcs-saved-cta-value-actions {
42
+ display: flex;
43
+ align-items: center;
44
+ justify-content: flex-end;
45
+ gap: $CAP_SPACE_08;
46
+ width: 100%;
47
+ min-width: 0;
48
+
49
+ .url-type {
50
+ flex-shrink: 0;
51
+ }
52
+
53
+ .url, .phone {
54
+ min-width: 0;
55
+ }
33
56
  }
34
57
 
35
58
  .url {
@@ -50,26 +73,43 @@
50
73
  font-weight: 500;
51
74
  }
52
75
 
53
- .rcs-saved-cta-edit-icon {
54
- position: absolute;
55
- right: 0.4375rem;
76
+ .rcs-saved-cta-action-icons {
77
+ display: flex;
78
+ align-items: center;
79
+ gap: $CAP_SPACE_08;
80
+ flex-shrink: 0;
56
81
  }
57
82
 
83
+ .rcs-saved-cta-edit-icon,
58
84
  .rcs-saved-cta-delete-icon {
59
- position: absolute;
60
- right: $CAP_SPACE_02;
85
+ cursor: pointer;
61
86
  }
62
87
  }
63
88
 
64
89
  // Button text / URL: count via CapInput `suffix` (antd affix, right-aligned inside field).
65
90
  // Phone: react-phone-input-2 — overlay count + padding on .form-control
66
- .rcs-cta-inner-char-count {
91
+ // Selector needs `.cap-heading.h5` too — CapHeading's own `.cap-heading.h5` rule (2 classes)
92
+ // otherwise beats a single-class `.rcs-cta-inner-char-count` selector and forces dark text,
93
+ // regardless of source order (light-grey placeholder look per Figma).
94
+ .cap-heading.h5.rcs-cta-inner-char-count {
67
95
  font-size: 0.75rem;
68
96
  line-height: 1.25rem;
69
97
  color: $FONT_COLOR_03;
70
98
  white-space: nowrap;
71
99
  }
72
100
 
101
+ // Button text: match the phone field's full-width treatment (Figma reference).
102
+ // `className` on CapInput lands on componentWithLabelHOC's OUTER wrapper div
103
+ // (confirmed via rendered DOM), not on the `.ant-input-affix-wrapper` itself —
104
+ // force the wrapper full-width directly rather than relying on descendant rules.
105
+ .rcs-cta-button-text {
106
+ width: 100%;
107
+
108
+ .ant-input-affix-wrapper {
109
+ width: 100%;
110
+ }
111
+ }
112
+
73
113
  .rcs-cta-input-with-inner-count {
74
114
  position: relative;
75
115
  width: 100%;
@@ -123,10 +163,26 @@
123
163
  }
124
164
  }
125
165
 
166
+ // The flag-dropdown sits on top of .form-control's left edge, so hovering the
167
+ // flag itself doesn't trigger `.form-control:hover` — bind the border-color
168
+ // change to the whole wrapper so the border reacts consistently everywhere.
169
+ .react-tel-input.rcs-cta-phone-input:hover .form-control {
170
+ border-color: $CAP_G11;
171
+ }
172
+
173
+ .react-tel-input.rcs-cta-phone-input:focus-within .form-control {
174
+ border-color: $CAP_G01;
175
+ }
176
+
126
177
  .rcs-cta-phone-input .flag-dropdown {
127
- top: 0.0625rem;
128
- bottom: 0.0625rem;
129
- left: 0.0625rem;
178
+ // Raw px, not rem: must exactly match .form-control's 1px border width.
179
+ // At this app's 14px root font-size, 0.0625rem resolved to 0.875px — the
180
+ // sub-pixel gap left the outer border looking "cut off" right where the
181
+ // flag-dropdown's opaque background sat, most visible on hover when the
182
+ // border color changed and made the gap's anti-aliasing stand out.
183
+ top: 1px;
184
+ bottom: 1px;
185
+ left: 1px;
130
186
  padding: 0;
131
187
  background: $CAP_WHITE;
132
188
  border: none;
@@ -166,6 +222,11 @@
166
222
  .rcs-button-cta-create-container.cap-row-v2 {
167
223
  display: flex;
168
224
  flex-direction: column;
225
+ // Cross-axis (=width, since this is a column-flex row) defaults to `align-items: flex-start`
226
+ // via antd's `.ant-row-top`, which lets child Cols shrink to content instead of filling the
227
+ // row — same issue CapRow's own `.cap-row-vertical` guards against. Force stretch here too,
228
+ // otherwise fields with no explicit width of their own (e.g. Button text) render too narrow.
229
+ align-items: stretch;
169
230
  flex-wrap: nowrap;
170
231
  width: 100%;
171
232
  border: solid 1px $CAP_G06;
@@ -176,6 +237,7 @@
176
237
  .rcs-button-cta-create.cap-row-v2 {
177
238
  display: flex;
178
239
  flex-direction: column;
240
+ align-items: stretch;
179
241
  flex-wrap: nowrap;
180
242
  margin: 0;
181
243
  }
@@ -207,11 +269,16 @@
207
269
 
208
270
  .button-disabled-tooltip-wrapper {
209
271
  display: inline-block;
210
- margin-top: 1.25rem;
211
272
  }
212
273
 
213
- // Space between Save and Delete on RCS / carousel CTA rows
274
+ // Space above Save/Delete row: kept on this stable outer wrapper (not on
275
+ // .button-disabled-tooltip-wrapper) since that inner CapRow's own display
276
+ // (flex vs inline-block) flips depending on whether CapTooltip renders its
277
+ // extra trigger span (title set vs empty), which made the gap disappear
278
+ // whenever the Save button was enabled.
214
279
  .rcs-cta-save-delete-btn {
280
+ margin-top: 1.25rem;
281
+
215
282
  .rcs-cta-delete-btn {
216
283
  margin-left: 0.75rem;
217
284
  }
@@ -55,9 +55,17 @@ const RcsPreviewContent = ({
55
55
  const renderRcsSuggestionsPreview = (suggestionsArg, options = {}) => {
56
56
  const { isCarousel = false } = options;
57
57
  const renderArray = [];
58
- const suggestions = Array.isArray(suggestionsArg)
58
+ const rawSuggestions = Array.isArray(suggestionsArg)
59
59
  ? suggestionsArg
60
60
  : (content?.suggestions || []);
61
+ // Skip unsaved/empty draft suggestions (e.g. the pre-seeded default phone-number slot
62
+ // on carousel card 1, or a suggestion mid-edit via the pencil icon — both explicitly set
63
+ // `isSaved: false`) — but suggestions hydrated from an already-persisted template never
64
+ // carry `isSaved` at all (it's a client-only "saved this session" flag), so `undefined`
65
+ // must still render; only an explicit `false` means "hide this".
66
+ const suggestions = rawSuggestions.filter(
67
+ (suggestion) => suggestion?.isSaved !== false && String(suggestion?.text || '').trim(),
68
+ );
61
69
 
62
70
  if (suggestions.length === 0) {
63
71
  return null;
@@ -83,25 +91,18 @@ const RcsPreviewContent = ({
83
91
  } else if (type === RCS_BUTTON_TYPES.CTA) {
84
92
  renderArray.push(
85
93
  <CapLabel key={suggestionKey} type="label21" className={ctaClass}>
86
- <CapIcon type="launch" size="xs" />
94
+ {/* "launch" icon's SVG has hardcoded <defs>/<mask> ids, which collide
95
+ and render as a solid square when 2+ CTA buttons appear together —
96
+ "open-in-new" is a single flat path with no ids, so it can't collide. */}
97
+ <CapIcon type="open-in-new" size="xs" />
87
98
  {text}
88
99
  </CapLabel>
89
100
  );
90
101
  } else if (type === RCS_BUTTON_TYPES.PHONE_NUMBER) {
91
- // Carousel: label text then phone icon (matches device-style RCS preview)
92
102
  renderArray.push(
93
103
  <CapLabel key={suggestionKey} type="label21" className={ctaClass}>
94
- {isCarousel ? (
95
- <>
96
- {text}
97
- <CapIcon type="call" size="xs" className="rcs-cta-preview-phone-icon-end" />
98
- </>
99
- ) : (
100
- <>
101
- <CapIcon type="call" size="xs" />
102
- {text}
103
- </>
104
- )}
104
+ <CapIcon type="call" size="xs" />
105
+ {text}
105
106
  </CapLabel>
106
107
  );
107
108
  }
@@ -143,68 +144,69 @@ const RcsPreviewContent = ({
143
144
  const titleShown = titleTrimmed || formatMessage(messages.rcsCarouselPreviewTitlePlaceholder);
144
145
  const bodyShown = bodyTrimmed || formatMessage(messages.rcsCarouselPreviewBodyPlaceholder);
145
146
  return (
146
- <CapRow
147
- key={key}
148
- className="message-pop align-left message-pop-carousel"
149
- >
150
- <CapRow className="whatsapp-content">
151
- {!isVideo && (
152
- <CapRow
153
- className="whatsapp-image rcs-carousel-media-wrap"
154
- style={getCarouselMediaAspectStyle(false)}
155
- >
156
- <CapImage
157
- src={card?.imageSrc ? card.imageSrc : rcsImageEmptyPreview}
158
- className="rcs-carousel-img"
159
- alt={formatMessage(messages.previewGenerated)}
160
- />
161
- </CapRow>
162
- )}
163
- {isVideo && (
164
- <CapTooltip
165
- title={formatMessage(messages.videoPreviewTooltip)}
166
- wrapperClassName="rcs-carousel-video-tooltip-wrapper"
167
- triggerClassName="rcs-carousel-video-tooltip-trigger"
168
- >
169
- <CapRow className="video-preview">
170
- <CapRow
171
- className="whatsapp-image rcs-carousel-media-wrap"
172
- style={getCarouselMediaAspectStyle(true)}
173
- >
174
- <CapImage
175
- src={card?.videoPreviewImg ? card.videoPreviewImg : rcsVideoEmptyPreview}
176
- className="rcs-carousel-img"
177
- alt={formatMessage(messages.previewGenerated)}
178
- />
179
- </CapRow>
180
- <CapRow className="icon-position">
181
- <CapImage className="video-icon" src={videoPlay} alt="Play" />
182
- </CapRow>
147
+ <CapRow key={key} className="rcs-carousel-item">
148
+ <CapRow className="message-pop align-left message-pop-carousel">
149
+ <CapRow className="whatsapp-content">
150
+ {!isVideo && (
151
+ <CapRow
152
+ className="whatsapp-image rcs-carousel-media-wrap"
153
+ style={getCarouselMediaAspectStyle(false)}
154
+ >
155
+ <CapImage
156
+ src={card?.imageSrc ? card.imageSrc : rcsImageEmptyPreview}
157
+ className="rcs-carousel-img"
158
+ alt={formatMessage(messages.previewGenerated)}
159
+ />
183
160
  </CapRow>
184
- </CapTooltip>
185
- )}
186
-
187
- <CapRow className="carousel-content">
188
- <CapLabel
189
- type={card.titleLabelType || 'label1'}
190
- className={`carousel-title${titleTrimmed ? '' : ' rcs-carousel-field-placeholder'}`}
191
- >
192
- {titleShown}
193
- </CapLabel>
194
- <CapLabel
195
- type={card.bodyLabelType || 'label2'}
196
- className={`carousel-message${bodyTrimmed ? '' : ' rcs-carousel-field-placeholder'}`}
197
- >
198
- {bodyShown}
199
- </CapLabel>
200
- </CapRow>
201
-
202
- {!!suggestionsNode && (
203
- <CapRow className="rcs-carousel-cta-stack" gutter={0}>
204
- {suggestionsNode}
161
+ )}
162
+ {isVideo && (
163
+ <CapTooltip
164
+ title={formatMessage(messages.videoPreviewTooltip)}
165
+ wrapperClassName="rcs-carousel-video-tooltip-wrapper"
166
+ triggerClassName="rcs-carousel-video-tooltip-trigger"
167
+ >
168
+ <CapRow className="video-preview">
169
+ <CapRow
170
+ className="whatsapp-image rcs-carousel-media-wrap"
171
+ style={getCarouselMediaAspectStyle(true)}
172
+ >
173
+ <CapImage
174
+ src={card?.videoPreviewImg ? card.videoPreviewImg : rcsVideoEmptyPreview}
175
+ className="rcs-carousel-img"
176
+ alt={formatMessage(messages.previewGenerated)}
177
+ />
178
+ </CapRow>
179
+ <CapRow className="icon-position">
180
+ <CapImage className="video-icon" src={videoPlay} alt="Play" />
181
+ </CapRow>
182
+ </CapRow>
183
+ </CapTooltip>
184
+ )}
185
+
186
+ <CapRow className="carousel-content">
187
+ <CapLabel
188
+ type={card.titleLabelType || 'label1'}
189
+ className={`carousel-title${titleTrimmed ? '' : ' rcs-carousel-field-placeholder'}`}
190
+ >
191
+ {titleShown}
192
+ </CapLabel>
193
+ <CapLabel
194
+ type={card.bodyLabelType || 'label2'}
195
+ className={`carousel-message${bodyTrimmed ? '' : ' rcs-carousel-field-placeholder'}`}
196
+ >
197
+ {bodyShown}
198
+ </CapLabel>
205
199
  </CapRow>
206
- )}
200
+ </CapRow>
207
201
  </CapRow>
202
+
203
+ {/* Deliberately OUTSIDE .message-pop-carousel — the CTA bar is its own
204
+ floating element below the card, not clipped/rounded as part of it. */}
205
+ {!!suggestionsNode && (
206
+ <CapRow className="rcs-carousel-cta-stack" gutter={0}>
207
+ {suggestionsNode}
208
+ </CapRow>
209
+ )}
208
210
  </CapRow>
209
211
  );
210
212
  })}
@@ -425,7 +427,7 @@ const RcsPreviewContent = ({
425
427
  {hasCarousel && (
426
428
  <CapRow
427
429
  className={`rcs-carousel-panel rcs-carousel-panel-${device}`}
428
- style={carouselPanelStyle}
430
+ // style={carouselPanelStyle}
429
431
  >
430
432
  {renderCarouselPreviewContent(carouselCardsNormalized)}
431
433
  <CapRow useLegacy className="sms-timestamp rcs-carousel-timestamp">