@capillarytech/creatives-library 8.0.262-alpha.0 → 8.0.262

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 (24) hide show
  1. package/package.json +1 -1
  2. package/v2Components/ErrorInfoNote/index.js +57 -112
  3. package/v2Components/ErrorInfoNote/messages.js +8 -12
  4. package/v2Components/ErrorInfoNote/style.scss +0 -4
  5. package/v2Components/HtmlEditor/HTMLEditor.js +46 -182
  6. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +11 -15
  7. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +7 -8
  8. package/v2Components/HtmlEditor/_htmlEditor.scss +6 -6
  9. package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +1 -1
  10. package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +4 -4
  11. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/_validationErrorDisplay.scss +17 -0
  12. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +15 -28
  13. package/v2Components/HtmlEditor/components/ValidationPanel/_validationPanel.scss +0 -4
  14. package/v2Components/HtmlEditor/components/ValidationPanel/index.js +7 -31
  15. package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +53 -26
  16. package/v2Components/HtmlEditor/components/ValidationTabs/index.js +74 -144
  17. package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +14 -14
  18. package/v2Components/HtmlEditor/hooks/useValidation.js +23 -3
  19. package/v2Components/HtmlEditor/utils/validationConstants.js +3 -4
  20. package/v2Containers/CreativesContainer/index.js +1 -3
  21. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +6 -9
  22. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +5 -49
  23. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +23 -38
  24. package/v2Containers/TemplatesV2/TemplatesV2.style.js +4 -2
