@explorer02/cfm-survey-sdk 0.3.8 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/cli/index.js +35 -35
  2. package/dist/cli/index.mjs +35 -35
  3. package/package.json +1 -1
  4. package/templates/docs/templates/CsatMatrixScale.tsx +60 -45
  5. package/templates/docs/templates/CustomSliderTrack.tsx +2 -2
  6. package/templates/docs/templates/Header.tsx +14 -13
  7. package/templates/docs/templates/LikertMatrixScale.tsx +2 -2
  8. package/templates/docs/templates/Question.tsx +31 -13
  9. package/templates/docs/templates/RankOrderScale.tsx +10 -2
  10. package/templates/docs/templates/SliderMatrixScale.tsx +3 -4
  11. package/templates/docs/templates/uiConfig.ts +89 -2
  12. package/templates/preview-harness/preview-bridge.inline.js +55 -15
  13. package/templates/preview-harness/previewPages.js +2 -2
  14. package/templates/preview-harness/previewPages.ts +1 -1
  15. package/templates/preview-harness/vite-app/src/PreviewConfigContext.tsx +8 -1
  16. package/templates/preview-harness/vite-app/src/QuestionPreview.tsx +17 -96
  17. package/templates/preview-harness/vite-app/src/SurveyPagePreview.tsx +32 -8
  18. package/templates/preview-harness/vite-app/src/fixtures/questions.ts +72 -47
  19. package/templates/preview-harness/vite-app/src/globals.css +6 -0
  20. package/templates/preview-harness/vite-app/src/mergePreviewQuestion.ts +173 -0
  21. package/templates/preview-harness/vite-app/src/preview-live-overrides.css +22 -0
  22. package/templates/previewBridge.ts +61 -14
  23. package/templates/survey-theme.css +4 -1
  24. package/templates/wizard-dist/assets/{PreviewMock-CS4WqhDB.js → PreviewMock-BlOAvUCN.js} +1 -1
  25. package/templates/wizard-dist/assets/TypePanel-DcbMaw1b.js +1 -0
  26. package/templates/wizard-dist/assets/index-2Bi_7Fdh.js +34 -0
  27. package/templates/wizard-dist/index.html +1 -1
  28. package/templates/wizard-dist/assets/TypePanel-BgTW2c74.js +0 -1
  29. package/templates/wizard-dist/assets/index-hjm-fxWO.js +0 -34
@@ -17,10 +17,12 @@
17
17
  delete pending[k];
18
18
  }
19
19
  }
20
- applyThemeSideEffects();
21
- }
20
+ applyThemeSideEffects();
21
+ syncCompanyNameVisibility();
22
+ syncQuestionChromeVisibility();
23
+ }
22
24
 
23
- function queue(vars) {
25
+ function queue(vars) {
24
26
  for (var k in vars) {
25
27
  if (Object.prototype.hasOwnProperty.call(vars, k)) pending[k] = vars[k];
26
28
  }
@@ -34,10 +36,12 @@
34
36
  root.style.setProperty(k, vars[k]);
35
37
  }
36
38
  }
37
- applyThemeSideEffects();
38
- }
39
+ applyThemeSideEffects();
40
+ syncCompanyNameVisibility();
41
+ syncQuestionChromeVisibility();
42
+ }
39
43
 
