@explorer02/cfm-survey-sdk 0.2.2 → 0.2.4
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 +140 -33
- package/dist/cli/index.mjs +140 -33
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +8 -1
- package/postinstall.js +8 -5
- package/templates/AGENT.md +62 -17
- package/templates/deploy-wizard-to-s3.sh +52 -0
- package/templates/docs/00-integration/agent-execution-flow.md +281 -0
- package/templates/docs/00-integration/agent-operating-contract.md +260 -0
- package/templates/docs/00-integration/analytics-events-catalog.md +54 -0
- package/templates/docs/00-integration/apply-ui-config.md +365 -0
- package/templates/docs/00-integration/canonical-survey-fixtures.md +94 -0
- package/templates/docs/00-integration/client-integration-guide.md +10 -8
- package/templates/docs/00-integration/client-lib-folder.md +115 -0
- package/templates/docs/00-integration/component-checklist.md +90 -54
- package/templates/docs/00-integration/constraints.md +13 -5
- package/templates/docs/00-integration/custom-field-logic-and-navigation.md +56 -0
- package/templates/docs/00-integration/display-logic-and-navigation.md +16 -1
- package/templates/docs/00-integration/file-upload-aws.md +116 -0
- package/templates/docs/00-integration/logic-fields-catalog.md +105 -0
- package/templates/docs/00-integration/partial-save-and-recovery.md +24 -0
- package/templates/docs/00-integration/progress.md +4 -2
- package/templates/docs/00-integration/question-type-sdk-matrix.md +11 -9
- package/templates/docs/00-integration/setup.md +15 -3
- package/templates/docs/00-integration/skip-logic-and-navigation.md +1 -1
- package/templates/docs/00-integration/survey-lifecycle-analytics.md +1 -1
- package/templates/docs/00-integration/ui-customization-wizard.md +144 -0
- package/templates/docs/00-integration/useSurveySDK.md +35 -1
- package/templates/docs/00-integration/wizard-api.md +134 -0
- package/templates/docs/00-integration/wizard-config-handoff.md +246 -0
- package/templates/docs/00-integration/wizard-preview-build-guide.md +400 -0
- package/templates/docs/00-integration/wizard-question-type-styling.md +195 -0
- package/templates/docs/01-components/01-survey-page.md +18 -8
- package/templates/docs/01-components/02-question.md +2 -2
- package/templates/docs/01-components/03-rating-scale.md +12 -18
- package/templates/docs/01-components/05-csat-matrix-scale.md +18 -26
- package/templates/docs/01-components/06-likert-matrix-scale.md +19 -38
- package/templates/docs/01-components/07-slider-matrix-scale.md +48 -33
- package/templates/docs/01-components/08-file-upload-scale.md +15 -52
- package/templates/docs/01-components/09-custom-slider-track.md +15 -25
- package/templates/docs/01-components/10-header-footer.md +23 -4
- package/templates/docs/01-components/11-progress-bar.md +14 -6
- package/templates/docs/01-components/13-matrix-dropdown.md +28 -33
- package/templates/docs/01-components/17-heatmap-scale.md +4 -4
- package/templates/docs/01-components/18-rank-order-scale.md +24 -31
- package/templates/docs/01-components/19-survey-sticky-chrome.md +79 -0
- package/templates/docs/01-components/README.md +2 -2
- package/templates/docs/02-reference/question-types/10-slider-matrix.md +38 -26
- package/templates/docs/02-reference/question-types/11-file-upload.md +36 -21
- package/templates/docs/02-reference/question-types/README.md +11 -1
- package/templates/docs/02-reference/routing-table.md +1 -0
- package/templates/docs/02-reference/value-derivation.md +1 -1
- package/templates/docs/03-ui-specs/00-question-shell.md +10 -0
- package/templates/docs/03-ui-specs/01-rating.md +28 -2
- package/templates/docs/03-ui-specs/02-radio.md +94 -24
- package/templates/docs/03-ui-specs/03-text.md +12 -1
- package/templates/docs/03-ui-specs/04-csat.md +90 -26
- package/templates/docs/03-ui-specs/06-slider.md +3 -1
- package/templates/docs/03-ui-specs/07-matrix-cfm.md +74 -35
- package/templates/docs/03-ui-specs/08-matrix-csat-rating.md +50 -16
- package/templates/docs/03-ui-specs/09-slider-matrix.md +70 -26
- package/templates/docs/03-ui-specs/10-file-upload.md +52 -20
- package/templates/docs/03-ui-specs/11-text-and-media.md +4 -0
- package/templates/docs/03-ui-specs/12-survey-chrome.md +92 -5
- package/templates/docs/03-ui-specs/13-heatmap.md +21 -3
- package/templates/docs/03-ui-specs/14-rank-order.md +108 -28
- package/templates/docs/03-ui-specs/README.md +11 -7
- package/templates/docs/03-ui-specs/shared/custom-slider-track.md +28 -35
- package/templates/docs/03-ui-specs/shared/matrix-dropdown.md +1 -1
- package/templates/docs/MANIFEST.json +187 -6
- package/templates/docs/index.md +116 -116
- package/templates/docs/templates/CsatMatrixScale.tsx +637 -0
- package/templates/docs/templates/CustomSliderTrack.tsx +144 -0
- package/templates/docs/templates/FileUploadScale.tsx +262 -0
- package/templates/docs/templates/HeatmapScale.tsx +114 -0
- package/templates/docs/templates/LikertMatrixScale.tsx +414 -0
- package/templates/docs/templates/MatrixDropdown.tsx +216 -0
- package/templates/docs/templates/Question.tsx +3 -0
- package/templates/docs/templates/RankOrderScale.tsx +353 -0
- package/templates/docs/templates/RatingScale.tsx +129 -0
- package/templates/docs/templates/SliderMatrixScale.tsx +248 -0
- package/templates/docs/templates/SurveyStickyChrome.tsx +24 -0
- package/templates/docs/templates/customFieldValues.ts +36 -0
- package/templates/docs/templates/implementation_plan.md +75 -8
- package/templates/docs/templates/labelStyles.ts +33 -0
- package/templates/docs/templates/selectionStyles.ts +100 -0
- package/templates/docs/templates/survey-inventory.schema.json +28 -23
- package/templates/docs/templates/surveyUiIcons.tsx +63 -0
- package/templates/docs/templates/surveyUiScaleUtils.ts +51 -0
- package/templates/docs/templates/verify-agent-build.sh +151 -0
- package/templates/preview-harness/preview-bridge.inline.js +201 -0
- package/templates/preview-harness/previewPages.js +108 -0
- package/templates/preview-harness/previewPages.ts +298 -0
- package/templates/preview-harness/vite-app/src/PreviewConfigContext.tsx +67 -0
- package/templates/preview-harness/vite-app/src/QuestionPreview.tsx +122 -0
- package/templates/preview-harness/vite-app/src/SurveyPagePreview.tsx +75 -0
- package/templates/preview-harness/vite-app/src/fixtures/questions.ts +311 -0
- package/templates/preview-harness/vite-app/src/globals.css +16 -0
- package/templates/preview-harness/vite-app/src/mount.tsx +9 -0
- package/templates/preview-harness/vite-app/src/preview-live-overrides.css +101 -0
- package/templates/preview-harness/vite-app/stubs/next-dynamic.tsx +25 -0
- package/templates/preview-harness/vite-app/stubs/next-image.tsx +29 -0
- package/templates/previewBridge.ts +153 -0
- package/templates/survey-theme.css +295 -0
- package/templates/survey-ui-config.schema.json +213 -0
- package/templates/wizard-dist/assets/PreviewMock-DgHfrVeb.js +1 -0
- package/templates/wizard-dist/assets/TypePanel-CFVC3Ptn.js +1 -0
- package/templates/wizard-dist/assets/index-BhWM50Yu.css +1 -0
- package/templates/wizard-dist/assets/index-DYK3X1e5.js +34 -0
- package/templates/wizard-dist/assets/vendor-BwkXDkd3.js +17 -0
- package/templates/wizard-dist/index.html +20 -0
- package/templates/wizard.html +13 -1129
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import {
|
|
5
|
+
FaFaceAngry,
|
|
6
|
+
FaFaceSadTear,
|
|
7
|
+
FaFaceFrown,
|
|
8
|
+
FaFaceFrownOpen,
|
|
9
|
+
FaFaceMeh,
|
|
10
|
+
FaFaceSmile,
|
|
11
|
+
FaFaceSmileBeam,
|
|
12
|
+
FaFaceGrin,
|
|
13
|
+
FaFaceGrinWide,
|
|
14
|
+
FaFaceGrinStars,
|
|
15
|
+
FaStar,
|
|
16
|
+
} from 'react-icons/fa6';
|
|
17
|
+
import { FaRegStar } from 'react-icons/fa';
|
|
18
|
+
|
|
19
|
+
const EMOJI_COLORS = [
|
|
20
|
+
'#dc2626', '#e04832', '#ea580c', '#f59e0b', '#eab308',
|
|
21
|
+
'#a3a323', '#84cc16', '#65a30d', '#22c55e', '#16a34a', '#059669',
|
|
22
|
+
] as const;
|
|
23
|
+
|
|
24
|
+
const FACE_ICONS = [
|
|
25
|
+
FaFaceAngry, FaFaceSadTear, FaFaceFrown, FaFaceFrownOpen, FaFaceMeh, FaFaceMeh,
|
|
26
|
+
FaFaceSmile, FaFaceSmileBeam, FaFaceGrin, FaFaceGrinWide, FaFaceGrinStars,
|
|
27
|
+
] as const;
|
|
28
|
+
|
|
29
|
+
const CsatEmojiSet: Record<number, React.ReactNode> = {};
|
|
30
|
+
for (let i = 0; i < 11; i++) {
|
|
31
|
+
const Icon = FACE_ICONS[i];
|
|
32
|
+
CsatEmojiSet[i + 1] = React.createElement(Icon as React.ElementType, {
|
|
33
|
+
className: 'transition-transform',
|
|
34
|
+
style: { color: EMOJI_COLORS[i], fontSize: '28px' },
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const EMOJI_SCALES: Record<number, number[]> = {
|
|
39
|
+
3: [3, 6, 8],
|
|
40
|
+
4: [3, 4, 7, 9],
|
|
41
|
+
5: [3, 4, 6, 7, 9],
|
|
42
|
+
6: [3, 4, 6, 7, 9, 11],
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export function getEmojiForIndex(index: number, total: number): React.ReactNode {
|
|
46
|
+
if (total <= 1) return CsatEmojiSet[6];
|
|
47
|
+
if (EMOJI_SCALES[total]) {
|
|
48
|
+
const key = EMOJI_SCALES[total][index];
|
|
49
|
+
if (key) return CsatEmojiSet[key];
|
|
50
|
+
}
|
|
51
|
+
const percent = index / (total - 1);
|
|
52
|
+
const key = Math.min(11, Math.max(1, Math.round(percent * 10) + 1));
|
|
53
|
+
return CsatEmojiSet[key];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const CsatStarIcons: { filled: React.ReactNode; empty: React.ReactNode } = {
|
|
57
|
+
filled: React.createElement(FaStar as React.ElementType, {
|
|
58
|
+
style: { color: '#e20074', fontSize: '28px' },
|
|
59
|
+
}),
|
|
60
|
+
empty: React.createElement(FaRegStar as React.ElementType, {
|
|
61
|
+
style: { color: '#d1d5db', fontSize: '28px', stroke: '#d1d5db', strokeWidth: '16px' },
|
|
62
|
+
}),
|
|
63
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Portable scale utilities — copy to src/lib/surveyUiScaleUtils.ts or co-locate with components.
|
|
3
|
+
*/
|
|
4
|
+
import type { NpsScalePoint, ScaleColumn } from '@explorer02/cfm-survey-sdk';
|
|
5
|
+
|
|
6
|
+
export const SCALE_COLORS: readonly string[] = [
|
|
7
|
+
'#e2001a', '#e4251b', '#ec610a', '#f18b00', '#f7b200', '#fcd900',
|
|
8
|
+
'#d2dc00', '#9fca00', '#6bb300', '#339a00', '#008000',
|
|
9
|
+
] as const;
|
|
10
|
+
|
|
11
|
+
export function getAccentColor(value: number): string {
|
|
12
|
+
return SCALE_COLORS[value] ?? '#e20074';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function tickColorFromIndex(index: number, total: number): string {
|
|
16
|
+
if (total <= 1) return '#e20074';
|
|
17
|
+
const hue = Math.round((index / (total - 1)) * 120);
|
|
18
|
+
return `hsl(${hue}, 90%, 40%)`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function nonNaScaleColumns(columns: ScaleColumn[]): ScaleColumn[] {
|
|
22
|
+
return columns.filter(col => col.id !== 'na');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function starValueFromOption(option: NpsScalePoint, index: number): number {
|
|
26
|
+
const numeric = Number(option.optionLabel);
|
|
27
|
+
return Number.isFinite(numeric) && option.optionLabel.trim() !== '' ? numeric : index;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function npsValueFromOption(option: NpsScalePoint, index: number): number {
|
|
31
|
+
const numeric = Number(option.optionLabel);
|
|
32
|
+
return Number.isFinite(numeric) && option.optionLabel.trim() !== '' ? numeric : index;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Matches SDK matrixColumnStoredValue — scale-matrix answers store column ids. */
|
|
36
|
+
export function matrixColumnStoredValue(column: { id: string; label: string }): string | null {
|
|
37
|
+
return column.id === 'na' ? null : column.id;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** @deprecated Use matrixColumnStoredValue */
|
|
41
|
+
export function columnSubmitValue(column: { id: string; label: string }): string | null {
|
|
42
|
+
return matrixColumnStoredValue(column);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function columnAccentColor(column: { id: string; label: string }): string | undefined {
|
|
46
|
+
const numeric = Number(column.label);
|
|
47
|
+
if (Number.isFinite(numeric) && column.label.trim() !== '') {
|
|
48
|
+
return getAccentColor(numeric);
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
@@ -20,6 +20,58 @@ if [ -n "$INTERNAL_IMPORTS" ]; then
|
|
|
20
20
|
fi
|
|
21
21
|
echo "OK: No internal SDK imports"
|
|
22
22
|
|
|
23
|
+
# Wizard-ready artifacts (Phase 5 — mandatory)
|
|
24
|
+
THEME_CSS=""
|
|
25
|
+
for candidate in src/styles/survey-theme.css styles/survey-theme.css app/globals.css; do
|
|
26
|
+
if [ -f "$candidate" ] && grep -q "cfm-" "$candidate" 2>/dev/null; then
|
|
27
|
+
THEME_CSS="$candidate"
|
|
28
|
+
break
|
|
29
|
+
fi
|
|
30
|
+
done
|
|
31
|
+
if [ -z "$THEME_CSS" ]; then
|
|
32
|
+
for candidate in src/styles/survey-theme.css styles/survey-theme.css; do
|
|
33
|
+
if [ -f "$candidate" ]; then THEME_CSS="$candidate"; break; fi
|
|
34
|
+
done
|
|
35
|
+
fi
|
|
36
|
+
if [ -z "$THEME_CSS" ]; then
|
|
37
|
+
echo "FAIL: src/styles/survey-theme.css missing — copy from docs/templates/survey-theme.css"
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
echo "OK: survey-theme.css present ($THEME_CSS)"
|
|
41
|
+
|
|
42
|
+
SEL_STYLES=""
|
|
43
|
+
for candidate in src/lib/surveyUi/selectionStyles.ts lib/surveyUi/selectionStyles.ts; do
|
|
44
|
+
if [ -f "$candidate" ]; then SEL_STYLES="$candidate"; break; fi
|
|
45
|
+
done
|
|
46
|
+
if [ -z "$SEL_STYLES" ]; then
|
|
47
|
+
echo "FAIL: selectionStyles.ts missing — copy from docs/templates/selectionStyles.ts"
|
|
48
|
+
exit 1
|
|
49
|
+
fi
|
|
50
|
+
echo "OK: selectionStyles.ts present ($SEL_STYLES)"
|
|
51
|
+
|
|
52
|
+
LBL_STYLES=""
|
|
53
|
+
for candidate in src/lib/surveyUi/labelStyles.ts lib/surveyUi/labelStyles.ts; do
|
|
54
|
+
if [ -f "$candidate" ]; then LBL_STYLES="$candidate"; break; fi
|
|
55
|
+
done
|
|
56
|
+
if [ -z "$LBL_STYLES" ]; then
|
|
57
|
+
echo "FAIL: labelStyles.ts missing — copy from docs/templates/labelStyles.ts"
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
echo "OK: labelStyles.ts present ($LBL_STYLES)"
|
|
61
|
+
|
|
62
|
+
if [ ! -f "./survey-ui-config.json" ]; then
|
|
63
|
+
echo "FAIL: ./survey-ui-config.json missing — draft config required for wizard seed"
|
|
64
|
+
exit 1
|
|
65
|
+
fi
|
|
66
|
+
echo "OK: survey-ui-config.json present"
|
|
67
|
+
|
|
68
|
+
CHROME_HOOKS=$(grep -rE "data-cfm-(logo|btn-next|survey-title)" src/ components/ app/ 2>/dev/null || true)
|
|
69
|
+
if [ -z "$CHROME_HOOKS" ]; then
|
|
70
|
+
echo "FAIL: No data-cfm-* chrome hooks found — see wizard-preview-build-guide.md"
|
|
71
|
+
exit 1
|
|
72
|
+
fi
|
|
73
|
+
echo "OK: data-cfm-* chrome hooks present"
|
|
74
|
+
|
|
23
75
|
REQUIRED_TYPES=(MCQ TEXTFIELD NPS_SCALE CFM_MATRIX CSAT_MATRIX RATING_MATRIX SLIDER_MATRIX FILE_UPLOAD TEXT_AND_MEDIA HEATMAP RANK_ORDER)
|
|
24
76
|
QUESTION_FILE=""
|
|
25
77
|
for candidate in src/components/Question.tsx components/Question.tsx app/components/Question.tsx; do
|
|
@@ -46,6 +98,105 @@ else
|
|
|
46
98
|
exit 1
|
|
47
99
|
fi
|
|
48
100
|
echo "OK: All 11 question.type branches present"
|
|
101
|
+
|
|
102
|
+
if grep -q "RANK_ORDER" "$QUESTION_FILE" || grep -q "RankOrderScale" "$QUESTION_FILE"; then
|
|
103
|
+
RANK_FILE=""
|
|
104
|
+
for candidate in src/components/RankOrderScale.tsx components/RankOrderScale.tsx app/components/RankOrderScale.tsx; do
|
|
105
|
+
if [ -f "$candidate" ]; then RANK_FILE="$candidate"; break; fi
|
|
106
|
+
done
|
|
107
|
+
if [ -z "$RANK_FILE" ]; then
|
|
108
|
+
echo "FAIL: RankOrderScale.tsx required when RANK_ORDER is wired in Question.tsx"
|
|
109
|
+
exit 1
|
|
110
|
+
fi
|
|
111
|
+
echo "OK: RankOrderScale.tsx exists ($RANK_FILE)"
|
|
112
|
+
if ! grep -q "@dnd-kit/core" package.json 2>/dev/null; then
|
|
113
|
+
echo "FAIL: @dnd-kit/core missing from package.json (required for RANK_ORDER)"
|
|
114
|
+
exit 1
|
|
115
|
+
fi
|
|
116
|
+
echo "OK: @dnd-kit in package.json"
|
|
117
|
+
if ! grep -q "getVisibleRankOrderOptionsForAnswers" "$QUESTION_FILE"; then
|
|
118
|
+
echo "WARN: getVisibleRankOrderOptionsForAnswers not found in Question.tsx"
|
|
119
|
+
fi
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
if grep -q "FILE_UPLOAD" "$QUESTION_FILE" || grep -q "FileUploadScale" "$QUESTION_FILE"; then
|
|
123
|
+
UPLOAD_FILE=""
|
|
124
|
+
for candidate in src/components/FileUploadScale.tsx components/FileUploadScale.tsx app/components/FileUploadScale.tsx; do
|
|
125
|
+
if [ -f "$candidate" ]; then UPLOAD_FILE="$candidate"; break; fi
|
|
126
|
+
done
|
|
127
|
+
if [ -z "$UPLOAD_FILE" ]; then
|
|
128
|
+
echo "FAIL: FileUploadScale.tsx required when FILE_UPLOAD is wired"
|
|
129
|
+
exit 1
|
|
130
|
+
fi
|
|
131
|
+
echo "OK: FileUploadScale.tsx exists ($UPLOAD_FILE)"
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
require_scale_file() {
|
|
135
|
+
local type_pattern="$1"
|
|
136
|
+
local scale_name="$2"
|
|
137
|
+
if grep -q "$type_pattern" "$QUESTION_FILE" || grep -q "$scale_name" "$QUESTION_FILE"; then
|
|
138
|
+
local SCALE_FILE=""
|
|
139
|
+
for candidate in "src/components/${scale_name}.tsx" "components/${scale_name}.tsx" "app/components/${scale_name}.tsx"; do
|
|
140
|
+
if [ -f "$candidate" ]; then SCALE_FILE="$candidate"; break; fi
|
|
141
|
+
done
|
|
142
|
+
if [ -z "$SCALE_FILE" ]; then
|
|
143
|
+
echo "FAIL: ${scale_name}.tsx required when ${type_pattern} is wired in Question.tsx"
|
|
144
|
+
exit 1
|
|
145
|
+
fi
|
|
146
|
+
echo "OK: ${scale_name}.tsx exists ($SCALE_FILE)"
|
|
147
|
+
fi
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
require_scale_file "SLIDER_MATRIX" "SliderMatrixScale"
|
|
151
|
+
require_scale_file "NPS_SCALE" "RatingScale"
|
|
152
|
+
require_scale_file "CFM_MATRIX" "LikertMatrixScale"
|
|
153
|
+
require_scale_file "CSAT_MATRIX" "CsatMatrixScale"
|
|
154
|
+
require_scale_file "RATING_MATRIX" "CsatMatrixScale"
|
|
155
|
+
require_scale_file "HEATMAP" "HeatmapScale"
|
|
156
|
+
|
|
157
|
+
if grep -qE "SLIDER_MATRIX|displayStyle.*graphics|RATING_MATRIX" "$QUESTION_FILE" 2>/dev/null; then
|
|
158
|
+
TRACK_FILE=""
|
|
159
|
+
for candidate in src/components/CustomSliderTrack.tsx components/CustomSliderTrack.tsx app/components/CustomSliderTrack.tsx; do
|
|
160
|
+
if [ -f "$candidate" ]; then TRACK_FILE="$candidate"; break; fi
|
|
161
|
+
done
|
|
162
|
+
if [ -z "$TRACK_FILE" ]; then
|
|
163
|
+
echo "FAIL: CustomSliderTrack.tsx required for SLIDER_MATRIX or graphics matrix"
|
|
164
|
+
exit 1
|
|
165
|
+
fi
|
|
166
|
+
echo "OK: CustomSliderTrack.tsx exists ($TRACK_FILE)"
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
if grep -q "getVisibleMcqOptionsForAnswers\|getVisibleMatrixItemsForAnswers" "$QUESTION_FILE"; then
|
|
170
|
+
if ! grep -q "customFieldValues" "$QUESTION_FILE"; then
|
|
171
|
+
echo "WARN: Question.tsx uses visibility helpers but may not pass customFieldValues 4th arg"
|
|
172
|
+
else
|
|
173
|
+
echo "OK: Question.tsx references customFieldValues for visibility helpers"
|
|
174
|
+
fi
|
|
175
|
+
fi
|
|
176
|
+
fi
|
|
177
|
+
|
|
178
|
+
SURVEY_PAGE=""
|
|
179
|
+
for candidate in src/components/SurveyPage.tsx components/SurveyPage.tsx app/components/SurveyPage.tsx; do
|
|
180
|
+
if [ -f "$candidate" ]; then SURVEY_PAGE="$candidate"; break; fi
|
|
181
|
+
done
|
|
182
|
+
|
|
183
|
+
if [ -n "$SURVEY_PAGE" ]; then
|
|
184
|
+
if grep -q "customFieldValues" "$SURVEY_PAGE" || grep -q "CUSTOM_FIELD_VALUES" "$SURVEY_PAGE"; then
|
|
185
|
+
echo "OK: SurveyPage wires customFieldValues or CUSTOM_FIELD_VALUES"
|
|
186
|
+
if grep -q "CUSTOM_FIELD_VALUES" "$SURVEY_PAGE"; then
|
|
187
|
+
LIB_FILE=""
|
|
188
|
+
for candidate in src/lib/customFieldValues.ts lib/customFieldValues.ts; do
|
|
189
|
+
if [ -f "$candidate" ]; then LIB_FILE="$candidate"; break; fi
|
|
190
|
+
done
|
|
191
|
+
if [ -z "$LIB_FILE" ]; then
|
|
192
|
+
echo "WARN: SurveyPage imports CUSTOM_FIELD_VALUES but src/lib/customFieldValues.ts not found"
|
|
193
|
+
else
|
|
194
|
+
echo "OK: customFieldValues lib file exists ($LIB_FILE)"
|
|
195
|
+
fi
|
|
196
|
+
fi
|
|
197
|
+
else
|
|
198
|
+
echo "WARN: SurveyPage found but no customFieldValues wiring — required when survey uses CUSTOM_FIELD logic"
|
|
199
|
+
fi
|
|
49
200
|
fi
|
|
50
201
|
|
|
51
202
|
echo "Running npm run build..."
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
var SNAPSHOT = 'CFM_UI_CONFIG_SNAPSHOT';
|
|
3
|
+
var DELTA = 'CFM_UI_CONFIG_DELTA';
|
|
4
|
+
var LEGACY = 'CFM_UI_CONFIG';
|
|
5
|
+
var rafId = null;
|
|
6
|
+
var pending = {};
|
|
7
|
+
var liveEdit = false;
|
|
8
|
+
|
|
9
|
+
function flush() {
|
|
10
|
+
rafId = null;
|
|
11
|
+
var root = document.documentElement;
|
|
12
|
+
for (var k in pending) {
|
|
13
|
+
if (Object.prototype.hasOwnProperty.call(pending, k)) {
|
|
14
|
+
if (root.style.getPropertyValue(k) !== pending[k]) {
|
|
15
|
+
root.style.setProperty(k, pending[k]);
|
|
16
|
+
}
|
|
17
|
+
delete pending[k];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function queue(vars) {
|
|
23
|
+
for (var k in vars) {
|
|
24
|
+
if (Object.prototype.hasOwnProperty.call(vars, k)) pending[k] = vars[k];
|
|
25
|
+
}
|
|
26
|
+
if (rafId == null) rafId = requestAnimationFrame(flush);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function applyImmediate(vars) {
|
|
30
|
+
var root = document.documentElement;
|
|
31
|
+
for (var k in vars) {
|
|
32
|
+
if (Object.prototype.hasOwnProperty.call(vars, k)) {
|
|
33
|
+
root.style.setProperty(k, vars[k]);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function setDisplay(selector, visible) {
|
|
39
|
+
document.querySelectorAll(selector).forEach(function (el) {
|
|
40
|
+
el.style.display = visible ? '' : 'none';
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function applyContent(patch) {
|
|
45
|
+
if (!patch) return;
|
|
46
|
+
if (patch.logoUrl !== undefined) {
|
|
47
|
+
document.querySelectorAll('[data-cfm-logo], header img, footer img').forEach(function (el) {
|
|
48
|
+
var img = el;
|
|
49
|
+
if (patch.logoUrl) {
|
|
50
|
+
img.src = patch.logoUrl;
|
|
51
|
+
img.style.display = '';
|
|
52
|
+
} else if (el.hasAttribute('data-cfm-logo')) {
|
|
53
|
+
img.style.display = 'none';
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
document.querySelectorAll('[data-cfm-logo-text]').forEach(function (el) {
|
|
57
|
+
if (patch.logoUrl) {
|
|
58
|
+
el.style.display = 'none';
|
|
59
|
+
} else {
|
|
60
|
+
el.style.display = '';
|
|
61
|
+
el.classList.remove('hidden');
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
if (patch.companyName !== undefined) {
|
|
66
|
+
document.querySelectorAll('[data-cfm-company]').forEach(function (el) {
|
|
67
|
+
el.textContent = patch.companyName || '';
|
|
68
|
+
});
|
|
69
|
+
document.querySelectorAll('[data-cfm-logo-text]').forEach(function (el) {
|
|
70
|
+
var logoImg = document.querySelector('[data-cfm-logo]');
|
|
71
|
+
var logoHidden = !logoImg || logoImg.style.display === 'none';
|
|
72
|
+
if (logoHidden) el.textContent = patch.companyName || el.textContent;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (patch.surveyTitle !== undefined) {
|
|
76
|
+
document.querySelectorAll('[data-cfm-survey-title]').forEach(function (el) {
|
|
77
|
+
el.textContent = patch.surveyTitle || '';
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
if (patch.browserTabTitle !== undefined && patch.browserTabTitle) {
|
|
81
|
+
document.title = patch.browserTabTitle;
|
|
82
|
+
}
|
|
83
|
+
if (patch.thankYouMessage !== undefined) {
|
|
84
|
+
document.querySelectorAll('[data-cfm-thank-you]').forEach(function (el) {
|
|
85
|
+
el.textContent = patch.thankYouMessage || '';
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
if (patch.buttonLabels && patch.buttonLabels.next) {
|
|
89
|
+
document.querySelectorAll('[data-cfm-btn-next]').forEach(function (el) {
|
|
90
|
+
el.textContent = patch.buttonLabels.next;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
if (patch.buttonLabels && patch.buttonLabels.back) {
|
|
94
|
+
document.querySelectorAll('[data-cfm-btn-back]').forEach(function (el) {
|
|
95
|
+
el.textContent = patch.buttonLabels.back;
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
if (patch.copyrightText !== undefined) {
|
|
99
|
+
document.querySelectorAll('[data-cfm-copyright]').forEach(function (el) {
|
|
100
|
+
el.textContent = patch.copyrightText || '';
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
if (patch.footerLinks) {
|
|
104
|
+
var nav = document.querySelector('[data-cfm-footer-links]');
|
|
105
|
+
if (nav) {
|
|
106
|
+
nav.innerHTML = patch.footerLinks
|
|
107
|
+
.map(function (l) {
|
|
108
|
+
return (
|
|
109
|
+
'<a href="' +
|
|
110
|
+
l.url +
|
|
111
|
+
'" class="cfm-footer-link" target="_blank" rel="noopener noreferrer">' +
|
|
112
|
+
l.label +
|
|
113
|
+
'</a>'
|
|
114
|
+
);
|
|
115
|
+
})
|
|
116
|
+
.join('');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (patch.layoutFlags) {
|
|
120
|
+
var flags = patch.layoutFlags;
|
|
121
|
+
if (flags.showCompanyName !== undefined) {
|
|
122
|
+
setDisplay('[data-cfm-company]', flags.showCompanyName);
|
|
123
|
+
}
|
|
124
|
+
if (flags.showHeader !== undefined) {
|
|
125
|
+
setDisplay('.cfm-header, header.cfm-header, .sticky.top-0', flags.showHeader);
|
|
126
|
+
}
|
|
127
|
+
if (flags.showFooter !== undefined) {
|
|
128
|
+
setDisplay('.cfm-footer, footer.cfm-footer', flags.showFooter);
|
|
129
|
+
}
|
|
130
|
+
if (flags.showProgressBar !== undefined) {
|
|
131
|
+
setDisplay('.cfm-progress, [data-cfm-progress]', flags.showProgressBar);
|
|
132
|
+
}
|
|
133
|
+
if (flags.showLanguageSwitcher !== undefined) {
|
|
134
|
+
setDisplay('[data-cfm-language]', flags.showLanguageSwitcher);
|
|
135
|
+
}
|
|
136
|
+
if (flags.showNextButton !== undefined) {
|
|
137
|
+
setDisplay('[data-cfm-btn-next]', flags.showNextButton);
|
|
138
|
+
}
|
|
139
|
+
if (flags.showBackButton !== undefined) {
|
|
140
|
+
setDisplay('[data-cfm-btn-back]', flags.showBackButton);
|
|
141
|
+
}
|
|
142
|
+
if (flags.showQuestionNumbers !== undefined) {
|
|
143
|
+
setDisplay('[data-cfm-question-number]', flags.showQuestionNumbers);
|
|
144
|
+
}
|
|
145
|
+
if (flags.showRequiredAsterisk !== undefined) {
|
|
146
|
+
setDisplay('[data-cfm-required]', flags.showRequiredAsterisk);
|
|
147
|
+
}
|
|
148
|
+
if (flags.showFooterLogo !== undefined) {
|
|
149
|
+
setDisplay('[data-cfm-footer-logo]', flags.showFooterLogo);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (patch.domAttributes) {
|
|
153
|
+
var root = document.documentElement;
|
|
154
|
+
for (var attr in patch.domAttributes) {
|
|
155
|
+
if (Object.prototype.hasOwnProperty.call(patch.domAttributes, attr)) {
|
|
156
|
+
root.setAttribute(attr, patch.domAttributes[attr]);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (patch.previewState && patch.previewState.textfieldPlaceholder !== undefined) {
|
|
161
|
+
document.querySelectorAll('[data-cfm-textfield]').forEach(function (el) {
|
|
162
|
+
el.setAttribute('placeholder', patch.previewState.textfieldPlaceholder || '');
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function handleMessage(event) {
|
|
168
|
+
var data = event.data;
|
|
169
|
+
if (!data || !data.type) return;
|
|
170
|
+
if (data.type !== SNAPSHOT && data.type !== DELTA && data.type !== LEGACY) return;
|
|
171
|
+
|
|
172
|
+
if (!liveEdit) {
|
|
173
|
+
liveEdit = true;
|
|
174
|
+
document.documentElement.classList.add('cfm-live-edit');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (data.cssVars) {
|
|
178
|
+
if (data.type === DELTA) queue(data.cssVars);
|
|
179
|
+
else applyImmediate(data.cssVars);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (data.contentPatch) {
|
|
183
|
+
applyContent(data.contentPatch);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (data.type === LEGACY && data.cssVars) {
|
|
187
|
+
applyImmediate(data.cssVars);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
window.addEventListener('message', handleMessage);
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
var channel = new BroadcastChannel('cfm-wizard-preview');
|
|
195
|
+
channel.addEventListener('message', function (event) {
|
|
196
|
+
handleMessage(event);
|
|
197
|
+
});
|
|
198
|
+
} catch (e) {
|
|
199
|
+
/* BroadcastChannel unavailable */
|
|
200
|
+
}
|
|
201
|
+
})();
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const sharedStyles = `
|
|
4
|
+
.cfm-header { height:var(--cfm-header-height); background:var(--cfm-header-bg); border-bottom:1px solid var(--cfm-header-border); display:flex; align-items:center; padding:0 var(--cfm-header-padding-x); box-shadow:0 2px 8px rgba(0,0,0,0.06); }
|
|
5
|
+
.cfm-header-logo { width:var(--cfm-header-logo-width); height:var(--cfm-header-logo-height); object-fit:contain; padding:var(--cfm-header-logo-padding); background:var(--cfm-primary); }
|
|
6
|
+
.cfm-header-company { font-size:var(--cfm-header-company-size); font-weight:var(--cfm-header-company-weight); color:var(--cfm-header-company-color); margin-left:16px; }
|
|
7
|
+
.cfm-footer { background:var(--cfm-footer-bg); color:var(--cfm-footer-text); padding:var(--cfm-footer-padding-y) var(--cfm-footer-padding-x); display:flex; flex-wrap:wrap; gap:16px; justify-content:space-between; align-items:flex-end; }
|
|
8
|
+
.cfm-footer-links { display:flex; gap:24px; flex-wrap:wrap; }
|
|
9
|
+
.cfm-footer-links a { color:var(--cfm-footer-link); text-decoration:none; font-size:12px; }
|
|
10
|
+
.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); }
|
|
11
|
+
.cfm-progress-fill { height:100%; width:45%; background:var(--cfm-progress-fill); border-radius:999px; }
|
|
12
|
+
.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; }
|
|
13
|
+
.cfm-mcq-option.selected { border-color:var(--cfm-mcq-selected-accent); background:var(--cfm-mcq-selected-bg); }
|
|
14
|
+
.cfm-nps-row { display:flex; gap:var(--cfm-nps-cell-gap); flex-wrap:wrap; }
|
|
15
|
+
.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; }
|
|
16
|
+
.cfm-nps-cell.selected { background:var(--cfm-nps-selected-fill); border:2px solid var(--cfm-accent); }
|
|
17
|
+
.cfm-matrix-row { display:flex; align-items:center; padding:8px 0; background:var(--cfm-row-band); margin-bottom:4px; }
|
|
18
|
+
.cfm-matrix-label { width:var(--cfm-matrix-row-width); flex-shrink:0; padding:0 16px; font-size:14px; font-weight:500; }
|
|
19
|
+
.cfm-matrix-cells { display:flex; gap:8px; flex:1; justify-content:center; }
|
|
20
|
+
.cfm-matrix-cell { width:var(--cfm-csat-cell-size); height:var(--cfm-csat-cell-size); border-radius:50%; border:2px solid #d1d5db; }
|
|
21
|
+
.cfm-matrix-cell.selected { border-color:var(--cfm-csat-ring-color); background:var(--cfm-selected-bg); }
|
|
22
|
+
.cfm-emoji { font-size:var(--cfm-csat-emoji-size); opacity:var(--cfm-csat-unselected-opacity); }
|
|
23
|
+
.cfm-emoji.selected { opacity:1; outline:2px solid var(--cfm-csat-ring-color); border-radius:8px; }
|
|
24
|
+
.cfm-star { color:var(--cfm-star-color); font-size:24px; opacity:var(--cfm-star-unselected-opacity); }
|
|
25
|
+
.cfm-star.selected { opacity:1; }
|
|
26
|
+
.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; }
|
|
27
|
+
.cfm-slider-track { height:8px; background:var(--cfm-slider-track); border-radius:4px; position:relative; margin:24px 0; }
|
|
28
|
+
.cfm-slider-thumb { width:20px; height:20px; background:var(--cfm-slider-thumb); border-radius:50%; position:absolute; top:-6px; left:60%; }
|
|
29
|
+
.cfm-tick { width:var(--cfm-slider-tick-size); height:var(--cfm-slider-tick-size); background:var(--cfm-slider-tick); border-radius:4px; font-size:11px; display:flex; align-items:center; justify-content:center; color:white; }
|
|
30
|
+
.cfm-dropzone { border:2px dashed var(--cfm-upload-accent); border-radius:var(--cfm-border-radius); padding:var(--cfm-upload-padding); text-align:center; color:var(--cfm-text); }
|
|
31
|
+
.cfm-input { width:100%; height:var(--cfm-input-height); border:1px solid var(--cfm-input-border); border-radius:var(--cfm-input-radius); padding:0 var(--cfm-input-padding); font-family:inherit; box-sizing:border-box; }
|
|
32
|
+
.cfm-rank-item { background:var(--cfm-rank-item-bg); padding:var(--cfm-rank-item-padding); margin-bottom:var(--cfm-rank-item-gap); border-radius:var(--cfm-border-radius); display:flex; align-items:center; gap:12px; }
|
|
33
|
+
.cfm-rank-badge { background:var(--cfm-rank-badge); color:white; width:24px; height:24px; border-radius:50%; display:flex; align-items:center; justify-content:center; font-size:12px; }
|
|
34
|
+
.cfm-handle { color:var(--cfm-rank-handle); cursor:grab; }
|
|
35
|
+
.cfm-pin { width:var(--cfm-heatmap-pin-size); height:var(--cfm-heatmap-pin-size); background:var(--cfm-heatmap-pin); border:2px solid white; border-radius:50%; position:absolute; }
|
|
36
|
+
.cfm-media { max-width:var(--cfm-media-max-width); border-radius:var(--cfm-media-radius); background:#e5e7eb; height:200px; display:flex; align-items:center; justify-content:center; }
|
|
37
|
+
.cfm-btn-primary { background:var(--cfm-primary); color:var(--cfm-button-text); border:none; border-radius:var(--cfm-border-radius); padding:10px 20px; }
|
|
38
|
+
.cfm-btn-back { background:transparent; color:var(--cfm-primary); border:1px solid var(--cfm-primary); border-radius:var(--cfm-border-radius); padding:10px 20px; }
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
function cells(n) {
|
|
42
|
+
return Array.from({ length: n }, (_, i) =>
|
|
43
|
+
`<div class="cfm-matrix-cell${i === 2 ? ' selected' : ''}"></div>`,
|
|
44
|
+
).join('');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function matrixPreview(label) {
|
|
48
|
+
return `<div class="cfm-matrix-row"><div class="cfm-matrix-label">${label}</div><div class="cfm-matrix-cells">${cells(5)}</div></div>`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const npsCells = Array.from({ length: 11 }, (_, i) =>
|
|
52
|
+
`<div class="cfm-nps-cell${i === 8 ? ' selected' : ''}">${i}</div>`,
|
|
53
|
+
).join('');
|
|
54
|
+
|
|
55
|
+
const PREVIEW_PAGE_DEFS = [
|
|
56
|
+
{ 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>` },
|
|
57
|
+
{ 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>` },
|
|
58
|
+
{ 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>` },
|
|
59
|
+
{ 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></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-row">${npsCells}</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>` },
|
|
60
|
+
{ key: 'intro', title: 'Intro', body: `<h1 data-cfm-survey-title style="font-size:28px;font-weight:var(--cfm-heading-weight)">Welcome</h1><p style="margin:16px 0;color:#6b7280">Please share your feedback.</p><button class="cfm-btn-primary">Start Survey</button>` },
|
|
61
|
+
{ 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>` },
|
|
62
|
+
{ 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>` },
|
|
63
|
+
{ 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>` },
|
|
64
|
+
{ key: 'TEXTFIELD_short', title: 'Text Short', body: `<p style="font-weight:600;margin-bottom:8px">Your name</p><input class="cfm-input" placeholder="Type here..." />` },
|
|
65
|
+
{ key: 'TEXTFIELD_long', title: 'Text Long', body: `<p style="font-weight:600;margin-bottom:8px">Comments</p><textarea class="cfm-input" style="height:120px;padding:12px" placeholder="Type your response..."></textarea>` },
|
|
66
|
+
{ key: 'NPS_SCALE', title: 'NPS', body: `<p style="font-weight:600;margin-bottom:8px">How likely are you to recommend us?</p><div class="cfm-nps-row">${npsCells}</div>` },
|
|
67
|
+
{ key: 'CFM_likert', title: 'CFM Likert', body: matrixPreview('Statement item 1') },
|
|
68
|
+
{ key: 'CFM_bipolar', title: 'CFM Bipolar', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width)">Left</div><div class="cfm-matrix-cells">${cells(5)}</div><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width);text-align:right">Right</div></div>` },
|
|
69
|
+
{ key: 'CFM_transpose', title: 'CFM Transpose', body: matrixPreview('Scale item (transposed)') },
|
|
70
|
+
{ key: 'CFM_carousel', title: 'CFM Carousel', body: `${matrixPreview('Row 1 of 3')}<div style="text-align:center;margin-top:12px"><span style="color:var(--cfm-matrix-carousel-dot)">●</span> ○ ○</div>` },
|
|
71
|
+
{ key: 'CFM_dropdown', title: 'CFM Dropdown', body: `<div class="cfm-matrix-label">Statement 1</div><select class="cfm-input" style="margin-top:8px"><option>Select...</option></select>` },
|
|
72
|
+
{ key: 'CSAT_emoji', title: 'CSAT Emoji', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label">How satisfied?</div><div class="cfm-matrix-cells"><span class="cfm-emoji selected">😞</span><span class="cfm-emoji">🙂</span><span class="cfm-emoji">😊</span></div></div>` },
|
|
73
|
+
{ key: 'CSAT_star', title: 'CSAT Star', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label">Rate us</div><div class="cfm-matrix-cells"><span class="cfm-star selected">★</span><span class="cfm-star selected">★</span><span class="cfm-star">★</span></div></div>` },
|
|
74
|
+
{ key: 'CSAT_numbered', title: 'CSAT Numbered', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label">Score</div><div class="cfm-matrix-cells"><div class="cfm-badge">1</div><div class="cfm-badge">2</div><div class="cfm-badge">3</div></div></div>` },
|
|
75
|
+
{ 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>` },
|
|
76
|
+
{ key: 'CSAT_carousel', title: 'CSAT Carousel', body: `${matrixPreview('Carousel row')}<div style="text-align:center"><span style="color:var(--cfm-csat-accent)">●</span> ○</div>` },
|
|
77
|
+
{ 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>` },
|
|
78
|
+
{ key: 'RATING_star', title: 'Rating Star', body: `<div class="cfm-matrix-cells" style="justify-content:flex-start;padding:16px"><span class="cfm-star selected">★</span><span class="cfm-star selected">★</span><span class="cfm-star">★</span></div>` },
|
|
79
|
+
{ key: 'RATING_numbered', title: 'Rating Numbered', body: `<div class="cfm-matrix-cells" style="padding:16px"><div class="cfm-badge">1</div><div class="cfm-badge">2</div><div class="cfm-badge">3</div></div>` },
|
|
80
|
+
{ key: 'RATING_radio', title: 'Rating Radio', body: matrixPreview('Recommend to a friend') },
|
|
81
|
+
{ 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>` },
|
|
82
|
+
{ 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>` },
|
|
83
|
+
{ 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>` },
|
|
84
|
+
{ key: 'SLIDER_matrix', title: 'Slider Matrix', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label">Statement 1</div><div style="flex:1;padding:0 16px"><div class="cfm-slider-track"><div class="cfm-slider-thumb"></div></div></div></div>` },
|
|
85
|
+
{ key: 'FILE_UPLOAD', title: 'File Upload', body: `<div data-cfm-dropzone-style="dashed" class="cfm-dropzone">Drop files here or click to upload</div>` },
|
|
86
|
+
{ key: 'TEXT_AND_MEDIA', title: 'Text & Media', body: `<div class="cfm-media">Media preview</div>` },
|
|
87
|
+
{ 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>` },
|
|
88
|
+
{ key: 'RANK_drag', title: 'Rank Drag', body: `<div class="cfm-rank-item"><span class="cfm-handle">⠿</span><span class="cfm-rank-badge">1</span> Item A</div><div class="cfm-rank-item"><span class="cfm-handle">⠿</span><span class="cfm-rank-badge">2</span> Item B</div>` },
|
|
89
|
+
{ key: 'RANK_dropdown', title: 'Rank Dropdown', body: `<div style="display:flex;align-items:center;gap:12px"><span>Item A</span><select class="cfm-input" style="width:80px"><option>1</option></select></div>` },
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
function buildPreviewHtml(def, inlineCss, inlineJs) {
|
|
93
|
+
return `<!DOCTYPE html>
|
|
94
|
+
<html lang="en">
|
|
95
|
+
<head>
|
|
96
|
+
<meta charset="UTF-8" />
|
|
97
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
98
|
+
<title>${def.title} — CFM Preview</title>
|
|
99
|
+
<style>${inlineCss}\n${sharedStyles}</style>
|
|
100
|
+
</head>
|
|
101
|
+
<body>
|
|
102
|
+
<div class="cfm-preview-root">${def.body}</div>
|
|
103
|
+
<script>${inlineJs}</script>
|
|
104
|
+
</body>
|
|
105
|
+
</html>`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = { PREVIEW_PAGE_DEFS, buildPreviewHtml, sharedStyles, PREVIEW_KEYS: PREVIEW_PAGE_DEFS.map((d) => d.key) };
|