@explorer02/cfm-survey-sdk 0.3.1 → 0.3.2
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 +94 -83
- package/dist/cli/index.mjs +94 -83
- package/package.json +1 -1
- package/templates/docs/templates/CsatMatrixScale.tsx +18 -24
- package/templates/docs/templates/CustomSliderTrack.tsx +5 -3
- package/templates/docs/templates/FileUploadScale.tsx +143 -18
- package/templates/docs/templates/HeatmapScale.tsx +11 -2
- package/templates/docs/templates/LanguageSelector.tsx +42 -19
- package/templates/docs/templates/LikertMatrixScale.tsx +43 -33
- package/templates/docs/templates/MatrixDropdown.tsx +12 -11
- package/templates/docs/templates/Question.tsx +109 -44
- package/templates/docs/templates/RankOrderScale.tsx +22 -5
- package/templates/docs/templates/RatingScale.tsx +5 -3
- package/templates/docs/templates/SliderMatrixScale.tsx +44 -16
- package/templates/docs/templates/selectionStyles.ts +88 -1
- package/templates/docs/templates/surveyUiIcons.tsx +2 -2
- package/templates/docs/templates/surveyUiScaleUtils.ts +38 -5
- package/templates/docs/templates/uiConfig.ts +56 -0
- package/templates/preview-harness/previewPages.js +4 -7
- package/templates/preview-harness/previewPages.ts +7 -34
- package/templates/preview-harness/vite-app/src/PreviewConfigContext.tsx +7 -0
- package/templates/preview-harness/vite-app/src/QuestionPreview.tsx +59 -30
- package/templates/preview-harness/vite-app/src/SurveyPagePreview.tsx +36 -8
- package/templates/preview-harness/vite-app/src/fixtures/questions.ts +182 -82
- package/templates/survey-theme.css +13 -4
- package/templates/wizard-dist/assets/{PreviewMock-Bax7oRAL.js → PreviewMock-DbbLpHdF.js} +1 -1
- package/templates/wizard-dist/assets/TypePanel-DQbV2iCf.js +1 -0
- package/templates/wizard-dist/assets/index-CY7WMJ93.js +34 -0
- package/templates/wizard-dist/index.html +1 -1
- package/templates/wizard-dist/assets/TypePanel-D2t4FPSd.js +0 -1
- package/templates/wizard-dist/assets/index-c5lka74l.js +0 -34
|
@@ -13,23 +13,56 @@ export function getAccentColor(value: number): string {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export function tickColorFromIndex(index: number, total: number): string {
|
|
16
|
+
if (typeof document !== 'undefined') {
|
|
17
|
+
const resolved = resolveNumberLabelColor(index);
|
|
18
|
+
if (resolved) return resolved;
|
|
19
|
+
}
|
|
16
20
|
if (total <= 1) return '#e20074';
|
|
17
21
|
const hue = Math.round((index / (total - 1)) * 120);
|
|
18
22
|
return `hsl(${hue}, 90%, 40%)`;
|
|
19
23
|
}
|
|
20
24
|
|
|
25
|
+
/** Reads wizard number-label CSS vars for traffic / monochrome / individual modes. */
|
|
26
|
+
export function resolveNumberLabelColor(index: number, fallback = ''): string {
|
|
27
|
+
if (typeof document === 'undefined') return fallback || getAccentColor(index);
|
|
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() ||
|
|
34
|
+
fallback ||
|
|
35
|
+
'#9CA3AF'
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
if (mode === 'individual') {
|
|
39
|
+
return (
|
|
40
|
+
getComputedStyle(root).getPropertyValue(`--cfm-number-color-${index}`).trim() ||
|
|
41
|
+
fallback ||
|
|
42
|
+
'#9CA3AF'
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
return fallback || getAccentColor(index);
|
|
46
|
+
}
|
|
47
|
+
|
|
21
48
|
export function nonNaScaleColumns(columns: ScaleColumn[]): ScaleColumn[] {
|
|
22
49
|
return columns.filter(col => col.id !== 'na');
|
|
23
50
|
}
|
|
24
51
|
|
|
52
|
+
function stripHtml(label: string): string {
|
|
53
|
+
return label.replace(/<[^>]*>/g, '').trim();
|
|
54
|
+
}
|
|
55
|
+
|
|
25
56
|
export function starValueFromOption(option: NpsScalePoint, index: number): number {
|
|
26
|
-
const
|
|
27
|
-
|
|
57
|
+
const label = stripHtml(option.optionLabel);
|
|
58
|
+
const numeric = Number(label);
|
|
59
|
+
return Number.isFinite(numeric) && label !== '' ? numeric : index;
|
|
28
60
|
}
|
|
29
61
|
|
|
30
62
|
export function npsValueFromOption(option: NpsScalePoint, index: number): number {
|
|
31
|
-
const
|
|
32
|
-
|
|
63
|
+
const label = stripHtml(option.optionLabel);
|
|
64
|
+
const numeric = Number(label);
|
|
65
|
+
return Number.isFinite(numeric) && label !== '' ? numeric : index;
|
|
33
66
|
}
|
|
34
67
|
|
|
35
68
|
/** Loose match for NPS/star stored answers (number) vs derived option values. */
|
|
@@ -40,7 +73,7 @@ export function isScaleAnswerSelected(
|
|
|
40
73
|
if (selectedValue === undefined || selectedValue === null) return false;
|
|
41
74
|
if (typeof selectedValue === 'number') return selectedValue === optionValue;
|
|
42
75
|
if (typeof selectedValue === 'string') {
|
|
43
|
-
const parsed = Number(selectedValue);
|
|
76
|
+
const parsed = Number(stripHtml(selectedValue));
|
|
44
77
|
return Number.isFinite(parsed) && parsed === optionValue;
|
|
45
78
|
}
|
|
46
79
|
return false;
|
|
@@ -31,3 +31,59 @@ export function getLogoSrc(options?: { staticExport?: boolean }): string | null
|
|
|
31
31
|
export function shouldShowCompanyName(): boolean {
|
|
32
32
|
return surveyUiConfig.global?.header?.showCompanyName !== false;
|
|
33
33
|
}
|
|
34
|
+
|
|
35
|
+
export function getSurveyTitle(): string {
|
|
36
|
+
return surveyUiConfig.global?.surveyTitle?.trim() ?? '';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function getThankYouMessage(): string {
|
|
40
|
+
return surveyUiConfig.global?.thankYouMessage?.trim() ?? '';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function getBrowserTabTitle(): string {
|
|
44
|
+
return surveyUiConfig.global?.browserTabTitle?.trim() ?? getSurveyTitle();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function getButtonLabels(): { next: string; back: string; submit: string } {
|
|
48
|
+
const buttons = surveyUiConfig.global?.buttons;
|
|
49
|
+
return {
|
|
50
|
+
next: buttons?.next?.trim() || 'Next',
|
|
51
|
+
back: buttons?.back?.trim() || 'Back',
|
|
52
|
+
submit: buttons?.submit?.trim() || 'Submit',
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function getFooterCopyright(): string {
|
|
57
|
+
return surveyUiConfig.global?.footer?.copyrightText?.trim() ?? '';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function getFooterLinks(): { label: string; url: string }[] {
|
|
61
|
+
const links = surveyUiConfig.global?.footer?.links;
|
|
62
|
+
return Array.isArray(links) ? links : [];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function getLayoutFlags() {
|
|
66
|
+
const layout = surveyUiConfig.global?.layout ?? {};
|
|
67
|
+
const header = surveyUiConfig.global?.header ?? {};
|
|
68
|
+
const footer = surveyUiConfig.global?.footer ?? {};
|
|
69
|
+
return {
|
|
70
|
+
showHeader: layout.showHeader !== false,
|
|
71
|
+
showFooter: layout.showFooter !== false,
|
|
72
|
+
showProgressBar: layout.showProgressBar !== false,
|
|
73
|
+
showLanguageSwitcher: layout.showLanguageSwitcher === true,
|
|
74
|
+
showNextButton: layout.showNextButton !== false,
|
|
75
|
+
showBackButton: layout.showBackButton !== false,
|
|
76
|
+
showQuestionNumbers: layout.showQuestionNumbers !== false,
|
|
77
|
+
showRequiredAsterisk: layout.showRequiredAsterisk !== false,
|
|
78
|
+
showCompanyName: header.showCompanyName !== false,
|
|
79
|
+
showFooterLogo: footer.showLogo !== false,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function getPlaceholders(): Record<string, string> {
|
|
84
|
+
return { ...(surveyUiConfig.placeholders ?? {}) };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function isDebugEnabled(): boolean {
|
|
88
|
+
return surveyUiConfig.debug?.enabled === true;
|
|
89
|
+
}
|
|
@@ -11,7 +11,7 @@ const sharedStyles = `
|
|
|
11
11
|
.cfm-progress { height:var(--cfm-progress-height); background:var(--cfm-progress-track); border-radius:999px; overflow:hidden; margin:var(--cfm-sticky-pt) 0 var(--cfm-sticky-pb); }
|
|
12
12
|
.cfm-progress-fill { height:100%; width:45%; background:var(--cfm-progress-fill); border-radius:999px; }
|
|
13
13
|
.cfm-mcq-option { border:2px solid #e5e7eb; border-radius:var(--cfm-mcq-border-radius); padding:var(--cfm-mcq-card-padding); margin-bottom:var(--cfm-mcq-option-gap); display:flex; align-items:center; gap:8px; }
|
|
14
|
-
.cfm-mcq-option.selected { border-color:var(--cfm-
|
|
14
|
+
.cfm-mcq-option.selected { border-color:var(--cfm-input-focus-ring); background:var(--cfm-mcq-selected-bg); }
|
|
15
15
|
.cfm-hint-row { display:flex; justify-content:space-between; font-size:12px; font-weight:600; margin-bottom:8px; }
|
|
16
16
|
.cfm-hint-min, .cfm-hint-max { color:var(--cfm-hint-label-color,#6b7280); background:var(--cfm-hint-label-bg,transparent); padding:2px 4px; border-radius:4px; }
|
|
17
17
|
.cfm-nps-badges { display:grid; grid-template-columns:repeat(11,minmax(0,1fr)); gap:var(--cfm-nps-cell-gap,2px); margin-bottom:8px; }
|
|
@@ -91,11 +91,8 @@ const npsCells = Array.from({ length: 11 }, (_, i) =>
|
|
|
91
91
|
const PREVIEW_PAGE_DEFS = [
|
|
92
92
|
{ key: 'header', title: 'Header', body: `<header class="cfm-header"><img data-cfm-logo class="cfm-header-logo" src="" alt="Logo" style="display:none" /><span data-cfm-company class="cfm-header-company">Company Name</span></header>` },
|
|
93
93
|
{ key: 'footer', title: 'Footer', body: `<footer class="cfm-footer"><div><img data-cfm-logo style="width:var(--cfm-footer-logo-width);height:var(--cfm-footer-logo-height);object-fit:contain;display:none" src="" alt="" /><p data-cfm-copyright style="font-size:12px;margin-top:8px">© Company</p></div><nav data-cfm-footer-links class="cfm-footer-links"></nav></footer>` },
|
|
94
|
-
{ key: 'chrome', title: 'Chrome', body: `<header class="cfm-header"><img data-cfm-logo class="cfm-header-logo" src="" alt="" style="display:none" /><span data-cfm-company class="cfm-header-company">Company</span></header><div class="cfm-progress"><div class="cfm-progress-fill"></div></div><p data-cfm-survey-title style="font-size:20px;font-weight:var(--cfm-heading-weight);margin:16px 0">Survey Title</p><div style="display:flex;gap:12px;margin-top:24px"><button data-cfm-btn-back class="cfm-btn-back">Back</button><button data-cfm-btn-next class="cfm-btn-primary">Next</button></div>` },
|
|
95
94
|
{ key: 'survey_page', title: 'Survey Page', body: `<header class="cfm-header"><img data-cfm-logo class="cfm-header-logo" src="" alt="" style="display:none" /><span data-cfm-company class="cfm-header-company">Company</span></header><div data-cfm-progress class="cfm-progress"><div class="cfm-progress-fill" style="width:35%"></div></div><div data-cfm-language style="text-align:right;margin:16px 0"><select class="cfm-input" style="width:auto"><option>English</option><option>Deutsch</option></select></div><p style="font-weight:600"><span data-cfm-question-number>1.</span> Sample question <span data-cfm-required style="color:red">*</span></p><div class="cfm-mcq-option selected"><span>◉</span> Option A</div><p style="font-weight:600;margin-top:24px"><span data-cfm-question-number>2.</span> NPS question</p><div class="cfm-nps-track"><div class="cfm-nps-row">${npsCells}</div></div><div style="display:flex;gap:12px;margin-top:24px"><button data-cfm-btn-back class="cfm-btn-back">Back</button><button data-cfm-btn-next class="cfm-btn-primary">Next</button></div><footer class="cfm-footer" style="margin-top:32px"><p data-cfm-copyright>© Company</p><nav data-cfm-footer-links class="cfm-footer-links"></nav></footer>` },
|
|
96
|
-
{ key: '
|
|
97
|
-
{ key: 'end', title: 'End', body: `<h1 style="font-size:28px;font-weight:var(--cfm-heading-weight)">Thank you!</h1><p data-cfm-thank-you style="margin:16px 0">Thank you for your feedback.</p>` },
|
|
98
|
-
{ key: 'MCQ_single', title: 'MCQ Single', body: `<p style="font-weight:600;margin-bottom:16px">1. Select one option</p><div data-cfm-mcq-style="outlined"><div class="cfm-mcq-option selected"><span>◉</span> Option A</div><div class="cfm-mcq-option"><span>○</span> Option B</div><div class="cfm-mcq-option"><span>○</span> Option C</div></div>` },
|
|
95
|
+
{ key: 'MCQ_single', title: 'MCQ Single', body: `<p style="font-weight:600;margin-bottom:16px"><span data-cfm-question-number>1.</span> Select one option</p><div data-cfm-mcq-style="outlined"><div class="cfm-mcq-option selected"><span style="display:inline-flex;width:20px;height:20px;border-radius:50%;border:2px solid var(--cfm-input-focus-ring);align-items:center;justify-content:center"><span style="width:10px;height:10px;border-radius:50%;background:var(--cfm-input-focus-ring)"></span></span> Option A</div><div class="cfm-mcq-option"><span style="display:inline-flex;width:20px;height:20px;border-radius:50%;border:1px solid #9ca3af"></span> Option B</div><div class="cfm-mcq-option"><span style="display:inline-flex;width:20px;height:20px;border-radius:50%;border:1px solid #9ca3af"></span> Option C</div></div>` },
|
|
99
96
|
{ key: 'MCQ_multiple', title: 'MCQ Multiple', body: `<p style="font-weight:600;margin-bottom:16px">Select all that apply</p><div class="cfm-mcq-option selected"><span>☑</span> Option A</div><div class="cfm-mcq-option"><span>☐</span> Option B</div>` },
|
|
100
97
|
{ key: 'TEXTFIELD_short', title: 'Text Short', body: `<p style="font-weight:600;margin-bottom:8px">Your name</p><input class="cfm-input" data-cfm-textfield placeholder="Type here..." />` },
|
|
101
98
|
{ key: 'TEXTFIELD_long', title: 'Text Long', body: `<p style="font-weight:600;margin-bottom:8px">Comments</p><textarea class="cfm-input" data-cfm-textfield style="height:120px;padding:12px" placeholder="Type your response..."></textarea>` },
|
|
@@ -111,13 +108,13 @@ const PREVIEW_PAGE_DEFS = [
|
|
|
111
108
|
{ key: 'CSAT_graphics', title: 'CSAT Graphics', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label">Slider row</div><div style="flex:1;padding:0 16px"><div class="cfm-slider-track"><div class="cfm-slider-thumb"></div></div></div></div>` },
|
|
112
109
|
{ key: 'CSAT_carousel', title: 'CSAT Carousel', body: `${matrixPreview('Carousel row', 'emoji')}<div style="text-align:center"><span style="color:var(--cfm-matrix-selected)">●</span> ○</div>` },
|
|
113
110
|
{ key: 'CSAT_dropdown', title: 'CSAT Dropdown', body: `<div class="cfm-matrix-label">Statement</div><select class="cfm-input" style="margin-top:8px"><option>Choose rating...</option></select>` },
|
|
114
|
-
{ key: 'RATING_star', title: 'Rating Star', body: `<div class="cfm-matrix-
|
|
111
|
+
{ key: 'RATING_star', title: 'Rating Star', body: `<div class="cfm-matrix-row" style="background:transparent;margin-bottom:4px"><div class="cfm-matrix-label"></div><div class="cfm-matrix-cells">${['Strongly Disagree','Disagree','Neutral','Agree','Strongly Agree'].map(l=>`<span class="cfm-matrix-col-label">${l}</span>`).join('')}</div></div><div class="cfm-matrix-row"><div class="cfm-matrix-label">Statement Item 1</div><div class="cfm-matrix-cells"><span class="cfm-star">★</span><span class="cfm-star">★</span><span class="cfm-star">★</span><span class="cfm-star">★</span><span class="cfm-star">★</span></div></div><div class="cfm-matrix-row" data-cfm-matrix-row-extra style="display:none"><div class="cfm-matrix-label">Statement Item 2</div><div class="cfm-matrix-cells"><span class="cfm-star">★</span><span class="cfm-star">★</span><span class="cfm-star">★</span><span class="cfm-star">★</span><span class="cfm-star">★</span></div></div>` },
|
|
115
112
|
{ key: 'RATING_numbered', title: 'Rating Numbered', body: `<div class="cfm-matrix-cells" style="padding:16px">${[1,2,3,4,5].map((n,i)=>`<div class="cfm-badge${i===1?' selected':''}" data-cfm-number-badge="${i}">${n}</div>`).join('')}</div>` },
|
|
116
113
|
{ key: 'RATING_radio', title: 'Rating Radio', body: matrixPreview('Recommend to a friend', 'radio') },
|
|
117
114
|
{ key: 'RATING_emoji', title: 'Rating Emoji', body: `<div class="cfm-matrix-cells" style="padding:16px"><span class="cfm-emoji">🙂</span><span class="cfm-emoji selected">😊</span><span class="cfm-emoji">🤩</span></div>` },
|
|
118
115
|
{ key: 'RATING_graphics', title: 'Rating Graphics', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label">Experience</div><div style="flex:1;padding:0 16px"><div class="cfm-slider-track"><div class="cfm-slider-thumb"></div></div></div></div>` },
|
|
119
116
|
{ key: 'RATING_slider', title: 'Rating Slider', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label">Rate on slider</div><div style="flex:1;padding:0 16px"><div class="cfm-slider-track"><div class="cfm-slider-thumb"></div></div></div></div>` },
|
|
120
|
-
{ key: 'SLIDER_matrix', title: 'Slider Matrix', body: `<div style="display:flex;gap:4px;margin-bottom:16px;padding-left:var(--cfm-matrix-row-width,180px)">${
|
|
117
|
+
{ key: 'SLIDER_matrix', title: 'Slider Matrix', body: `<div style="display:flex;gap:4px;margin-bottom:16px;padding-left:var(--cfm-matrix-row-width,180px)">${Array.from({length:11},(_,i)=>`<div class="cfm-tick" data-cfm-number-badge="${i}" style="background:${TRAFFIC[i]??'#6b7280'}">${i}</div>`).join('')}</div><div style="display:flex;gap:8px;margin-bottom:8px;padding-left:var(--cfm-matrix-row-width,180px)"><span class="cfm-slider-tick-label">Label 1</span><span class="cfm-slider-tick-label" style="margin-left:auto">Label 2</span></div><div class="cfm-matrix-row" style="background:var(--cfm-slider-row-band,#f3f4f6);border-radius:8px;padding:12px 0"><div class="cfm-matrix-label">Statement Item 1</div><div style="flex:1;padding:0 16px"><div class="cfm-slider-track"><div class="cfm-slider-thumb" style="left:50%"></div></div></div></div><div class="cfm-matrix-row" data-cfm-matrix-row-extra style="display:none;background:var(--cfm-slider-row-band,#f3f4f6);border-radius:8px;padding:12px 0"><div class="cfm-matrix-label">Statement Item 2</div><div style="flex:1;padding:0 16px"><div class="cfm-slider-track"><div class="cfm-slider-thumb" style="left:0%"></div></div></div></div>` },
|
|
121
118
|
{ key: 'FILE_UPLOAD', title: 'File Upload', body: `<div class="cfm-dropzone" data-cfm-dropzone>Drop files here or click to upload</div>` },
|
|
122
119
|
{ key: 'TEXT_AND_MEDIA', title: 'Text & Media', body: `<div class="cfm-media">Media preview</div><p style="font-size:var(--cfm-media-caption-size,12px);color:var(--cfm-media-caption-color,#6b7280);margin-top:8px">Caption text</p>` },
|
|
123
120
|
{ key: 'HEATMAP', title: 'Heatmap', body: `<div style="position:relative;background:#e5e7eb;height:240px;border-radius:var(--cfm-border-radius)"><div class="cfm-pin" style="top:40%;left:55%"></div><div class="cfm-pin" style="top:60%;left:30%"></div></div>` },
|
|
@@ -11,17 +11,17 @@ const sharedStyles = `
|
|
|
11
11
|
.cfm-progress { height:var(--cfm-progress-height); background:var(--cfm-progress-track); border-radius:999px; overflow:hidden; margin:var(--cfm-sticky-pt) 0 var(--cfm-sticky-pb); }
|
|
12
12
|
.cfm-progress-fill { height:100%; width:45%; background:var(--cfm-progress-fill); border-radius:999px; }
|
|
13
13
|
.cfm-mcq-option { border:2px solid #e5e7eb; border-radius:var(--cfm-mcq-border-radius); padding:var(--cfm-mcq-card-padding); margin-bottom:var(--cfm-mcq-option-gap); display:flex; align-items:center; gap:8px; }
|
|
14
|
-
.cfm-mcq-option.selected { border-color:var(--cfm-
|
|
14
|
+
.cfm-mcq-option.selected { border-color:var(--cfm-input-focus-ring); background:var(--cfm-mcq-selected-bg); }
|
|
15
15
|
.cfm-nps-row { display:flex; gap:var(--cfm-nps-cell-gap); flex-wrap:wrap; }
|
|
16
|
-
.cfm-nps-cell { width:var(--cfm-nps-cell-size); height:var(--cfm-nps-cell-size); border-radius:var(--cfm-border-radius); background:var(--cfm-nps-track-bar); display:flex; align-items:center; justify-content:center; font-size:14px; }
|
|
17
|
-
.cfm-nps-cell.selected {
|
|
16
|
+
.cfm-nps-cell { width:var(--cfm-nps-cell-size); height:var(--cfm-nps-cell-size); border-radius:var(--cfm-border-radius); background:var(--cfm-nps-track-bar); display:flex; align-items:center; justify-content:center; font-size:14px; border:2px solid transparent; box-sizing:border-box; }
|
|
17
|
+
.cfm-nps-cell.selected { border-color:var(--cfm-input-focus-ring); background:var(--cfm-matrix-selected); }
|
|
18
18
|
.cfm-matrix-row { display:flex; align-items:center; padding:8px 0; background:var(--cfm-row-band); margin-bottom:4px; }
|
|
19
19
|
.cfm-matrix-label { width:var(--cfm-matrix-row-width); flex-shrink:0; padding:0 16px; font-size:14px; font-weight:500; }
|
|
20
20
|
.cfm-matrix-cells { display:flex; gap:8px; flex:1; justify-content:center; }
|
|
21
|
-
.cfm-matrix-cell { width:var(--cfm-csat-cell-size); height:var(--cfm-csat-cell-size); border-radius:50%; border:2px solid #d1d5db; }
|
|
22
|
-
.cfm-matrix-cell.selected { border-color:var(--cfm-
|
|
21
|
+
.cfm-matrix-cell { width:var(--cfm-csat-cell-size); height:var(--cfm-csat-cell-size); border-radius:50%; border:2px solid #d1d5db; box-sizing:border-box; }
|
|
22
|
+
.cfm-matrix-cell.selected { border-color:var(--cfm-input-focus-ring); background:var(--cfm-matrix-selected); }
|
|
23
23
|
.cfm-emoji { font-size:var(--cfm-csat-emoji-size); opacity:var(--cfm-csat-unselected-opacity); }
|
|
24
|
-
.cfm-emoji.selected { opacity:1; outline:2px solid var(--cfm-
|
|
24
|
+
.cfm-emoji.selected { opacity:1; outline:2px solid var(--cfm-input-focus-ring); background:var(--cfm-matrix-selected); border-radius:50%; }
|
|
25
25
|
.cfm-star { color:var(--cfm-star-color); font-size:24px; opacity:var(--cfm-star-unselected-opacity); }
|
|
26
26
|
.cfm-star.selected { opacity:1; }
|
|
27
27
|
.cfm-badge { width:40px; height:40px; border-radius:8px; background:var(--cfm-badge-color); color:white; display:flex; align-items:center; justify-content:center; font-weight:600; }
|
|
@@ -75,20 +75,6 @@ export const PREVIEW_PAGE_DEFS: PreviewPageDef[] = [
|
|
|
75
75
|
<nav data-cfm-footer-links class="cfm-footer-links"></nav>
|
|
76
76
|
</footer>`,
|
|
77
77
|
},
|
|
78
|
-
{
|
|
79
|
-
key: 'chrome',
|
|
80
|
-
title: 'Chrome',
|
|
81
|
-
body: `<header class="cfm-header">
|
|
82
|
-
<img data-cfm-logo class="cfm-header-logo" src="" alt="" style="display:none" />
|
|
83
|
-
<span data-cfm-company class="cfm-header-company">Company</span>
|
|
84
|
-
</header>
|
|
85
|
-
<div class="cfm-progress"><div class="cfm-progress-fill"></div></div>
|
|
86
|
-
<p data-cfm-survey-title style="font-size:20px;font-weight:var(--cfm-heading-weight);margin:16px 0">Survey Title</p>
|
|
87
|
-
<div style="display:flex;gap:12px;margin-top:24px">
|
|
88
|
-
<button data-cfm-btn-back class="cfm-btn-back">Back</button>
|
|
89
|
-
<button data-cfm-btn-next class="cfm-btn-primary">Next</button>
|
|
90
|
-
</div>`,
|
|
91
|
-
},
|
|
92
78
|
{
|
|
93
79
|
key: 'survey_page',
|
|
94
80
|
title: 'Survey Page',
|
|
@@ -101,7 +87,7 @@ export const PREVIEW_PAGE_DEFS: PreviewPageDef[] = [
|
|
|
101
87
|
<p style="font-weight:600"><span data-cfm-question-number>1.</span> Sample question <span data-cfm-required style="color:red">*</span></p>
|
|
102
88
|
<div class="cfm-mcq-option selected"><span>◉</span> Option A</div>
|
|
103
89
|
<p style="font-weight:600;margin-top:24px"><span data-cfm-question-number>2.</span> NPS question</p>
|
|
104
|
-
<div class="cfm-nps-row">${Array.from({ length: 11 }, (_, i) => `<div class="cfm-nps-cell">${i}</div>`).join('')}</div>
|
|
90
|
+
<div class="cfm-nps-row">${Array.from({ length: 11 }, (_, i) => `<div class="cfm-nps-cell${i === 8 ? ' selected' : ''}">${i}</div>`).join('')}</div>
|
|
105
91
|
<div style="display:flex;gap:12px;margin-top:24px">
|
|
106
92
|
<button data-cfm-btn-back class="cfm-btn-back">Back</button>
|
|
107
93
|
<button data-cfm-btn-next class="cfm-btn-primary">Next</button>
|
|
@@ -111,19 +97,6 @@ export const PREVIEW_PAGE_DEFS: PreviewPageDef[] = [
|
|
|
111
97
|
<nav data-cfm-footer-links class="cfm-footer-links"></nav>
|
|
112
98
|
</footer>`,
|
|
113
99
|
},
|
|
114
|
-
{
|
|
115
|
-
key: 'intro',
|
|
116
|
-
title: 'Intro',
|
|
117
|
-
body: `<h1 data-cfm-survey-title style="font-size:28px;font-weight:var(--cfm-heading-weight)">Welcome</h1>
|
|
118
|
-
<p style="margin:16px 0;color:#6b7280">Please take a few minutes to share your feedback.</p>
|
|
119
|
-
<button class="cfm-btn-primary">Start Survey</button>`,
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
key: 'end',
|
|
123
|
-
title: 'End',
|
|
124
|
-
body: `<h1 style="font-size:28px;font-weight:var(--cfm-heading-weight)">Thank you!</h1>
|
|
125
|
-
<p data-cfm-thank-you style="margin:16px 0">Thank you for your feedback.</p>`,
|
|
126
|
-
},
|
|
127
100
|
{
|
|
128
101
|
key: 'MCQ_single',
|
|
129
102
|
title: 'MCQ Single',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createContext, useContext, useEffect, useState, type ReactNode } from 'react';
|
|
2
2
|
|
|
3
3
|
export type PreviewStatePatch = {
|
|
4
|
+
activePreviewKey?: string;
|
|
4
5
|
multiStatement?: boolean;
|
|
5
6
|
textfieldPlaceholder?: string;
|
|
6
7
|
npsButtonStyle?: string;
|
|
@@ -40,6 +41,12 @@ export function PreviewConfigProvider({ children }: { children: ReactNode }) {
|
|
|
40
41
|
root.setAttribute(key, value as string);
|
|
41
42
|
}
|
|
42
43
|
}
|
|
44
|
+
if (data.cssVars) {
|
|
45
|
+
const root = document.documentElement;
|
|
46
|
+
for (const [key, value] of Object.entries(data.cssVars)) {
|
|
47
|
+
root.style.setProperty(key, value as string);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
43
50
|
};
|
|
44
51
|
|
|
45
52
|
window.addEventListener('message', handler);
|
|
@@ -27,12 +27,33 @@ function resolveNumberLabelColor(index: number): string {
|
|
|
27
27
|
return '';
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
function applyMatrixRowCount<T extends SurveyQuestion & { statementRows?: { id: string; statementText: string }[] }>(
|
|
31
|
+
q: T,
|
|
32
|
+
multiStatement: boolean | undefined,
|
|
33
|
+
): T {
|
|
34
|
+
if (!('statementRows' in q) || !Array.isArray(q.statementRows)) return q;
|
|
35
|
+
const rows = q.statementRows;
|
|
36
|
+
if (multiStatement) {
|
|
37
|
+
if (rows.length >= 2) return q;
|
|
38
|
+
return {
|
|
39
|
+
...q,
|
|
40
|
+
statementRows: [
|
|
41
|
+
...rows,
|
|
42
|
+
{ id: 'r2', statementText: 'Statement Item 2' },
|
|
43
|
+
...(rows.length === 1 ? [{ id: 'r3', statementText: 'Statement Item 3' }] : []),
|
|
44
|
+
],
|
|
45
|
+
} as T;
|
|
46
|
+
}
|
|
47
|
+
return { ...q, statementRows: rows.slice(0, 1) } as T;
|
|
48
|
+
}
|
|
49
|
+
|
|
30
50
|
function QuestionPreviewInner({ question, initialValue }: QuestionPreviewProps) {
|
|
31
51
|
const { previewState } = usePreviewConfig();
|
|
32
52
|
const [value, setValue] = useState<AnswerValue | undefined>(initialValue);
|
|
33
53
|
|
|
34
54
|
const mergedQuestion = useMemo(() => {
|
|
35
55
|
let q = { ...question } as SurveyQuestion;
|
|
56
|
+
const previewKey = previewState.activePreviewKey ?? '';
|
|
36
57
|
|
|
37
58
|
if (previewState.textfieldPlaceholder && q.type === 'TEXTFIELD') {
|
|
38
59
|
q = { ...q, placeholder: previewState.textfieldPlaceholder };
|
|
@@ -40,31 +61,15 @@ function QuestionPreviewInner({ question, initialValue }: QuestionPreviewProps)
|
|
|
40
61
|
|
|
41
62
|
if (previewState.npsButtonStyle && q.type === 'NPS_SCALE') {
|
|
42
63
|
const style = previewState.npsButtonStyle;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
q = {
|
|
53
|
-
...q,
|
|
54
|
-
statementRows:
|
|
55
|
-
rows.length >= 2
|
|
56
|
-
? rows
|
|
57
|
-
: [
|
|
58
|
-
...rows,
|
|
59
|
-
{
|
|
60
|
-
id: 'r2',
|
|
61
|
-
statementText: 'Second statement row',
|
|
62
|
-
},
|
|
63
|
-
],
|
|
64
|
-
} as typeof q;
|
|
65
|
-
} else {
|
|
66
|
-
q = { ...q, statementRows: rows.slice(0, 1) } as typeof q;
|
|
67
|
-
}
|
|
64
|
+
const buttonStyle =
|
|
65
|
+
style === 'numbered'
|
|
66
|
+
? 'numbered'
|
|
67
|
+
: style === 'pill'
|
|
68
|
+
? 'pill'
|
|
69
|
+
: style === 'emoji'
|
|
70
|
+
? 'emoji'
|
|
71
|
+
: 'standard';
|
|
72
|
+
q = { ...q, buttonStyle };
|
|
68
73
|
}
|
|
69
74
|
|
|
70
75
|
if (previewState.hintMinText !== undefined || previewState.hintMaxText !== undefined) {
|
|
@@ -86,13 +91,37 @@ function QuestionPreviewInner({ question, initialValue }: QuestionPreviewProps)
|
|
|
86
91
|
}
|
|
87
92
|
}
|
|
88
93
|
|
|
89
|
-
if (
|
|
90
|
-
|
|
94
|
+
if (q.type === 'CFM_MATRIX') {
|
|
95
|
+
if (previewKey === 'CFM_bipolar') {
|
|
96
|
+
q = {
|
|
97
|
+
...q,
|
|
98
|
+
statementLayout: 'bipolar',
|
|
99
|
+
showColumnHeaders: false,
|
|
100
|
+
gridLayout: 'standard',
|
|
101
|
+
transposeTable: false,
|
|
102
|
+
};
|
|
103
|
+
} else if (previewKey === 'CFM_transpose') {
|
|
104
|
+
q = { ...q, statementLayout: 'likert', transposeTable: true, gridLayout: 'standard' };
|
|
105
|
+
} else if (previewKey === 'CFM_carousel') {
|
|
106
|
+
q = { ...q, statementLayout: 'likert', gridLayout: 'carousel', transposeTable: false };
|
|
107
|
+
} else if (previewKey === 'CFM_dropdown') {
|
|
108
|
+
q = { ...q, statementLayout: 'likert', gridLayout: 'dropdown', transposeTable: false };
|
|
109
|
+
} else {
|
|
110
|
+
q = { ...q, statementLayout: 'likert', gridLayout: 'standard', transposeTable: false };
|
|
111
|
+
}
|
|
91
112
|
}
|
|
92
|
-
|
|
93
|
-
|
|
113
|
+
|
|
114
|
+
if (q.type === 'RANK_ORDER') {
|
|
115
|
+
if (previewState.rankVariant === 'drag' || previewKey === 'RANK_drag') {
|
|
116
|
+
q = { ...q, interactionMode: 'dragAndDrop', optionDisplay: 'textAndImage' };
|
|
117
|
+
}
|
|
118
|
+
if (previewState.rankVariant === 'dropdown' || previewKey === 'RANK_dropdown') {
|
|
119
|
+
q = { ...q, interactionMode: 'dropdown', optionDisplay: 'textAndImage' };
|
|
120
|
+
}
|
|
94
121
|
}
|
|
95
122
|
|
|
123
|
+
q = applyMatrixRowCount(q, previewState.multiStatement);
|
|
124
|
+
|
|
96
125
|
return q;
|
|
97
126
|
}, [question, previewState]);
|
|
98
127
|
|
|
@@ -109,7 +138,7 @@ function QuestionPreviewInner({ question, initialValue }: QuestionPreviewProps)
|
|
|
109
138
|
|
|
110
139
|
return (
|
|
111
140
|
<div
|
|
112
|
-
className="
|
|
141
|
+
className="mx-auto max-w-4xl px-4 py-6"
|
|
113
142
|
style={{ fontFamily: 'var(--cfm-font-family)' }}
|
|
114
143
|
data-cfm-question-area
|
|
115
144
|
>
|
|
@@ -4,11 +4,23 @@ import SurveyStickyChrome from '@/components/SurveyStickyChrome';
|
|
|
4
4
|
import LanguageSelector from '@/components/LanguageSelector';
|
|
5
5
|
import Question from '@/components/Question';
|
|
6
6
|
import Footer from '@/components/Footer';
|
|
7
|
-
import {
|
|
7
|
+
import { PreviewConfigProvider } from './PreviewConfigContext';
|
|
8
|
+
import {
|
|
9
|
+
FIXTURE_SURVEY_PAGE_MCQ,
|
|
10
|
+
FIXTURE_SURVEY_PAGE_NPS,
|
|
11
|
+
PREVIEW_LANGUAGES,
|
|
12
|
+
} from './fixtures/questions';
|
|
8
13
|
|
|
9
|
-
|
|
14
|
+
function SurveyPagePreviewInner() {
|
|
10
15
|
const [lang, setLang] = useState('en');
|
|
11
|
-
const [
|
|
16
|
+
const [mcqAnswer, setMcqAnswer] = useState<AnswerValue>('a');
|
|
17
|
+
const [npsAnswer, setNpsAnswer] = useState<AnswerValue>(8);
|
|
18
|
+
|
|
19
|
+
const allQuestions = [FIXTURE_SURVEY_PAGE_MCQ, FIXTURE_SURVEY_PAGE_NPS];
|
|
20
|
+
const allAnswers = {
|
|
21
|
+
[FIXTURE_SURVEY_PAGE_MCQ.id]: mcqAnswer,
|
|
22
|
+
[FIXTURE_SURVEY_PAGE_NPS.id]: npsAnswer,
|
|
23
|
+
};
|
|
12
24
|
|
|
13
25
|
return (
|
|
14
26
|
<div
|
|
@@ -30,13 +42,20 @@ export function SurveyPagePreview() {
|
|
|
30
42
|
onChange={setLang}
|
|
31
43
|
/>
|
|
32
44
|
|
|
33
|
-
<div className="space-y-
|
|
45
|
+
<div className="space-y-8">
|
|
46
|
+
<Question
|
|
47
|
+
question={FIXTURE_SURVEY_PAGE_MCQ}
|
|
48
|
+
selectedValue={mcqAnswer}
|
|
49
|
+
allAnswers={allAnswers}
|
|
50
|
+
allQuestions={allQuestions}
|
|
51
|
+
onSelect={setMcqAnswer}
|
|
52
|
+
/>
|
|
34
53
|
<Question
|
|
35
54
|
question={FIXTURE_SURVEY_PAGE_NPS}
|
|
36
|
-
selectedValue={
|
|
37
|
-
allAnswers={
|
|
38
|
-
allQuestions={
|
|
39
|
-
onSelect={
|
|
55
|
+
selectedValue={npsAnswer}
|
|
56
|
+
allAnswers={allAnswers}
|
|
57
|
+
allQuestions={allQuestions}
|
|
58
|
+
onSelect={setNpsAnswer}
|
|
40
59
|
/>
|
|
41
60
|
</div>
|
|
42
61
|
|
|
@@ -73,3 +92,12 @@ export function SurveyPagePreview() {
|
|
|
73
92
|
</div>
|
|
74
93
|
);
|
|
75
94
|
}
|
|
95
|
+
|
|
96
|
+
/** Full survey page mockup for wizard Theme / Layout steps — header, progress, language, cards, footer. */
|
|
97
|
+
export function SurveyPagePreview() {
|
|
98
|
+
return (
|
|
99
|
+
<PreviewConfigProvider>
|
|
100
|
+
<SurveyPagePreviewInner />
|
|
101
|
+
</PreviewConfigProvider>
|
|
102
|
+
);
|
|
103
|
+
}
|