40
- function setDisplay(selector, visible) {
44
+ function setDisplay(selector, visible) {
41
45
  document.querySelectorAll(selector).forEach(function (el) {
42
46
  el.style.display = visible ? '' : 'none';
43
47
  });
@@ -81,6 +85,46 @@
81
85
  applyDropzoneStyle();
82
86
  }
83
87
 
88
+ function isHeaderLogoVisible() {
89
+ var logoImg = document.querySelector('header [data-cfm-logo], .cfm-header [data-cfm-logo]');
90
+ return Boolean(
91
+ logoImg &&
92
+ logoImg.style.display !== 'none' &&
93
+ logoImg.getAttribute('src'),
94
+ );
95
+ }
96
+
97
+ function syncCompanyNameVisibility(layoutShowCompany) {
98
+ var showFromCss = readVar('--cfm-header-show-company', '1') !== '0';
99
+ var showToggle =
100
+ layoutShowCompany !== undefined ? !!layoutShowCompany : showFromCss;
101
+
102
+ document.querySelectorAll('[data-cfm-company]').forEach(function (el) {
103
+ var hasName = Boolean((el.textContent || '').trim());
104
+ var show = showToggle && hasName && isHeaderLogoVisible();
105
+ el.style.display = show ? '' : 'none';
106
+ });
107
+ }
108
+
109
+ function syncQuestionChromeVisibility(layoutShowNumbers, layoutShowRequired) {
110
+ var showNumbers =
111
+ layoutShowNumbers !== undefined
112
+ ? !!layoutShowNumbers
113
+ : readVar('--cfm-show-question-numbers', '1') !== '0';
114
+ var showRequired =
115
+ layoutShowRequired !== undefined
116
+ ? !!layoutShowRequired
117
+ : readVar('--cfm-show-required-asterisk', '1') !== '0';
118
+
119
+ document.querySelectorAll('[data-cfm-question-number]').forEach(function (el) {
120
+ var hasNumber = Boolean((el.textContent || '').trim());
121
+ el.style.display = showNumbers && hasNumber ? '' : 'none';
122
+ });
123
+ document.querySelectorAll('[data-cfm-required]').forEach(function (el) {
124
+ el.style.display = showRequired ? '' : 'none';
125
+ });
126
+ }
127
+
84
128
  function applyContent(patch) {
85
129
  if (!patch) return;
86
130
  if (patch.logoUrl !== undefined) {
@@ -103,17 +147,11 @@
103
147
  el.classList.remove('hidden');
104
148
  }
105
149
  });
106
- document.querySelectorAll('[data-cfm-company]').forEach(function (el) {
107
- if (patch.logoUrl) {
108
- el.style.display = el.textContent ? '' : 'none';
109
- }
110
- });
111
150
  }
