@explorer02/cfm-survey-sdk 0.4.1 → 0.4.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/dist/cli/index.js +44 -40
- package/dist/cli/index.mjs +42 -38
- package/package.json +1 -1
- package/templates/docs/templates/CsatMatrixScale.tsx +30 -34
- package/templates/docs/templates/LikertMatrixScale.tsx +6 -3
- package/templates/docs/templates/RatingScale.tsx +8 -25
- package/templates/docs/templates/SliderMatrixScale.tsx +2 -1
- package/templates/docs/templates/labelStyles.ts +17 -0
- package/templates/docs/templates/selectionStyles.ts +58 -0
- package/templates/docs/templates/surveyUiIcons.tsx +6 -1
- package/templates/preview-harness/preview-bridge.inline.js +13 -0
- package/templates/preview-harness/preview-google-fonts.html +6 -0
- package/templates/preview-harness/previewPages.js +3 -2
- package/templates/preview-harness/vite-app/src/PreviewConfigContext.tsx +29 -10
- package/templates/preview-harness/vite-app/src/globals.css +4 -3
- package/templates/preview-harness/vite-app/src/mergePreviewQuestion.ts +24 -2
- package/templates/preview-harness/vite-app/src/preview-live-overrides.css +5 -0
- package/templates/previewBridge.ts +14 -0
- package/templates/survey-theme.css +2 -0
- package/templates/wizard-dist/assets/{PreviewMock-MsONyaq9.js → PreviewMock-DkCiCfU3.js} +1 -1
- package/templates/wizard-dist/assets/TypePanel-CUani1B7.js +1 -0
- package/templates/wizard-dist/assets/index-COdmy8Xp.js +34 -0
- package/templates/wizard-dist/index.html +1 -1
- package/templates/wizard-dist/assets/TypePanel-D24BxbHk.js +0 -1
- package/templates/wizard-dist/assets/index-C7RSJr09.js +0 -34
|
@@ -8,7 +8,8 @@ import {
|
|
|
8
8
|
matrixRadioRingStyle,
|
|
9
9
|
matrixRadioDotStyle,
|
|
10
10
|
matrixRowBackgroundStyle,
|
|
11
|
-
|
|
11
|
+
matrixNumberedCellStyle,
|
|
12
|
+
matrixStatementRowsContainerStyle,
|
|
12
13
|
unselectedOpacityStyle,
|
|
13
14
|
emojiSizeStyle,
|
|
14
15
|
cellSelectedVar,
|
|
@@ -16,7 +17,7 @@ import {
|
|
|
16
17
|
mcqSelectedAccentVar,
|
|
17
18
|
mcqSelectedBgVar,
|
|
18
19
|
} from '@/lib/surveyUi/selectionStyles';
|
|
19
|
-
import { csatColumnLabelStyle, ratingColumnLabelStyle, stackAnchorLabelLines, ratingAnchorLabelPillStyle } from '@/lib/surveyUi/labelStyles';
|
|
20
|
+
import { csatColumnLabelStyle, ratingColumnLabelStyle, stackAnchorLabelLines, ratingAnchorLabelPillStyle, csatAnchorLabelPillStyle } from '@/lib/surveyUi/labelStyles';
|
|
20
21
|
|
|
21
22
|
const MATRIX_ROW_LABEL_WIDTH = 'var(--cfm-matrix-row-width, 180px)';
|
|
22
23
|
|
|
@@ -34,19 +35,25 @@ function anchorLabelPositionPercent(
|
|
|
34
35
|
question: CsatMatrixQuestion,
|
|
35
36
|
): number {
|
|
36
37
|
if (labelCount === 1) return 50;
|
|
37
|
-
const useFullSpan =
|
|
38
|
+
const useFullSpan =
|
|
39
|
+
isGraphics || question.type === 'RATING_MATRIX' || question.type === 'CSAT_MATRIX';
|
|
38
40
|
const startPercent = useFullSpan ? 0 : 0.5 / columnCount;
|
|
39
41
|
const rangePercent = useFullSpan ? 1 : (columnCount - 1) / columnCount;
|
|
40
42
|
return (startPercent + (index / (labelCount - 1)) * rangePercent) * 100;
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
function anchorLabelRowMinHeight(question: CsatMatrixQuestion, hasLabels: boolean): string {
|
|
44
|
-
if (
|
|
46
|
+
if (
|
|
47
|
+
hasLabels &&
|
|
48
|
+
(question.type === 'RATING_MATRIX' || question.type === 'CSAT_MATRIX')
|
|
49
|
+
) {
|
|
50
|
+
return '44px';
|
|
51
|
+
}
|
|
45
52
|
return '24px';
|
|
46
53
|
}
|
|
47
54
|
|
|
48
55
|
function renderAnchorLabelContent(label: string, question: CsatMatrixQuestion): React.ReactNode {
|
|
49
|
-
if (question.type !== 'RATING_MATRIX') return label;
|
|
56
|
+
if (question.type !== 'RATING_MATRIX' && question.type !== 'CSAT_MATRIX') return label;
|
|
50
57
|
const lines = stackAnchorLabelLines(label);
|
|
51
58
|
if (lines.length === 1) return label;
|
|
52
59
|
return lines.map((line, idx) => (
|
|
@@ -65,6 +72,14 @@ function anchorLabelBoxStyle(question: CsatMatrixQuestion, isGraphics: boolean):
|
|
|
65
72
|
...ratingAnchorLabelPillStyle(),
|
|
66
73
|
};
|
|
67
74
|
}
|
|
75
|
+
if (question.type === 'CSAT_MATRIX') {
|
|
76
|
+
return {
|
|
77
|
+
textAlign: 'center',
|
|
78
|
+
fontSize: '12px',
|
|
79
|
+
fontWeight: 600,
|
|
80
|
+
...csatAnchorLabelPillStyle(),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
68
83
|
return {
|
|
69
84
|
textAlign: 'center',
|
|
70
85
|
fontSize: isGraphics ? '12px' : '13px',
|
|
@@ -158,7 +173,7 @@ function CsatMatrixVerticalList({
|
|
|
158
173
|
const { statementRows, hasNotApplicableOption: hasNotApplicable } = question;
|
|
159
174
|
|
|
160
175
|
return (
|
|
161
|
-
<div style={
|
|
176
|
+
<div style={matrixStatementRowsContainerStyle()}>
|
|
162
177
|
{statementRows.map(row => (
|
|
163
178
|
<div key={row.id} style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
|
|
164
179
|
<h3 style={{ fontSize: '15px', fontWeight: 500, color: '#111827', lineHeight: 1.6, margin: 0 }}>
|
|
@@ -282,7 +297,9 @@ function CsatMatrixGrid({
|
|
|
282
297
|
position: 'relative',
|
|
283
298
|
minHeight: anchorLabelRowMinHeight(question, Boolean(showLabels)),
|
|
284
299
|
minWidth: 0,
|
|
285
|
-
...(isGraphics || question.type === 'RATING_MATRIX'
|
|
300
|
+
...(isGraphics || question.type === 'RATING_MATRIX' || question.type === 'CSAT_MATRIX'
|
|
301
|
+
? { margin: '0 8px' }
|
|
302
|
+
: {}),
|
|
286
303
|
}}>
|
|
287
304
|
{labels!.map((label, i) => {
|
|
288
305
|
const percent = anchorLabelPositionPercent(i, n, m, isGraphics, question);
|
|
@@ -343,7 +360,7 @@ function CsatMatrixGrid({
|
|
|
343
360
|
)}
|
|
344
361
|
|
|
345
362
|
{/* Grid Rows */}
|
|
346
|
-
<div>
|
|
363
|
+
<div style={matrixStatementRowsContainerStyle()}>
|
|
347
364
|
{statementRows.map((row, rowIdx) => (
|
|
348
365
|
<div key={row.id} style={{
|
|
349
366
|
display: 'flex', alignItems: 'center', borderRadius: '8px',
|
|
@@ -418,21 +435,8 @@ function CsatMatrixGrid({
|
|
|
418
435
|
} : isStar ? {
|
|
419
436
|
padding: '4px',
|
|
420
437
|
transform: isHovered ? 'scale(1.1)' : 'scale(1)',
|
|
421
|
-
color: isSelected ? cellSelectedVar : focusRingVar,
|
|
422
438
|
...(isSelected ? {} : unselectedOpacityStyle()),
|
|
423
|
-
} : isNumbered ? {
|
|
424
|
-
width: '40px', height: '40px', borderRadius: '8px',
|
|
425
|
-
fontSize: '14px', fontWeight: 500,
|
|
426
|
-
...(isSelected
|
|
427
|
-
? scaleCellSelectedStyle(true)
|
|
428
|
-
: {
|
|
429
|
-
border: '1px solid #d1d5db',
|
|
430
|
-
backgroundColor: '#fff',
|
|
431
|
-
color: '#111827',
|
|
432
|
-
...unselectedOpacityStyle(),
|
|
433
|
-
}),
|
|
434
|
-
transition: 'all 0.15s',
|
|
435
|
-
} : { padding: '8px', ...(!isSelected ? unselectedOpacityStyle() : {}) }),
|
|
439
|
+
} : isNumbered ? matrixNumberedCellStyle(displayIdx, isSelected) : { padding: '8px', ...(!isSelected ? unselectedOpacityStyle() : {}) }),
|
|
436
440
|
}}
|
|
437
441
|
>
|
|
438
442
|
{(EmojiNode ?? StarNode ?? (isNumbered ? (displayIdx + 1) : RadioNode)) as React.ReactNode}
|
|
@@ -523,7 +527,9 @@ function CsatMatrixCarousel({
|
|
|
523
527
|
position: 'relative',
|
|
524
528
|
minHeight: anchorLabelRowMinHeight(question, Boolean(showLabels)),
|
|
525
529
|
marginBottom: '16px',
|
|
526
|
-
...(isGraphics || question.type === 'RATING_MATRIX'
|
|
530
|
+
...(isGraphics || question.type === 'RATING_MATRIX' || question.type === 'CSAT_MATRIX'
|
|
531
|
+
? { padding: '0 8px' }
|
|
532
|
+
: {}),
|
|
527
533
|
}}>
|
|
528
534
|
{labels!.map((label, i) => {
|
|
529
535
|
const m = scaleColumns.length;
|
|
@@ -601,17 +607,7 @@ function CsatMatrixCarousel({
|
|
|
601
607
|
...(isSelected ? { backgroundColor: 'rgba(252, 231, 243, 0.6)', boxShadow: '0 0 0 2px rgba(226, 0, 116, 0.3)', borderRadius: '50%' } : {}),
|
|
602
608
|
} : isStar ? {
|
|
603
609
|
padding: '4px', transform: isHovered ? 'scale(1.1)' : 'scale(1)', opacity: isSelected ? 1 : isHovered ? 1 : 0.5,
|
|
604
|
-
} : isNumbered ? {
|
|
605
|
-
width: '40px', height: '40px', borderRadius: '8px', fontSize: '14px', fontWeight: 500,
|
|
606
|
-
...(isSelected
|
|
607
|
-
? scaleCellSelectedStyle(true)
|
|
608
|
-
: {
|
|
609
|
-
border: '1px solid #d1d5db',
|
|
610
|
-
backgroundColor: '#fff',
|
|
611
|
-
color: '#111827',
|
|
612
|
-
...unselectedOpacityStyle(),
|
|
613
|
-
}),
|
|
614
|
-
} : { padding: '8px', ...(!isSelected ? unselectedOpacityStyle() : {}) }),
|
|
610
|
+
} : isNumbered ? matrixNumberedCellStyle(displayIdx, isSelected) : { padding: '8px', ...(!isSelected ? unselectedOpacityStyle() : {}) }),
|
|
615
611
|
}}
|
|
616
612
|
>
|
|
617
613
|
{(EmojiNode ?? StarNode ?? (isNumbered ? (displayIdx + 1) : RadioNode)) as React.ReactNode}
|
|
@@ -3,7 +3,7 @@ import type { CfmMatrixQuestion, MatrixRowAnswers } from '@explorer02/cfm-survey
|
|
|
3
3
|
import { columnSubmitValue } from './surveyUiScaleUtils';
|
|
4
4
|
import MatrixDropdown from './MatrixDropdown';
|
|
5
5
|
import { matrixColumnLabelStyle } from '@/lib/surveyUi/labelStyles';
|
|
6
|
-
import { focusRingVar, matrixCarouselDotVar, matrixRadioDotStyle, matrixRadioRingStyle, matrixRowBackgroundStyle } from '@/lib/surveyUi/selectionStyles';
|
|
6
|
+
import { focusRingVar, matrixCarouselDotVar, matrixRadioDotStyle, matrixRadioRingStyle, matrixRowBackgroundStyle, matrixStatementRowsContainerStyle } from '@/lib/surveyUi/selectionStyles';
|
|
7
7
|
|
|
8
8
|
const MATRIX_ROW_LABEL_WIDTH = 'var(--cfm-matrix-row-width, 180px)';
|
|
9
9
|
const BIPOLAR_COL_WIDTH = 'var(--cfm-matrix-bipolar-width, 25%)';
|
|
@@ -33,7 +33,7 @@ function LikertMatrixDropdownLayout({
|
|
|
33
33
|
const { statementRows, scaleColumns } = question;
|
|
34
34
|
|
|
35
35
|
return (
|
|
36
|
-
<div style={
|
|
36
|
+
<div style={matrixStatementRowsContainerStyle()}>
|
|
37
37
|
{statementRows.map(row => (
|
|
38
38
|
<div key={row.id} style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
39
39
|
<div style={{ fontSize: '15px', fontWeight: 500, color: '#374151' }} dangerouslySetInnerHTML={{ __html: row.statementText }} />
|
|
@@ -251,7 +251,10 @@ function LikertMatrixGridLayout({
|
|
|
251
251
|
<div style={{ width: '100%', maxWidth: '100%', minWidth: 0, overflowX: 'auto', boxSizing: 'border-box' }}>
|
|
252
252
|
{!repeatColumnHeaders && renderHeader()}
|
|
253
253
|
|
|
254
|
-
<div style={{
|
|
254
|
+
<div style={{
|
|
255
|
+
...matrixStatementRowsContainerStyle(),
|
|
256
|
+
gap: repeatColumnHeaders ? '24px' : 'var(--cfm-matrix-statement-row-gap, 0px)',
|
|
257
|
+
}}>
|
|
255
258
|
{gridRows.map((rowItem, rowIdx) => {
|
|
256
259
|
return (
|
|
257
260
|
<div key={rowItem.id} style={{ display: 'flex', flexDirection: 'column' }}>
|
|
@@ -4,12 +4,13 @@ import type { AnswerValue, NpsScalePoint } from '@explorer02/cfm-survey-sdk';
|
|
|
4
4
|
import {
|
|
5
5
|
scaleRadioRingStyle,
|
|
6
6
|
scaleRadioDotStyle,
|
|
7
|
-
|
|
7
|
+
npsNumberBadgeStyle,
|
|
8
8
|
} from '@/lib/surveyUi/selectionStyles';
|
|
9
9
|
import {
|
|
10
10
|
getAccentColor,
|
|
11
11
|
isScaleAnswerSelected,
|
|
12
12
|
npsValueFromOption,
|
|
13
|
+
resolveNumberLabelColor,
|
|
13
14
|
starValueFromOption,
|
|
14
15
|
} from './surveyUiScaleUtils';
|
|
15
16
|
import { getEmojiForIndex } from './surveyUiIcons';
|
|
@@ -23,25 +24,6 @@ type RatingScaleProps = {
|
|
|
23
24
|
buttonStyle?: 'standard' | 'numbered' | 'emoji' | 'pill';
|
|
24
25
|
};
|
|
25
26
|
|
|
26
|
-
function resolveBadgeColor(index: number, value: number): string {
|
|
27
|
-
if (typeof document === 'undefined') return getAccentColor(value);
|
|
28
|
-
|
|
29
|
-
const root = document.documentElement;
|
|
30
|
-
const mode = getComputedStyle(root).getPropertyValue('--cfm-number-label-mode').trim();
|
|
31
|
-
if (mode === 'monochrome') {
|
|
32
|
-
return (
|
|
33
|
-
getComputedStyle(root).getPropertyValue('--cfm-number-mono-color').trim() || getAccentColor(value)
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
if (mode === 'individual') {
|
|
37
|
-
return (
|
|
38
|
-
getComputedStyle(root).getPropertyValue(`--cfm-number-color-${index}`).trim() ||
|
|
39
|
-
getAccentColor(value)
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
return getAccentColor(value);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
27
|
export default function RatingScale({
|
|
46
28
|
questionId,
|
|
47
29
|
options,
|
|
@@ -57,7 +39,9 @@ export default function RatingScale({
|
|
|
57
39
|
: npsValueFromOption(option as NpsScalePoint, index);
|
|
58
40
|
const numericValue = typeof value === 'number' ? value : index;
|
|
59
41
|
const color =
|
|
60
|
-
variant === 'nps'
|
|
42
|
+
variant === 'nps'
|
|
43
|
+
? resolveNumberLabelColor(index, getAccentColor(numericValue))
|
|
44
|
+
: 'var(--cfm-primary, #e20074)';
|
|
61
45
|
return { id: option.id, label: option.optionLabel, value, color };
|
|
62
46
|
});
|
|
63
47
|
|
|
@@ -117,15 +101,14 @@ export default function RatingScale({
|
|
|
117
101
|
return (
|
|
118
102
|
<div key={`${questionId}-badge-${option.value}`} className="flex justify-center">
|
|
119
103
|
<div
|
|
120
|
-
className=
|
|
104
|
+
className={`flex items-center justify-center rounded-[3px] text-[10px] font-bold shadow-sm sm:text-xs${isSelected ? ' selected' : ''}`}
|
|
121
105
|
style={{
|
|
122
106
|
width: 'var(--cfm-nps-cell-size, 24px)',
|
|
123
107
|
height: 'var(--cfm-nps-cell-size, 24px)',
|
|
124
|
-
...(isSelected
|
|
125
|
-
? scaleCellSelectedStyle(true)
|
|
126
|
-
: { backgroundColor: option.color }),
|
|
108
|
+
...npsNumberBadgeStyle(option.color, isSelected),
|
|
127
109
|
}}
|
|
128
110
|
data-cfm-number-badge={index}
|
|
111
|
+
data-cfm-nps-badge
|
|
129
112
|
title={option.label}
|
|
130
113
|
>
|
|
131
114
|
{option.label}
|
|
@@ -10,6 +10,7 @@ import type { SliderMatrixQuestion, MatrixRowAnswers } from '@explorer02/cfm-sur
|
|
|
10
10
|
import { tickColorFromIndex } from './surveyUiScaleUtils';
|
|
11
11
|
import { CustomSliderTrack } from './CustomSliderTrack';
|
|
12
12
|
import { hintLabelStyle, sliderTickLabelStyle } from '@/lib/surveyUi/labelStyles';
|
|
13
|
+
import { matrixStatementRowsContainerStyle } from '@/lib/surveyUi/selectionStyles';
|
|
13
14
|
|
|
14
15
|
const MATRIX_ROW_LABEL_WIDTH = 'var(--cfm-matrix-row-width, 180px)';
|
|
15
16
|
const SLIDER_ROW_BAND = 'var(--cfm-slider-row-band, var(--cfm-row-band, #f3f4f6))';
|
|
@@ -168,7 +169,7 @@ export function SliderMatrixScale({ question, selectedValue = {}, onSelect }: Sl
|
|
|
168
169
|
};
|
|
169
170
|
|
|
170
171
|
return (
|
|
171
|
-
<div style={{
|
|
172
|
+
<div style={{ ...matrixStatementRowsContainerStyle(), width: '100%', maxWidth: '100%', minWidth: 0, overflowX: 'auto', boxSizing: 'border-box' }}>
|
|
172
173
|
{renderHeaderRow()}
|
|
173
174
|
|
|
174
175
|
{statementRows.map(row => {
|
|
@@ -75,6 +75,23 @@ export function stackAnchorLabelLines(label: string): string[] {
|
|
|
75
75
|
return [words.slice(0, mid).join(' '), words.slice(mid).join(' ')].filter(Boolean);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
/** CSAT matrix anchor pills — stacked text, fixed width, same look on every format. */
|
|
79
|
+
export function csatAnchorLabelPillStyle(): CSSProperties {
|
|
80
|
+
return {
|
|
81
|
+
...columnLabelPillStyle('csat'),
|
|
82
|
+
display: 'inline-flex',
|
|
83
|
+
flexDirection: 'column',
|
|
84
|
+
alignItems: 'center',
|
|
85
|
+
justifyContent: 'center',
|
|
86
|
+
whiteSpace: 'normal',
|
|
87
|
+
maxWidth: '72px',
|
|
88
|
+
minWidth: '52px',
|
|
89
|
+
padding: '3px 6px',
|
|
90
|
+
wordBreak: 'break-word',
|
|
91
|
+
lineHeight: 1.15,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
78
95
|
/** Rating matrix anchor pills — stacked text, fixed width, same look on every format. */
|
|
79
96
|
export function ratingAnchorLabelPillStyle(): CSSProperties {
|
|
80
97
|
return {
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* --cfm-input-focus-ring = focus ring (outer border + inner dot)
|
|
8
8
|
*/
|
|
9
9
|
import type { CSSProperties } from 'react';
|
|
10
|
+
import { resolveNumberLabelColor } from './surveyUiScaleUtils';
|
|
10
11
|
|
|
11
12
|
/** Terminal hex fallbacks match apps/client — visible before survey-theme.css loads. */
|
|
12
13
|
export const cellSelectedVar = 'var(--cfm-matrix-selected, var(--cfm-primary, #fbe8f3))';
|
|
@@ -173,10 +174,67 @@ export function scaleCellSelectedStyle(isSelected: boolean): CSSProperties {
|
|
|
173
174
|
: {};
|
|
174
175
|
}
|
|
175
176
|
|
|
177
|
+
/** NPS numbered badge row — React-owned; do not patch via preview bridge DOM hooks. */
|
|
178
|
+
export function npsNumberBadgeStyle(backgroundColor: string, isSelected: boolean): CSSProperties {
|
|
179
|
+
const base: CSSProperties = {
|
|
180
|
+
color: '#ffffff',
|
|
181
|
+
boxSizing: 'border-box',
|
|
182
|
+
};
|
|
183
|
+
if (isSelected) {
|
|
184
|
+
return {
|
|
185
|
+
...base,
|
|
186
|
+
border: `2px solid ${focusRingVar}`,
|
|
187
|
+
backgroundColor: cellSelectedVar,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
...base,
|
|
192
|
+
backgroundColor,
|
|
193
|
+
border: 'none',
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** CSAT / Rating matrix numbered cell (unselected uses traffic/mono/individual badge colors). */
|
|
198
|
+
export function matrixNumberedCellStyle(index: number, isSelected: boolean): CSSProperties {
|
|
199
|
+
const base: CSSProperties = {
|
|
200
|
+
width: '40px',
|
|
201
|
+
height: '40px',
|
|
202
|
+
borderRadius: '8px',
|
|
203
|
+
fontSize: '14px',
|
|
204
|
+
fontWeight: 500,
|
|
205
|
+
transition: 'all 0.15s',
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
if (isSelected) {
|
|
209
|
+
return {
|
|
210
|
+
...base,
|
|
211
|
+
border: `2px solid ${focusRingVar}`,
|
|
212
|
+
backgroundColor: cellSelectedVar,
|
|
213
|
+
color: 'var(--cfm-matrix-number-text-color, #ffffff)',
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
...base,
|
|
219
|
+
border: '1px solid #d1d5db',
|
|
220
|
+
backgroundColor: resolveNumberLabelColor(index),
|
|
221
|
+
color: 'var(--cfm-matrix-number-text-color, #ffffff)',
|
|
222
|
+
...unselectedOpacityStyle(),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
176
226
|
export function unselectedOpacityStyle(): CSSProperties {
|
|
177
227
|
return { opacity: 'var(--cfm-csat-unselected-opacity, 0.5)' };
|
|
178
228
|
}
|
|
179
229
|
|
|
230
|
+
export function matrixStatementRowsContainerStyle(): CSSProperties {
|
|
231
|
+
return {
|
|
232
|
+
display: 'flex',
|
|
233
|
+
flexDirection: 'column',
|
|
234
|
+
gap: 'var(--cfm-matrix-statement-row-gap, 0px)',
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
180
238
|
/** Matrix statement row — zebra when --cfm-matrix-zebra-enabled is 1 (wizard multi-statement preview). */
|
|
181
239
|
export function matrixRowBackgroundStyle(rowIdx: number): CSSProperties {
|
|
182
240
|
let zebraOn = false;
|
|
@@ -58,6 +58,11 @@ export const CsatStarIcons: { filled: React.ReactNode; empty: React.ReactNode }
|
|
|
58
58
|
style: { color: 'var(--cfm-csat-accent, var(--cfm-primary, #e20074))', fontSize: 'var(--cfm-csat-emoji-size, 28px)' },
|
|
59
59
|
}),
|
|
60
60
|
empty: React.createElement(FaRegStar as React.ElementType, {
|
|
61
|
-
style: {
|
|
61
|
+
style: {
|
|
62
|
+
color: 'var(--cfm-star-color, var(--cfm-csat-accent, var(--cfm-primary, #e20074)))',
|
|
63
|
+
fontSize: 'var(--cfm-csat-emoji-size, 28px)',
|
|
64
|
+
stroke: 'var(--cfm-star-color, var(--cfm-csat-accent, var(--cfm-primary, #e20074)))',
|
|
65
|
+
strokeWidth: '16px',
|
|
66
|
+
},
|
|
62
67
|
}),
|
|
63
68
|
};
|
|
@@ -2,10 +2,21 @@
|
|
|
2
2
|
var SNAPSHOT = 'CFM_UI_CONFIG_SNAPSHOT';
|
|
3
3
|
var DELTA = 'CFM_UI_CONFIG_DELTA';
|
|
4
4
|
var LEGACY = 'CFM_UI_CONFIG';
|
|
5
|
+
var PREVIEW_FONTS_HREF =
|
|
6
|
+
'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Roboto:wght@400;500;600;700&family=Open+Sans:wght@400;500;600;700&family=Lato:wght@400;500;600;700&family=Poppins:wght@400;500;600;700&family=Montserrat:wght@400;500;600;700&family=Nunito:wght@400;500;600;700&family=Raleway:wght@400;500;600;700&family=Source+Sans+3:wght@400;500;600;700&family=Merriweather:wght@400;500;600;700&family=Playfair+Display:wght@400;500;600;700&display=swap';
|
|
5
7
|
var rafId = null;
|
|
6
8
|
var pending = {};
|
|
7
9
|
var liveEdit = false;
|
|
8
10
|
|
|
11
|
+
function ensurePreviewFonts() {
|
|
12
|
+
if (document.getElementById('cfm-preview-fonts')) return;
|
|
13
|
+
var link = document.createElement('link');
|
|
14
|
+
link.id = 'cfm-preview-fonts';
|
|
15
|
+
link.rel = 'stylesheet';
|
|
16
|
+
link.href = PREVIEW_FONTS_HREF;
|
|
17
|
+
document.head.appendChild(link);
|
|
18
|
+
}
|
|
19
|
+
|
|
9
20
|
function flush() {
|
|
10
21
|
rafId = null;
|
|
11
22
|
var root = document.documentElement;
|
|
@@ -57,6 +68,7 @@ function setDisplay(selector, visible) {
|
|
|
57
68
|
var mode = readVar('--cfm-number-label-mode', 'traffic');
|
|
58
69
|
var mono = readVar('--cfm-number-mono-color', '#9ca3af');
|
|
59
70
|
document.querySelectorAll('[data-cfm-number-badge]').forEach(function (el) {
|
|
71
|
+
if (el.closest('[data-cfm-nps-area]')) return;
|
|
60
72
|
if (el.classList.contains('selected')) return;
|
|
61
73
|
var i = parseInt(el.getAttribute('data-cfm-number-badge'), 10);
|
|
62
74
|
if (isNaN(i)) return;
|
|
@@ -282,6 +294,7 @@ function setDisplay(selector, visible) {
|
|
|
282
294
|
|
|
283
295
|
if (!liveEdit) {
|
|
284
296
|
liveEdit = true;
|
|
297
|
+
ensurePreviewFonts();
|
|
285
298
|
document.documentElement.classList.add('cfm-live-edit');
|
|
286
299
|
}
|
|
287
300
|
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<link id="cfm-preview-fonts" rel="preconnect" href="https://fonts.googleapis.com" />
|
|
2
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
3
|
+
<link
|
|
4
|
+
rel="stylesheet"
|
|
5
|
+
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Roboto:wght@400;500;600;700&family=Open+Sans:wght@400;500;600;700&family=Lato:wght@400;500;600;700&family=Poppins:wght@400;500;600;700&family=Montserrat:wght@400;500;600;700&family=Nunito:wght@400;500;600;700&family=Raleway:wght@400;500;600;700&family=Source+Sans+3:wght@400;500;600;700&family=Merriweather:wght@400;500;600;700&family=Playfair+Display:wght@400;500;600;700&display=swap"
|
|
6
|
+
/>
|
|
@@ -122,14 +122,15 @@ const PREVIEW_PAGE_DEFS = [
|
|
|
122
122
|
{ key: 'RANK_dropdown', title: 'Rank Dropdown', body: `<div class="cfm-rank-item"><span>Item A</span><select class="cfm-input cfm-rank-select" style="width:80px;margin-left:auto"><option>1</option><option>2</option></select></div>` },
|
|
123
123
|
];
|
|
124
124
|
|
|
125
|
-
function buildPreviewHtml(def, inlineCss, inlineJs) {
|
|
125
|
+
function buildPreviewHtml(def, inlineCss, inlineJs, fontsSnippet) {
|
|
126
|
+
const fonts = fontsSnippet ? `${fontsSnippet}\n` : '';
|
|
126
127
|
return `<!DOCTYPE html>
|
|
127
128
|
<html lang="en">
|
|
128
129
|
<head>
|
|
129
130
|
<meta charset="UTF-8" />
|
|
130
131
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
131
132
|
<title>${def.title} — CFM Preview</title>
|
|
132
|
-
<style>${inlineCss}\n${sharedStyles}</style>
|
|
133
|
+
${fonts}<style>${inlineCss}\n${sharedStyles}</style>
|
|
133
134
|
</head>
|
|
134
135
|
<body>
|
|
135
136
|
<div class="cfm-preview-root">${def.body}</div>
|
|
@@ -17,26 +17,37 @@ export const PREVIEW_BRIDGE_SNAPSHOT = 'CFM_UI_CONFIG_SNAPSHOT';
|
|
|
17
17
|
export const PREVIEW_BRIDGE_DELTA = 'CFM_UI_CONFIG_DELTA';
|
|
18
18
|
export const PREVIEW_BRIDGE_READY = 'CFM_PREVIEW_READY';
|
|
19
19
|
|
|
20
|
-
const
|
|
21
|
-
'#
|
|
22
|
-
'#
|
|
23
|
-
];
|
|
20
|
+
const SCALE_COLORS = [
|
|
21
|
+
'#e2001a', '#e4251b', '#ec610a', '#f18b00', '#f7b200', '#fcd900',
|
|
22
|
+
'#d2dc00', '#9fca00', '#6bb300', '#339a00', '#008000',
|
|
23
|
+
] as const;
|
|
24
|
+
|
|
25
|
+
function trafficColor(index: number): string {
|
|
26
|
+
return SCALE_COLORS[index] ?? '#9ca3af';
|
|
27
|
+
}
|
|
24
28
|
|
|
25
29
|
function readVar(name: string, fallback: string): string {
|
|
26
30
|
return getComputedStyle(document.documentElement).getPropertyValue(name).trim() || fallback;
|
|
27
31
|
}
|
|
28
32
|
|
|
29
|
-
function
|
|
33
|
+
function resolveLiveNumberLabelColor(index: number): string {
|
|
30
34
|
const mode = readVar('--cfm-number-label-mode', 'traffic');
|
|
31
|
-
|
|
35
|
+
if (mode === 'monochrome') {
|
|
36
|
+
return readVar('--cfm-number-mono-color', '#9ca3af');
|
|
37
|
+
}
|
|
38
|
+
if (mode === 'individual') {
|
|
39
|
+
return readVar(`--cfm-number-color-${index}`, '#9ca3af');
|
|
40
|
+
}
|
|
41
|
+
return trafficColor(index);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function applyNumberBadgeColors(): void {
|
|
32
45
|
document.querySelectorAll('[data-cfm-number-badge]').forEach((el) => {
|
|
46
|
+
if (el.closest('[data-cfm-nps-area]')) return;
|
|
33
47
|
if (el.classList.contains('selected')) return;
|
|
34
48
|
const i = parseInt(el.getAttribute('data-cfm-number-badge') ?? '', 10);
|
|
35
49
|
if (Number.isNaN(i)) return;
|
|
36
|
-
|
|
37
|
-
if (mode === 'monochrome') bg = mono;
|
|
38
|
-
if (mode === 'individual') bg = readVar(`--cfm-number-color-${i}`, '#9ca3af');
|
|
39
|
-
(el as HTMLElement).style.backgroundColor = bg;
|
|
50
|
+
(el as HTMLElement).style.backgroundColor = resolveLiveNumberLabelColor(i);
|
|
40
51
|
});
|
|
41
52
|
}
|
|
42
53
|
|
|
@@ -82,11 +93,17 @@ const PreviewConfigContext = createContext<PreviewConfigContextValue>({
|
|
|
82
93
|
configRevision: 0,
|
|
83
94
|
});
|
|
84
95
|
|
|
96
|
+
function enableLiveEditMode(): void {
|
|
97
|
+
document.documentElement.classList.add('cfm-live-edit');
|
|
98
|
+
}
|
|
99
|
+
|
|
85
100
|
export function PreviewConfigProvider({ children }: { children: ReactNode }) {
|
|
86
101
|
const [previewState, setPreviewState] = useState<PreviewStatePatch>({});
|
|
87
102
|
const [configRevision, setConfigRevision] = useState(0);
|
|
88
103
|
|
|
89
104
|
useEffect(() => {
|
|
105
|
+
enableLiveEditMode();
|
|
106
|
+
|
|
90
107
|
const requestSnapshot = () => {
|
|
91
108
|
try {
|
|
92
109
|
window.parent.postMessage({ type: PREVIEW_BRIDGE_READY }, '*');
|
|
@@ -102,6 +119,8 @@ export function PreviewConfigProvider({ children }: { children: ReactNode }) {
|
|
|
102
119
|
if (!data?.type) return;
|
|
103
120
|
if (data.type !== PREVIEW_BRIDGE_SNAPSHOT && data.type !== PREVIEW_BRIDGE_DELTA) return;
|
|
104
121
|
|
|
122
|
+
enableLiveEditMode();
|
|
123
|
+
|
|
105
124
|
const isSnapshot = data.type === PREVIEW_BRIDGE_SNAPSHOT;
|
|
106
125
|
let shouldBumpRevision = isSnapshot;
|
|
107
126
|
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
body {
|
|
8
8
|
margin: 0;
|
|
9
|
-
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
|
10
|
-
color: #262626;
|
|
11
|
-
background-color: #ffffff;
|
|
9
|
+
font-family: var(--cfm-font-family, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif);
|
|
10
|
+
color: var(--cfm-text, #262626);
|
|
11
|
+
background-color: var(--cfm-background, #ffffff);
|
|
12
12
|
max-width: 100%;
|
|
13
13
|
overflow-x: hidden;
|
|
14
14
|
box-sizing: border-box;
|
|
@@ -19,4 +19,5 @@ body {
|
|
|
19
19
|
max-width: 100%;
|
|
20
20
|
overflow-x: hidden;
|
|
21
21
|
box-sizing: border-box;
|
|
22
|
+
font-family: inherit;
|
|
22
23
|
}
|
|
@@ -224,10 +224,32 @@ export function mergePreviewQuestion(
|
|
|
224
224
|
});
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
-
/** Survey page layout — each question
|
|
227
|
+
/** Survey page layout — each embedded question uses its type-tab customization. */
|
|
228
228
|
export function mergeSurveyPageQuestion(
|
|
229
229
|
question: SurveyQuestion,
|
|
230
230
|
previewState: PreviewStatePatch,
|
|
231
231
|
): SurveyQuestion {
|
|
232
|
-
|
|
232
|
+
const normalized = normalizeSurveyPagePreviewState(previewState);
|
|
233
|
+
let state = normalized;
|
|
234
|
+
|
|
235
|
+
if (question.type === 'NPS_SCALE') {
|
|
236
|
+
state = {
|
|
237
|
+
...normalized,
|
|
238
|
+
npsButtonStyle:
|
|
239
|
+
previewState.npsButtonStyle
|
|
240
|
+
?? normalized.npsButtonStyle
|
|
241
|
+
?? (question.buttonStyle as string | undefined),
|
|
242
|
+
hintMinText: previewState.hintMinText ?? normalized.hintMinText,
|
|
243
|
+
hintMaxText: previewState.hintMaxText ?? normalized.hintMaxText,
|
|
244
|
+
};
|
|
245
|
+
} else if (question.type === 'MCQ') {
|
|
246
|
+
state = {
|
|
247
|
+
...normalized,
|
|
248
|
+
mcqOptionStyle: previewState.mcqOptionStyle ?? normalized.mcqOptionStyle,
|
|
249
|
+
};
|
|
250
|
+
} else if (question.type === 'CFM_MATRIX') {
|
|
251
|
+
state = { ...normalized, multiStatement: true };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return mergePreviewQuestion(question, state);
|
|
233
255
|
}
|
|
@@ -122,6 +122,7 @@ html.cfm-live-edit #root {
|
|
|
122
122
|
max-width: 100%;
|
|
123
123
|
overflow-x: hidden;
|
|
124
124
|
box-sizing: border-box;
|
|
125
|
+
font-family: var(--cfm-font-family, system-ui, sans-serif) !important;
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
html.cfm-live-edit [data-cfm-question-area],
|
|
@@ -136,3 +137,7 @@ html.cfm-live-edit [data-cfm-nps-area] {
|
|
|
136
137
|
max-width: 100%;
|
|
137
138
|
overflow-x: auto;
|
|
138
139
|
}
|
|
140
|
+
|
|
141
|
+
html.cfm-live-edit [data-cfm-nps-area] [data-cfm-nps-badge] {
|
|
142
|
+
color: #ffffff !important;
|
|
143
|
+
}
|
|
@@ -33,6 +33,18 @@ let rafId: number | null = null;
|
|
|
33
33
|
const pendingVars: Record<string, string> = {};
|
|
34
34
|
let liveEditClass = false;
|
|
35
35
|
|
|
36
|
+
const PREVIEW_FONTS_HREF =
|
|
37
|
+
'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Roboto:wght@400;500;600;700&family=Open+Sans:wght@400;500;600;700&family=Lato:wght@400;500;600;700&family=Poppins:wght@400;500;600;700&family=Montserrat:wght@400;500;600;700&family=Nunito:wght@400;500;600;700&family=Raleway:wght@400;500;600;700&family=Source+Sans+3:wght@400;500;600;700&family=Merriweather:wght@400;500;600;700&family=Playfair+Display:wght@400;500;600;700&display=swap';
|
|
38
|
+
|
|
39
|
+
function ensurePreviewFonts(): void {
|
|
40
|
+
if (document.getElementById('cfm-preview-fonts')) return;
|
|
41
|
+
const link = document.createElement('link');
|
|
42
|
+
link.id = 'cfm-preview-fonts';
|
|
43
|
+
link.rel = 'stylesheet';
|
|
44
|
+
link.href = PREVIEW_FONTS_HREF;
|
|
45
|
+
document.head.appendChild(link);
|
|
46
|
+
}
|
|
47
|
+
|
|
36
48
|
function flushCssVars(): void {
|
|
37
49
|
rafId = null;
|
|
38
50
|
const root = document.documentElement;
|
|
@@ -285,6 +297,7 @@ function applyThemeSideEffects(): void {
|
|
|
285
297
|
const mode = readCssVar('--cfm-number-label-mode', 'traffic');
|
|
286
298
|
const mono = readCssVar('--cfm-number-mono-color', '#9ca3af');
|
|
287
299
|
document.querySelectorAll('[data-cfm-number-badge]').forEach((el) => {
|
|
300
|
+
if (el.closest('[data-cfm-nps-area]')) return;
|
|
288
301
|
if (el.classList.contains('selected')) return;
|
|
289
302
|
const i = parseInt(el.getAttribute('data-cfm-number-badge') ?? '', 10);
|
|
290
303
|
if (Number.isNaN(i)) return;
|
|
@@ -302,6 +315,7 @@ function applyThemeSideEffects(): void {
|
|
|
302
315
|
function setLiveEditMode(on: boolean): void {
|
|
303
316
|
if (liveEditClass === on) return;
|
|
304
317
|
liveEditClass = on;
|
|
318
|
+
if (on) ensurePreviewFonts();
|
|
305
319
|
document.documentElement.classList.toggle('cfm-live-edit', on);
|
|
306
320
|
}
|
|
307
321
|
|
|
@@ -98,10 +98,12 @@
|
|
|
98
98
|
--cfm-matrix-bipolar-width: 25%;
|
|
99
99
|
--cfm-matrix-carousel-dot: #2563eb;
|
|
100
100
|
--cfm-matrix-cell-padding: 8px;
|
|
101
|
+
--cfm-matrix-statement-row-gap: 8px;
|
|
101
102
|
|
|
102
103
|
--cfm-csat-accent: var(--cfm-accent);
|
|
103
104
|
--cfm-csat-emoji-size: 24px;
|
|
104
105
|
--cfm-csat-unselected-opacity: 0.5;
|
|
106
|
+
--cfm-matrix-number-text-color: #ffffff;
|
|
105
107
|
--cfm-csat-ring-color: var(--cfm-accent);
|
|
106
108
|
--cfm-csat-cell-size: 40px;
|
|
107
109
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{u as x,j as e}from"./index-
|
|
1
|
+
import{u as x,j as e}from"./index-COdmy8Xp.js";import{b as t}from"./vendor-BwkXDkd3.js";function p(d=150){const l=x(r=>r.config),[a,s]=t.useState(l);return t.useEffect(()=>{const r=window.setTimeout(()=>s(l),d);return()=>window.clearTimeout(r)},[l,d]),a}const h=t.memo(function(){const l=p(150),a=x(n=>n.logoPreviewDataUrl),{global:s}=l,{layout:r,colorScheme:o,buttons:c}=s,m=t.useMemo(()=>r.borderStyle==="sharp"?"rounded-none":r.borderStyle==="pill"?"rounded-[32px]":"rounded-2xl",[r.borderStyle]),i=t.useMemo(()=>r.fontStyle==="serif"?"Georgia, serif":r.fontStyle==="system"?"system-ui, sans-serif":"Inter, sans-serif",[r.fontStyle]),u=a??s.logo.url,f=t.useMemo(()=>({backgroundColor:o.background,color:o.text,borderColor:o.secondary,fontFamily:i}),[o,i]);return e.jsxs("div",{className:`overflow-hidden border shadow-md ${m}`,style:f,children:[r.showHeader&&e.jsxs("div",{className:"flex items-center gap-3 border-b px-5 py-4",style:{borderColor:o.secondary},children:[u&&e.jsx("img",{src:u,alt:"Logo",className:"max-h-6 w-auto object-contain",loading:"lazy"}),e.jsx("span",{className:"text-xs font-semibold tracking-wider",children:s.companyName})]}),e.jsxs("div",{className:"space-y-6 p-6",children:[r.showProgressBar&&e.jsxs("div",{className:"space-y-1.5",children:[e.jsxs("div",{className:"flex justify-between text-[10px] font-semibold text-slate-400",children:[e.jsx("span",{children:"Progress"}),e.jsx("span",{children:"35%"})]}),e.jsx("div",{className:"h-1.5 w-full overflow-hidden rounded-full bg-slate-100",children:e.jsx("div",{className:"h-full w-[35%] rounded-full",style:{backgroundColor:o.accent}})})]}),e.jsxs("div",{className:"space-y-3",children:[e.jsxs("h4",{className:"flex items-start gap-1.5 text-sm font-bold leading-snug",children:[r.showQuestionNumbers&&e.jsx("span",{className:"font-medium text-slate-400",children:"Q1."}),e.jsx("span",{children:s.surveyTitle}),r.showRequiredAsterisk&&e.jsx("span",{className:"text-red-500",children:"*"})]}),e.jsx("p",{className:"text-[11px] text-slate-400",children:"How would you rate your overall experience?"}),e.jsx("div",{className:"grid grid-cols-5 gap-2",children:[1,2,3,4,5].map(n=>e.jsx("div",{className:`flex h-9 items-center justify-center border text-xs font-semibold ${r.borderStyle==="pill"?"rounded-full":r.borderStyle==="sharp"?"rounded-none":"rounded-lg"}`,style:n===1?{backgroundColor:o.secondary,borderColor:o.primary,color:o.primary}:{backgroundColor:o.background,borderColor:o.secondary,color:o.text},children:n},n))})]})]}),e.jsxs("div",{className:"flex items-center justify-between border-t bg-slate-50/50 px-6 py-4",children:[r.showBackButton&&e.jsx("span",{className:`border px-3 py-1.5 text-[10px] font-semibold ${r.borderStyle==="pill"?"rounded-full":r.borderStyle==="sharp"?"rounded-none":"rounded-lg"}`,style:{borderColor:o.secondary,color:o.text},children:c.back}),e.jsx("div",{className:"flex-1"}),r.showNextButton&&e.jsx("span",{className:`px-4 py-1.5 text-[10px] font-bold ${r.borderStyle==="pill"?"rounded-full":r.borderStyle==="sharp"?"rounded-none":"rounded-lg"}`,style:{backgroundColor:o.primary,color:o.buttonText},children:c.next})]})]})});export{h as PreviewMock};
|