@@ -465,9 +465,8 @@ describe('HTMLEditor - API Validation Errors', () => {
465
465
  expect.objectContaining({
466
466
  validationComplete: false,
467
467
  issueCounts: expect.objectContaining({
468
- html: 0,
469
- label: 0,
470
- liquid: 0,
468
+ errors: 0,
469
+ warnings: 0,
471
470
  total: 0,
472
471
  }),
473
472
  })
@@ -704,13 +703,12 @@ describe('HTMLEditor - API Validation Errors', () => {
704
703
 
705
704
  renderWithIntl({ onValidationChange });
706
705
 
707
- // Should categorize as HTML issue
706
+ // HTML hint issues are non-blocking (warnings)
708
707
  expect(onValidationChange).toHaveBeenCalledWith(
709
708
  expect.objectContaining({
710
709
  issueCounts: expect.objectContaining({
711
- html: 1,
712
- label: 0,
713
- liquid: 0,
710
+ errors: 0,
711
+ warnings: 1,
714
712
  total: 1,
715
713
  }),
716
714
  })
@@ -742,13 +740,12 @@ describe('HTMLEditor - API Validation Errors', () => {
742
740
 
743
741
  renderWithIntl({ onValidationChange });
744
742
 
745
- // Should categorize as label issue
743
+ // Label/tag-pair issues are non-blocking (warnings)
746
744
  expect(onValidationChange).toHaveBeenCalledWith(
747
745
  expect.objectContaining({
748
746
  issueCounts: expect.objectContaining({
749
- html: 0,
750
- label: 1,
751
- liquid: 0,
747
+ errors: 0,
748
+ warnings: 1,
752
749
  total: 1,
753
750
  }),
754
751
  })
@@ -780,13 +777,12 @@ describe('HTMLEditor - API Validation Errors', () => {
780
777
 
781
778
  renderWithIntl({ onValidationChange });
782
779
 
783
- // Should categorize as liquid issue
780
+ // Liquid-validator with severity error is blocking (errors)
784
781
  expect(onValidationChange).toHaveBeenCalledWith(
785
782
  expect.objectContaining({
786
783
  issueCounts: expect.objectContaining({
787
- html: 0,
788
- label: 0,
789
- liquid: 1,
784
+ errors: 1,
785
+ warnings: 0,
790
786
  total: 1,
791
787
  }),
792
788
  })
@@ -3106,19 +3106,18 @@ describe('HTMLEditor', () => {
3106
3106
  });
3107
3107
 
3108
3108
  const issueCounts = ref.current.getIssueCounts();
3109
+ // None of the mock issues are blocking (no BLOCKING_ERROR_RULE_IDS, liquid has no severity 'error')
3109
3110
  expect(issueCounts).toEqual({
3110
- html: 1,
3111
- label: 1,
3112
- liquid: 1,
3111
+ errors: 0,
3112
+ warnings: 3,
3113
3113
  total: 3,
3114
3114
  });
3115
3115
 
3116
3116
  const validationState = ref.current.getValidationState();
3117
3117
  expect(validationState.hasErrors).toBe(true);
3118
3118
  expect(validationState.issueCounts).toEqual({
3119
- html: 1,
3120
- label: 1,
3121
- liquid: 1,
3119
+ errors: 0,
3120
+ warnings: 3,
3122
3121
  total: 3,
3123
3122
  });
3124
3123
  });
@@ -3150,10 +3149,10 @@ describe('HTMLEditor', () => {
3150
3149
  });
3151
3150
 
3152
3151
  expect(ref.current.getIssueCounts()).toEqual({
3153
- html: 0, label: 0, liquid: 0, total: 0,
3152
+ errors: 0, warnings: 0, total: 0,
3154
3153
  });
3155
3154
  expect(ref.current.getValidationState().issueCounts).toEqual({
3156
- html: 0, label: 0, liquid: 0, total: 0,
3155
+ errors: 0, warnings: 0, total: 0,
3157
3156
  });
3158
3157
  });
3159
3158
  });
@@ -88,12 +88,12 @@
88
88
 
89
89
  &__content {
90
90
  height: calc(95vh - 5%); // Same as editor pane - maximize to full available height
91
- max-height: calc(95vh - 50%);
91
+ max-height: calc(95vh - 30%);
92
92
  }
93
93
 
94
94
  iframe {
95
95
  height: calc(95vh - 5%); // Same as editor pane - maximize to full available height
96
- max-height: calc(95vh - 50%);
96
+ max-height: calc(95vh - 30%);
97
97
  }
98
98
  }
99
99
  }
@@ -281,12 +281,12 @@
281
281
 
282
282
  &__content {
283
283
  height: calc(95vh - 5%); // Same as editor pane - maximize to full available height
284
- max-height: calc(95vh - 50%);
284
+ max-height: calc(95vh - 30%);
285
285
  }
286
286
 
287
287
  iframe {
288
288
  height: calc(95vh - 5%); // Same as editor pane - maximize to full available height
289
- max-height: calc(95vh - 50%);
289
+ max-height: calc(95vh - 30%);
290
290
  }
291
291
  }
292
292
  }
@@ -299,12 +299,12 @@
299
299
 
300
300
  &__content {
301
301
  height: calc(95vh - 5%); // Same as editor pane - maximize to full available height
302
- max-height: calc(95vh - 50%);
302
+ max-height: calc(95vh - 30%);
303
303
  }
304
304
 
305
305
  iframe {
306
306
  height: calc(95vh - 5%); // Same as editor pane - maximize to full available height
307
- max-height: calc(95vh - 50%);
307
+ max-height: calc(95vh - 30%);
308
308
  }
309
309
  }
310
310
  }
@@ -66,7 +66,7 @@
66
66
 
67
67
  // Add Label button visibility: 50% opacity while typing, 100% on hover