112
151
  if (patch.companyName !== undefined) {
113
152
  var name = patch.companyName || '';
114
153
  document.querySelectorAll('[data-cfm-company]').forEach(function (el) {
115
154
  el.textContent = name;
116
- el.style.display = name ? '' : 'none';
117
155
  });
118
156
  document.querySelectorAll('[data-cfm-logo-text]').forEach(function (el) {
119
157
  var logoImg = document.querySelector('[data-cfm-logo]');
@@ -174,7 +212,7 @@
174
212
  if (patch.layoutFlags) {
175
213
  var flags = patch.layoutFlags;
176
214
  if (flags.showCompanyName !== undefined) {
177
- setDisplay('[data-cfm-company]', flags.showCompanyName);
215
+ syncCompanyNameVisibility(flags.showCompanyName);
178
216
  }
179
217
  if (flags.showHeader !== undefined) {
180
218
  setDisplay('.cfm-header, header.cfm-header', flags.showHeader);
@@ -195,10 +233,10 @@
195
233
  setDisplay('[data-cfm-btn-back]', flags.showBackButton);
196
234
  }
197
235
  if (flags.showQuestionNumbers !== undefined) {
198
- setDisplay('[data-cfm-question-number]', flags.showQuestionNumbers);
236
+ syncQuestionChromeVisibility(flags.showQuestionNumbers, undefined);
199
237
  }
200
238
  if (flags.showRequiredAsterisk !== undefined) {
201
- setDisplay('[data-cfm-required]', flags.showRequiredAsterisk);
239
+ syncQuestionChromeVisibility(undefined, flags.showRequiredAsterisk);
202
240
  }
203
241
  if (flags.showFooterLogo !== undefined) {
204
242
  setDisplay('[data-cfm-footer-logo]', flags.showFooterLogo);
@@ -232,6 +270,8 @@
232
270
  applyMultiStatement(!!patch.previewState.multiStatement);
233
271
  }
234
272
  }
273
+ syncCompanyNameVisibility();
274
+ syncQuestionChromeVisibility();
235
275
  applyThemeSideEffects();
236
276
  }
237
277
 
@@ -97,8 +97,8 @@ const PREVIEW_PAGE_DEFS = [
97
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..." />` },
98
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>` },
99
99
  { key: 'NPS_SCALE', title: 'NPS', body: npsPreview() },
100
- { key: 'CFM_likert', title: 'CFM Likert', body: `${matrixPreview('Statement item 1', 'radio')}<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">${cells(5)}</div></div>` },
101
- { key: 'CFM_bipolar', title: 'CFM Bipolar', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width,25%)">Left statement</div><div class="cfm-matrix-cells">${cells(5)}</div><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width,25%);text-align:right">Right statement</div></div>` },
100
+ { key: 'CFM_likert', title: 'CFM Likert', body: `${matrixPreview('Statement item 1', 'radio')}<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">${cells(5)}</div></div><div class="cfm-matrix-row" data-cfm-matrix-row-extra style="display:none"><div class="cfm-matrix-label">Statement item 3</div><div class="cfm-matrix-cells">${cells(5)}</div></div>` },
101
+ { key: 'CFM_bipolar', title: 'CFM Bipolar', body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width,25%)">Statement 1</div><div class="cfm-matrix-cells">${cells(5)}</div><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width,25%);text-align:right">Opposite 1</div></div><div class="cfm-matrix-row" data-cfm-matrix-row-extra style="display:none"><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width,25%)">Statement 2</div><div class="cfm-matrix-cells">${cells(5)}</div><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width,25%);text-align:right">Opposite 2</div></div><div class="cfm-matrix-row" data-cfm-matrix-row-extra style="display:none"><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width,25%)">Statement 3</div><div class="cfm-matrix-cells">${cells(5)}</div><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width,25%);text-align:right">Opposite 3</div></div>` },
102
102
  { key: 'CFM_transpose', title: 'CFM Transpose', body: matrixPreview('Scale item (transposed row)', 'radio') },
103
103
  { key: 'CFM_carousel', title: 'CFM Carousel', body: `${matrixPreview('Row 1 of 3', 'radio')}<div style="text-align:center;margin-top:12px"><span style="color:var(--cfm-matrix-selected)">●</span> ○ ○</div>` },
104
104
  { key: 'CFM_dropdown', title: 'CFM Dropdown', body: `<div class="cfm-matrix-label" style="width:100%;margin-bottom:8px">Statement 1</div><select class="cfm-input"><option>Select...</option><option>Col 1</option><option>Col 2</option></select>` },
@@ -141,7 +141,7 @@ export const PREVIEW_PAGE_DEFS: PreviewPageDef[] = [
141
141
  {
142
142
  key: 'CFM_bipolar',
143
143
  title: 'CFM Bipolar',
144
- body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width)">Left statement</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 statement</div></div>`,
144
+ body: `<div class="cfm-matrix-row"><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width)">Statement 1</div><div class="cfm-matrix-cells">${cells(5)}</div><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width);text-align:right">Opposite 1</div></div><div class="cfm-matrix-row" data-cfm-matrix-row-extra style="display:none"><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width)">Statement 2</div><div class="cfm-matrix-cells">${cells(5)}</div><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width);text-align:right">Opposite 2</div></div><div class="cfm-matrix-row" data-cfm-matrix-row-extra style="display:none"><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width)">Statement 3</div><div class="cfm-matrix-cells">${cells(5)}</div><div class="cfm-matrix-label" style="width:var(--cfm-matrix-bipolar-width);text-align:right">Opposite 3</div></div>`,
145
145
  },
