@explorer02/cfm-survey-sdk 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +18 -18
- package/dist/cli/index.mjs +14 -14
- package/dist/index.d.mts +465 -580
- package/dist/index.d.ts +465 -580
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +6 -4
- package/postinstall.js +96 -69
- package/templates/AGENT.md +18 -8
- package/templates/docs/00-integration/analytics-events-catalog.md +54 -0
- package/templates/docs/00-integration/answer-logic-and-navigation.md +50 -0
- package/templates/docs/00-integration/aws-deploy.md +281 -0
- package/templates/docs/00-integration/client-integration-guide.md +351 -0
- package/templates/docs/00-integration/component-checklist.md +55 -22
- package/templates/docs/00-integration/constraints.md +49 -4
- package/templates/docs/00-integration/custom-field-logic-and-navigation.md +174 -0
- package/templates/docs/00-integration/display-logic-and-navigation.md +64 -0
- package/templates/docs/00-integration/end-page-logic.md +64 -0
- 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 +98 -0
- package/templates/docs/00-integration/placeholders-and-tokens.md +116 -0
- package/templates/docs/00-integration/progress.md +61 -0
- package/templates/docs/00-integration/question-display-variants.md +65 -0
- package/templates/docs/00-integration/question-numbering.md +51 -0
- package/templates/docs/00-integration/question-type-sdk-matrix.md +244 -0
- package/templates/docs/00-integration/setup.md +84 -0
- package/templates/docs/00-integration/skip-logic-and-navigation.md +80 -0
- package/templates/docs/00-integration/survey-lifecycle-analytics.md +94 -0
- package/templates/docs/00-integration/useSurveySDK.md +151 -23
- package/templates/docs/01-components/01-survey-page.md +76 -54
- package/templates/docs/01-components/02-question.md +61 -58
- package/templates/docs/01-components/03-rating-scale.md +11 -1
- package/templates/docs/01-components/04-star-rating-scale.md +45 -0
- package/templates/docs/01-components/05-csat-matrix-scale.md +20 -11
- package/templates/docs/01-components/06-likert-matrix-scale.md +14 -4
- package/templates/docs/01-components/07-slider-matrix-scale.md +11 -2
- package/templates/docs/01-components/08-file-upload-scale.md +23 -51
- package/templates/docs/01-components/09-custom-slider-track.md +2 -2
- package/templates/docs/01-components/11-progress-bar.md +10 -4
- package/templates/docs/01-components/13-matrix-dropdown.md +29 -32
- package/templates/docs/01-components/14-intro-page.md +34 -0
- package/templates/docs/01-components/15-end-page.md +31 -0
- package/templates/docs/01-components/16-paused-page.md +27 -0
- package/templates/docs/01-components/17-heatmap-scale.md +75 -0
- package/templates/docs/01-components/18-rank-order-scale.md +93 -0
- package/templates/docs/01-components/README.md +42 -36
- package/templates/docs/02-reference/config-field-index.md +141 -139
- package/templates/docs/02-reference/exports.md +149 -0
- package/templates/docs/02-reference/question-types/01-rating.md +31 -32
- package/templates/docs/02-reference/question-types/04-csat.md +51 -42
- package/templates/docs/02-reference/question-types/05-rating-scale.md +16 -11
- package/templates/docs/02-reference/question-types/06-slider.md +2 -2
- package/templates/docs/02-reference/question-types/07-matrix-cfm.md +44 -30
- package/templates/docs/02-reference/question-types/08-matrix-csat.md +4 -26
- package/templates/docs/02-reference/question-types/09-matrix-rating.md +29 -13
- package/templates/docs/02-reference/question-types/10-slider-matrix.md +15 -4
- package/templates/docs/02-reference/question-types/11-file-upload.md +43 -17
- package/templates/docs/02-reference/question-types/12-heatmap.md +59 -0
- package/templates/docs/02-reference/question-types/13-rank-order.md +101 -0
- package/templates/docs/02-reference/question-types/README.md +96 -47
- package/templates/docs/02-reference/routing-table.md +33 -18
- package/templates/docs/02-reference/value-derivation.md +63 -0
- package/templates/docs/03-ui-specs/00-question-shell.md +19 -88
- package/templates/docs/03-ui-specs/01-rating.md +18 -52
- package/templates/docs/03-ui-specs/02-radio.md +80 -37
- package/templates/docs/03-ui-specs/03-text.md +2 -2
- package/templates/docs/03-ui-specs/04-csat.md +68 -33
- package/templates/docs/03-ui-specs/05-rating-scale.md +21 -18
- package/templates/docs/03-ui-specs/06-slider.md +3 -1
- package/templates/docs/03-ui-specs/07-matrix-cfm.md +44 -25
- package/templates/docs/03-ui-specs/08-matrix-csat-rating.md +19 -43
- package/templates/docs/03-ui-specs/09-slider-matrix.md +18 -1
- package/templates/docs/03-ui-specs/10-file-upload.md +42 -21
- package/templates/docs/03-ui-specs/11-text-and-media.md +29 -29
- package/templates/docs/03-ui-specs/12-survey-chrome.md +58 -14
- package/templates/docs/03-ui-specs/13-heatmap.md +80 -0
- package/templates/docs/03-ui-specs/14-rank-order.md +128 -0
- package/templates/docs/03-ui-specs/README.md +16 -14
- package/templates/docs/03-ui-specs/shared/custom-slider-track.md +28 -35
- package/templates/docs/03-ui-specs/shared/matrix-dropdown.md +5 -0
- package/templates/docs/MANIFEST.json +476 -69
- package/templates/docs/index.md +82 -30
- package/templates/docs/templates/CustomSliderTrack.tsx +144 -0
- package/templates/docs/templates/MatrixDropdown.tsx +216 -0
- package/templates/docs/templates/Question.tsx +221 -154
- package/templates/docs/templates/RankOrderScale.tsx +353 -0
- package/templates/docs/templates/deploy-to-aws.sh +114 -0
- package/templates/docs/templates/heatmapCoords.ts +58 -0
- package/templates/docs/templates/implementation_plan.md +126 -67
- package/templates/docs/templates/survey-inventory.schema.json +77 -0
- package/templates/docs/templates/surveyUiIcons.tsx +52 -0
- package/templates/docs/templates/verify-agent-build.sh +85 -0
- package/templates/docs/01-components/04-csat-scale.md +0 -40
- package/templates/docs/02-question-types/01-rating.md +0 -44
|
@@ -1,102 +1,33 @@
|
|
|
1
1
|
# UI Spec: Question Shell (`Question.tsx`)
|
|
2
2
|
|
|
3
3
|
> **Canonical template:** [`templates/Question.tsx`](../templates/Question.tsx) — copy this dispatcher verbatim
|
|
4
|
-
> **
|
|
5
|
-
> **SDK:** All types extend `QuestionBase` from `fetchSurvey/types/base.ts`
|
|
4
|
+
> **SDK:** Each question type is standalone — see `fetchSurvey/types/*.ts`
|
|
6
5
|
> **Component blueprint:** `01-components/02-question.md`
|
|
7
|
-
> **Required files:** `00-integration/component-checklist.md`
|
|
8
6
|
|
|
9
|
-
## Matrix
|
|
7
|
+
## Matrix routing (CRITICAL)
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
Each matrix variant has its own `question.type` — **no `subType`**:
|
|
12
10
|
|
|
13
11
|
- `CFM_MATRIX` → `LikertMatrixScale`
|
|
14
12
|
- `CSAT_MATRIX` | `RATING_MATRIX` → `CsatMatrixScale`
|
|
13
|
+
- `SLIDER_MATRIX` → `SliderMatrixScale`
|
|
15
14
|
|
|
16
|
-
Never use a `default` fallback that shows `"not yet implemented"
|
|
15
|
+
Never use a `default` fallback that shows `"not yet implemented"`.
|
|
17
16
|
|
|
18
|
-
##
|
|
17
|
+
## Shell fields (duplicated on every question type)
|
|
19
18
|
|
|
20
19
|
| Field | Implemented | UI impact |
|
|
21
20
|
|-------|-------------|-----------|
|
|
22
|
-
| `id` | ✅ | `section id={question.id}` —
|
|
23
|
-
| `
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
26
|
-
| `
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
│ [optional description HTML] │
|
|
36
|
-
│ ┌─ type-specific scale (see per-type specs) ─────────────────────┐ │
|
|
37
|
-
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
38
|
-
│ [validation error banner if validationError prop] │
|
|
39
|
-
└─────────────────────────────────────────────────────────────────────────┘
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
## Dispatcher Routing
|
|
43
|
-
|
|
44
|
-
| `question.type` | Condition | Component |
|
|
45
|
-
|-----------------|-----------|-----------|
|
|
46
|
-
| `rating` | — | `RatingScale` + label row in Question |
|
|
47
|
-
| `radio` | — | Inline card radios in Question |
|
|
48
|
-
| `text` | — | Inline textarea in Question |
|
|
49
|
-
| `csat` | — | `CsatScale` + min/max labels in Question |
|
|
50
|
-
| `matrix` | `CSAT_MATRIX` \| `RATING_MATRIX` | `CsatMatrixScale` |
|
|
51
|
-
| `matrix` | `CFM_MATRIX` | `LikertMatrixScale` |
|
|
52
|
-
| `slider_matrix` | — | `SliderMatrixScale` |
|
|
53
|
-
| `file_upload` | — | `FileUploadScale` |
|
|
54
|
-
| `text_and_media` | `mediaUrl` present | Inline media in Question |
|
|
55
|
-
| `slider` | — | ❌ Not routed — see `06-slider.md` |
|
|
56
|
-
| `rating_scale` | — | ❌ Not routed — see `05-rating-scale.md` |
|
|
57
|
-
|
|
58
|
-
## Label Positioning (rating)
|
|
59
|
-
|
|
60
|
-
Rendered in Question before `RatingScale`:
|
|
61
|
-
|
|
62
|
-
```tsx
|
|
63
|
-
// midLabel CSS left:
|
|
64
|
-
left: `${(midLabelIndex / (options.length - 1)) * 100}%`
|
|
65
|
-
// with -translate-x-1/2 for centering
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
minLabel: `absolute left-0`, maxLabel: `absolute right-0`, container `relative h-6 text-xs font-semibold text-gray-500`.
|
|
69
|
-
|
|
70
|
-
## Label Positioning (csat)
|
|
71
|
-
|
|
72
|
-
minLabel / maxLabel: `flex justify-between text-xs font-semibold text-gray-500` above `CsatScale`.
|
|
73
|
-
|
|
74
|
-
## Validation Error Banner
|
|
75
|
-
|
|
76
|
-
```tsx
|
|
77
|
-
className="mt-4 rounded-[4px] border border-[#333333] bg-[#d9d9d9] py-3.5 px-4"
|
|
78
|
-
// text: text-[13px] font-bold text-gray-900
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
## Props Contract
|
|
82
|
-
|
|
83
|
-
```typescript
|
|
84
|
-
type QuestionProps = {
|
|
85
|
-
question: SurveyQuestion;
|
|
86
|
-
selectedValue?: AnswerValue;
|
|
87
|
-
validationError?: string;
|
|
88
|
-
onSelect: (value: AnswerValue) => void;
|
|
89
|
-
};
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
Wire `onSelect` → `onAction({ type: 'CHANGE', payload: { questionId, value } })`.
|
|
93
|
-
|
|
94
|
-
## Agent Checklist
|
|
95
|
-
|
|
96
|
-
- [ ] `id={question.id}` on outermost `<section>`
|
|
97
|
-
- [ ] HTML rendering for text and description
|
|
98
|
-
- [ ] Copied from `templates/Question.tsx` — no stub/default fallback branches
|
|
99
|
-
- [ ] Matrix routes by `subType`, not type alone
|
|
100
|
-
- [ ] `slider_matrix` → `SliderMatrixScale`; `file_upload` → `FileUploadScale`
|
|
101
|
-
- [ ] Validation error banner below scale
|
|
102
|
-
- [ ] Rating/csat endpoint labels positioned per formulas above
|
|
21
|
+
| `id` | ✅ | `section id={question.id}` — scroll-to-error |
|
|
22
|
+
| `questionText` | ✅ | Heading via `dangerouslySetInnerHTML`; when the survey defines `displayVariants`, the SDK resolves the active variant text on `state.currentQuestions` before render |
|
|
23
|
+
| `questionDescription` | ✅ | Optional HTML below title |
|
|
24
|
+
| `questionHint` | ❌ | Optional hint below description |
|
|
25
|
+
| `required` | ✅ | Red `*` after title |
|
|
26
|
+
| `requiredErrorMessage` | ✅ | Validation error banner |
|
|
27
|
+
| `questionNumber` | ✅ | Prefix before title (e.g. `3.`); SDK resolves active-path ordinals on `state.currentQuestions` (see [`question-numbering.md`](../../00-integration/question-numbering.md)) |
|
|
28
|
+
| `containerMedia` | ❌ | Side media — `containerMedia?.url` etc.; not in reference client |
|
|
29
|
+
|
|
30
|
+
## SDK hook
|
|
31
|
+
|
|
32
|
+
- Answers: `state.answers` (not `state.values`)
|
|
33
|
+
- Change action: `{ type: 'CHANGE', payload: { questionId, answerValue } }`
|
|
@@ -1,80 +1,46 @@
|
|
|
1
|
-
# UI Spec: NPS Rating (`type: '
|
|
1
|
+
# UI Spec: NPS Rating (`type: 'NPS_SCALE'`)
|
|
2
2
|
|
|
3
|
-
> **
|
|
4
|
-
> **SDK type:** `RatingQuestion` — `fetchSurvey/types/rating.ts`
|
|
3
|
+
> **SDK type:** `NpsQuestion` — `fetchSurvey/types/nps.ts`
|
|
5
4
|
> **Component blueprint:** `01-components/03-rating-scale.md`
|
|
5
|
+
> **Value derivation:** `02-reference/value-derivation.md`
|
|
6
6
|
|
|
7
7
|
## Config Inventory
|
|
8
8
|
|
|
9
9
|
| Field | Implemented | UI impact |
|
|
10
10
|
|-------|-------------|-----------|
|
|
11
11
|
| `options[]` | ✅ | Badge row + radio row (dual layer) |
|
|
12
|
-
| `options[].
|
|
13
|
-
| `options[].
|
|
14
|
-
|
|
|
12
|
+
| `options[].optionLabel` | ✅ | Displayed in badge |
|
|
13
|
+
| `options[].id` | ✅ | Stable option id (display only) |
|
|
14
|
+
| Submit value | ✅ derived | Parse `optionLabel` as number or use index — see value-derivation |
|
|
15
|
+
| Badge color | ✅ derived | Traffic-light table from numeric value |
|
|
15
16
|
| `minLabel` | ✅ | Rendered in Question.tsx above scale |
|
|
16
17
|
| `midLabel` | ✅ | Absolute positioned in Question.tsx |
|
|
17
18
|
| `maxLabel` | ✅ | Absolute positioned in Question.tsx |
|
|
18
19
|
| `midLabelIndex` | ✅ | Mid label `left` percentage |
|
|
19
|
-
| `reverseScaleOrder` |
|
|
20
|
-
| `buttonVariant` | ❌ | Client
|
|
20
|
+
| `reverseScaleOrder` | ✅ | Reverse option index before selection/color lookup |
|
|
21
|
+
| `buttonVariant` | ❌ | Client uses dual-layer radio layout |
|
|
21
22
|
|
|
22
|
-
##
|
|
23
|
+
## Answer logic on scale points
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
[minLabel] [midLabel @ %] [maxLabel]
|
|
26
|
-
┌──┬──┬──┬── ... ──┬──┐ ← Row 1: colored badges (NOT clickable)
|
|
27
|
-
│0 │1 │2 │3 │ ... │10│ grid repeat(options.length), h-5 w-5 sm:h-6 w-6
|
|
28
|
-
└──┴──┴──┴── ... ──┴──┘ rounded-[3px], white bold text, option.color bg
|
|
29
|
-
┌──────────────────────────────────────────────┐
|
|
30
|
-
│ ○ ○ ● ○ ... ○ │ ← Row 2: gray track bg-[#f2f2f2]
|
|
31
|
-
└──────────────────────────────────────────────┘ h-11 sm:h-12, rounded-md
|
|
32
|
-
↑ selected cell: inner bg-[#fbe8f3]
|
|
33
|
-
radio: border-[4px] border-[#e20074] bg-white
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## Config Decision Tree
|
|
37
|
-
|
|
38
|
-
```
|
|
39
|
-
rating
|
|
40
|
-
└── RatingScale (always dual-layer in reference client)
|
|
41
|
-
├── Row 1: display-only colored badges
|
|
42
|
-
└── Row 2: sr-only radio + custom circle per cell
|
|
43
|
-
```
|
|
25
|
+
When scale points have answer logic, filter with `getVisibleScaleColumnsForAnswers(question, answers, allQuestions, customFieldValues?)` before render.
|
|
44
26
|
|
|
45
|
-
|
|
27
|
+
## Implementation Recipe — reverseScaleOrder
|
|
46
28
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
## Interactive States
|
|
50
|
-
|
|
51
|
-
| State | Badge row | Track cell | Radio circle |
|
|
52
|
-
|-------|-----------|------------|--------------|
|
|
53
|
-
| Default | Full color | Transparent inner | `border-2 border-gray-300` |
|
|
54
|
-
| Hover | — | `hover:bg-gray-200/40` | — |
|
|
55
|
-
| Selected | Unchanged | `bg-[#fbe8f3]` | `border-[4px] border-[#e20074]` |
|
|
56
|
-
|
|
57
|
-
## Tooltips
|
|
58
|
-
|
|
59
|
-
Recommended: `title={option.label}` on each track `<label>`. Not in reference client yet.
|
|
29
|
+
When `reverseScaleOrder === true`, map visual index `i` to stored index `options.length - 1 - i` before deriving submit value and badge color.
|
|
60
30
|
|
|
61
31
|
## Answer Mutation
|
|
62
32
|
|
|
63
33
|
```typescript
|
|
64
|
-
|
|
65
|
-
//
|
|
34
|
+
const value = npsValueFromOption(option, index);
|
|
35
|
+
onSelect(value) // number
|
|
36
|
+
// → onAction CHANGE { questionId, answerValue: number }
|
|
66
37
|
```
|
|
67
38
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
- `role="radiogroup"` on wrapper
|
|
71
|
-
- Native `<input type="radio" className="sr-only">` per cell
|
|
72
|
-
- `name={questionId}` groups radios
|
|
39
|
+
See [`02-reference/value-derivation.md`](../02-reference/value-derivation.md).
|
|
73
40
|
|
|
74
41
|
## Agent Checklist
|
|
75
42
|
|
|
43
|
+
- [ ] Derive submit value and badge color in UI — types omit `optionValue` / `accentColor`
|
|
76
44
|
- [ ] Two-row grid with `gridTemplateColumns: repeat(options.length, 1fr)`
|
|
77
|
-
- [ ] Badges use `option.color`, not hardcoded NPS colors unless option lacks color
|
|
78
45
|
- [ ] Only track row is clickable
|
|
79
46
|
- [ ] min/mid/max labels in Question wrapper with midLabelIndex formula
|
|
80
|
-
- [ ] Selected state: pink inner track + thick magenta radio ring
|
|
@@ -1,57 +1,100 @@
|
|
|
1
|
-
# UI Spec:
|
|
1
|
+
# UI Spec: MCQ (`type: 'MCQ'`)
|
|
2
2
|
|
|
3
|
-
> **
|
|
4
|
-
> **SDK type:** `RadioQuestion` — `fetchSurvey/types/radio.ts`
|
|
3
|
+
> **SDK type:** `McqQuestion` — `fetchSurvey/types/mcq.ts`
|
|
5
4
|
> **Component blueprint:** `01-components/02-question.md`
|
|
5
|
+
> **Dispatcher:** inline in `Question.tsx` (see [`templates/Question.tsx`](../templates/Question.tsx))
|
|
6
6
|
|
|
7
7
|
## Config Inventory
|
|
8
8
|
|
|
9
|
-
| Field |
|
|
10
|
-
|
|
11
|
-
| `options[]` |
|
|
12
|
-
| `options[].
|
|
13
|
-
| `options[].
|
|
14
|
-
| `
|
|
9
|
+
| Field | UI impact |
|
|
10
|
+
|-------|-----------|
|
|
11
|
+
| `options[]` | One card per visible option |
|
|
12
|
+
| `options[].optionLabel` | HTML via `dangerouslySetInnerHTML` |
|
|
13
|
+
| `options[].id` | **Submit value** — answer is `option.id` |
|
|
14
|
+
| `selectionMode` | `'single'` → radio cards; `'multiple'` → checkbox cards + `string[]` answer |
|
|
15
|
+
| `defaultOptionIds?` | Pre-selected on load (SDK seeds `state.answers`) |
|
|
16
|
+
| `minSelections?` / `maxSelections?` | Answer-condition count (SDK validates on NEXT) |
|
|
17
|
+
| `answerConditionErrorMessage?` | Custom validation banner text |
|
|
18
|
+
| `requiredErrorMessage?` | Required-field error text |
|
|
15
19
|
|
|
16
|
-
##
|
|
20
|
+
## Selection mode (not `isMultiSelect`)
|
|
17
21
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
Use `question.selectionMode` from the SDK — **never** `isMultiSelect`.
|
|
23
|
+
|
|
24
|
+
| `selectionMode` | UI | Answer shape |
|
|
25
|
+
|-----------------|-----|--------------|
|
|
26
|
+
| `'single'` (default) | Radio cards; one selected | `string` (option `id`) |
|
|
27
|
+
| `'multiple'` | Checkbox cards; toggle on click | `string[]` of option ids |
|
|
28
|
+
|
|
29
|
+
Multi-select toggle pattern:
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
const handleToggle = (optionId: string) => {
|
|
33
|
+
const next = selectedIds.includes(optionId)
|
|
34
|
+
? selectedIds.filter(id => id !== optionId)
|
|
35
|
+
: question.maxSelections && selectedIds.length >= question.maxSelections
|
|
36
|
+
? selectedIds // block add at max
|
|
37
|
+
: [...selectedIds, optionId];
|
|
38
|
+
onSelect(next);
|
|
39
|
+
};
|
|
25
40
|
```
|
|
26
41
|
|
|
27
|
-
|
|
42
|
+
Filter options before render: `getVisibleMcqOptionsForAnswers(question, allAnswers, allQuestions, customFieldValues?)`.
|
|
28
43
|
|
|
29
|
-
|
|
30
|
-
|-------|--------|------------|-------|
|
|
31
|
-
| Unselected | `#e5e5e5` | `white` | `border-gray-400`, empty |
|
|
32
|
-
| Unselected hover | `#e5e5e5` | `hover:bg-gray-50/50` | — |
|
|
33
|
-
| Selected | `#e20074` | `#fdf2f8` | `border-2 border-[#e20074]` + inner dot `h-2.5 w-2.5 bg-[#e20074]` |
|
|
44
|
+
---
|
|
34
45
|
|
|
35
|
-
##
|
|
46
|
+
## Implementation Recipe — Default answers
|
|
36
47
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
48
|
+
| Concern | SDK behavior | Client UI contract |
|
|
49
|
+
|---------|--------------|-------------------|
|
|
50
|
+
| **Config** | API `DEFAULT_ANSWER` → `question.defaultOptionIds?: string[]` | Inventory in implementation plan §4b |
|
|
51
|
+
| **Initial load** | `buildInitialAnswers` / `applyMcqDefaults` writes into `state.answers` | Bind checked state to `selectedValue` (`state.answers[question.id]`) |
|
|
52
|
+
| **Single-select** | Default = `defaultOptionIds[0]` | Pre-selected radio when present in `state.answers` |
|
|
53
|
+
| **Multi-select** | Default = full `defaultOptionIds` array | Pre-checked boxes for each id |
|
|
54
|
+
| **User override** | `applyMcqDefaults` never overwrites user selection | Toggle via `onAction(CHANGE)` |
|
|
55
|
+
| **Validation / submit** | SDK re-applies defaults in `validateCurrentPage` and submit if empty | Client does **not** re-apply defaults at NEXT/SUBMIT |
|
|
56
|
+
| **Answer logic** | Hidden defaults may exist in `state.answers` until SDK sanitizes | Show checked state only for **visible** options |
|
|
57
|
+
| **Template fallback** | `Question.tsx` uses `selectedValue ?? defaultOptionIds` defensively | Prefer `state.answers`; fallback is display-only |
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Implementation Recipe — Answer conditions
|
|
62
|
+
|
|
63
|
+
When API `ANSWER_CONDITION` is enabled, SDK maps:
|
|
64
|
+
|
|
65
|
+
- `minSelections` — minimum selected count (multi-select)
|
|
66
|
+
- `maxSelections` — maximum selected count (multi-select)
|
|
67
|
+
- `answerConditionErrorMessage` — shown in `state.validationErrors[questionId]`
|
|
68
|
+
|
|
69
|
+
**Client must:**
|
|
70
|
+
|
|
71
|
+
1. Render `validationError` banner when SDK sets an error (do not duplicate count validation).
|
|
72
|
+
2. Block adding options when `selectedIds.length >= maxSelections` in multi-select UI.
|
|
73
|
+
3. Pass through `onAction(CHANGE)` with updated array — SDK validates on NEXT.
|
|
74
|
+
|
|
75
|
+
Skip logic may use `NUM_OF_SELECTED_OPTIONS` — see [`logic-fields-catalog.md`](../00-integration/logic-fields-catalog.md).
|
|
76
|
+
|
|
77
|
+
---
|
|
44
78
|
|
|
45
79
|
## Answer Mutation
|
|
46
80
|
|
|
47
81
|
```typescript
|
|
48
|
-
|
|
82
|
+
// Single-select
|
|
83
|
+
onSelect(option.id)
|
|
84
|
+
onAction({ type: 'CHANGE', payload: { questionId, answerValue: string } })
|
|
85
|
+
|
|
86
|
+
// Multi-select
|
|
87
|
+
onSelect(selectedIds) // string[]
|
|
88
|
+
onAction({ type: 'CHANGE', payload: { questionId, answerValue: string[] } })
|
|
49
89
|
```
|
|
50
90
|
|
|
51
91
|
## Agent Checklist
|
|
52
92
|
|
|
53
|
-
- [ ]
|
|
54
|
-
- [ ]
|
|
55
|
-
- [ ] Selected: magenta border + pink
|
|
56
|
-
- [ ]
|
|
57
|
-
- [ ]
|
|
93
|
+
- [ ] Use `selectionMode`, not `isMultiSelect`
|
|
94
|
+
- [ ] Full-width card options; custom circular radio / checkbox indicators
|
|
95
|
+
- [ ] Selected: magenta border `#e20074` + pink bg `#fdf2f8`
|
|
96
|
+
- [ ] Multi-select: `string[]` answer; block at `maxSelections`
|
|
97
|
+
- [ ] Defaults: bind to `state.answers`; pre-selected visual on load when `defaultOptionIds` set
|
|
98
|
+
- [ ] `getVisibleMcqOptionsForAnswers` with optional `customFieldValues` 4th arg
|
|
99
|
+
- [ ] Store `option.id` — never label text
|
|
100
|
+
- [ ] Matrix row: [`question-type-sdk-matrix.md#mcq`](../00-integration/question-type-sdk-matrix.md#mcq)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
# UI Spec: Text Field (`type: '
|
|
1
|
+
# UI Spec: Text Field (`type: 'TEXTFIELD'`)
|
|
2
2
|
|
|
3
3
|
> **Reference:** `apps/client/src/components/Question.tsx` (inline)
|
|
4
|
-
> **SDK type:** `
|
|
4
|
+
> **SDK type:** `TextInputQuestion` — `fetchSurvey/types/text.ts`
|
|
5
5
|
|
|
6
6
|
## Config Inventory
|
|
7
7
|
|
|
@@ -1,57 +1,92 @@
|
|
|
1
|
-
# UI Spec: CSAT (`type: '
|
|
1
|
+
# UI Spec: CSAT Matrix Grid (`type: 'CSAT_MATRIX'`)
|
|
2
2
|
|
|
3
|
-
> **
|
|
3
|
+
> **Implement from this spec** (npm installs). Monorepo reference: `apps/client/src/components/CsatMatrixScale.tsx`
|
|
4
4
|
> **SDK type:** `CsatQuestion` — `fetchSurvey/types/csat.ts`
|
|
5
|
-
> **Component blueprint:** `01-components/
|
|
5
|
+
> **Component blueprint:** `01-components/05-csat-matrix-scale.md`
|
|
6
6
|
|
|
7
7
|
## Config Inventory
|
|
8
8
|
|
|
9
9
|
| Field | Implemented | UI impact |
|
|
10
10
|
|-------|-------------|-----------|
|
|
11
|
-
| `
|
|
12
|
-
| `
|
|
13
|
-
| `
|
|
14
|
-
| `
|
|
15
|
-
| `
|
|
16
|
-
| `
|
|
11
|
+
| `statementRows[]`, `scaleColumns[]` | ✅ | Grid / carousel / dropdown data |
|
|
12
|
+
| `displayStyle` | ✅ | Grid vs vertical list routing |
|
|
13
|
+
| `gridLayout` | ✅ | carousel vs standard vs dropdown |
|
|
14
|
+
| `hasNotApplicableOption` | ✅ | Checkbox below row (vertical) or N/A column (grid) |
|
|
15
|
+
| `reverseScaleOrder` | ✅ | Flip emoji/star/number index |
|
|
16
|
+
| `scaleAnchorLabels[]` | ✅ | Anchor row above columns |
|
|
17
17
|
|
|
18
18
|
## Config Decision Tree
|
|
19
19
|
|
|
20
20
|
```
|
|
21
|
-
|
|
22
|
-
├──
|
|
23
|
-
|
|
24
|
-
│
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
CSAT_MATRIX
|
|
22
|
+
├── gridLayout === 'carousel' → CsatMatrixCarousel
|
|
23
|
+
├── displayStyle in (dropdown, selectbox) → CsatMatrixVerticalList
|
|
24
|
+
│ ├── dropdown → MatrixDropdown placeholder "Select..."
|
|
25
|
+
│ ├── selectbox → full-width cards #fdf2f8 selected
|
|
26
|
+
│ └── else → card radio list
|
|
27
|
+
└── else → CsatMatrixGrid
|
|
28
|
+
├── displayStyle === 'emoji' → client-side emoji icons (react-icons) + hover scale + ring when selected
|
|
29
|
+
├── displayStyle === 'star' → opacity 0.5 unselected; filled/empty icons
|
|
30
|
+
├── displayStyle === 'numbered' → 40×40 badge; selected magenta fill
|
|
31
|
+
└── displayStyle === 'graphics' → CustomSliderTrack per row (carousel)
|
|
31
32
|
```
|
|
32
33
|
|
|
33
|
-
##
|
|
34
|
+
## Grid Structure
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
| Selected | `border-[#e20074]` | `#fdf2f8` | `scale-110` |
|
|
40
|
-
| Star mode | transparent | transparent | no border/bg on button |
|
|
36
|
+
- Row label: `180px` fixed column
|
|
37
|
+
- N/A column: `56px` when `hasNotApplicableOption`
|
|
38
|
+
- Alternating row backgrounds
|
|
39
|
+
- Column headers OR `scaleAnchorLabels[]` anchor row with percentage positioning
|
|
41
40
|
|
|
42
|
-
##
|
|
41
|
+
## Carousel — Implementation Recipe
|
|
43
42
|
|
|
44
|
-
|
|
43
|
+
Same rules as CFM carousel (prev/next disabled at bounds, dot click jumps row, no auto-advance). CSAT uses **pink** active dot `#e20074` (not Likert blue).
|
|
44
|
+
|
|
45
|
+
## N/A mutex
|
|
46
|
+
|
|
47
|
+
| Layout | Behavior |
|
|
48
|
+
|--------|----------|
|
|
49
|
+
| Grid N/A column | Selecting N/A clears row; selecting column clears N/A |
|
|
50
|
+
| Vertical checkbox | `hasNotApplicableOption` checkbox below row sets `null` |
|
|
51
|
+
|
|
52
|
+
## Emoji / star / numbered — react-icons mapping
|
|
53
|
+
|
|
54
|
+
Copy [`templates/surveyUiIcons.tsx`](../templates/surveyUiIcons.tsx) or implement `getEmojiForIndex(index, totalColumns)`.
|
|
55
|
+
|
|
56
|
+
**5-point CSAT emoji index → icon key:**
|
|
57
|
+
|
|
58
|
+
| Column index (0-based) | Sentiment | Maps to 11-point scale key |
|
|
59
|
+
|--------------------------|-----------|----------------------------|
|
|
60
|
+
| 0 | Very negative | 3 |
|
|
61
|
+
| 1 | Negative | 4 |
|
|
62
|
+
| 2 | Neutral | 6 |
|
|
63
|
+
| 3 | Positive | 7 |
|
|
64
|
+
| 4 | Very positive | 9 |
|
|
65
|
+
|
|
66
|
+
**Star mode:** filled `FaStar` `#e20074` when selected; `FaRegStar` `#d1d5db` opacity 0.5 when unselected; hover ring `ring-2 ring-[#e20074] ring-offset-2`.
|
|
67
|
+
|
|
68
|
+
**Numbered mode:** 40×40 badge; selected = magenta fill `#e20074` white text; unselected = gray border.
|
|
69
|
+
|
|
70
|
+
**Graphics mode:** `CustomSliderTrack` per row with `ticks={scaleColumns.length}`, `tickLabels={scaleColumns.map(c => c.label)}`, `sliderType="graphics"`.
|
|
71
|
+
|
|
72
|
+
Apply `reverseScaleOrder` by flipping column index before icon lookup.
|
|
73
|
+
|
|
74
|
+
## Scale column labels on slider (Day 19)
|
|
75
|
+
|
|
76
|
+
Pass `tickLabels={visibleScaleColumns.map(col => col.label)}` to `CustomSliderTrack` so tooltips and graphics thumb show mapped labels, not raw numeric indices.
|
|
45
77
|
|
|
46
78
|
## Answer Mutation
|
|
47
79
|
|
|
80
|
+
Store **`column.id`** via `matrixColumnStoredValue(col)` — see `02-reference/value-derivation.md`.
|
|
81
|
+
|
|
48
82
|
```typescript
|
|
49
|
-
onSelect(
|
|
83
|
+
onSelect(MatrixRowAnswers)
|
|
84
|
+
onAction({ type: 'CHANGE', payload: { questionId, answerValue: MatrixRowAnswers } })
|
|
50
85
|
```
|
|
51
86
|
|
|
52
87
|
## Agent Checklist
|
|
53
88
|
|
|
54
|
-
- [ ]
|
|
55
|
-
- [ ]
|
|
56
|
-
- [ ]
|
|
57
|
-
- [ ]
|
|
89
|
+
- [ ] All displayStyle × gridLayout branches
|
|
90
|
+
- [ ] reverseScaleOrder on emoji/star/numbered
|
|
91
|
+
- [ ] Carousel pink dot indicators
|
|
92
|
+
- [ ] Vertical selectbox cards match MCQ card styling
|
|
@@ -1,49 +1,52 @@
|
|
|
1
|
-
# UI Spec: Rating
|
|
1
|
+
# UI Spec: Star Rating (`type: 'star_rating'`)
|
|
2
2
|
|
|
3
|
-
> **
|
|
4
|
-
>
|
|
5
|
-
> **
|
|
3
|
+
> **DEPRECATED:** Not in the current `SurveyQuestion` union. API RATING pages map to `NPS_SCALE` or CSAT/RATING matrix display styles. See `01-rating.md` and matrix specs instead.
|
|
4
|
+
>
|
|
5
|
+
> **SDK type:** `StarRatingQuestion` — `fetchSurvey/types/starRating.ts`
|
|
6
|
+
> **Component blueprint:** `01-components/04-star-rating-scale.md`
|
|
7
|
+
> **Routed in template:** `Question.tsx` → `RatingScale` for `star_rating`
|
|
6
8
|
|
|
7
9
|
## Config Inventory
|
|
8
10
|
|
|
9
11
|
| Field | Implemented | UI impact |
|
|
10
12
|
|-------|-------------|-----------|
|
|
11
|
-
| `options[]` |
|
|
12
|
-
| `scaleStyle` |
|
|
13
|
-
| `minLabel` / `midLabel` / `maxLabel` |
|
|
14
|
-
| `midLabelIndex` |
|
|
15
|
-
| `reverseScaleOrder` |
|
|
13
|
+
| `options[]` | ✅ | Star/emoji buttons (no NPS traffic-light colors) |
|
|
14
|
+
| `scaleStyle` | ✅ | `'star'` or `'emoji'` |
|
|
15
|
+
| `minLabel` / `midLabel` / `maxLabel` | ✅ | Same formulas as NPS in Question shell |
|
|
16
|
+
| `midLabelIndex` | ✅ | `(midLabelIndex / (options.length - 1)) * 100`% |
|
|
17
|
+
| `reverseScaleOrder` | partial | Reverse options before render |
|
|
16
18
|
|
|
17
|
-
## Expected UI
|
|
19
|
+
## Expected UI
|
|
18
20
|
|
|
19
21
|
```
|
|
20
22
|
[minLabel] [midLabel @ %] [maxLabel]
|
|
21
23
|
┌────┬────┬────┬────┬────┐
|
|
22
24
|
│ ★ │ ★ │ ★ │ ★ │ ★ │ scaleStyle: 'star'
|
|
23
25
|
└────┴────┴────┴────┴────┘
|
|
24
|
-
OR emoji row from
|
|
26
|
+
OR emoji row from client-side react-icons when scaleStyle: 'emoji'
|
|
25
27
|
```
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
Selected styling: `border-[#e20074] bg-[#fdf2f8] scale-110`.
|
|
28
30
|
|
|
29
31
|
## Config Decision Tree
|
|
30
32
|
|
|
31
33
|
```
|
|
32
|
-
|
|
33
|
-
├── scaleStyle === 'star' →
|
|
34
|
-
├── scaleStyle === 'emoji' →
|
|
35
|
-
└── else → numbered badges (no
|
|
34
|
+
star_rating
|
|
35
|
+
├── scaleStyle === 'star' → client-side star icons (react-icons) per option
|
|
36
|
+
├── scaleStyle === 'emoji' → client-side emoji icons (react-icons)
|
|
37
|
+
└── else → numbered badges (no accentColor)
|
|
36
38
|
```
|
|
37
39
|
|
|
38
40
|
## Answer Mutation
|
|
39
41
|
|
|
40
42
|
```typescript
|
|
41
43
|
onSelect(number)
|
|
44
|
+
onAction({ type: 'CHANGE', payload: { questionId, answerValue: number } })
|
|
42
45
|
```
|
|
43
46
|
|
|
44
47
|
## Agent Checklist
|
|
45
48
|
|
|
46
|
-
- [ ]
|
|
49
|
+
- [ ] Dispatcher branch for `star_rating` in Question.tsx
|
|
47
50
|
- [ ] No traffic-light NPS colors on options
|
|
48
51
|
- [ ] min/mid/max labels with midLabelIndex formula
|
|
49
|
-
- [ ] Match
|
|
52
|
+
- [ ] Match star/emoji bounding box rules from `04-star-rating-scale.md`
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# UI Spec: Standalone Slider (`type: 'slider'`)
|
|
2
2
|
|
|
3
|
-
> **
|
|
3
|
+
> **DEPRECATED:** Not in the current `SurveyQuestion` union. API SLIDER pages map to `SLIDER_MATRIX` or matrix `displayStyle: 'graphics'`. See `10-slider-matrix.md` and `CustomSliderTrack` specs.
|
|
4
|
+
>
|
|
5
|
+
> **Reference:** Standalone slider is not routed in current `Question.tsx`
|
|
4
6
|
> **SDK type:** `SliderQuestion` — `fetchSurvey/types/slider.ts`
|
|
5
7
|
> **Component blueprint:** `01-components/09-custom-slider-track.md`
|
|
6
8
|
|