@explorer02/cfm-survey-sdk 0.1.8 → 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/dist/index.d.mts +24 -26
- package/dist/index.d.ts +24 -26
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
- package/postinstall.js +21 -30
- package/templates/AGENT.md +24 -10
- package/templates/docs/00-integration/constraints.md +54 -0
- package/templates/docs/00-integration/setup.md +80 -0
- package/templates/docs/00-integration/useSurveySDK.md +107 -0
- package/templates/docs/{03-client-components → 01-components}/01-survey-page.md +2 -0
- package/templates/docs/{03-client-components → 01-components}/02-question.md +2 -0
- package/templates/docs/{03-client-components → 01-components}/03-rating-scale.md +2 -0
- package/templates/docs/{03-client-components → 01-components}/04-csat-scale.md +3 -1
- package/templates/docs/{03-client-components → 01-components}/05-csat-matrix-scale.md +2 -0
- package/templates/docs/{03-client-components → 01-components}/06-likert-matrix-scale.md +2 -0
- package/templates/docs/{03-client-components → 01-components}/07-slider-matrix-scale.md +2 -0
- package/templates/docs/{03-client-components → 01-components}/08-file-upload-scale.md +2 -0
- package/templates/docs/{03-client-components → 01-components}/09-custom-slider-track.md +2 -0
- package/templates/docs/{03-client-components → 01-components}/10-header-footer.md +3 -1
- package/templates/docs/{03-client-components → 01-components}/11-progress-bar.md +2 -0
- package/templates/docs/{03-client-components → 01-components}/12-language-selector.md +2 -0
- package/templates/docs/{03-client-components → 01-components}/13-matrix-dropdown.md +2 -0
- package/templates/docs/{03-client-components → 01-components}/README.md +3 -3
- package/templates/docs/02-question-types/01-rating.md +0 -8
- package/templates/docs/02-reference/config-field-index.md +158 -0
- package/templates/docs/02-reference/question-types/01-rating.md +52 -0
- package/templates/docs/02-reference/question-types/README.md +71 -0
- package/templates/docs/02-reference/routing-table.md +37 -0
- 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 +150 -0
- package/templates/docs/index.md +111 -142
- package/templates/docs/templates/implementation_plan.md +91 -0
- package/templates/docs/01-sdk-core/01-fetch-survey.md +0 -68
- package/templates/docs/01-sdk-core/02-survey-mapper.md +0 -85
- package/templates/docs/01-sdk-core/03-question-mappers.md +0 -114
- package/templates/docs/01-sdk-core/04-pagination.md +0 -72
- package/templates/docs/01-sdk-core/05-validation.md +0 -66
- package/templates/docs/01-sdk-core/06-submit-survey.md +0 -90
- package/templates/docs/01-sdk-core/07-language-handling.md +0 -111
- package/templates/docs/01-sdk-core/08-icons-and-emojis.md +0 -56
- package/templates/docs/01-sdk-core/README.md +0 -53
- package/templates/docs/02-question-types/02-radio.md +0 -26
- package/templates/docs/02-question-types/03-text.md +0 -26
- package/templates/docs/02-question-types/12-text-and-media.md +0 -35
- package/templates/docs/02-question-types/README.md +0 -74
- package/templates/docs/04-critical-rules/01-import-rules.md +0 -51
- package/templates/docs/04-critical-rules/02-action-dispatching.md +0 -56
- package/templates/docs/04-critical-rules/03-scroll-navigation.md +0 -37
- package/templates/docs/04-critical-rules/04-logo-branding.md +0 -42
- package/templates/docs/04-critical-rules/05-troubleshooting.md +0 -46
- package/templates/docs/04-critical-rules/README.md +0 -29
- /package/templates/docs/{02-question-types → 02-reference/question-types}/04-csat.md +0 -0
- /package/templates/docs/{02-question-types → 02-reference/question-types}/05-rating-scale.md +0 -0
- /package/templates/docs/{02-question-types → 02-reference/question-types}/06-slider.md +0 -0
- /package/templates/docs/{02-question-types → 02-reference/question-types}/07-matrix-cfm.md +0 -0
- /package/templates/docs/{02-question-types → 02-reference/question-types}/08-matrix-csat.md +0 -0
- /package/templates/docs/{02-question-types → 02-reference/question-types}/09-matrix-rating.md +0 -0
- /package/templates/docs/{02-question-types → 02-reference/question-types}/10-slider-matrix.md +0 -0
- /package/templates/docs/{02-question-types → 02-reference/question-types}/11-file-upload.md +0 -0
|
@@ -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
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# UI Spec: CSAT (`type: 'csat'`)
|
|
2
|
+
|
|
3
|
+
> **Reference:** `apps/client/src/components/CsatScale.tsx`
|
|
4
|
+
> **SDK type:** `CsatQuestion` — `fetchSurvey/types/csat.ts`
|
|
5
|
+
> **Component blueprint:** `01-components/04-csat-scale.md`
|
|
6
|
+
|
|
7
|
+
## Config Inventory
|
|
8
|
+
|
|
9
|
+
| Field | Implemented | UI impact |
|
|
10
|
+
|-------|-------------|-----------|
|
|
11
|
+
| `options[]` | ✅ | Scale buttons or dropdown options |
|
|
12
|
+
| `buttonType` | ✅ partial | Branches below |
|
|
13
|
+
| `labelPosition` | ✅ | `'top'` → flex-col-reverse; `'hidden'` → no label |
|
|
14
|
+
| `minLabel` / `maxLabel` | ✅ | Rendered in Question.tsx above scale |
|
|
15
|
+
| `hasNotApplicable` | ❌ | N/A option in options array still renders |
|
|
16
|
+
| `reverseScaleOrder` | ❌ | Document: reverse options before map |
|
|
17
|
+
|
|
18
|
+
## Config Decision Tree
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
csat
|
|
22
|
+
├── buttonType === 'dropdown'
|
|
23
|
+
│ └── Native <select>, placeholder "Select an option"
|
|
24
|
+
│ └── Parse value 'null' → null
|
|
25
|
+
└── else (emoji | star | numbered | number | default)
|
|
26
|
+
└── flex flex-wrap gap-3 buttons
|
|
27
|
+
├── emoji → getEmojiForIndex(index, scaleLength)
|
|
28
|
+
├── star → CsatStarIcons filled/empty; labels hidden
|
|
29
|
+
├── numbered/number → mono font value
|
|
30
|
+
└── default → bold value or 'N/A' if null
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Interactive States
|
|
34
|
+
|
|
35
|
+
| State | Border | Background | Transform |
|
|
36
|
+
|-------|--------|------------|-----------|
|
|
37
|
+
| Default | `border-gray-200` | `white` | scale-100 |
|
|
38
|
+
| Hover | `border-gray-300` | `hover:bg-gray-50` | — |
|
|
39
|
+
| Selected | `border-[#e20074]` | `#fdf2f8` | `scale-110` |
|
|
40
|
+
| Star mode | transparent | transparent | no border/bg on button |
|
|
41
|
+
|
|
42
|
+
## Tooltips
|
|
43
|
+
|
|
44
|
+
Recommended: `title={option.label}` on each button. Not in reference client.
|
|
45
|
+
|
|
46
|
+
## Answer Mutation
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
onSelect(string | number | null)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Agent Checklist
|
|
53
|
+
|
|
54
|
+
- [ ] Dropdown branch with magenta focus ring
|
|
55
|
+
- [ ] Emoji/star/numbered branches per decision tree
|
|
56
|
+
- [ ] `labelPosition` controls label above/below/hidden
|
|
57
|
+
- [ ] Implement `reverseScaleOrder` and standalone `hasNotApplicable` (SDK gaps)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# UI Spec: Rating Scale (`type: 'rating_scale'`)
|
|
2
|
+
|
|
3
|
+
> **Reference:** ❌ Not routed in `Question.tsx` — reuse `CsatScale.tsx` patterns
|
|
4
|
+
> **SDK type:** `RatingScaleQuestion` — `fetchSurvey/types/ratingScale.ts`
|
|
5
|
+
> **Component blueprint:** `01-components/03-rating-scale.md`
|
|
6
|
+
|
|
7
|
+
## Config Inventory
|
|
8
|
+
|
|
9
|
+
| Field | Implemented | UI impact |
|
|
10
|
+
|-------|-------------|-----------|
|
|
11
|
+
| `options[]` | ❌ gap | Star/emoji buttons (no NPS colors) |
|
|
12
|
+
| `scaleStyle` | ❌ gap | `'star'` or `'emoji'` |
|
|
13
|
+
| `minLabel` / `midLabel` / `maxLabel` | ❌ gap | Same formulas as NPS in Question shell |
|
|
14
|
+
| `midLabelIndex` | ❌ gap | `(midLabelIndex / (options.length - 1)) * 100`% |
|
|
15
|
+
| `reverseScaleOrder` | ❌ gap | Reverse options before render |
|
|
16
|
+
|
|
17
|
+
## Expected UI (from CsatScale + rating labels)
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
[minLabel] [midLabel @ %] [maxLabel]
|
|
21
|
+
┌────┬────┬────┬────┬────┐
|
|
22
|
+
│ ★ │ ★ │ ★ │ ★ │ ★ │ scaleStyle: 'star'
|
|
23
|
+
└────┴────┴────┴────┴────┘
|
|
24
|
+
OR emoji row from getEmojiForIndex when scaleStyle: 'emoji'
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Reuse CsatScale selected styling: `border-[#e20074] bg-[#fdf2f8] scale-110`.
|
|
28
|
+
|
|
29
|
+
## Config Decision Tree
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
rating_scale
|
|
33
|
+
├── scaleStyle === 'star' → CsatStarIcons per option
|
|
34
|
+
├── scaleStyle === 'emoji' → getEmojiForIndex
|
|
35
|
+
└── else → numbered badges (no option.color)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Answer Mutation
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
onSelect(number)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Agent Checklist
|
|
45
|
+
|
|
46
|
+
- [ ] Add dispatcher branch in Question for `rating_scale`
|
|
47
|
+
- [ ] No traffic-light NPS colors on options
|
|
48
|
+
- [ ] min/mid/max labels with midLabelIndex formula
|
|
49
|
+
- [ ] Match CsatScale interactive states
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# UI Spec: Standalone Slider (`type: 'slider'`)
|
|
2
|
+
|
|
3
|
+
> **Reference:** ❌ Not routed in `Question.tsx` — reuse `CustomSliderTrack.tsx`
|
|
4
|
+
> **SDK type:** `SliderQuestion` — `fetchSurvey/types/slider.ts`
|
|
5
|
+
> **Component blueprint:** `01-components/09-custom-slider-track.md`
|
|
6
|
+
|
|
7
|
+
## Config Inventory
|
|
8
|
+
|
|
9
|
+
| Field | Implemented | UI impact |
|
|
10
|
+
|-------|-------------|-----------|
|
|
11
|
+
| `min` / `max` / `step` | ❌ gap | Track range |
|
|
12
|
+
| `defaultValue` | ❌ gap | Initial thumb position |
|
|
13
|
+
| `minLabel` / `maxLabel` | ❌ gap | End labels above track |
|
|
14
|
+
| `enableInputBox` | ❌ gap | See SliderMatrixScale input pattern |
|
|
15
|
+
| `displayValues` | ❌ gap | Pass to CustomSliderTrack |
|
|
16
|
+
|
|
17
|
+
## Layout Anatomy
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
[minLabel] [maxLabel]
|
|
21
|
+
┌──────────────────────────────────────────────────┐
|
|
22
|
+
│ ───●──────────────── (CustomSliderTrack) │
|
|
23
|
+
└──────────────────────────────────────────────────┘
|
|
24
|
+
[optional number input + steppers if enableInputBox]
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Reference Patterns
|
|
28
|
+
|
|
29
|
+
Copy from [`09-slider-matrix.md`](09-slider-matrix.md) single-row layout and [`shared/custom-slider-track.md`](shared/custom-slider-track.md).
|
|
30
|
+
|
|
31
|
+
## Answer Mutation
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
onSelect(number)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Agent Checklist
|
|
38
|
+
|
|
39
|
+
- [ ] Add Question dispatcher for `slider`
|
|
40
|
+
- [ ] CustomSliderTrack with min/max/step
|
|
41
|
+
- [ ] Optional input box with clamp min/max
|
|
42
|
+
- [ ] End labels flex justify-between
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# UI Spec: CFM Matrix (`type: 'matrix'`, `subType: 'CFM_MATRIX'`)
|
|
2
|
+
|
|
3
|
+
> **Reference:** `apps/client/src/components/LikertMatrixScale.tsx`
|
|
4
|
+
> **SDK type:** `MatrixQuestion` — `fetchSurvey/types/matrix.ts`
|
|
5
|
+
> **Component blueprint:** `01-components/06-likert-matrix-scale.md`
|
|
6
|
+
|
|
7
|
+
## Config Inventory
|
|
8
|
+
|
|
9
|
+
| Field | Implemented | UI impact |
|
|
10
|
+
|-------|-------------|-----------|
|
|
11
|
+
| `rows[]`, `columns[]` | ✅ | Grid / carousel / dropdown data |
|
|
12
|
+
| `matrixFormat` | ✅ | Orchestrator routing |
|
|
13
|
+
| `answerType` | ✅ | `'multiple'` → checkboxes |
|
|
14
|
+
| `matrixType: 'bipolar'` | ✅ | 25% left + 25% right columns |
|
|
15
|
+
| `labels[]` | ✅ | Anchor labels above scale |
|
|
16
|
+
| `transposeTable` | ✅ | Swap row/col iteration |
|
|
17
|
+
| `repeatScale` | ✅ | Re-render header per row |
|
|
18
|
+
| `enableScale` | ✅ | Show/hide column header row |
|
|
19
|
+
| `hasNotApplicable` | ✅ | N/A column with mutex |
|
|
20
|
+
|
|
21
|
+
## Config Decision Tree
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
CFM_MATRIX
|
|
25
|
+
├── matrixFormat === 'dropdown' → LikertMatrixDropdownLayout (MatrixDropdown per row)
|
|
26
|
+
├── matrixFormat === 'carousel' → LikertMatrixCarouselLayout (one row, prev/next, blue dots)
|
|
27
|
+
└── else → LikertMatrixGridLayout
|
|
28
|
+
├── transposeTable ? columns×rows : rows×columns
|
|
29
|
+
├── repeatScale ? header per row : single header
|
|
30
|
+
├── enableScale ? column headers : hidden
|
|
31
|
+
└── matrixType === 'bipolar' ? 25% | scale | 25% layout
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Interactive Behaviors
|
|
35
|
+
|
|
36
|
+
| Behavior | Condition | Action |
|
|
37
|
+
|----------|-----------|--------|
|
|
38
|
+
| Click selected radio again | single-select grid/carousel | Clear row (`undefined`) |
|
|
39
|
+
| N/A selected | any | Other cols opacity 0.4, disabled |
|
|
40
|
+
| Multi N/A | answerType multiple | Sets `[null]` exclusively |
|
|
41
|
+
| Multi toggle | answerType multiple | Add/remove from array |
|
|
42
|
+
|
|
43
|
+
## Grid Layout
|
|
44
|
+
|
|
45
|
+
- Row label column: `180px` (likert) or `25%` (bipolar)
|
|
46
|
+
- Zebra rows: even rows `rgba(249,250,251,0.8)`
|
|
47
|
+
- Native inputs: `accentColor: #e20074`, 18×18px
|
|
48
|
+
|
|
49
|
+
## Carousel
|
|
50
|
+
|
|
51
|
+
- Blue active dot: `#2563eb` (Likert only)
|
|
52
|
+
- Card: white, border, shadow, centered row text
|
|
53
|
+
|
|
54
|
+
## Answer Mutation
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
onSelect(MatrixAnswerMap) // Record<rowId, value | value[] | null>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Agent Checklist
|
|
61
|
+
|
|
62
|
+
- [ ] All three matrixFormat branches
|
|
63
|
+
- [ ] transposeTable + repeatScale + enableScale
|
|
64
|
+
- [ ] Bipolar 25/50/25 layout with rightText
|
|
65
|
+
- [ ] Single-select deselect on re-click
|
|
66
|
+
- [ ] N/A mutex with opacity 0.4
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# UI Spec: CSAT / Rating Matrix (`subType: 'CSAT_MATRIX' | 'RATING_MATRIX'`)
|
|
2
|
+
|
|
3
|
+
> **Reference:** `apps/client/src/components/CsatMatrixScale.tsx`
|
|
4
|
+
> **SDK type:** `MatrixQuestion` — `fetchSurvey/types/matrix.ts`
|
|
5
|
+
> **Component blueprint:** `01-components/05-csat-matrix-scale.md`
|
|
6
|
+
|
|
7
|
+
## Config Inventory
|
|
8
|
+
|
|
9
|
+
| Field | Implemented | UI impact |
|
|
10
|
+
|-------|-------------|-----------|
|
|
11
|
+
| `buttonType` | ✅ | Grid vs vertical list routing |
|
|
12
|
+
| `matrixFormat` | ✅ | carousel vs standard |
|
|
13
|
+
| `hasNotApplicable` | ✅ | Checkbox below row (vertical) or N/A column (grid) |
|
|
14
|
+
| `reverseScaleOrder` | ✅ | Flip emoji/star/number index |
|
|
15
|
+
| `labels[]` | ✅ | Anchor row above columns |
|
|
16
|
+
| `columns[].color` | ✅ | RATING_MATRIX badge colors |
|
|
17
|
+
|
|
18
|
+
## Config Decision Tree
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
CSAT_MATRIX | RATING_MATRIX
|
|
22
|
+
├── matrixFormat === 'carousel' → CsatMatrixCarousel
|
|
23
|
+
├── buttonType in (dropdown, selectbox, radio) → CsatMatrixVerticalList
|
|
24
|
+
│ ├── dropdown → MatrixDropdown placeholder "Select..."
|
|
25
|
+
│ ├── selectbox → full-width cards #fdf2f8 selected
|
|
26
|
+
│ └── else → card radio list
|
|
27
|
+
└── else → CsatMatrixGrid
|
|
28
|
+
├── emoji → getEmojiForIndex + hover scale + ring when selected
|
|
29
|
+
├── star → opacity 0.5 unselected; filled/empty icons
|
|
30
|
+
├── numbered → 40×40 badge; selected magenta fill
|
|
31
|
+
└── graphics → CustomSliderTrack per row (not in grid — carousel only)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Grid Structure
|
|
35
|
+
|
|
36
|
+
- Row label: `180px` fixed column
|
|
37
|
+
- N/A column: `56px` when `hasNotApplicable`
|
|
38
|
+
- Alternating row backgrounds
|
|
39
|
+
- Column headers OR `labels[]` anchor row with percentage positioning
|
|
40
|
+
|
|
41
|
+
## Carousel
|
|
42
|
+
|
|
43
|
+
- Pink active dot expands: `20px × 8px` vs `8px` inactive
|
|
44
|
+
- Column labels below cells
|
|
45
|
+
- Emoji tooltip via hover state (`hoveredCell`)
|
|
46
|
+
- Graphics mode: slider maps column index
|
|
47
|
+
|
|
48
|
+
## Tooltips
|
|
49
|
+
|
|
50
|
+
`title={col.label}` recommended on grid cells (emoji hover uses scale transform).
|
|
51
|
+
|
|
52
|
+
## Answer Mutation
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
onSelect(MatrixAnswerMap)
|
|
56
|
+
// hasNotApplicable: null for N/A; undefined clears
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Agent Checklist
|
|
60
|
+
|
|
61
|
+
- [ ] scaleColumns = columns.filter(c => c.value !== null)
|
|
62
|
+
- [ ] All buttonType × matrixFormat branches
|
|
63
|
+
- [ ] reverseScaleOrder on emoji/star/numbered
|
|
64
|
+
- [ ] Carousel pink dot indicators
|
|
65
|
+
- [ ] Vertical selectbox cards match standalone radio styling
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# UI Spec: Slider Matrix (`type: 'slider_matrix'`)
|
|
2
|
+
|
|
3
|
+
> **Reference:** `apps/client/src/components/SliderMatrixScale.tsx`
|
|
4
|
+
> **SDK type:** `SliderMatrixQuestion` — `fetchSurvey/types/sliderMatrix.ts`
|
|
5
|
+
> **Component blueprint:** `01-components/07-slider-matrix-scale.md`
|
|
6
|
+
> **Shared:** [`shared/custom-slider-track.md`](shared/custom-slider-track.md)
|
|
7
|
+
|
|
8
|
+
## Config Inventory
|
|
9
|
+
|
|
10
|
+
| Field | Implemented | UI impact |
|
|
11
|
+
|-------|-------------|-----------|
|
|
12
|
+
| `rows[]` (min/max/step) | ✅ | Per-row slider range |
|
|
13
|
+
| `labels[]` | ✅ | Header anchor labels |
|
|
14
|
+
| `tickValues[]` | ✅ | Colored badges above track |
|
|
15
|
+
| `ticks` | ✅ | Discrete marks + htmlStep |
|
|
16
|
+
| `sliderType: 'graphics'` | ✅ | Plain text tick labels; emoji thumb |
|
|
17
|
+
| `enableInputBox` | ✅ | 80px input + stepper buttons |
|
|
18
|
+
| `displayValues` | ✅ | Passed to CustomSliderTrack |
|
|
19
|
+
| `enableNotApplicable` | ✅ | Checkbox; value `'N/A'` string |
|
|
20
|
+
|
|
21
|
+
## Layout Anatomy
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
[header: labels[] + tickValues badges OR computed marks]
|
|
25
|
+
┌─ row band (#f3f4f6, rounded-xl) ─────────────────────────────┐
|
|
26
|
+
│ 25% statement │ CustomSliderTrack │ 25% rightText? │ input? │ N/A? │
|
|
27
|
+
└───────────────────────────────────────────────────────────────┘
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Header Tick Positioning
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
percentage = ((tv.value - min) / (max - min)) * 100
|
|
34
|
+
sliceWidth = tickValues.length > 1 ? 100 / (tickValues.length - 1) : 100
|
|
35
|
+
// Colored badge: tv.color bg, white text, rounded 4px
|
|
36
|
+
// graphics: plain text label instead of colored badge
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## N/A Behavior
|
|
40
|
+
|
|
41
|
+
- Checked → `rowVal === 'N/A'`; slider disabled, input grayed
|
|
42
|
+
- Unchecked → reset to `defaultValue ?? min`
|
|
43
|
+
|
|
44
|
+
## Input Box
|
|
45
|
+
|
|
46
|
+
- Clamp to row.min / row.max on change
|
|
47
|
+
- Steppers use `htmlStep` increment/decrement
|
|
48
|
+
|
|
49
|
+
## Answer Mutation
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
onSelect(Record<string, number | null | 'N/A'>)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Agent Checklist
|
|
56
|
+
|
|
57
|
+
- [ ] Shared header from first row min/max/step
|
|
58
|
+
- [ ] Gray row bands with 25% statement columns
|
|
59
|
+
- [ ] CustomSliderTrack per row with correct htmlStep
|
|
60
|
+
- [ ] tickValues colored badges (non-graphics)
|
|
61
|
+
- [ ] N/A mutex disables slider + input
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# UI Spec: File Upload (`type: 'file_upload'`)
|
|
2
|
+
|
|
3
|
+
> **Reference:** `apps/client/src/components/FileUploadScale.tsx`
|
|
4
|
+
> **SDK type:** `FileUploadQuestion` — `fetchSurvey/types/fileUpload.ts`
|
|
5
|
+
> **Component blueprint:** `01-components/08-file-upload-scale.md`
|
|
6
|
+
|
|
7
|
+
## Config Inventory
|
|
8
|
+
|
|
9
|
+
| Field | Implemented | UI impact |
|
|
10
|
+
|-------|-------------|-----------|
|
|
11
|
+
| `uploadMessage` | ✅ | HTML in dropzone (bold 16px) |
|
|
12
|
+
| `supportedFileFormats` | ✅ | Validation + accept attribute |
|
|
13
|
+
| `maxFileCount` | ✅ | Total file limit |
|
|
14
|
+
| `fileSizeLimit` | ✅ | MB limit |
|
|
15
|
+
| `fileSizeLimitType` | ✅ | `PER_FILE` or `IN_TOTAL` |
|
|
16
|
+
|
|
17
|
+
## Layout Anatomy
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
[uploadMessage HTML in dashed dropzone]
|
|
21
|
+
File limit: N; File size limit: XMB/file|in total; Supports: ...
|
|
22
|
+
[local error banner — red if validation fails]
|
|
23
|
+
[file list cards: icon, name, size, remove ×]
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Interactive States
|
|
27
|
+
|
|
28
|
+
| State | Border | Background |
|
|
29
|
+
|-------|--------|------------|
|
|
30
|
+
| Default | `#d1d5db` dashed | `#f9fafb` |
|
|
31
|
+
| Hover | `#e20074` | `#fdf2f8` |
|
|
32
|
+
| Drag active | `#e20074` | `#fdf2f8` |
|
|
33
|
+
|
|
34
|
+
## Local Validation (before SDK)
|
|
35
|
+
|
|
36
|
+
1. Max file count exceeded
|
|
37
|
+
2. Unsupported extension
|
|
38
|
+
3. PER_FILE size exceeded
|
|
39
|
+
4. IN_TOTAL cumulative size exceeded
|
|
40
|
+
|
|
41
|
+
Error banner: `#fee2e2` bg, `#b91c1c` text.
|
|
42
|
+
|
|
43
|
+
## Answer Mutation
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
onSelect(File[] | undefined) // undefined when all removed
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Agent Checklist
|
|
50
|
+
|
|
51
|
+
- [ ] Hidden `<input type="file">` triggered by dropzone click
|
|
52
|
+
- [ ] Drag enter/over/leave/drop handlers
|
|
53
|
+
- [ ] Dynamic constraints line in dropzone
|
|
54
|
+
- [ ] File list with remove button (hover red)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# UI Spec: Text and Media (`type: 'text_and_media'`)
|
|
2
|
+
|
|
3
|
+
> **Reference:** `apps/client/src/components/Question.tsx` (inline)
|
|
4
|
+
> **SDK type:** `TextAndMediaQuestion` — `fetchSurvey/types/textAndMedia.ts`
|
|
5
|
+
> **Component blueprint:** `01-components/02-question.md`
|
|
6
|
+
|
|
7
|
+
## Config Inventory
|
|
8
|
+
|
|
9
|
+
| Field | Implemented | UI impact |
|
|
10
|
+
|-------|-------------|-----------|
|
|
11
|
+
| `mediaUrl` | ✅ | Required to render media block |
|
|
12
|
+
| `mediaMimeType` | ✅ | `video/` or `VIDEO` → `<video>`; else `<img>` |
|
|
13
|
+
| `mediaTitle` | ✅ | Caption below media, centered gray text |
|
|
14
|
+
| `mediaAlignment` | ✅ | Flex justify: CENTER / RIGHT / default left |
|
|
15
|
+
| `mediaSize` | ✅ | Width `%` on media container; default 100% |
|
|
16
|
+
|
|
17
|
+
No answer input — display-only question.
|
|
18
|
+
|
|
19
|
+
## Layout Anatomy
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
[question title + description — standard shell]
|
|
23
|
+
|
|
24
|
+
┌─ flex container (justify per mediaAlignment) ──────────────┐
|
|
25
|
+
│ ┌─ media block (width: mediaSize%) ──────────────────┐ │
|
|
26
|
+
│ │ <video controls> or <img> │ │
|
|
27
|
+
│ │ [optional mediaTitle caption] │ │
|
|
28
|
+
│ └─────────────────────────────────────────────────────┘ │
|
|
29
|
+
└────────────────────────────────────────────────────────────┘
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Alignment Mapping
|
|
33
|
+
|
|
34
|
+
| `mediaAlignment` | CSS |
|
|
35
|
+
|------------------|-----|
|
|
36
|
+
| `TOP_CENTER`, `BOTTOM_CENTER` | `justify-center` |
|
|
37
|
+
| `TOP_RIGHT`, `BOTTOM_RIGHT` | `justify-end` |
|
|
38
|
+
| default | `justify-start` |
|
|
39
|
+
|
|
40
|
+
## Answer Mutation
|
|
41
|
+
|
|
42
|
+
None — no `onSelect` payload for this type.
|
|
43
|
+
|
|
44
|
+
## Agent Checklist
|
|
45
|
+
|
|
46
|
+
- [ ] Render only when `mediaUrl` is present
|
|
47
|
+
- [ ] Video vs image from mime type
|
|
48
|
+
- [ ] `max-w-full rounded-md shadow-sm` on media element
|
|
49
|
+
- [ ] Optional centered caption for `mediaTitle`
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# UI Spec: Survey Chrome
|
|
2
|
+
|
|
3
|
+
> **Reference:** `SurveyPage.tsx`, `Header.tsx`, `Footer.tsx`, `ProgressBar.tsx`, `LanguageSelector.tsx`
|
|
4
|
+
> **Component blueprints:** `01-components/01-survey-page.md`, `10-header-footer.md`, `11-progress-bar.md`, `12-language-selector.md`
|
|
5
|
+
|
|
6
|
+
## SurveyPage — 5-State Machine
|
|
7
|
+
|
|
8
|
+
| State | Condition | UI |
|
|
9
|
+
|-------|-----------|-----|
|
|
10
|
+
| Loading | `surveyQueryResults.isLoading` | Centered "Loading..." |
|
|
11
|
+
| Error | `surveyQueryResults.error` | Red error message |
|
|
12
|
+
| Empty | `!survey` | "No survey data" |
|
|
13
|
+
| Submitted | `submitSurveyResults.data` | Thank you message |
|
|
14
|
+
| Active | else | Questions + nav |
|
|
15
|
+
|
|
16
|
+
## Active Layout
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
Header
|
|
20
|
+
main (max-w-4xl)
|
|
21
|
+
ProgressBar(progressPercentage)
|
|
22
|
+
LanguageSelector (hidden if ≤1 language)
|
|
23
|
+
Questions (space-y-12)
|
|
24
|
+
Submit error (if any)
|
|
25
|
+
Nav: Back (if page > 0) + Next/Submit
|
|
26
|
+
Footer
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Navigation Labels
|
|
30
|
+
|
|
31
|
+
German when `survey.language.startsWith('de')`:
|
|
32
|
+
|
|
33
|
+
| Action | EN | DE |
|
|
34
|
+
|--------|----|----|
|
|
35
|
+
| Back | Zurück | Back |
|
|
36
|
+
| Next | Weiter | Next |
|
|
37
|
+
| Submit | Absenden | Submit |
|
|
38
|
+
| Submitting | Wird gesendet... | Submitting... |
|
|
39
|
+
|
|
40
|
+
## Button Tokens
|
|
41
|
+
|
|
42
|
+
- Back: `border-[#e20074] text-[#e20074] hover:bg-[#fdf2f8]`
|
|
43
|
+
- Primary: `bg-[#e20074] text-white hover:opacity-90`
|
|
44
|
+
|
|
45
|
+
## ProgressBar
|
|
46
|
+
|
|
47
|
+
Pink fill bar, `300ms` width transition on `progressPercentage`.
|
|
48
|
+
|
|
49
|
+
## LanguageSelector
|
|
50
|
+
|
|
51
|
+
- Hidden when `languages.length <= 1`
|
|
52
|
+
- Focus ring: `#2563eb`
|
|
53
|
+
|
|
54
|
+
## Agent Checklist
|
|
55
|
+
|
|
56
|
+
- [ ] All 5 states implemented
|
|
57
|
+
- [ ] `onAction({ type: 'CHANGE' })` wired per question
|
|
58
|
+
- [ ] PREVIOUS / NEXT actions on nav buttons
|
|
59
|
+
- [ ] ProgressBar receives `state.progressPercentage`
|
|
60
|
+
- [ ] Header/Footer on every state branch
|