146
146
  {
147
147
  key: 'CFM_transpose',
@@ -70,14 +70,19 @@ export function PreviewConfigProvider({ children }: { children: ReactNode }) {
70
70
  const data = event.data;
71
71
  if (!data?.type) return;
72
72
  if (data.type !== PREVIEW_BRIDGE_SNAPSHOT && data.type !== PREVIEW_BRIDGE_DELTA) return;
73
+
74
+ let shouldBumpRevision = false;
75
+
73
76
  if (data.contentPatch?.previewState) {
74
77
  setPreviewState((prev) => ({ ...prev, ...data.contentPatch.previewState }));
78
+ shouldBumpRevision = true;
75
79
  }
76
80
  if (data.contentPatch?.domAttributes) {
77
81
  const root = document.documentElement;
78
82
  for (const [key, value] of Object.entries(data.contentPatch.domAttributes)) {
79
83
  root.setAttribute(key, value as string);
80
84
  }
85
+ shouldBumpRevision = true;
81
86
  }
82
87
  if (data.cssVars) {
83
88
  const root = document.documentElement;
@@ -85,7 +90,9 @@ export function PreviewConfigProvider({ children }: { children: ReactNode }) {
85
90
  root.style.setProperty(key, value as string);
86
91
  }
87
92
  }
88
- setConfigRevision((r) => r + 1);
93
+ if (shouldBumpRevision) {
94
+ setConfigRevision((r) => r + 1);
95
+ }
89
96
  applyThemeSideEffects();
90
97
  };
91
98
 
@@ -2,6 +2,7 @@ import { useMemo, useState } from 'react';
2
2
  import type { AnswerValue, SurveyQuestion } from '@explorer02/cfm-survey-sdk';
3
3
  import Question from '@/components/Question';
4
4
  import { PreviewConfigProvider, usePreviewConfig } from './PreviewConfigContext';
5
+ import { mergePreviewQuestion } from './mergePreviewQuestion';
5
6
 
6
7
  type QuestionPreviewProps = {
7
8
  question: SurveyQuestion;
@@ -27,103 +28,14 @@ function resolveNumberLabelColor(index: number): string {
27
28
  return '';
28
29
  }
29
30
 
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
-
50
31
  function QuestionPreviewInner({ question, initialValue }: QuestionPreviewProps) {
51
32
  const { previewState, configRevision } = usePreviewConfig();
52
33
  const [value, setValue] = useState<AnswerValue | undefined>(initialValue);
53
34
 
54
- const mergedQuestion = useMemo(() => {
55
- let q = { ...question } as SurveyQuestion;
56
- const previewKey = previewState.activePreviewKey ?? '';
57
-
58
- if (previewState.textfieldPlaceholder && q.type === 'TEXTFIELD') {
59
- q = { ...q, placeholder: previewState.textfieldPlaceholder };
60
- }
61
-
62
- if (previewState.npsButtonStyle && q.type === 'NPS_SCALE') {
63
- const style = previewState.npsButtonStyle;
64
- const buttonStyle =
65
- style === 'numbered'
66
- ? 'numbered'
67
- : style === 'pill'
68
- ? 'pill'
69
- : style === 'emoji'
70
- ? 'emoji'
71
- : 'standard';
72
- q = { ...q, buttonStyle };
73
- }
74
-
75
- if (previewState.hintMinText !== undefined || previewState.hintMaxText !== undefined) {
76
- if (q.type === 'NPS_SCALE') {
77
- q = {
78
- ...q,
79
- minLabel: previewState.hintMinText ?? q.minLabel,
80
- maxLabel: previewState.hintMaxText ?? q.maxLabel,
81
- };
82
- }
83
- if (q.type === 'SLIDER_MATRIX') {
84
- q = {
85
- ...q,
86
- scaleAnchorLabels: [
87
- previewState.hintMinText ?? q.scaleAnchorLabels?.[0] ?? '',
88
- previewState.hintMaxText ?? q.scaleAnchorLabels?.[1] ?? '',
89
- ],
90
- };
91
- }
92
- }
93
-
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
- }
112
- }
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
- }
121
- }
122
-
123
- q = applyMatrixRowCount(q, previewState.multiStatement);
124
-
125
- return q;
126
- }, [question, previewState]);
35
+ const mergedQuestion = useMemo(
36
+ () => mergePreviewQuestion(question, previewState),
37
+ [question, previewState],
38
+ );
127
39
 
128
40
  const heatmapInitial =
129
41
  previewState.heatmapPreviewPin && mergedQuestion.type === 'HEATMAP'
@@ -136,16 +48,25 @@ function QuestionPreviewInner({ question, initialValue }: QuestionPreviewProps)
136
48
  ]
137
49
  : initialValue;
138
50
 
