@explorer02/cfm-survey-sdk 0.1.9 → 0.2.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.
- package/package.json +1 -1
- package/templates/docs/01-components/01-survey-page.md +2 -0
- package/templates/docs/01-components/02-question.md +2 -0
- package/templates/docs/01-components/03-rating-scale.md +2 -0
- package/templates/docs/01-components/04-csat-scale.md +3 -1
- package/templates/docs/01-components/05-csat-matrix-scale.md +2 -0
- package/templates/docs/01-components/06-likert-matrix-scale.md +2 -0
- package/templates/docs/01-components/07-slider-matrix-scale.md +2 -0
- package/templates/docs/01-components/08-file-upload-scale.md +2 -0
- package/templates/docs/01-components/09-custom-slider-track.md +2 -0
- package/templates/docs/01-components/10-header-footer.md +3 -1
- package/templates/docs/01-components/11-progress-bar.md +2 -0
- package/templates/docs/01-components/12-language-selector.md +2 -0
- package/templates/docs/01-components/13-matrix-dropdown.md +2 -0
- package/templates/docs/01-components/README.md +2 -2
- package/templates/docs/02-reference/config-field-index.md +158 -0
- package/templates/docs/02-reference/routing-table.md +16 -16
- package/templates/docs/03-ui-specs/00-question-shell.md +89 -0
- package/templates/docs/03-ui-specs/01-rating.md +80 -0
- package/templates/docs/03-ui-specs/02-radio.md +57 -0
- package/templates/docs/03-ui-specs/03-text.md +51 -0
- package/templates/docs/03-ui-specs/04-csat.md +57 -0
- package/templates/docs/03-ui-specs/05-rating-scale.md +49 -0
- package/templates/docs/03-ui-specs/06-slider.md +42 -0
- package/templates/docs/03-ui-specs/07-matrix-cfm.md +66 -0
- package/templates/docs/03-ui-specs/08-matrix-csat-rating.md +65 -0
- package/templates/docs/03-ui-specs/09-slider-matrix.md +61 -0
- package/templates/docs/03-ui-specs/10-file-upload.md +54 -0
- package/templates/docs/03-ui-specs/11-text-and-media.md +49 -0
- package/templates/docs/03-ui-specs/12-survey-chrome.md +60 -0
- package/templates/docs/03-ui-specs/README.md +79 -0
- package/templates/docs/03-ui-specs/shared/custom-slider-track.md +52 -0
- package/templates/docs/03-ui-specs/shared/matrix-dropdown.md +38 -0
- package/templates/docs/MANIFEST.json +22 -1
- package/templates/docs/index.md +14 -3
- package/templates/docs/templates/implementation_plan.md +13 -0
package/package.json
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Survey Page Orchestrator Architectural Blueprint
|
|
2
2
|
|
|
3
|
+
> **UI spec (authoritative):** `03-ui-specs/12-survey-chrome.md`
|
|
4
|
+
> **Reference implementation:** `apps/client/src/components/SurveyPage.tsx`
|
|
3
5
|
> **Target Component**: `SurveyPage`
|
|
4
6
|
> **Role**: The main entry point that wires the UI to the `useSurveySDK` hook, manages loading/error states, orchestrates pagination, and controls submission.
|
|
5
7
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Question Dispatcher Architectural Blueprint
|
|
2
2
|
|
|
3
|
+
> **UI spec (authoritative):** `03-ui-specs/00-question-shell.md`
|
|
4
|
+
> **Reference implementation:** `apps/client/src/components/Question.tsx`
|
|
3
5
|
> **Target Component**: `Question`
|
|
4
6
|
> **Role**: Wrapper card component that routes to the correct scale component based on `question.type`.
|
|
5
7
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Rating Scale Architectural Blueprint
|
|
2
2
|
|
|
3
|
+
> **UI spec (authoritative):** `03-ui-specs/01-rating.md` (NPS) · `03-ui-specs/05-rating-scale.md` (star/emoji gap)
|
|
4
|
+
> **Reference implementation:** `apps/client/src/components/RatingScale.tsx`
|
|
3
5
|
> **Target Component**: `RatingScale`
|
|
4
6
|
> **Handles**: `type: 'rating'`, `type: 'rating_scale'`
|
|
5
7
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# CSAT & Rating Architectural Blueprints
|
|
2
2
|
|
|
3
|
-
> **
|
|
3
|
+
> **UI spec (authoritative):** `03-ui-specs/04-csat.md`
|
|
4
|
+
> **Reference implementation:** `apps/client/src/components/CsatScale.tsx`
|
|
5
|
+
> **Target Components**: `CsatScale`
|
|
4
6
|
> **Handles**: `type: 'csat'`, `'rating'`, `'rating_scale'`, CSAT matrices.
|
|
5
7
|
|
|
6
8
|
## Core Responsibility
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# CSAT Matrix Architectural Blueprint
|
|
2
2
|
|
|
3
|
+
> **UI spec (authoritative):** `03-ui-specs/08-matrix-csat-rating.md`
|
|
4
|
+
> **Reference implementation:** `apps/client/src/components/CsatMatrixScale.tsx`
|
|
3
5
|
> **Target Component**: `CsatMatrixScale`
|
|
4
6
|
> **Handles**: `type: 'matrix'` (CSAT_MATRIX and RATING_MATRIX subTypes)
|
|
5
7
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Likert Matrix Architectural Blueprint
|
|
2
2
|
|
|
3
|
+
> **UI spec (authoritative):** `03-ui-specs/07-matrix-cfm.md`
|
|
4
|
+
> **Reference implementation:** `apps/client/src/components/LikertMatrixScale.tsx`
|
|
3
5
|
> **Target Component**: `LikertMatrixScale`
|
|
4
6
|
> **Handles**: `type: 'matrix'` (CFM_MATRIX subType)
|
|
5
7
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Slider Matrix Architectural Blueprint
|
|
2
2
|
|
|
3
|
+
> **UI spec (authoritative):** `03-ui-specs/09-slider-matrix.md`
|
|
4
|
+
> **Reference implementation:** `apps/client/src/components/SliderMatrixScale.tsx`
|
|
3
5
|
> **Target Component**: `SliderMatrixScale`
|
|
4
6
|
> **Handles**: `type: 'slider_matrix'`
|
|
5
7
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# File Upload Architectural Blueprint
|
|
2
2
|
|
|
3
|
+
> **UI spec (authoritative):** `03-ui-specs/10-file-upload.md`
|
|
4
|
+
> **Reference implementation:** `apps/client/src/components/FileUploadScale.tsx`
|
|
3
5
|
> **Target Component**: `FileUploadScale`
|
|
4
6
|
> **Handles**: `type: 'file_upload'`
|
|
5
7
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Custom Slider Track Architectural Blueprint
|
|
2
2
|
|
|
3
|
+
> **UI spec (authoritative):** `03-ui-specs/shared/custom-slider-track.md`
|
|
4
|
+
> **Reference implementation:** `apps/client/src/components/CustomSliderTrack.tsx`
|
|
3
5
|
> **Target Component**: `CustomSliderTrack`
|
|
4
6
|
> **Used By**: `SliderMatrixScale`, `CsatMatrixScale`, `CsatScale`
|
|
5
7
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Theme & Logo Engineering Blueprint
|
|
2
2
|
|
|
3
|
-
> **
|
|
3
|
+
> **UI spec (authoritative):** `03-ui-specs/12-survey-chrome.md`
|
|
4
|
+
> **Reference implementation:** `apps/client/src/components/Header.tsx`, `Footer.tsx`
|
|
5
|
+
> **Target Components**: `Header`, `Footer`
|
|
4
6
|
> **Handles**: Branding, Mockup Replication
|
|
5
7
|
|
|
6
8
|
## Core Responsibility
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Language Selector
|
|
2
2
|
|
|
3
|
+
> **UI spec (authoritative):** `03-ui-specs/12-survey-chrome.md`
|
|
4
|
+
> **Reference implementation:** `apps/client/src/components/LanguageSelector.tsx`
|
|
3
5
|
> **Component**: `LanguageSelector`
|
|
4
6
|
> **Role**: Allows the user to switch the survey language.
|
|
5
7
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Matrix Dropdown
|
|
2
2
|
|
|
3
|
+
> **UI spec (authoritative):** `03-ui-specs/shared/matrix-dropdown.md`
|
|
4
|
+
> **Reference implementation:** `apps/client/src/components/MatrixDropdown.tsx`
|
|
3
5
|
> **Component**: `MatrixDropdown`
|
|
4
6
|
> **Used by**: `CsatMatrixScale`, `LikertMatrixScale`
|
|
5
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Client Components Overview
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
>
|
|
3
|
+
> Wiring blueprints for React components. **Authoritative UI details** are in [`03-ui-specs/`](../03-ui-specs/README.md) (reference-client driven).
|
|
4
|
+
> This folder documents props, `onSelect` wiring, and dispatcher architecture.
|
|
5
5
|
|
|
6
6
|
## Component Architecture
|
|
7
7
|
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Config Field Index
|
|
2
|
+
|
|
3
|
+
> Master map: every SDK config field → reference client file → implemented? → UI spec doc.
|
|
4
|
+
> Cross-check this when building any question with non-default config.
|
|
5
|
+
|
|
6
|
+
## QuestionBase (all types)
|
|
7
|
+
|
|
8
|
+
| Field | Types | Client file | Implemented? | Spec doc |
|
|
9
|
+
|-------|-------|-------------|--------------|----------|
|
|
10
|
+
| `id` | all | Question.tsx | ✅ | 00-question-shell |
|
|
11
|
+
| `text` | all | Question.tsx | ✅ | 00-question-shell |
|
|
12
|
+
| `description` | all | Question.tsx | ✅ | 00-question-shell |
|
|
13
|
+
| `required` | all | Question.tsx | ✅ | 00-question-shell |
|
|
14
|
+
| `requiredErrorMessage` | all | Question.tsx | ✅ | 00-question-shell |
|
|
15
|
+
| `questionNumber` | all | — | ❌ | 00-question-shell |
|
|
16
|
+
| `containerMediaUrl` | all | — | ❌ | 00-question-shell |
|
|
17
|
+
| `containerMediaMimeType` | all | — | ❌ | 00-question-shell |
|
|
18
|
+
| `containerMediaAlignment` | all | — | ❌ | 00-question-shell |
|
|
19
|
+
| `containerMediaSize` | all | — | ❌ | 00-question-shell |
|
|
20
|
+
|
|
21
|
+
## rating
|
|
22
|
+
|
|
23
|
+
| Field | Values | Client file | Implemented? | Spec doc |
|
|
24
|
+
|-------|--------|-------------|--------------|----------|
|
|
25
|
+
| `options[]` | SurveyOption[] | RatingScale.tsx | ✅ | 01-rating |
|
|
26
|
+
| `options[].color` | hex | RatingScale.tsx | ✅ | 01-rating |
|
|
27
|
+
| `minLabel` | string | Question.tsx | ✅ | 01-rating |
|
|
28
|
+
| `midLabel` | string | Question.tsx | ✅ | 01-rating |
|
|
29
|
+
| `maxLabel` | string | Question.tsx | ✅ | 01-rating |
|
|
30
|
+
| `midLabelIndex` | number | Question.tsx | ✅ | 01-rating |
|
|
31
|
+
| `reverseScaleOrder` | boolean | — | ❌ | 01-rating |
|
|
32
|
+
| `buttonVariant` | radio/numbered/emoji | — | ❌ | 01-rating |
|
|
33
|
+
|
|
34
|
+
## radio
|
|
35
|
+
|
|
36
|
+
| Field | Values | Client file | Implemented? | Spec doc |
|
|
37
|
+
|-------|--------|-------------|--------------|----------|
|
|
38
|
+
| `options[]` | SurveyOption[] | Question.tsx | ✅ | 02-radio |
|
|
39
|
+
| `isMultiSelect` | boolean | — | ❌ | 02-radio |
|
|
40
|
+
|
|
41
|
+
## text
|
|
42
|
+
|
|
43
|
+
| Field | Values | Client file | Implemented? | Spec doc |
|
|
44
|
+
|-------|--------|-------------|--------------|----------|
|
|
45
|
+
| `placeholder` | string | Question.tsx | ✅ | 03-text |
|
|
46
|
+
| `maxCharacterCount` | number | Question.tsx | ✅ | 03-text |
|
|
47
|
+
| `inputVariant` | long/short | Question.tsx | partial | 03-text |
|
|
48
|
+
|
|
49
|
+
## csat
|
|
50
|
+
|
|
51
|
+
| Field | Values | Client file | Implemented? | Spec doc |
|
|
52
|
+
|-------|--------|-------------|--------------|----------|
|
|
53
|
+
| `options[]` | SurveyOption[] | CsatScale.tsx | ✅ | 04-csat |
|
|
54
|
+
| `buttonType` | emoji/star/numbered/... | CsatScale.tsx | ✅ | 04-csat |
|
|
55
|
+
| `labelPosition` | top/bottom/hidden | CsatScale.tsx | ✅ | 04-csat |
|
|
56
|
+
| `minLabel` / `maxLabel` | string | Question.tsx | ✅ | 04-csat |
|
|
57
|
+
| `hasNotApplicable` | boolean | — | ❌ | 04-csat |
|
|
58
|
+
| `reverseScaleOrder` | boolean | — | ❌ | 04-csat |
|
|
59
|
+
|
|
60
|
+
## rating_scale
|
|
61
|
+
|
|
62
|
+
| Field | Values | Client file | Implemented? | Spec doc |
|
|
63
|
+
|-------|--------|-------------|--------------|----------|
|
|
64
|
+
| `options[]` | SurveyOption[] | — | ❌ | 05-rating-scale |
|
|
65
|
+
| `scaleStyle` | star/emoji | — | ❌ | 05-rating-scale |
|
|
66
|
+
| `minLabel` / `midLabel` / `maxLabel` | string | — | ❌ | 05-rating-scale |
|
|
67
|
+
| `midLabelIndex` | number | — | ❌ | 05-rating-scale |
|
|
68
|
+
| `reverseScaleOrder` | boolean | — | ❌ | 05-rating-scale |
|
|
69
|
+
|
|
70
|
+
## slider
|
|
71
|
+
|
|
72
|
+
| Field | Values | Client file | Implemented? | Spec doc |
|
|
73
|
+
|-------|--------|-------------|--------------|----------|
|
|
74
|
+
| `min` / `max` / `step` | number | — | ❌ | 06-slider |
|
|
75
|
+
| `defaultValue` | number | — | ❌ | 06-slider |
|
|
76
|
+
| `minLabel` / `maxLabel` | string | — | ❌ | 06-slider |
|
|
77
|
+
| `enableInputBox` | boolean | SliderMatrixScale.tsx | partial | 06-slider |
|
|
78
|
+
| `displayValues` | boolean | CustomSliderTrack.tsx | partial | 06-slider, shared/custom-slider-track |
|
|
79
|
+
|
|
80
|
+
## matrix (shared)
|
|
81
|
+
|
|
82
|
+
| Field | Values | Client file | Implemented? | Spec doc |
|
|
83
|
+
|-------|--------|-------------|--------------|----------|
|
|
84
|
+
| `subType` | CFM/CSAT/RATING | Question.tsx | ✅ | 07, 08 |
|
|
85
|
+
| `rows[]` | MatrixRow[] | Likert/CsatMatrix | ✅ | 07, 08 |
|
|
86
|
+
| `columns[]` | MatrixColumn[] | Likert/CsatMatrix | ✅ | 07, 08 |
|
|
87
|
+
| `matrixFormat` | standard/carousel/dropdown | Likert/CsatMatrix | ✅ | 07, 08 |
|
|
88
|
+
| `hasNotApplicable` | boolean | Likert/CsatMatrix | ✅ | 07, 08 |
|
|
89
|
+
| `reverseScaleOrder` | boolean | CsatMatrixScale | ✅ | 08 |
|
|
90
|
+
| `labels[]` | string[] | Likert/CsatMatrix | ✅ | 07, 08 |
|
|
91
|
+
| `buttonType` | SurveyButtonType | CsatMatrixScale | ✅ | 08 |
|
|
92
|
+
|
|
93
|
+
## matrix — CFM only
|
|
94
|
+
|
|
95
|
+
| Field | Values | Client file | Implemented? | Spec doc |
|
|
96
|
+
|-------|--------|-------------|--------------|----------|
|
|
97
|
+
| `matrixType` | likert/bipolar | LikertMatrixScale | ✅ | 07-matrix-cfm |
|
|
98
|
+
| `answerType` | single/multiple | LikertMatrixScale | ✅ | 07-matrix-cfm |
|
|
99
|
+
| `transposeTable` | boolean | LikertMatrixScale | ✅ | 07-matrix-cfm |
|
|
100
|
+
| `repeatScale` | boolean | LikertMatrixScale | ✅ | 07-matrix-cfm |
|
|
101
|
+
| `enableScale` | boolean | LikertMatrixScale | ✅ | 07-matrix-cfm |
|
|
102
|
+
|
|
103
|
+
## slider_matrix
|
|
104
|
+
|
|
105
|
+
| Field | Values | Client file | Implemented? | Spec doc |
|
|
106
|
+
|-------|--------|-------------|--------------|----------|
|
|
107
|
+
| `rows[].min/max/step` | number | SliderMatrixScale | ✅ | 09-slider-matrix |
|
|
108
|
+
| `labels[]` | string[] | SliderMatrixScale | ✅ | 09-slider-matrix |
|
|
109
|
+
| `ticks` | number | SliderMatrixScale | ✅ | 09-slider-matrix |
|
|
110
|
+
| `tickValues[]` | TickValue[] | SliderMatrixScale | ✅ | 09-slider-matrix |
|
|
111
|
+
| `enableInputBox` | boolean | SliderMatrixScale | ✅ | 09-slider-matrix |
|
|
112
|
+
| `displayValues` | boolean | CustomSliderTrack | ✅ | 09-slider-matrix |
|
|
113
|
+
| `enableNotApplicable` | boolean | SliderMatrixScale | ✅ | 09-slider-matrix |
|
|
114
|
+
| `sliderType` | graphics | SliderMatrixScale | ✅ | 09-slider-matrix |
|
|
115
|
+
|
|
116
|
+
## file_upload
|
|
117
|
+
|
|
118
|
+
| Field | Values | Client file | Implemented? | Spec doc |
|
|
119
|
+
|-------|--------|-------------|--------------|----------|
|
|
120
|
+
| `uploadMessage` | HTML string | FileUploadScale | ✅ | 10-file-upload |
|
|
121
|
+
| `supportedFileFormats` | string[] | FileUploadScale | ✅ | 10-file-upload |
|
|
122
|
+
| `maxFileCount` | number | FileUploadScale | ✅ | 10-file-upload |
|
|
123
|
+
| `fileSizeLimit` | number (MB) | FileUploadScale | ✅ | 10-file-upload |
|
|
124
|
+
| `fileSizeLimitType` | PER_FILE/IN_TOTAL | FileUploadScale | ✅ | 10-file-upload |
|
|
125
|
+
|
|
126
|
+
## text_and_media
|
|
127
|
+
|
|
128
|
+
| Field | Values | Client file | Implemented? | Spec doc |
|
|
129
|
+
|-------|--------|-------------|--------------|----------|
|
|
130
|
+
| `mediaUrl` | string | Question.tsx | ✅ | 11-text-and-media |
|
|
131
|
+
| `mediaMimeType` | string | Question.tsx | ✅ | 11-text-and-media |
|
|
132
|
+
| `mediaTitle` | string | Question.tsx | ✅ | 11-text-and-media |
|
|
133
|
+
| `mediaAlignment` | string | Question.tsx | ✅ | 11-text-and-media |
|
|
134
|
+
| `mediaSize` | number (%) | Question.tsx | ✅ | 11-text-and-media |
|
|
135
|
+
|
|
136
|
+
## Implementation Gaps (no client branch yet)
|
|
137
|
+
|
|
138
|
+
| Gap | SDK type/field | Extend from |
|
|
139
|
+
|-----|----------------|-------------|
|
|
140
|
+
| `rating_scale` dispatcher | `type: 'rating_scale'` | CsatScale star/emoji patterns |
|
|
141
|
+
| `slider` dispatcher | `type: 'slider'` | CustomSliderTrack + SliderMatrixScale row |
|
|
142
|
+
| `radio.isMultiSelect` | checkbox cards | 02-radio card styling |
|
|
143
|
+
| `text.inputVariant: 'short'` | single-line input | 03-text focus tokens |
|
|
144
|
+
| `rating.reverseScaleOrder` | reverse options array | 01-rating |
|
|
145
|
+
| `rating.buttonVariant` | numbered clickable badges | 01-rating |
|
|
146
|
+
| `csat.hasNotApplicable` / `reverseScaleOrder` | standalone CSAT | 04-csat, 08-matrix N/A |
|
|
147
|
+
| `containerMedia*` on any type | container asset | 11-text-and-media media block |
|
|
148
|
+
|
|
149
|
+
## buttonType × matrixFormat Quick Reference
|
|
150
|
+
|
|
151
|
+
| buttonType | matrixFormat | Layout component | Spec |
|
|
152
|
+
|------------|--------------|------------------|------|
|
|
153
|
+
| (CFM default) | standard | LikertMatrixGrid | 07 |
|
|
154
|
+
| (CFM default) | carousel | LikertMatrixCarousel | 07 |
|
|
155
|
+
| (CFM default) | dropdown | LikertMatrixDropdown | 07 |
|
|
156
|
+
| emoji/star/numbered | standard | CsatMatrixGrid | 08 |
|
|
157
|
+
| emoji/star/numbered/graphics | carousel | CsatMatrixCarousel | 08 |
|
|
158
|
+
| dropdown/selectbox/radio | any | CsatMatrixVerticalList | 08 |
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
# Question Type → Component Routing
|
|
2
2
|
|
|
3
|
-
> Canonical mapping. For UI implementation
|
|
3
|
+
> Canonical mapping. For **UI implementation** (config branches, tokens, states), read `03-ui-specs/`. For wiring, read `01-components/`. For data shapes, read `02-reference/question-types/`. For field audit, read `config-field-index.md`.
|
|
4
4
|
|
|
5
|
-
| `question.type` | `question.subType` | Component | Answer Shape | Reference Doc |
|
|
6
|
-
|
|
7
|
-
| `'rating'` | — | `RatingScale` | `number` | `question-types/01-rating.md` |
|
|
8
|
-
| `'radio'` | — | Inline in `Question` | `string \| number` | `question-types/README.md#radio` |
|
|
9
|
-
| `'text'` | — | Inline in `Question` | `string` | `question-types/README.md#text` |
|
|
10
|
-
| `'csat'` | — | `CsatScale` | `number \| null` | `question-types/04-csat.md` |
|
|
11
|
-
| `'rating_scale'` | — | `RatingScale` | `number` | `question-types/05-rating-scale.md` |
|
|
12
|
-
| `'slider'` | — | `CustomSliderTrack` | `number` | `question-types/06-slider.md` |
|
|
13
|
-
| `'matrix'` | `'CFM_MATRIX'` | `LikertMatrixScale` | `MatrixAnswerMap` | `question-types/07-matrix-cfm.md` |
|
|
14
|
-
| `'matrix'` | `'CSAT_MATRIX'` | `CsatMatrixScale` | `MatrixAnswerMap` | `question-types/08-matrix-csat.md` |
|
|
15
|
-
| `'matrix'` | `'RATING_MATRIX'` | `CsatMatrixScale` | `MatrixAnswerMap` | `question-types/08-matrix-csat.md` |
|
|
16
|
-
| `'rating_matrix'` | — | `CsatMatrixScale` | `MatrixAnswerMap` | `question-types/09-matrix-rating.md` |
|
|
17
|
-
| `'slider_matrix'` | — | `SliderMatrixScale` | `MatrixAnswerMap` | `question-types/10-slider-matrix.md` |
|
|
18
|
-
| `'file_upload'` | — | `FileUploadScale` | `UploadedFile[]` | `question-types/11-file-upload.md` |
|
|
19
|
-
| `'text_and_media'` | — | Inline in `Question` | (no answer) | `question-types/README.md#text_and_media` |
|
|
5
|
+
| `question.type` | `question.subType` | Component | Answer Shape | Reference Doc | UI Spec |
|
|
6
|
+
|-----------------|-------------------|-----------|--------------|---------------|---------|
|
|
7
|
+
| `'rating'` | — | `RatingScale` | `number` | `question-types/01-rating.md` | `03-ui-specs/01-rating.md` |
|
|
8
|
+
| `'radio'` | — | Inline in `Question` | `string \| number` | `question-types/README.md#radio` | `03-ui-specs/02-radio.md` |
|
|
9
|
+
| `'text'` | — | Inline in `Question` | `string` | `question-types/README.md#text` | `03-ui-specs/03-text.md` |
|
|
10
|
+
| `'csat'` | — | `CsatScale` | `number \| null` | `question-types/04-csat.md` | `03-ui-specs/04-csat.md` |
|
|
11
|
+
| `'rating_scale'` | — | `RatingScale` | `number` | `question-types/05-rating-scale.md` | `03-ui-specs/05-rating-scale.md` |
|
|
12
|
+
| `'slider'` | — | `CustomSliderTrack` | `number` | `question-types/06-slider.md` | `03-ui-specs/06-slider.md` |
|
|
13
|
+
| `'matrix'` | `'CFM_MATRIX'` | `LikertMatrixScale` | `MatrixAnswerMap` | `question-types/07-matrix-cfm.md` | `03-ui-specs/07-matrix-cfm.md` |
|
|
14
|
+
| `'matrix'` | `'CSAT_MATRIX'` | `CsatMatrixScale` | `MatrixAnswerMap` | `question-types/08-matrix-csat.md` | `03-ui-specs/08-matrix-csat-rating.md` |
|
|
15
|
+
| `'matrix'` | `'RATING_MATRIX'` | `CsatMatrixScale` | `MatrixAnswerMap` | `question-types/08-matrix-csat.md` | `03-ui-specs/08-matrix-csat-rating.md` |
|
|
16
|
+
| `'rating_matrix'` | — | `CsatMatrixScale` | `MatrixAnswerMap` | `question-types/09-matrix-rating.md` | `03-ui-specs/08-matrix-csat-rating.md` |
|
|
17
|
+
| `'slider_matrix'` | — | `SliderMatrixScale` | `MatrixAnswerMap` | `question-types/10-slider-matrix.md` | `03-ui-specs/09-slider-matrix.md` |
|
|
18
|
+
| `'file_upload'` | — | `FileUploadScale` | `UploadedFile[]` | `question-types/11-file-upload.md` | `03-ui-specs/10-file-upload.md` |
|
|
19
|
+
| `'text_and_media'` | — | Inline in `Question` | (no answer) | `question-types/README.md#text_and_media` | `03-ui-specs/11-text-and-media.md` |
|
|
20
20
|
|
|
21
21
|
## Component Blueprint Docs
|
|
22
22
|
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# UI Spec: Question Shell (`Question.tsx`)
|
|
2
|
+
|
|
3
|
+
> **Reference:** `apps/client/src/components/Question.tsx`
|
|
4
|
+
> **SDK:** All types extend `QuestionBase` from `fetchSurvey/types/base.ts`
|
|
5
|
+
> **Component blueprint:** `01-components/02-question.md`
|
|
6
|
+
|
|
7
|
+
## Config Inventory (QuestionBase)
|
|
8
|
+
|
|
9
|
+
| Field | Implemented | UI impact |
|
|
10
|
+
|-------|-------------|-----------|
|
|
11
|
+
| `id` | ✅ | `section id={question.id}` — required for scroll-to-error |
|
|
12
|
+
| `text` | ✅ | Heading via `dangerouslySetInnerHTML` |
|
|
13
|
+
| `description` | ✅ | Optional HTML block below title |
|
|
14
|
+
| `required` | ✅ | Red `*` after title (`text-red-500`) |
|
|
15
|
+
| `requiredErrorMessage` | ✅ | Shown via SDK validation in error banner |
|
|
16
|
+
| `questionNumber` | ❌ | Not rendered in reference client |
|
|
17
|
+
| `containerMediaUrl` | ❌ | Not rendered on generic questions |
|
|
18
|
+
|
|
19
|
+
## Layout Anatomy
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
┌─ section (rounded-xl border border-gray-200 bg-white p-6 shadow-sm) ─────┐
|
|
23
|
+
│ h2: question.text (HTML) + red * if required │
|
|
24
|
+
│ [optional description HTML] │
|
|
25
|
+
│ ┌─ type-specific scale (see per-type specs) ─────────────────────┐ │
|
|
26
|
+
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
27
|
+
│ [validation error banner if validationError prop] │
|
|
28
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Dispatcher Routing
|
|
32
|
+
|
|
33
|
+
| `question.type` | Condition | Component |
|
|
34
|
+
|-----------------|-----------|-----------|
|
|
35
|
+
| `rating` | — | `RatingScale` + label row in Question |
|
|
36
|
+
| `radio` | — | Inline card radios in Question |
|
|
37
|
+
| `text` | — | Inline textarea in Question |
|
|
38
|
+
| `csat` | — | `CsatScale` + min/max labels in Question |
|
|
39
|
+
| `matrix` | `CSAT_MATRIX` \| `RATING_MATRIX` | `CsatMatrixScale` |
|
|
40
|
+
| `matrix` | `CFM_MATRIX` | `LikertMatrixScale` |
|
|
41
|
+
| `slider_matrix` | — | `SliderMatrixScale` |
|
|
42
|
+
| `file_upload` | — | `FileUploadScale` |
|
|
43
|
+
| `text_and_media` | `mediaUrl` present | Inline media in Question |
|
|
44
|
+
| `slider` | — | ❌ Not routed — see `06-slider.md` |
|
|
45
|
+
| `rating_scale` | — | ❌ Not routed — see `05-rating-scale.md` |
|
|
46
|
+
|
|
47
|
+
## Label Positioning (rating)
|
|
48
|
+
|
|
49
|
+
Rendered in Question before `RatingScale`:
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
// midLabel CSS left:
|
|
53
|
+
left: `${(midLabelIndex / (options.length - 1)) * 100}%`
|
|
54
|
+
// with -translate-x-1/2 for centering
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
minLabel: `absolute left-0`, maxLabel: `absolute right-0`, container `relative h-6 text-xs font-semibold text-gray-500`.
|
|
58
|
+
|
|
59
|
+
## Label Positioning (csat)
|
|
60
|
+
|
|
61
|
+
minLabel / maxLabel: `flex justify-between text-xs font-semibold text-gray-500` above `CsatScale`.
|
|
62
|
+
|
|
63
|
+
## Validation Error Banner
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
className="mt-4 rounded-[4px] border border-[#333333] bg-[#d9d9d9] py-3.5 px-4"
|
|
67
|
+
// text: text-[13px] font-bold text-gray-900
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Props Contract
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
type QuestionProps = {
|
|
74
|
+
question: SurveyQuestion;
|
|
75
|
+
selectedValue?: AnswerValue;
|
|
76
|
+
validationError?: string;
|
|
77
|
+
onSelect: (value: AnswerValue) => void;
|
|
78
|
+
};
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Wire `onSelect` → `onAction({ type: 'CHANGE', payload: { questionId, value } })`.
|
|
82
|
+
|
|
83
|
+
## Agent Checklist
|
|
84
|
+
|
|
85
|
+
- [ ] `id={question.id}` on outermost `<section>`
|
|
86
|
+
- [ ] HTML rendering for text and description
|
|
87
|
+
- [ ] Exhaustive type switch — all 12 types handled or explicitly noted as gap
|
|
88
|
+
- [ ] Validation error banner below scale
|
|
89
|
+
- [ ] Rating/csat endpoint labels positioned per formulas above
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# UI Spec: NPS Rating (`type: 'rating'`)
|
|
2
|
+
|
|
3
|
+
> **Reference:** `apps/client/src/components/RatingScale.tsx` + label row in `Question.tsx`
|
|
4
|
+
> **SDK type:** `RatingQuestion` — `fetchSurvey/types/rating.ts`
|
|
5
|
+
> **Component blueprint:** `01-components/03-rating-scale.md`
|
|
6
|
+
|
|
7
|
+
## Config Inventory
|
|
8
|
+
|
|
9
|
+
| Field | Implemented | UI impact |
|
|
10
|
+
|-------|-------------|-----------|
|
|
11
|
+
| `options[]` | ✅ | Badge row + radio row (dual layer) |
|
|
12
|
+
| `options[].color` | ✅ | Badge background; fallback `#e20074` |
|
|
13
|
+
| `options[].label` | ✅ | Displayed in badge |
|
|
14
|
+
| `options[].value` | ✅ | Selection value |
|
|
15
|
+
| `minLabel` | ✅ | Rendered in Question.tsx above scale |
|
|
16
|
+
| `midLabel` | ✅ | Absolute positioned in Question.tsx |
|
|
17
|
+
| `maxLabel` | ✅ | Absolute positioned in Question.tsx |
|
|
18
|
+
| `midLabelIndex` | ✅ | Mid label `left` percentage |
|
|
19
|
+
| `reverseScaleOrder` | ❌ | Document for agents when SDK sends true |
|
|
20
|
+
| `buttonVariant` | ❌ | Client always uses dual-layer radio layout |
|
|
21
|
+
|
|
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
|
+
## Answer Mutation
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
onSelect(option.value) // number
|
|
65
|
+
// → onAction CHANGE { questionId, value: number }
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Accessibility
|
|
69
|
+
|
|
70
|
+
- `role="radiogroup"` on wrapper
|
|
71
|
+
- Native `<input type="radio" className="sr-only">` per cell
|
|
72
|
+
- `name={questionId}` groups radios
|
|
73
|
+
|
|
74
|
+
## Agent Checklist
|
|
75
|
+
|
|
76
|
+
- [ ] Two-row grid with `gridTemplateColumns: repeat(options.length, 1fr)`
|
|
77
|
+
- [ ] Badges use `option.color`, not hardcoded NPS colors unless option lacks color
|
|
78
|
+
- [ ] Only track row is clickable
|
|
79
|
+
- [ ] min/mid/max labels in Question wrapper with midLabelIndex formula
|
|
80
|
+
- [ ] Selected state: pink inner track + thick magenta radio ring
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# UI Spec: Radio MCQ (`type: 'radio'`)
|
|
2
|
+
|
|
3
|
+
> **Reference:** `apps/client/src/components/Question.tsx` (inline)
|
|
4
|
+
> **SDK type:** `RadioQuestion` — `fetchSurvey/types/radio.ts`
|
|
5
|
+
> **Component blueprint:** `01-components/02-question.md`
|
|
6
|
+
|
|
7
|
+
## Config Inventory
|
|
8
|
+
|
|
9
|
+
| Field | Implemented | UI impact |
|
|
10
|
+
|-------|-------------|-----------|
|
|
11
|
+
| `options[]` | ✅ | One card per option |
|
|
12
|
+
| `options[].label` | ✅ | HTML via `dangerouslySetInnerHTML` |
|
|
13
|
+
| `options[].value` | ✅ | Selection value; `null` → empty string on input |
|
|
14
|
+
| `isMultiSelect` | ❌ | SDK supports; client is single-select only |
|
|
15
|
+
|
|
16
|
+
## Layout Anatomy
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
┌─ label (full-width card) ─────────────────────────────────────┐
|
|
20
|
+
│ (●) Option label HTML │ selected
|
|
21
|
+
└───────────────────────────────────────────────────────────────┘
|
|
22
|
+
┌─ label ───────────────────────────────────────────────────────┐
|
|
23
|
+
│ (○) Option label HTML │ unselected
|
|
24
|
+
└───────────────────────────────────────────────────────────────┘
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Interactive States
|
|
28
|
+
|
|
29
|
+
| State | Border | Background | Radio |
|
|
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
|
+
```
|
|
44
|
+
|
|
45
|
+
## Answer Mutation
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
onSelect(option.value) // string | number
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Agent Checklist
|
|
52
|
+
|
|
53
|
+
- [ ] Full-width card options with gap-3 vertical stack
|
|
54
|
+
- [ ] Custom circular radio indicator (not browser default)
|
|
55
|
+
- [ ] Selected: magenta border + pink background
|
|
56
|
+
- [ ] HTML labels
|
|
57
|
+
- [ ] If `isMultiSelect`: implement checkbox cards (SDK gap in reference)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# UI Spec: Text Field (`type: 'text'`)
|
|
2
|
+
|
|
3
|
+
> **Reference:** `apps/client/src/components/Question.tsx` (inline)
|
|
4
|
+
> **SDK type:** `TextQuestion` — `fetchSurvey/types/text.ts`
|
|
5
|
+
|
|
6
|
+
## Config Inventory
|
|
7
|
+
|
|
8
|
+
| Field | Implemented | UI impact |
|
|
9
|
+
|-------|-------------|-----------|
|
|
10
|
+
| `placeholder` | ✅ | Textarea placeholder; default `'Type your response here...'` |
|
|
11
|
+
| `maxCharacterCount` | ✅ | Counter uses this; default 1000 |
|
|
12
|
+
| `inputVariant: 'long'` | ✅ | Always textarea (4 rows) in reference |
|
|
13
|
+
| `inputVariant: 'short'` | ❌ | Should render single-line `<input>` when SDK sends `'short'` |
|
|
14
|
+
|
|
15
|
+
## Layout Anatomy
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
┌─ textarea (w-full rounded-[4px] border border-gray-300 p-4) ──┐
|
|
19
|
+
│ placeholder text │
|
|
20
|
+
│ │
|
|
21
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
22
|
+
N Zeichen verbleiben. ← counter
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Interactive States
|
|
26
|
+
|
|
27
|
+
| State | Style |
|
|
28
|
+
|-------|-------|
|
|
29
|
+
| Default | `border-gray-300` |
|
|
30
|
+
| Focus | `focus:border-[#e20074] focus:ring-1 focus:ring-[#e20074]` |
|
|
31
|
+
|
|
32
|
+
## Character Counter
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
remaining = maxCharacterCount ?? 1000 - selectedValue.length
|
|
36
|
+
// Display: "{remaining} Zeichen verbleiben." (German in reference client)
|
|
37
|
+
// Agents: use survey language for i18n
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Answer Mutation
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
onSelect(string) // onChange from textarea
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Agent Checklist
|
|
47
|
+
|
|
48
|
+
- [ ] Textarea with 4 rows for long variant
|
|
49
|
+
- [ ] Character counter bottom-right
|
|
50
|
+
- [ ] Magenta focus ring
|
|
51
|
+
- [ ] Implement `inputVariant: 'short'` as single-line input when present
|