68
68
  .code-editor-pane__actions {
69
- opacity: 0.7; // 70% transparent while typing
69
+ opacity: 0.8; // 80% transparent while typing
70
70
  pointer-events: auto; // Always clickable
71
71
  transition: opacity 0.2s ease;
72
72
 
@@ -6,7 +6,7 @@
6
6
 
7
7
  .preview-pane {
8
8
  height: 100%;
9
- max-height: 31.25rem;
9
+ max-height: 33.25rem;
10
10
  position: relative;
11
11
  background-color: $CAP_G09;
12
12
  padding: 1rem;
@@ -67,8 +67,8 @@
67
67
  }
68
68
 
69
69
  &__content {
70
- height: calc(100% - 2rem);
71
- max-height: 28.125rem;
70
+ height: calc(100%);
71
+ max-height: 34.25rem;
72
72
  display: flex;
73
73
  align-items: center;
74
74
  justify-content: center;
@@ -83,7 +83,7 @@
83
83
  background-color: $CAP_WHITE;
84
84
  width: 100%;
85
85
  height: 28.125rem;
86
- max-height: 31.25rem;
86
+ max-height: 34.25rem;
87
87
 
88
88
  &--mobile {
89
89
  // Width and height now controlled by React inline styles
@@ -1,5 +1,7 @@
1
1
  /**
2
2
  * ValidationErrorDisplay Styles
3
+ *
4
+ * When collapsed, panel sticks to footer (header bar only visible).
3
5
  */
4
6
 
5
7
  @import '~@capillarytech/cap-ui-library/styles/_variables.scss';
@@ -9,6 +11,7 @@
9
11
  flex-direction: column;
10
12
  width: 100%;
11
13
  margin-bottom: 1rem;
14
+ flex-shrink: 0;
12
15
 
13
16
  // Ensure proper spacing when used in different contexts
14
17
  &:last-child {
@@ -16,6 +19,12 @@
16
19
  padding-bottom: 0.25rem;
17
20
  }
18
21
 
22
+ // Collapsed: panel stays in footer as a slim bar (tabs + expand arrow only)
23
+ &--collapsed {
24
+ margin-bottom: 0;
25
+ padding-bottom: 0.25rem;
26
+ }
27
+
19
28
  // Integration with ErrorInfoNote component
20
29
  .error-info-note {
21
30
  width: 100%;
@@ -24,9 +33,17 @@
24
33
  // Responsive adjustments
25
34
  @media (max-width: 768px) {
26
35
  margin-bottom: 0.75rem;
36
+
37
+ &.validation-error-display--collapsed {
38
+ margin-bottom: 0;
39
+ }
27
40
  }
28
41
 
29
42
  @media (max-width: 576px) {
30
43
  margin-bottom: 0.5rem;
44
+
45
+ &.validation-error-display--collapsed {
46
+ margin-bottom: 0;
47
+ }
31
48
  }
32
49
  }
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * ValidationErrorDisplay - HTML Editor validation display
3
3
  *
4
- * This component displays validation errors using the new ValidationTabs component
5
- * with tabbed interface for HTML, Label, and Liquid issues.
4
+ * Displays validation errors and warnings in two tabs (Errors, Warnings).
5
+ * Panel can be collapsed to a sticky footer bar; it does not disappear.
6
6
  */
7
7
 
8
8
  import React, { useState } from 'react';
@@ -17,41 +17,31 @@ import './_validationErrorDisplay.scss';
17
17
  /**
18
18
  * ValidationErrorDisplay Component
19
19
  *
20
- * Displays validation errors using the ValidationTabs component
20
+ * Displays validation issues in Errors and Warnings tabs; collapse toggles visibility but panel stays in footer.
21
21
  */
22
22
  const ValidationErrorDisplay = ({
23
23
  validation,
24
24
  onErrorClick,
25
- onClose,
26
- isLiquidEnabled = false,
27
25
  className = '',
28
26
  }) => {
29
- // Track if panel is dismissed
30
- const [isDismissed, setIsDismissed] = useState(false);
27
+ const [isCollapsed, setIsCollapsed] = useState(false);
31
28
 
32
- // Handle close - dismiss temporarily
33
- const handleClose = () => {
34
- setIsDismissed(true);
35
- if (onClose) {
36
- onClose();
37
- }
29
+ const handleToggleCollapse = () => {
30
+ setIsCollapsed((prev) => !prev);
38
31
  };
39
32
 
40
- // Reset dismissed state when validation changes (new errors appear)
41
- React.useEffect(() => {
42
- if (hasValidationErrors(validation)) {
43
- setIsDismissed(false);
44
- }
45
- }, [validation]);
33
+ // Expand panel when user clicks redirection (so panel does not stay stuck to footer)
34
+ const handleExpand = () => {
35
+ setIsCollapsed(false);
36
+ };
46
37
 
47
- // Don't render if no validation, no errors, or dismissed
48
- if (!hasValidationErrors(validation) || isDismissed) {
38
+ if (!hasValidationErrors(validation)) {
49
39
  return null;
50
40
  }
51
41
 
52
42
  return (
53
43
  <div
54
- className={`validation-error-display ${className}`}
44
+ className={`validation-error-display ${isCollapsed ? 'validation-error-display--collapsed' : ''} ${className}`}
55
45
  role="alert"
56
46
  aria-live="polite"
57
47
  aria-label="Validation errors"
@@ -59,8 +49,9 @@ const ValidationErrorDisplay = ({
59
49
  <ValidationTabs
60
50
  validation={validation}
61
51
  onErrorClick={onErrorClick}
62
- onClose={handleClose}
63
- isLiquidEnabled={isLiquidEnabled}
52
+ isCollapsed={isCollapsed}
53
+ onToggleCollapse={handleToggleCollapse}
54
+ onExpand={handleExpand}
64
55
  />
65
56
  </div>
66
57
  );
@@ -72,16 +63,12 @@ ValidationErrorDisplay.propTypes = {
72
63
  getAllIssues: PropTypes.func,
73
64
  }),
74
65
  onErrorClick: PropTypes.func,
75
- onClose: PropTypes.func,
76
- isLiquidEnabled: PropTypes.bool,
77
66
  className: PropTypes.string,
78
67
  };
79
68
 
80
69
  ValidationErrorDisplay.defaultProps = {
81
70
  validation: null,
82
71
  onErrorClick: null,
83
- onClose: null,
84
- isLiquidEnabled: false,
85
72
  className: '',
86
73
  };
87
74
 
@@ -198,10 +198,6 @@
198
198
  color: #1890ff;
199
199
  }
200
200
 
201
- &--warning {
202
- color: #faad14;
203
- }
204
-
205
201
  &--security {
206
202
  color: #ff4d4f;
207
203
  }
@@ -38,7 +38,6 @@ const ValidationPanel = ({
38
38
  onErrorClick,
39
39
  showLineNumbers = true,
40
40
  groupBySource = false,
41
- variant = 'email',
42
41
  }) => {
43
42
  const [activeKeys, setActiveKeys] = useState(['errors', 'warnings']);
44
43
 
@@ -84,24 +83,7 @@ const ValidationPanel = ({
84
83
  };
85
84
 
86
85
  // Get icon for issue type
87
- // Blocking errors use error-icon, warnings use alert-warning
88
- const getIssueIcon = (issue) => {
89
- const { source, severity } = issue || {};
90
- if (source === 'security') {
91
- return <ShieldOutlined className="validation-issue__icon validation-issue__icon--security" />;
92
- }
93
-
94
- // Only show error icon for blocking errors (API errors or Rule Group #1)
95
- if (isBlockingError(issue)) {
96
- return <CapIcon type="error-icon" className="validation-issue__icon validation-issue__icon--error" />;
97
- }
98
-
99
- // All other issues show as warnings
100
- if (severity === SEVERITY.INFO) {
101
- return <CapIcon type="info" className="validation-issue__icon validation-issue__icon--info" />;
102
- }
103
- return <CapIcon type="alert-warning" className="validation-issue__icon validation-issue__icon--warning" />;
104
- };
86
+ // Blocking errors use error-icon, warnings use warning
105
87
 
106
88
  // Get source icon
107
89
  const getSourceIcon = (source) => {
@@ -145,9 +127,6 @@ const ValidationPanel = ({
145
127
  }
146
128
  }}
147
129
  >
148
- <div className="validation-issue__icon">
149
- {getIssueIcon(issue)}
150
- </div>
151
130
 
152
131
  <div className="validation-issue__content">
153
132
  <div className="validation-issue__message">
@@ -184,24 +163,21 @@ const ValidationPanel = ({
184
163
  );
185
164
 
186
165
  // Render panel header with count
187
- const renderPanelHeader = (title, count, severity, issues = []) => {
188
- // Check if any issue in this group is a blocking error
189
- const hasBlocking = issues.some((issue) => isBlockingError(issue));
190
- // Use blocking error icon only if there are blocking errors, otherwise use warning icon
191
- const iconIssue = hasBlocking ? { rule: 'blocking', source: 'blocking' } : { severity: 'warning' };
166
+ const renderPanelHeader = (title, count, severity, issues = []) =>
167
+ // Check if any issue in this group is a blocking error
168
+ // Use blocking error icon only if there are blocking errors, otherwise use warning icon
192
169
 
193
- return (
170
+ (
194
171
  <div className="validation-panel__header">
195
172
  <span className="validation-panel__title">
196
- {getIssueIcon(iconIssue)}
197
173
  <FormattedMessage {...title} />
198
174
  </span>
199
175
  {count > 0 && (
200
176
  <Badge count={count} style={{ backgroundColor: getSeverityColor(severity) }} />
201
177
  )}
202
178
  </div>
203
- );
204
- };
179
+ )
180
+ ;
205
181
 
206
182
  // Get severity color
207
183
  const getSeverityColor = (severity) => {
@@ -23,8 +23,8 @@
23
23
  // Override CapTab styles for proper spacing
24
24
  .cap-tab-v2 {
25
25
  .ant-tabs-bar {
26
- padding: 0.5rem 0.5rem;
27
- background-color: $CAP_COLOR_05;
26
+ padding-right: 0.5rem;
27
+ padding-top: 0.5rem;
28
28
  }
29
29
  .ant-tabs-nav {
30
30
  margin-bottom: 0;
@@ -37,29 +37,31 @@
37
37
  .ant-tabs-tab {
38
38
  padding: 0.5rem 0.75rem; // Add horizontal padding for spacing
39
39
  margin-right: 0; // Remove margin, use padding instead
40
-
41
- color: $CAP_G03;
40
+
41
+ color: $CAP_G04;
42
42
  font-size: 0.875rem;
43
43
  font-weight: 400;
44
44
  line-height: 1;
45
45
  letter-spacing: 0;
46
- background-color: $CAP_COLOR_05;
47
46
  border-radius: 0.25rem;
48
47
  border-bottom: none; // Remove bottom border
49
-
50
- & + .ant-tabs-tab {
51
- margin-left: 1.5rem; // Add space between tabs
48
+
49
+ &.ant-tabs-tab {
50
+ margin-left: 1rem; // Add space between tabs
51
+ font-weight: 400;
52
52
  }
53
-
53
+
54
54
  &:hover {
55
55
  color: $CAP_COLOR_05;
56
- background-color: $CAP_COLOR_05;
57
56
  }
58
57
 
59
58
  &.ant-tabs-tab-active {
59
+ color: $FONT_COLOR_01;
60
+ font-weight: 500;
61
+
60
62
  .ant-tabs-tab-btn {
61
- color: $CAP_COLOR_05;
62
- font-weight: 600;
63
+ color: $FONT_COLOR_01;
64
+ font-weight: 500;
63
65
  }
64
66
  }
65
67
  }
@@ -73,12 +75,20 @@
73
75
  padding-bottom: 0; // No bottom padding
74
76
  }
75
77
  }
78
+
79
+ .ant-tabs-content {
80
+ margin-top: $CAP_SPACE_08;
81
+ }
76
82
  }
77
83
 
78
84
  &__tab-label {
79
85
  display: flex;
80
86
  align-items: center;
81
87
  gap: 0.25rem;
88
+
89
+ &--warnings {
90
+ color: $CAP_G01;
91
+ }
82
92
  }
83
93
 
84
94
  &__tab-count {
@@ -90,10 +100,9 @@
90
100
  align-items: center;
91
101
  flex-shrink: 0;
92
102
  padding-top: 0.5rem;
93
- background-color: $CAP_COLOR_05;
94
103
  }
95
104
 
96
- &__close {
105
+ &__collapse-toggle {
97
106
  display: flex;
98
107
  align-items: center;
99
108
  justify-content: center;
@@ -107,20 +116,30 @@
107
116
  color: $CAP_G03;
108
117
  transition: all 0.2s ease;
109
118
 
110
- // &:hover {
111
- // background-color: rgba($CAP_COLOR_05, 0.1);
112
- // color: $CAP_COLOR_05;
113
- // }
114
-
115
119
  .cap-icon-v2 {
116
120
  font-size: 0.875rem;
117
121
  }
122
+
123
+ &:hover {
124
+ color: $CAP_COLOR_05;
125
+ }
126
+ }
127
+
128
+ // When collapsed: hide tab content so only header (tabs + arrow) sticks to footer
129
+ &--collapsed {
130
+ .ant-tabs-content-holder,
131
+ .ant-tabs-content,
132
+ .ant-tabs-tabpane {
133
+ display: none !important;
134
+ }
118
135
  }
119
136
 
120
137
  &__content {
121
- max-height: 15rem; // Limit height for many errors
138
+ height: 8rem; // Limit height for many errors
139
+ max-height: 12.5rem;
122
140
  overflow-y: auto;
123
141
  padding-bottom: 0; // Remove bottom padding completely
142
+ margin-left:2%;
124
143
 
125
144
  // Custom scrollbar
126
145
  &::-webkit-scrollbar {
@@ -144,8 +163,7 @@
144
163
  &__item {
145
164
  display: flex;
146
165
  align-items: flex-start;
147
- gap: 0.5rem;
148
- padding: 0.5rem 0.5rem; // Reduced from 0.375rem
166
+ padding: 0.1rem 0.5rem; // Reduced from 0.375rem
149
167
  margin-bottom: 3px;
150
168
 
151
169
  &:last-child {
@@ -157,11 +175,21 @@
157
175
  .validation-tabs__icon--error {
158
176
  color: $CAP_RED;
159
177
  }
178
+ .validation-tabs__item-location,
179
+ .validation-tabs__item-message {
180
+ color: $CAP_RED;
181
+ }
160
182
  }
161
183
 
162
184
  &--warning {
163
185
  .validation-tabs__icon--warning {
164
- color: $CAP_YELLOW;
186
+ color: $CAP_G01;
187
+ }
188
+ .validation-tabs__item-message {
189
+ color: $CAP_G01;
190
+ }
191
+ .validation-tabs__item-location {
192
+ color: $CAP_G03;
165
193
  }
166
194
  }
167
195
  }
@@ -170,7 +198,6 @@
170
198
  flex-shrink: 0;
171
199
  display: flex;
172
200
  align-items: center;
173
- padding-top: 0.125rem;
174
201
  }
175
202
 
176
203
  &__icon {
@@ -181,7 +208,7 @@
181
208
  }
182
209
 
183
210
  &--warning {
184
- color: $CAP_YELLOW;
211
+ color: $CAP_G01;
185
212
  }
186
213
  }
187
214
 
@@ -239,7 +266,7 @@
239
266
  .ant-tabs-tab {
240
267
  margin-right: 1rem;
241
268
  font-size: 0.8125rem;
242
- background-color: $CAP_COLOR_05;
269
+ font-weight: 400;
243
270
  }
244
271
  }
245
272