51
+ const rankDropdownInitial =
52
+ mergedQuestion.type === 'RANK_ORDER' &&
53
+ (mergedQuestion as { interactionMode?: string }).interactionMode === 'dropdown' &&
54
+ value === undefined
55
+ ? { o1: 1 }
56
+ : undefined;
57
+
58
+ const resolvedValue = heatmapInitial ?? rankDropdownInitial ?? value;
59
+
139
60
  return (
140
61
  <div
141
- className="mx-auto max-w-4xl px-4 py-6"
142
- style={{ fontFamily: 'var(--cfm-font-family)' }}
62
+ className="mx-auto w-full min-w-0 max-w-full overflow-x-auto px-4 py-6 box-border"
63
+ style={{ fontFamily: 'var(--cfm-font-family)', maxWidth: 'min(100%, var(--cfm-max-width, 48rem))' }}
143
64
  data-cfm-question-area
144
65
  >
145
66
  <Question
146
67
  key={configRevision}
147
68
  question={mergedQuestion}
148
- selectedValue={heatmapInitial ?? value}
69
+ selectedValue={resolvedValue}
149
70
  allAnswers={{}}
150
71
  allQuestions={[mergedQuestion]}
151
72
  onSelect={setValue}
@@ -1,26 +1,43 @@
1
- import { useState } from 'react';
2
- import type { AnswerValue } from '@explorer02/cfm-survey-sdk';
1
+ import { useMemo, useState } from 'react';
2
+ import type { AnswerValue, MatrixRowAnswers } from '@explorer02/cfm-survey-sdk';
3
3
  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
7
  import { PreviewConfigProvider, usePreviewConfig } from './PreviewConfigContext';
8
+ import { mergeSurveyPageQuestion } from './mergePreviewQuestion';
8
9
  import {
9
10
  FIXTURE_SURVEY_PAGE_MCQ,
11
+ FIXTURE_SURVEY_PAGE_CFM_LIKERT,
10
12
  FIXTURE_SURVEY_PAGE_NPS,
11
13
  PREVIEW_LANGUAGES,
12
14
  } from './fixtures/questions';
13
15
 
14
16
  function SurveyPagePreviewInner() {
15
- const { configRevision } = usePreviewConfig();
17
+ const { previewState, configRevision } = usePreviewConfig();
16
18
  const [lang, setLang] = useState('en');
17
19
  const [mcqAnswer, setMcqAnswer] = useState<AnswerValue>('a');
20
+ const [matrixAnswer, setMatrixAnswer] = useState<MatrixRowAnswers>({});
18
21
  const [npsAnswer, setNpsAnswer] = useState<AnswerValue>(8);
19
22
 
20
- const allQuestions = [FIXTURE_SURVEY_PAGE_MCQ, FIXTURE_SURVEY_PAGE_NPS];
23
+ const mcqQuestion = useMemo(
24
+ () => mergeSurveyPageQuestion(FIXTURE_SURVEY_PAGE_MCQ, previewState),
25
+ [previewState],
26
+ );
27
+ const matrixQuestion = useMemo(
28
+ () => mergeSurveyPageQuestion(FIXTURE_SURVEY_PAGE_CFM_LIKERT, previewState),
29
+ [previewState],
30
+ );
31
+ const npsQuestion = useMemo(
32
+ () => mergeSurveyPageQuestion(FIXTURE_SURVEY_PAGE_NPS, previewState),
33
+ [previewState],
34
+ );
35
+
36
+ const allQuestions = [mcqQuestion, matrixQuestion, npsQuestion];
21
37
  const allAnswers = {
22
- [FIXTURE_SURVEY_PAGE_MCQ.id]: mcqAnswer,
23
- [FIXTURE_SURVEY_PAGE_NPS.id]: npsAnswer,
38
+ [mcqQuestion.id]: mcqAnswer,
39
+ [matrixQuestion.id]: matrixAnswer,
40
+ [npsQuestion.id]: npsAnswer,
24
41
  };
25
42
 
26
43
  return (
@@ -45,14 +62,21 @@ function SurveyPagePreviewInner() {
45
62
 
46
63
  <div className="space-y-8" key={configRevision}>
47
64
  <Question
48
- question={FIXTURE_SURVEY_PAGE_MCQ}
65
+ question={mcqQuestion}
49
66
  selectedValue={mcqAnswer}
50
67
  allAnswers={allAnswers}
51
68
  allQuestions={allQuestions}
52
69
  onSelect={setMcqAnswer}
53
70
  />
54
71
  <Question
55
- question={FIXTURE_SURVEY_PAGE_NPS}
72
+ question={matrixQuestion}
73
+ selectedValue={matrixAnswer}
74
+ allAnswers={allAnswers}
75
+ allQuestions={allQuestions}
76
+ onSelect={setMatrixAnswer}
77
+ />
78
+ <Question
79
+ question={npsQuestion}
56
80
  selectedValue={npsAnswer}
57
81
  allAnswers={allAnswers}
58
82
  allQuestions={allQuestions}
@@ -19,27 +19,37 @@ const LIKERT_COLUMNS = [
19
19
  { id: 'c5', label: 'Strongly Agree' },
20
20
  ];
21
21
 
22
- const MATRIX_ANCHOR_LABELS = [
23
- 'Strongly Disagree',
24
- 'Disagree',
25
- 'Neutral',
26
- 'Agree',
27
- 'Strongly Agree',
28
- ];
29
-
30
- const SCALE_COLUMNS_10 = Array.from({ length: 10 }, (_, i) => ({
31
- id: `c${i}`,
32
- label: String(i + 1),
33
- }));
22
+ export const MATRIX_ROW_SINGLE = [{ id: 'r1', statementText: 'Statement Item 1' }];
34
23
 
35
- const MATRIX_ROW_SINGLE = [{ id: 'r1', statementText: 'Statement Item 1' }];
36
-
37
- const MATRIX_ROWS_MULTI = [
24
+ export const MATRIX_ROWS_MULTI = [
38
25
  { id: 'r1', statementText: 'Statement Item 1' },
39
26
  { id: 'r2', statementText: 'Statement Item 2' },
40
27
  { id: 'r3', statementText: 'Statement Item 3' },
41
28
  ];
42
29
 
30
+ /** Bipolar matrix preview rows — left statement + matching right opposite per row. */
31
+ export const BIPOLAR_ROWS_SINGLE = [
32
+ { id: 'r1', statementText: 'Statement 1', oppositeStatementText: 'Opposite 1' },
33
+ ];
34
+
35
+ export const BIPOLAR_ROWS_MULTI = [
36
+ { id: 'r1', statementText: 'Statement 1', oppositeStatementText: 'Opposite 1' },
37
+ { id: 'r2', statementText: 'Statement 2', oppositeStatementText: 'Opposite 2' },
38
+ { id: 'r3', statementText: 'Statement 3', oppositeStatementText: 'Opposite 3' },
39
+ ];
40
+
41
+ const SLIDER_ROW_DEFAULTS = { min: 0, max: 10, step: 1 };
42
+
43
+ export const SLIDER_ROWS_SINGLE = [
44
+ { id: 'r1', statementText: 'Statement Item 1', ...SLIDER_ROW_DEFAULTS },
45
+ ];
46
+
47
+ export const SLIDER_ROWS_MULTI = [
48
+ { id: 'r1', statementText: 'Statement Item 1', ...SLIDER_ROW_DEFAULTS },
49
+ { id: 'r2', statementText: 'Statement Item 2', ...SLIDER_ROW_DEFAULTS },
50
+ { id: 'r3', statementText: 'Statement Item 3', ...SLIDER_ROW_DEFAULTS },
51
+ ];
52
+
43
53
  const SLIDER_TICKS = Array.from({ length: 11 }, (_, i) => ({
44
54
  tickPosition: i,
45
55
  tickLabel: String(i),
@@ -92,7 +102,7 @@ export const FIXTURE_NPS = base({
92
102
  type: 'NPS_SCALE',
93
103
  questionText:
94
104
  'Based on your experience, how likely are you to recommend [Company/Product/Service] to your friends and family?',
95
- buttonStyle: 'standard',
105
+ buttonStyle: 'numbered',
96
106
  minLabel: 'Not Likely At All',
97
107
  maxLabel: 'Extremely Likely',
98
108
  options: Array.from({ length: 11 }, (_, i) => ({
@@ -117,8 +127,8 @@ export const FIXTURE_SURVEY_PAGE_MCQ = base({
117
127
  export const FIXTURE_SURVEY_PAGE_NPS = base({
118
128
  id: 'preview-survey-nps',
119
129
  type: 'NPS_SCALE',
120
- questionNumber: 6,
121
- buttonStyle: 'standard',
130
+ questionNumber: 3,
131
+ buttonStyle: 'numbered',
122
132
  questionText:
123
133
  'Based on your experience, how likely are you to recommend [Company/Product/Service] to your friends and family?',
124
134
  minLabel: 'Not Likely At All',
@@ -129,6 +139,23 @@ export const FIXTURE_SURVEY_PAGE_NPS = base({
129
139
  })),
130
140
  });
131
141
 
142
+ /** Likert matrix with 3 statements — used on survey_page preview for zebra row styling. */
143
+ export const FIXTURE_SURVEY_PAGE_CFM_LIKERT = base({
144
+ id: 'preview-survey-cfm-likert',
145
+ type: 'CFM_MATRIX',
146
+ questionNumber: 2,
147
+ questionText: 'Please rate each statement below.',
148
+ statementLayout: 'likert',
149
+ gridLayout: 'standard',
150
+ transposeTable: false,
151
+ selectionMode: 'single',
152
+ showColumnHeaders: true,
153
+ repeatColumnHeaders: false,
154
+ hasNotApplicableOption: false,
155
+ statementRows: MATRIX_ROWS_MULTI,
156
+ scaleColumns: LIKERT_COLUMNS,
157
+ });
158
+
132
159
  export const FIXTURE_TEXT_SHORT = base({
133
160
  type: 'TEXTFIELD',
134
161
  questionText: 'Question',
@@ -167,13 +194,7 @@ export const FIXTURE_CFM_BIPOLAR = base({
167
194
  showColumnHeaders: false,
168
195
  repeatColumnHeaders: false,
169
196
  hasNotApplicableOption: false,
170
- statementRows: [
171
- {
172
- id: 'r1',
173
- statementText: 'Difficult',
174
- oppositeStatementText: 'Easy',
175
- },
176
- ],
197
+ statementRows: BIPOLAR_ROWS_SINGLE,
177
198
  scaleColumns: Array.from({ length: 5 }, (_, i) => ({ id: `c${i}`, label: '' })),
178
199
  scaleAnchorLabels: ['Hard', '', '', '', 'Easy'],
179
200
  });
@@ -217,13 +238,20 @@ export const FIXTURE_CFM_DROPDOWN = base({
217
238
  scaleColumns: LIKERT_COLUMNS,
218
239
  });
219
240
 
241
+ const LIKERT_ANCHOR_LABELS = LIKERT_COLUMNS.map((c) => c.label);
242
+
243
+ const RATING_TEN_COLUMNS = Array.from({ length: 10 }, (_, i) => ({
244
+ id: `c${i}`,
245
+ label: String(i + 1),
246
+ }));
247
+
220
248
  export const FIXTURE_CSAT_EMOJI = base({
221
249
  type: 'CSAT_MATRIX',
222
250
  displayStyle: 'emoji',
223
251
  gridLayout: 'standard',
224
252
  statementRows: MATRIX_ROW_SINGLE,
225
- scaleColumns: SCALE_COLUMNS_10,
226
- scaleAnchorLabels: MATRIX_ANCHOR_LABELS,
253
+ scaleColumns: LIKERT_COLUMNS,
254
+ scaleAnchorLabels: LIKERT_ANCHOR_LABELS,
227
255
  });
228
256
 
229
257
  export const FIXTURE_CSAT_STAR = base({
@@ -231,8 +259,8 @@ export const FIXTURE_CSAT_STAR = base({
231
259
  displayStyle: 'star',
232
260
  gridLayout: 'standard',
233
261
  statementRows: MATRIX_ROW_SINGLE,
234
- scaleColumns: SCALE_COLUMNS_10,
235
- scaleAnchorLabels: MATRIX_ANCHOR_LABELS,
262
+ scaleColumns: LIKERT_COLUMNS,
263
+ scaleAnchorLabels: LIKERT_ANCHOR_LABELS,
236
264
  });
237
265
 
238
266
  export const FIXTURE_CSAT_NUMBERED = base({
@@ -240,8 +268,8 @@ export const FIXTURE_CSAT_NUMBERED = base({
240
268
  displayStyle: 'numbered',
241
269
  gridLayout: 'standard',
242
270
  statementRows: MATRIX_ROWS_MULTI,
243
- scaleColumns: SCALE_COLUMNS_10,
244
- scaleAnchorLabels: MATRIX_ANCHOR_LABELS,
271
+ scaleColumns: LIKERT_COLUMNS,
272
+ scaleAnchorLabels: LIKERT_ANCHOR_LABELS,
245
273
  });
246
274
 
247
275
  export const FIXTURE_CSAT_GRAPHICS = base({
@@ -249,8 +277,8 @@ export const FIXTURE_CSAT_GRAPHICS = base({
249
277
  displayStyle: 'graphics',
250
278
  gridLayout: 'standard',
251
279
  statementRows: MATRIX_ROW_SINGLE,
252
- scaleColumns: SCALE_COLUMNS_10,
253
- scaleAnchorLabels: MATRIX_ANCHOR_LABELS,
280
+ scaleColumns: LIKERT_COLUMNS,
281
+ scaleAnchorLabels: LIKERT_ANCHOR_LABELS,
254
282
  });
255
283
 
256
284
  export const FIXTURE_CSAT_CAROUSEL = base({
@@ -280,8 +308,8 @@ export const FIXTURE_RATING_STAR = base({
280
308
  gridLayout: 'standard',
281
309
  showColumnHeaders: true,
282
310
  statementRows: MATRIX_ROWS_MULTI,
283
- scaleColumns: SCALE_COLUMNS_10,
284
- scaleAnchorLabels: MATRIX_ANCHOR_LABELS,
311
+ scaleColumns: RATING_TEN_COLUMNS,
312
+ scaleAnchorLabels: LIKERT_ANCHOR_LABELS,
285
313
  });
286
314
 
287
315
  export const FIXTURE_RATING_NUMBERED = base({
@@ -289,8 +317,8 @@ export const FIXTURE_RATING_NUMBERED = base({
289
317
  displayStyle: 'numbered',
290
318
  gridLayout: 'standard',
291
319
  statementRows: MATRIX_ROWS_MULTI,
292
- scaleColumns: SCALE_COLUMNS_10,
293
- scaleAnchorLabels: MATRIX_ANCHOR_LABELS,
320
+ scaleColumns: RATING_TEN_COLUMNS,
321
+ scaleAnchorLabels: LIKERT_ANCHOR_LABELS,
294
322
  });
295
323
 
296
324
  export const FIXTURE_RATING_RADIO = base({
@@ -298,8 +326,8 @@ export const FIXTURE_RATING_RADIO = base({
298
326
  displayStyle: 'standard',
299
327
  gridLayout: 'standard',
300
328
  statementRows: MATRIX_ROW_SINGLE,
301
- scaleColumns: SCALE_COLUMNS_10,
302
- scaleAnchorLabels: MATRIX_ANCHOR_LABELS,
329
+ scaleColumns: RATING_TEN_COLUMNS,
330
+ scaleAnchorLabels: LIKERT_ANCHOR_LABELS,
303
331
  });
304
332
 
305
333
  export const FIXTURE_RATING_EMOJI = base({
@@ -307,8 +335,8 @@ export const FIXTURE_RATING_EMOJI = base({
307
335
  displayStyle: 'emoji',
308
336
  gridLayout: 'standard',
309
337
  statementRows: MATRIX_ROW_SINGLE,
310
- scaleColumns: SCALE_COLUMNS_10,
311
- scaleAnchorLabels: MATRIX_ANCHOR_LABELS,
338
+ scaleColumns: RATING_TEN_COLUMNS,
339
+ scaleAnchorLabels: LIKERT_ANCHOR_LABELS,
312
340
  });
313
341
 
314
342
  export const FIXTURE_RATING_GRAPHICS = base({
@@ -316,8 +344,8 @@ export const FIXTURE_RATING_GRAPHICS = base({
316
344
  displayStyle: 'graphics',
317
345
  gridLayout: 'standard',
318
346
  statementRows: MATRIX_ROW_SINGLE,
319
- scaleColumns: SCALE_COLUMNS_10,
320
- scaleAnchorLabels: MATRIX_ANCHOR_LABELS,
347
+ scaleColumns: RATING_TEN_COLUMNS,
348
+ scaleAnchorLabels: LIKERT_ANCHOR_LABELS,
321
349
  });
322
350
 
323
351
  export const FIXTURE_RATING_SLIDER = base({
@@ -339,10 +367,7 @@ export const FIXTURE_RATING_SLIDER = base({
339
367
 
340
368
  export const FIXTURE_SLIDER = base({
341
369
  type: 'SLIDER_MATRIX',
342
- statementRows: [
343
- { id: 'r1', statementText: 'Statement Item 1', min: 0, max: 10, step: 1 },
344
- { id: 'r2', statementText: 'Statement Item 2', min: 0, max: 10, step: 1 },
345
- ],
370
+ statementRows: SLIDER_ROWS_MULTI.slice(0, 2),
346
371
  scaleAnchorLabels: ['Label 1', 'Label 2'],
347
372
  ticks: {
348
373
  count: 11,
@@ -9,8 +9,14 @@ body {
9
9
  font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
10
10
  color: #262626;
11
11
  background-color: #ffffff;
12
+ max-width: 100%;
13
+ overflow-x: hidden;
14
+ box-sizing: border-box;
12
15
  }
13
16
 
14
17
  #root {
15
18
  min-height: 100vh;
19
+ max-width: 100%;
20
+ overflow-x: hidden;
21
+ box-sizing: border-box;
16
22
  }