@explorer02/cfm-survey-sdk 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +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 +14 -8
- 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 +45 -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 +49 -0
- package/templates/docs/00-integration/end-page-logic.md +64 -0
- package/templates/docs/00-integration/partial-save-and-recovery.md +74 -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 +242 -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 +12 -3
- package/templates/docs/01-components/07-slider-matrix-scale.md +11 -2
- package/templates/docs/01-components/08-file-upload-scale.md +10 -1
- 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 +3 -1
- 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 +100 -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 +13 -2
- 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 +86 -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 +12 -54
- package/templates/docs/03-ui-specs/02-radio.md +21 -33
- package/templates/docs/03-ui-specs/03-text.md +2 -2
- package/templates/docs/03-ui-specs/04-csat.md +38 -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 +24 -21
- package/templates/docs/03-ui-specs/08-matrix-csat-rating.md +17 -45
- package/templates/docs/03-ui-specs/09-slider-matrix.md +1 -1
- package/templates/docs/03-ui-specs/10-file-upload.md +1 -1
- package/templates/docs/03-ui-specs/11-text-and-media.md +29 -29
- package/templates/docs/03-ui-specs/12-survey-chrome.md +20 -14
- package/templates/docs/03-ui-specs/13-heatmap.md +73 -0
- package/templates/docs/03-ui-specs/14-rank-order.md +59 -0
- package/templates/docs/03-ui-specs/README.md +13 -13
- package/templates/docs/03-ui-specs/shared/custom-slider-track.md +2 -2
- package/templates/docs/03-ui-specs/shared/matrix-dropdown.md +5 -0
- package/templates/docs/MANIFEST.json +451 -69
- package/templates/docs/index.md +80 -29
- package/templates/docs/templates/Question.tsx +221 -154
- 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 +108 -69
- package/templates/docs/templates/survey-inventory.schema.json +72 -0
- package/templates/docs/templates/verify-agent-build.sh +53 -0
- package/templates/docs/01-components/04-csat-scale.md +0 -40
- package/templates/docs/02-question-types/01-rating.md +0 -44
|
@@ -1,35 +1,50 @@
|
|
|
1
1
|
# Question Type → Component Routing
|
|
2
2
|
|
|
3
|
-
> Canonical mapping.
|
|
4
|
-
|
|
5
|
-
| `question.type` |
|
|
6
|
-
|
|
7
|
-
| `
|
|
8
|
-
| `
|
|
9
|
-
| `
|
|
10
|
-
| `
|
|
11
|
-
| `
|
|
12
|
-
| `
|
|
13
|
-
| `
|
|
14
|
-
| `
|
|
15
|
-
| `
|
|
16
|
-
| `
|
|
17
|
-
| `
|
|
18
|
-
|
|
19
|
-
|
|
3
|
+
> Canonical mapping. Dispatch on `question.type` only — each matrix variant is its own type (no `subType`).
|
|
4
|
+
|
|
5
|
+
| `question.type` | SDK Type | Component | Answer Shape | Reference Doc | UI Spec |
|
|
6
|
+
|-----------------|----------|-----------|--------------|---------------|---------|
|
|
7
|
+
| `MCQ` | `McqQuestion` | Inline in `Question` | `string \| string[]` | `question-types/README.md#mcq` | `03-ui-specs/02-radio.md` |
|
|
8
|
+
| `TEXTFIELD` | `TextInputQuestion` | Inline in `Question` | `string` | `question-types/README.md#textfield` | `03-ui-specs/03-text.md` |
|
|
9
|
+
| `NPS_SCALE` | `NpsQuestion` | `RatingScale` | `number` | `question-types/01-rating.md` | `03-ui-specs/01-rating.md` |
|
|
10
|
+
| `CFM_MATRIX` | `CfmMatrixQuestion` | `LikertMatrixScale` | `MatrixRowAnswers` | `question-types/07-matrix-cfm.md` | `03-ui-specs/07-matrix-cfm.md` |
|
|
11
|
+
| `CSAT_MATRIX` | `CsatQuestion` | `CsatMatrixScale` | `MatrixRowAnswers` | `question-types/04-csat.md` | `03-ui-specs/04-csat.md` |
|
|
12
|
+
| `RATING_MATRIX` | `RatingMatrixQuestion` | `CsatMatrixScale` | `MatrixRowAnswers` | `question-types/09-matrix-rating.md` | `03-ui-specs/08-matrix-csat-rating.md` |
|
|
13
|
+
| `SLIDER_MATRIX` | `SliderMatrixQuestion` | `SliderMatrixScale` | `MatrixRowAnswers` | `question-types/10-slider-matrix.md` | `03-ui-specs/09-slider-matrix.md` |
|
|
14
|
+
| `FILE_UPLOAD` | `FileUploadQuestion` | `FileUploadScale` | `UploadedFileAnswer[]` | `question-types/11-file-upload.md` | `03-ui-specs/10-file-upload.md` |
|
|
15
|
+
| `TEXT_AND_MEDIA` | `TextAndMediaQuestion` | Inline in `Question` | (no answer) | `question-types/README.md#text_and_media` | `03-ui-specs/11-text-and-media.md` |
|
|
16
|
+
| `HEATMAP` | `HeatmapQuestion` | `HeatmapScale` | `HeatmapAnswer` | `question-types/12-heatmap.md` | `03-ui-specs/13-heatmap.md` |
|
|
17
|
+
| `RANK_ORDER` | `RankOrderQuestion` | `RankOrderScale` | `RankOrderAnswers` | `question-types/13-rank-order.md` | `03-ui-specs/14-rank-order.md` |
|
|
18
|
+
|
|
19
|
+
Use `QUESTION_TYPE` from the SDK for dispatch:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { QUESTION_TYPE } from '@explorer02/cfm-survey-sdk';
|
|
23
|
+
|
|
24
|
+
{question.type === QUESTION_TYPE.NPS_SCALE && <RatingScale ... />}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
> **Legacy note:** API RATING/SLIDER page types are mapped into `NPS_SCALE`, matrix display styles, or `SLIDER_MATRIX` — not separate `question.type` values.
|
|
28
|
+
|
|
29
|
+
## SDK hook contract
|
|
30
|
+
|
|
31
|
+
- Read answers from `state.answers` (`SurveyAnswers`)
|
|
32
|
+
- Dispatch `{ type: 'CHANGE', payload: { questionId, answerValue } }`
|
|
33
|
+
- Pass `allAnswers` and `allQuestions` to `Question` for placeholder resolution
|
|
20
34
|
|
|
21
35
|
## Component Blueprint Docs
|
|
22
36
|
|
|
23
37
|
| Component | Blueprint |
|
|
24
38
|
|-----------|-----------|
|
|
25
39
|
| `SurveyPage` | `01-components/01-survey-page.md` |
|
|
40
|
+
| `IntroPage` / `EndPage` / `PausedPage` | `01-components/14-intro-page.md`, `15-end-page.md`, `16-paused-page.md` |
|
|
26
41
|
| `Question` | `01-components/02-question.md` |
|
|
27
42
|
| `RatingScale` | `01-components/03-rating-scale.md` |
|
|
28
|
-
| `CsatScale` | `01-components/04-csat-scale.md` |
|
|
29
43
|
| `CsatMatrixScale` | `01-components/05-csat-matrix-scale.md` |
|
|
30
44
|
| `LikertMatrixScale` | `01-components/06-likert-matrix-scale.md` |
|
|
31
45
|
| `SliderMatrixScale` | `01-components/07-slider-matrix-scale.md` |
|
|
32
46
|
| `FileUploadScale` | `01-components/08-file-upload-scale.md` |
|
|
47
|
+
| `HeatmapScale` | `01-components/17-heatmap-scale.md` |
|
|
33
48
|
| `CustomSliderTrack` | `01-components/09-custom-slider-track.md` |
|
|
34
49
|
| `Header` / `Footer` | `01-components/10-header-footer.md` |
|
|
35
50
|
| `ProgressBar` | `01-components/11-progress-bar.md` |
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Client-Side Value Derivation
|
|
2
|
+
|
|
3
|
+
> SDK types expose **display** fields only for scale options and matrix columns. Submit values are derived at submit/partial-save time.
|
|
4
|
+
|
|
5
|
+
## Matrix columns (`ScaleColumn`)
|
|
6
|
+
|
|
7
|
+
**Import from SDK** (preferred):
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { matrixColumnStoredValue } from '@explorer02/cfm-survey-sdk';
|
|
11
|
+
// monorepo: import { matrixColumnStoredValue } from '@repo/sdk';
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
type ScaleColumn = { id: string; label: string };
|
|
16
|
+
|
|
17
|
+
// matrixColumnStoredValue(column) → column.id, except N/A → null
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
- **Stored matrix answers** use `matrixColumnStoredValue(col)` → always the column **`id`** (string), except N/A → `null`
|
|
21
|
+
- Matrix answers: `MatrixRowAnswers = Record<statementRowId, columnId | columnId[] | null>`
|
|
22
|
+
- **Submit / partial save**: SDK `resolveSubmitValues` maps stored column ids → API label or numeric string (id-only lookup)
|
|
23
|
+
- N/A column: SDK adds `{ id: 'na', label: 'N/A' }` when `hasNotApplicableOption` is true
|
|
24
|
+
- Filter N/A from scale pickers: `scaleColumns.filter(col => col.id !== 'na')`
|
|
25
|
+
- Slider matrix rows still store numeric slider positions (unchanged)
|
|
26
|
+
|
|
27
|
+
Monorepo client mirror: `apps/client/src/lib/surveyUi/scaleValues.ts` exports the same helper for matrix UI components.
|
|
28
|
+
|
|
29
|
+
## NPS / star rating options
|
|
30
|
+
|
|
31
|
+
Options are `{ id: string; optionLabel: string }` — no `optionValue` on the type.
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
function npsValueFromOption(option: NpsScalePoint, index: number): number {
|
|
35
|
+
const numeric = Number(option.optionLabel);
|
|
36
|
+
return Number.isFinite(numeric) && option.optionLabel.trim() !== '' ? numeric : index;
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
NPS questions use `question.type === 'NPS_SCALE'`. Store a **number** in `SurveyAnswers`. Badge colors: traffic-light table keyed by numeric value (0–10).
|
|
41
|
+
|
|
42
|
+
## MCQ
|
|
43
|
+
|
|
44
|
+
- Selected answer: **`option.id`** (string), not a separate `optionValue` field
|
|
45
|
+
|
|
46
|
+
## Rank order
|
|
47
|
+
|
|
48
|
+
- Stored answer: **`RankOrderAnswers`** — `Record<optionId, rankNumber>` (1-based)
|
|
49
|
+
- Dropdown: use `assignRankWithoutDuplicates(current, optionId, rank)` before `onSelect`
|
|
50
|
+
- Drag-and-drop: use `buildRankOrderFromOrderedOptionIds(orderedOptionIds)` after reorder
|
|
51
|
+
- Submit / partial save:
|
|
52
|
+
- `questionToAnswers[questionId]` → ordered **option id** array (rank 1 → N)
|
|
53
|
+
- `optionLevelAnswers[`${questionId}-:-${optionId}`]` → `[String(rank)]`
|
|
54
|
+
- Filter visible options: `getVisibleRankOrderOptionsForAnswers(question, answers, allQuestions, customFieldValues?)`
|
|
55
|
+
|
|
56
|
+
## Media
|
|
57
|
+
|
|
58
|
+
- Shell side media: `question.containerMedia?.url`, `containerMedia?.mimeType`, etc.
|
|
59
|
+
- Text & media block: `question.media?.url`, `question.media?.title`, etc.
|
|
60
|
+
|
|
61
|
+
## Shared types (exported)
|
|
62
|
+
|
|
63
|
+
`StatementRow`, `ScaleColumn`, `ContainerMedia`, `QuestionMedia`, `SurveyAnswers`, `matrixColumnStoredValue` — see [`exports.md`](exports.md).
|
|
@@ -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,17 +1,18 @@
|
|
|
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 |
|
|
@@ -19,62 +20,19 @@
|
|
|
19
20
|
| `reverseScaleOrder` | ❌ | Document for agents when SDK sends true |
|
|
20
21
|
| `buttonVariant` | ❌ | Client always uses dual-layer radio layout |
|
|
21
22
|
|
|
22
|
-
## Layout Anatomy
|
|
23
|
-
|
|
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
|
-
```
|
|
44
|
-
|
|
45
|
-
If `buttonVariant === 'numbered'` (SDK gap): badges become clickable buttons instead — not in reference client.
|
|
46
|
-
|
|
47
|
-
If `reverseScaleOrder`: reverse `options` array before mapping both rows.
|
|
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.
|
|
60
|
-
|
|
61
23
|
## Answer Mutation
|
|
62
24
|
|
|
63
25
|
```typescript
|
|
64
|
-
|
|
65
|
-
//
|
|
26
|
+
const value = npsValueFromOption(option, index);
|
|
27
|
+
onSelect(value) // number
|
|
28
|
+
// → onAction CHANGE { questionId, answerValue: number }
|
|
66
29
|
```
|
|
67
30
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
- `role="radiogroup"` on wrapper
|
|
71
|
-
- Native `<input type="radio" className="sr-only">` per cell
|
|
72
|
-
- `name={questionId}` groups radios
|
|
31
|
+
See [`02-reference/value-derivation.md`](../02-reference/value-derivation.md).
|
|
73
32
|
|
|
74
33
|
## Agent Checklist
|
|
75
34
|
|
|
35
|
+
- [ ] Derive submit value and badge color in UI — types omit `optionValue` / `accentColor`
|
|
76
36
|
- [ ] Two-row grid with `gridTemplateColumns: repeat(options.length, 1fr)`
|
|
77
|
-
- [ ] Badges use `option.color`, not hardcoded NPS colors unless option lacks color
|
|
78
37
|
- [ ] Only track row is clickable
|
|
79
38
|
- [ ] min/mid/max labels in Question wrapper with midLabelIndex formula
|
|
80
|
-
- [ ] Selected state: pink inner track + thick magenta radio ring
|
|
@@ -1,7 +1,6 @@
|
|
|
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`
|
|
6
5
|
|
|
7
6
|
## Config Inventory
|
|
@@ -9,43 +8,26 @@
|
|
|
9
8
|
| Field | Implemented | UI impact |
|
|
10
9
|
|-------|-------------|-----------|
|
|
11
10
|
| `options[]` | ✅ | One card per option |
|
|
12
|
-
| `options[].
|
|
13
|
-
| `options[].
|
|
14
|
-
| `isMultiSelect` |
|
|
11
|
+
| `options[].optionLabel` | ✅ | HTML via `dangerouslySetInnerHTML` |
|
|
12
|
+
| `options[].id` | ✅ | **Submit value** — selected answer is `option.id` |
|
|
13
|
+
| `isMultiSelect` | ⚠️ | SDK supports multi-select; implement checkbox cards when `true` |
|
|
15
14
|
|
|
16
|
-
##
|
|
15
|
+
## Multi-select note
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
┌─ label (full-width card) ─────────────────────────────────────┐
|
|
20
|
-
│ (●) Option label HTML │ selected
|
|
21
|
-
└───────────────────────────────────────────────────────────────┘
|
|
22
|
-
┌─ label ───────────────────────────────────────────────────────┐
|
|
23
|
-
│ (○) Option label HTML │ unselected
|
|
24
|
-
└───────────────────────────────────────────────────────────────┘
|
|
25
|
-
```
|
|
17
|
+
When `question.isMultiSelect === true`:
|
|
26
18
|
|
|
27
|
-
|
|
19
|
+
- Answer shape is `string[]` (or `number[]`) of selected `option.id` values
|
|
20
|
+
- Toggle options on click; do not replace the entire array with a single id
|
|
21
|
+
- onAction CHANGE: `{ questionId, answerValue: selectedIds[] }`
|
|
22
|
+
- Still filter via `getVisibleMcqOptionsForAnswers` before render
|
|
28
23
|
|
|
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]` |
|
|
34
|
-
|
|
35
|
-
## Structure
|
|
36
|
-
|
|
37
|
-
```tsx
|
|
38
|
-
<label className="flex items-center gap-4 rounded-lg border py-4 px-5 cursor-pointer">
|
|
39
|
-
<input type="radio" name={question.id} className="sr-only" />
|
|
40
|
-
<div className="h-5 w-5 rounded-full border ..."> {/* custom radio */} </div>
|
|
41
|
-
<span dangerouslySetInnerHTML={{ __html: option.label }} />
|
|
42
|
-
</label>
|
|
43
|
-
```
|
|
24
|
+
When `false` (default), single-select radio behavior applies.
|
|
44
25
|
|
|
45
26
|
## Answer Mutation
|
|
46
27
|
|
|
47
28
|
```typescript
|
|
48
|
-
onSelect(option.
|
|
29
|
+
onSelect(option.id) // string
|
|
30
|
+
// → onAction CHANGE { questionId, answerValue: string }
|
|
49
31
|
```
|
|
50
32
|
|
|
51
33
|
## Agent Checklist
|
|
@@ -54,4 +36,10 @@ onSelect(option.value) // string | number
|
|
|
54
36
|
- [ ] Custom circular radio indicator (not browser default)
|
|
55
37
|
- [ ] Selected: magenta border + pink background
|
|
56
38
|
- [ ] HTML labels
|
|
57
|
-
- [ ] If `isMultiSelect`: implement checkbox cards
|
|
39
|
+
- [ ] If `isMultiSelect`: implement checkbox cards with array answer shape
|
|
40
|
+
|
|
41
|
+
## SDK Integration Checklist (agent)
|
|
42
|
+
|
|
43
|
+
- [ ] Visibility helper: `getVisibleMcqOptionsForAnswers`
|
|
44
|
+
- [ ] Store `option.id` — never option label text
|
|
45
|
+
- [ ] 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,62 @@
|
|
|
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
|
|
43
42
|
|
|
44
|
-
|
|
43
|
+
- Pink active dot expands: `20px × 8px` vs `8px` inactive
|
|
44
|
+
- Column labels below cells
|
|
45
|
+
- Emoji tooltip via hover state
|
|
46
|
+
- Graphics mode: slider maps column index
|
|
45
47
|
|
|
46
48
|
## Answer Mutation
|
|
47
49
|
|
|
50
|
+
Store **`column.id`** via `matrixColumnStoredValue(col)` — see `02-reference/value-derivation.md`.
|
|
51
|
+
|
|
48
52
|
```typescript
|
|
49
|
-
onSelect(
|
|
53
|
+
onSelect(MatrixRowAnswers)
|
|
54
|
+
onAction({ type: 'CHANGE', payload: { questionId, answerValue: MatrixRowAnswers } })
|
|
50
55
|
```
|
|
51
56
|
|
|
52
57
|
## Agent Checklist
|
|
53
58
|
|
|
54
|
-
- [ ]
|
|
55
|
-
- [ ]
|
|
56
|
-
- [ ]
|
|
57
|
-
- [ ]
|
|
59
|
+
- [ ] All displayStyle × gridLayout branches
|
|
60
|
+
- [ ] reverseScaleOrder on emoji/star/numbered
|
|
61
|
+
- [ ] Carousel pink dot indicators
|
|
62
|
+
- [ ] 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
|
|