@explorer02/cfm-survey-sdk 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +18 -18
- package/dist/cli/index.mjs +14 -14
- package/dist/index.d.mts +465 -580
- package/dist/index.d.ts +465 -580
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +6 -4
- package/postinstall.js +96 -69
- package/templates/AGENT.md +18 -8
- package/templates/docs/00-integration/analytics-events-catalog.md +54 -0
- package/templates/docs/00-integration/answer-logic-and-navigation.md +50 -0
- package/templates/docs/00-integration/aws-deploy.md +281 -0
- package/templates/docs/00-integration/client-integration-guide.md +351 -0
- package/templates/docs/00-integration/component-checklist.md +55 -22
- package/templates/docs/00-integration/constraints.md +49 -4
- package/templates/docs/00-integration/custom-field-logic-and-navigation.md +174 -0
- package/templates/docs/00-integration/display-logic-and-navigation.md +64 -0
- package/templates/docs/00-integration/end-page-logic.md +64 -0
- package/templates/docs/00-integration/file-upload-aws.md +116 -0
- package/templates/docs/00-integration/logic-fields-catalog.md +105 -0
- package/templates/docs/00-integration/partial-save-and-recovery.md +98 -0
- package/templates/docs/00-integration/placeholders-and-tokens.md +116 -0
- package/templates/docs/00-integration/progress.md +61 -0
- package/templates/docs/00-integration/question-display-variants.md +65 -0
- package/templates/docs/00-integration/question-numbering.md +51 -0
- package/templates/docs/00-integration/question-type-sdk-matrix.md +244 -0
- package/templates/docs/00-integration/setup.md +84 -0
- package/templates/docs/00-integration/skip-logic-and-navigation.md +80 -0
- package/templates/docs/00-integration/survey-lifecycle-analytics.md +94 -0
- package/templates/docs/00-integration/useSurveySDK.md +151 -23
- package/templates/docs/01-components/01-survey-page.md +76 -54
- package/templates/docs/01-components/02-question.md +61 -58
- package/templates/docs/01-components/03-rating-scale.md +11 -1
- package/templates/docs/01-components/04-star-rating-scale.md +45 -0
- package/templates/docs/01-components/05-csat-matrix-scale.md +20 -11
- package/templates/docs/01-components/06-likert-matrix-scale.md +14 -4
- package/templates/docs/01-components/07-slider-matrix-scale.md +11 -2
- package/templates/docs/01-components/08-file-upload-scale.md +23 -51
- package/templates/docs/01-components/09-custom-slider-track.md +2 -2
- package/templates/docs/01-components/11-progress-bar.md +10 -4
- package/templates/docs/01-components/13-matrix-dropdown.md +29 -32
- package/templates/docs/01-components/14-intro-page.md +34 -0
- package/templates/docs/01-components/15-end-page.md +31 -0
- package/templates/docs/01-components/16-paused-page.md +27 -0
- package/templates/docs/01-components/17-heatmap-scale.md +75 -0
- package/templates/docs/01-components/18-rank-order-scale.md +93 -0
- package/templates/docs/01-components/README.md +42 -36
- package/templates/docs/02-reference/config-field-index.md +141 -139
- package/templates/docs/02-reference/exports.md +149 -0
- package/templates/docs/02-reference/question-types/01-rating.md +31 -32
- package/templates/docs/02-reference/question-types/04-csat.md +51 -42
- package/templates/docs/02-reference/question-types/05-rating-scale.md +16 -11
- package/templates/docs/02-reference/question-types/06-slider.md +2 -2
- package/templates/docs/02-reference/question-types/07-matrix-cfm.md +44 -30
- package/templates/docs/02-reference/question-types/08-matrix-csat.md +4 -26
- package/templates/docs/02-reference/question-types/09-matrix-rating.md +29 -13
- package/templates/docs/02-reference/question-types/10-slider-matrix.md +15 -4
- package/templates/docs/02-reference/question-types/11-file-upload.md +43 -17
- package/templates/docs/02-reference/question-types/12-heatmap.md +59 -0
- package/templates/docs/02-reference/question-types/13-rank-order.md +101 -0
- package/templates/docs/02-reference/question-types/README.md +96 -47
- package/templates/docs/02-reference/routing-table.md +33 -18
- package/templates/docs/02-reference/value-derivation.md +63 -0
- package/templates/docs/03-ui-specs/00-question-shell.md +19 -88
- package/templates/docs/03-ui-specs/01-rating.md +18 -52
- package/templates/docs/03-ui-specs/02-radio.md +80 -37
- package/templates/docs/03-ui-specs/03-text.md +2 -2
- package/templates/docs/03-ui-specs/04-csat.md +68 -33
- package/templates/docs/03-ui-specs/05-rating-scale.md +21 -18
- package/templates/docs/03-ui-specs/06-slider.md +3 -1
- package/templates/docs/03-ui-specs/07-matrix-cfm.md +44 -25
- package/templates/docs/03-ui-specs/08-matrix-csat-rating.md +19 -43
- package/templates/docs/03-ui-specs/09-slider-matrix.md +18 -1
- package/templates/docs/03-ui-specs/10-file-upload.md +42 -21
- package/templates/docs/03-ui-specs/11-text-and-media.md +29 -29
- package/templates/docs/03-ui-specs/12-survey-chrome.md +58 -14
- package/templates/docs/03-ui-specs/13-heatmap.md +80 -0
- package/templates/docs/03-ui-specs/14-rank-order.md +128 -0
- package/templates/docs/03-ui-specs/README.md +16 -14
- package/templates/docs/03-ui-specs/shared/custom-slider-track.md +28 -35
- package/templates/docs/03-ui-specs/shared/matrix-dropdown.md +5 -0
- package/templates/docs/MANIFEST.json +476 -69
- package/templates/docs/index.md +82 -30
- package/templates/docs/templates/CustomSliderTrack.tsx +144 -0
- package/templates/docs/templates/MatrixDropdown.tsx +216 -0
- package/templates/docs/templates/Question.tsx +221 -154
- package/templates/docs/templates/RankOrderScale.tsx +353 -0
- package/templates/docs/templates/deploy-to-aws.sh +114 -0
- package/templates/docs/templates/heatmapCoords.ts +58 -0
- package/templates/docs/templates/implementation_plan.md +126 -67
- package/templates/docs/templates/survey-inventory.schema.json +77 -0
- package/templates/docs/templates/surveyUiIcons.tsx +52 -0
- package/templates/docs/templates/verify-agent-build.sh +85 -0
- package/templates/docs/01-components/04-csat-scale.md +0 -40
- package/templates/docs/02-question-types/01-rating.md +0 -44
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
# UI Spec: CFM Matrix (`type: '
|
|
1
|
+
# UI Spec: CFM Matrix (`type: 'CFM_MATRIX'`)
|
|
2
2
|
|
|
3
3
|
> **Implement from this spec** (npm installs). Monorepo reference: `apps/client/src/components/LikertMatrixScale.tsx`
|
|
4
|
-
> **SDK type:** `
|
|
4
|
+
> **SDK type:** `CfmMatrixQuestion` — `fetchSurvey/types/cfmMatrix.ts`
|
|
5
5
|
> **Component blueprint:** `01-components/06-likert-matrix-scale.md`
|
|
6
6
|
|
|
7
7
|
## Config Inventory
|
|
8
8
|
|
|
9
9
|
| Field | Implemented | UI impact |
|
|
10
10
|
|-------|-------------|-----------|
|
|
11
|
-
| `
|
|
12
|
-
| `
|
|
13
|
-
| `
|
|
14
|
-
| `
|
|
15
|
-
| `
|
|
11
|
+
| `statementRows[]`, `scaleColumns[]` | ✅ | Grid / carousel / dropdown data |
|
|
12
|
+
| `gridLayout` | ✅ | Orchestrator routing |
|
|
13
|
+
| `selectionMode` | ✅ | `'multiple'` → checkboxes |
|
|
14
|
+
| `statementLayout: 'bipolar'` | ✅ | 25% left + 25% right columns |
|
|
15
|
+
| `scaleAnchorLabels[]` | ✅ | Anchor labels above scale |
|
|
16
16
|
| `transposeTable` | ✅ | Swap row/col iteration |
|
|
17
|
-
| `
|
|
18
|
-
| `
|
|
19
|
-
| `
|
|
17
|
+
| `repeatColumnHeaders` | ✅ | Re-render header per row |
|
|
18
|
+
| `showColumnHeaders` | ✅ | Show/hide column header row |
|
|
19
|
+
| `hasNotApplicableOption` | ✅ | N/A column with mutex |
|
|
20
20
|
|
|
21
21
|
## Config Decision Tree
|
|
22
22
|
|
|
23
23
|
```
|
|
24
24
|
CFM_MATRIX
|
|
25
|
-
├──
|
|
26
|
-
├──
|
|
25
|
+
├── gridLayout === 'dropdown' → LikertMatrixDropdownLayout (MatrixDropdown per row)
|
|
26
|
+
├── gridLayout === 'carousel' → LikertMatrixCarouselLayout (one row, prev/next, blue dots)
|
|
27
27
|
└── else → LikertMatrixGridLayout
|
|
28
28
|
├── transposeTable ? columns×rows : rows×columns
|
|
29
|
-
├──
|
|
30
|
-
├──
|
|
31
|
-
└──
|
|
29
|
+
├── repeatColumnHeaders ? header per row : single header
|
|
30
|
+
├── showColumnHeaders ? column headers : hidden
|
|
31
|
+
└── statementLayout === 'bipolar' ? 25% | scale | 25% layout
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
## Interactive Behaviors
|
|
@@ -37,8 +37,8 @@ CFM_MATRIX
|
|
|
37
37
|
|----------|-----------|--------|
|
|
38
38
|
| Click selected radio again | single-select grid/carousel | Clear row (`undefined`) |
|
|
39
39
|
| N/A selected | any | Other cols opacity 0.4, disabled |
|
|
40
|
-
| Multi N/A |
|
|
41
|
-
| Multi toggle |
|
|
40
|
+
| Multi N/A | selectionMode multiple | Sets `[null]` exclusively |
|
|
41
|
+
| Multi toggle | selectionMode multiple | Add/remove from array |
|
|
42
42
|
|
|
43
43
|
## Grid Layout
|
|
44
44
|
|
|
@@ -46,21 +46,40 @@ CFM_MATRIX
|
|
|
46
46
|
- Zebra rows: even rows `rgba(249,250,251,0.8)`
|
|
47
47
|
- Native inputs: `accentColor: #e20074`, 18×18px
|
|
48
48
|
|
|
49
|
-
## Carousel
|
|
49
|
+
## Carousel — Implementation Recipe
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
| Rule | Behavior |
|
|
52
|
+
|------|----------|
|
|
53
|
+
| Prev button | Disabled when `currentRowIndex === 0` |
|
|
54
|
+
| Next button | Disabled when on last visible row |
|
|
55
|
+
| Dot indicators | Click dot → jump to that row index |
|
|
56
|
+
| On answer | **Do not** auto-advance to next row |
|
|
57
|
+
| Keyboard | Optional Left/Right for prev/next |
|
|
58
|
+
| Active dot (Likert) | `#2563eb`, expanded pill `20×8px` vs `8×8px` inactive |
|
|
53
59
|
|
|
54
|
-
|
|
60
|
+
One statement row visible at a time; column headers below cells when `showColumnHeaders`.
|
|
61
|
+
|
|
62
|
+
## N/A mutex (all layouts)
|
|
63
|
+
|
|
64
|
+
| Action | Result |
|
|
65
|
+
|--------|--------|
|
|
66
|
+
| Select N/A | Clear other column values for row; other inputs opacity 0.4 + disabled |
|
|
67
|
+
| Select column after N/A | Clear N/A first |
|
|
68
|
+
| Multi-select + N/A | N/A sets `[null]` exclusively |
|
|
69
|
+
|
|
70
|
+
## Implementation Recipe
|
|
71
|
+
|
|
72
|
+
Store **`column.id`** via `matrixColumnStoredValue(col)` — see `02-reference/value-derivation.md`.
|
|
55
73
|
|
|
56
74
|
```typescript
|
|
57
|
-
onSelect(
|
|
75
|
+
onSelect(MatrixRowAnswers) // Record<statementRows[].id, value | value[] | null>
|
|
76
|
+
onAction({ type: 'CHANGE', payload: { questionId, answerValue: MatrixRowAnswers } })
|
|
58
77
|
```
|
|
59
78
|
|
|
60
79
|
## Agent Checklist
|
|
61
80
|
|
|
62
|
-
- [ ] All three
|
|
63
|
-
- [ ] transposeTable +
|
|
64
|
-
- [ ] Bipolar 25/50/25 layout with
|
|
81
|
+
- [ ] All three gridLayout branches
|
|
82
|
+
- [ ] transposeTable + repeatColumnHeaders + showColumnHeaders
|
|
83
|
+
- [ ] Bipolar 25/50/25 layout with oppositeStatementText
|
|
65
84
|
- [ ] Single-select deselect on re-click
|
|
66
85
|
- [ ] N/A mutex with opacity 0.4
|
|
@@ -1,65 +1,41 @@
|
|
|
1
|
-
# UI Spec:
|
|
1
|
+
# UI Spec: Rating Matrix Grid (`type: 'RATING_MATRIX'`)
|
|
2
2
|
|
|
3
3
|
> **Implement from this spec** (npm installs). Monorepo reference: `apps/client/src/components/CsatMatrixScale.tsx`
|
|
4
|
-
> **SDK type:** `
|
|
4
|
+
> **SDK type:** `RatingMatrixQuestion` — `fetchSurvey/types/ratingMatrix.ts`
|
|
5
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
|
-
| `
|
|
11
|
+
| `statementRows[]`, `scaleColumns[]` | ✅ | Grid / carousel / dropdown data |
|
|
12
|
+
| `displayStyle` | ✅ | Grid vs vertical list routing |
|
|
13
|
+
| `gridLayout` | ✅ | carousel vs standard vs dropdown |
|
|
14
|
+
| `hasNotApplicableOption` | ✅ | N/A column or checkbox |
|
|
14
15
|
| `reverseScaleOrder` | ✅ | Flip emoji/star/number index |
|
|
15
|
-
| `
|
|
16
|
-
| `
|
|
16
|
+
| `scaleAnchorLabels[]` | ✅ | Anchor row above columns |
|
|
17
|
+
| `minLabel` / `maxLabel` | ✅ | Rendered in Question.tsx above scale |
|
|
18
|
+
| `scaleColumns[].accentColor` | ✅ | NPS-style badge colors when numbered |
|
|
17
19
|
|
|
18
20
|
## Config Decision Tree
|
|
19
21
|
|
|
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
|
|
22
|
+
Same as `CSAT_MATRIX` — reuse `CsatMatrixScale`. See [04-csat.md](04-csat.md) for emoji/star/carousel/N/A/graphics recipes.
|
|
40
23
|
|
|
41
|
-
## Carousel
|
|
24
|
+
## Carousel / N/A / scale labels
|
|
42
25
|
|
|
43
|
-
|
|
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).
|
|
26
|
+
Follow CSAT Implementation Recipes in [04-csat.md](04-csat.md): pink carousel dots, N/A mutex, `tickLabels` from `scaleColumns[].label` on graphics sliders.
|
|
51
27
|
|
|
52
28
|
## Answer Mutation
|
|
53
29
|
|
|
30
|
+
Store **`column.id`** via `matrixColumnStoredValue(col)` — see `02-reference/value-derivation.md`.
|
|
31
|
+
|
|
54
32
|
```typescript
|
|
55
|
-
onSelect(
|
|
56
|
-
|
|
33
|
+
onSelect(MatrixRowAnswers)
|
|
34
|
+
onAction({ type: 'CHANGE', payload: { questionId, answerValue: MatrixRowAnswers } })
|
|
57
35
|
```
|
|
58
36
|
|
|
59
37
|
## Agent Checklist
|
|
60
38
|
|
|
61
|
-
- [ ]
|
|
62
|
-
- [ ]
|
|
63
|
-
- [ ]
|
|
64
|
-
- [ ] Carousel pink dot indicators
|
|
65
|
-
- [ ] Vertical selectbox cards match standalone radio styling
|
|
39
|
+
- [ ] Reuse CsatMatrixScale with `RATING_MATRIX` type
|
|
40
|
+
- [ ] Apply accentColor on numbered badges when present
|
|
41
|
+
- [ ] minLabel / maxLabel above grid when set
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# UI Spec: Slider Matrix (`type: '
|
|
1
|
+
# UI Spec: Slider Matrix (`type: 'SLIDER_MATRIX'`)
|
|
2
2
|
|
|
3
3
|
> **Implement from this spec** (npm installs). Monorepo reference: `apps/client/src/components/SliderMatrixScale.tsx`
|
|
4
4
|
> **SDK type:** `SliderMatrixQuestion` — `fetchSurvey/types/sliderMatrix.ts`
|
|
@@ -36,6 +36,23 @@ sliceWidth = tickValues.length > 1 ? 100 / (tickValues.length - 1) : 100
|
|
|
36
36
|
// graphics: plain text label instead of colored badge
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
## CustomSliderTrack per row — scale labels
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
<CustomSliderTrack
|
|
43
|
+
min={row.min}
|
|
44
|
+
max={row.max}
|
|
45
|
+
htmlStep={row.htmlStep}
|
|
46
|
+
value={rowVal as number}
|
|
47
|
+
tickLabels={row.tickLabels ?? scaleColumns.map(c => c.label)}
|
|
48
|
+
ticks={row.ticks}
|
|
49
|
+
sliderType={question.sliderStyle === 'graphics' ? 'graphics' : undefined}
|
|
50
|
+
onChange={v => updateRow(row.id, v)}
|
|
51
|
+
/>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Pass **`scaleColumns[].label`** (or row tick labels) as `tickLabels` — required for correct tooltip text (Day 19 fix).
|
|
55
|
+
|
|
39
56
|
## N/A Behavior
|
|
40
57
|
|
|
41
58
|
- Checked → `rowVal === 'N/A'`; slider disabled, input grayed
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
# UI Spec: File Upload (`type: '
|
|
1
|
+
# UI Spec: File Upload (`type: 'FILE_UPLOAD'`)
|
|
2
2
|
|
|
3
|
-
> **Implement from this spec** (npm installs). Monorepo reference: `apps/client/src/components/FileUploadScale.tsx`
|
|
4
3
|
> **SDK type:** `FileUploadQuestion` — `fetchSurvey/types/fileUpload.ts`
|
|
5
4
|
> **Component blueprint:** `01-components/08-file-upload-scale.md`
|
|
5
|
+
> **Upload API:** [`00-integration/file-upload-aws.md`](../00-integration/file-upload-aws.md)
|
|
6
6
|
|
|
7
7
|
## Config Inventory
|
|
8
8
|
|
|
9
|
-
| Field |
|
|
10
|
-
|
|
11
|
-
| `uploadMessage` |
|
|
12
|
-
| `supportedFileFormats` |
|
|
13
|
-
| `maxFileCount` |
|
|
14
|
-
| `fileSizeLimit` |
|
|
15
|
-
| `fileSizeLimitType` |
|
|
9
|
+
| Field | 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
16
|
|
|
17
17
|
## Layout Anatomy
|
|
18
18
|
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
[uploadMessage HTML in dashed dropzone]
|
|
21
21
|
File limit: N; File size limit: XMB/file|in total; Supports: ...
|
|
22
22
|
[local error banner — red if validation fails]
|
|
23
|
-
[file list cards: icon, name, size, remove ×]
|
|
23
|
+
[file list cards: icon, name, size, uploading…, remove ×]
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
## Interactive States
|
|
@@ -31,24 +31,45 @@
|
|
|
31
31
|
| Hover | `#e20074` | `#fdf2f8` |
|
|
32
32
|
| Drag active | `#e20074` | `#fdf2f8` |
|
|
33
33
|
|
|
34
|
-
##
|
|
34
|
+
## Implementation Recipe
|
|
35
35
|
|
|
36
|
-
1
|
|
37
|
-
2. Unsupported extension
|
|
38
|
-
3. PER_FILE size exceeded
|
|
39
|
-
4. IN_TOTAL cumulative size exceeded
|
|
36
|
+
### Phase 1 — Local pick + validate
|
|
40
37
|
|
|
41
|
-
|
|
38
|
+
1. Hidden `<input type="file" multiple={maxFileCount > 1}>`
|
|
39
|
+
2. Drag enter/over/leave/drop on dropzone
|
|
40
|
+
3. Reject: wrong extension, count exceeded, size exceeded
|
|
41
|
+
|
|
42
|
+
### Phase 2 — Upload (required for submit)
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
User picks File
|
|
46
|
+
→ POST {uploadApi}/get-upload-url { fileName, contentType, questionId }
|
|
47
|
+
← { uploadUrl, fileId, mediaUrl }
|
|
48
|
+
→ PUT uploadUrl (binary body, file Content-Type)
|
|
49
|
+
→ onSelect([...existing, { id: fileId, mediaUrl, name, mimeType, size }])
|
|
50
|
+
→ onAction(CHANGE)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
See [`file-upload-aws.md`](../00-integration/file-upload-aws.md).
|
|
54
|
+
|
|
55
|
+
### Phase 3 — Remove
|
|
56
|
+
|
|
57
|
+
Remove from list → `onSelect(undefined)` when empty, else updated `UploadedFileAnswer[]`.
|
|
42
58
|
|
|
43
59
|
## Answer Mutation
|
|
44
60
|
|
|
45
61
|
```typescript
|
|
46
|
-
|
|
62
|
+
// After upload completes — NOT raw File[]
|
|
63
|
+
onSelect: (value: UploadedFileAnswer[] | undefined) => void
|
|
64
|
+
onAction({ type: 'CHANGE', payload: { questionId, answerValue: UploadedFileAnswer[] } })
|
|
47
65
|
```
|
|
48
66
|
|
|
67
|
+
**Non-submittable:** storing `File[]` only — SDK submit drops attachments.
|
|
68
|
+
|
|
49
69
|
## Agent Checklist
|
|
50
70
|
|
|
51
|
-
- [ ]
|
|
52
|
-
- [ ]
|
|
53
|
-
- [ ]
|
|
54
|
-
- [ ]
|
|
71
|
+
- [ ] Local validation before upload
|
|
72
|
+
- [ ] Presigned upload wired; progress/spinner per file
|
|
73
|
+
- [ ] Final answer shape: `UploadedFileAnswer[]` with `id` + `mediaUrl`
|
|
74
|
+
- [ ] Remove clears answer or passes updated array
|
|
75
|
+
- [ ] Matrix row: [`question-type-sdk-matrix.md#file_upload`](../00-integration/question-type-sdk-matrix.md#file_upload)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# UI Spec: Text and Media (`type: '
|
|
1
|
+
# UI Spec: Text and Media (`type: 'TEXT_AND_MEDIA'`)
|
|
2
2
|
|
|
3
3
|
> **Reference:** `apps/client/src/components/Question.tsx` (inline)
|
|
4
4
|
> **SDK type:** `TextAndMediaQuestion` — `fetchSurvey/types/textAndMedia.ts`
|
|
@@ -8,42 +8,42 @@
|
|
|
8
8
|
|
|
9
9
|
| Field | Implemented | UI impact |
|
|
10
10
|
|-------|-------------|-----------|
|
|
11
|
-
| `
|
|
12
|
-
| `
|
|
13
|
-
| `
|
|
14
|
-
| `
|
|
15
|
-
| `
|
|
11
|
+
| `media?.url` | ✅ | Required to render media block |
|
|
12
|
+
| `media?.mimeType` | ✅ | `video/` or `VIDEO` → `<video>`; else `<img>` |
|
|
13
|
+
| `media?.title` | ✅ | Caption below media, centered gray text |
|
|
14
|
+
| `media?.alignment` | ✅ | Flex justify: CENTER / RIGHT / default left |
|
|
15
|
+
| `media?.size` | ✅ | Width `%` on media container; default 100% |
|
|
16
16
|
|
|
17
17
|
No answer input — display-only question.
|
|
18
18
|
|
|
19
|
-
##
|
|
19
|
+
## Media alignment anatomy
|
|
20
20
|
|
|
21
21
|
```
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
│
|
|
27
|
-
│
|
|
28
|
-
│
|
|
29
|
-
|
|
22
|
+
┌─────────────────────────────────────────┐
|
|
23
|
+
│ questionText (HTML) │
|
|
24
|
+
│ questionDescription (optional) │
|
|
25
|
+
├─────────────────────────────────────────┤
|
|
26
|
+
│ [ media block — flex container ] │
|
|
27
|
+
│ alignment LEFT: justify-start │
|
|
28
|
+
│ alignment CENTER: justify-center │
|
|
29
|
+
│ alignment RIGHT: justify-end │
|
|
30
|
+
│ width: media.size % (default 100) │
|
|
31
|
+
│ <img> or <video> max-w-full │
|
|
32
|
+
│ caption: media.title (centered gray) │
|
|
33
|
+
└─────────────────────────────────────────┘
|
|
30
34
|
```
|
|
31
35
|
|
|
32
|
-
|
|
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.
|
|
36
|
+
Container-level media (survey chrome) is separate — see `03-ui-specs/12-survey-chrome.md`.
|
|
43
37
|
|
|
44
38
|
## Agent Checklist
|
|
45
39
|
|
|
46
|
-
- [ ] Render only when `
|
|
47
|
-
- [ ] Video vs image from
|
|
40
|
+
- [ ] Render only when `question.media?.url` is present
|
|
41
|
+
- [ ] Video vs image from `media.mimeType`
|
|
48
42
|
- [ ] `max-w-full rounded-md shadow-sm` on media element
|
|
49
|
-
- [ ] Optional centered caption for `
|
|
43
|
+
- [ ] Optional centered caption for `media.title`
|
|
44
|
+
- [ ] Respect `media.alignment` (LEFT / CENTER / RIGHT) and `media.size` width %
|
|
45
|
+
|
|
46
|
+
## SDK Integration Checklist (agent)
|
|
47
|
+
|
|
48
|
+
- [ ] No CHANGE action — display-only
|
|
49
|
+
- [ ] Matrix row: [`question-type-sdk-matrix.md#text_and_media`](../00-integration/question-type-sdk-matrix.md#text_and_media)
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
# UI Spec: Survey Chrome
|
|
2
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`
|
|
3
|
+
> **Reference:** `SurveyPage.tsx`, `Header.tsx`, `Footer.tsx`, `ProgressBar.tsx`, `LanguageSelector.tsx`, `IntroPage.tsx`, `EndPage.tsx`, `PausedPage.tsx`
|
|
4
|
+
> **Component blueprints:** `01-components/01-survey-page.md`, `10-header-footer.md`, `11-progress-bar.md`, `12-language-selector.md`, `14-intro-page.md`, `15-end-page.md`, `16-paused-page.md`
|
|
5
5
|
|
|
6
|
-
## SurveyPage —
|
|
6
|
+
## SurveyPage — Phase Router
|
|
7
7
|
|
|
8
8
|
| State | Condition | UI |
|
|
9
9
|
|-------|-----------|-----|
|
|
10
10
|
| Loading | `surveyQueryResults.isLoading` | Centered "Loading..." |
|
|
11
11
|
| Error | `surveyQueryResults.error` | Red error message |
|
|
12
12
|
| Empty | `!survey` | "No survey data" |
|
|
13
|
-
|
|
|
14
|
-
|
|
|
13
|
+
| Intro | `state.phase.kind === 'intro'` | `IntroPage` + Start CTA |
|
|
14
|
+
| Paused | `state.phase.kind === 'paused'` | `PausedPage` |
|
|
15
|
+
| End | `state.phase.kind === 'end'` | `EndPage` |
|
|
16
|
+
| Submitted | `state.phase.kind === 'submitted'` | Terminal message |
|
|
17
|
+
| Expired | `state.phase.kind === 'expired'` | Terminal message |
|
|
18
|
+
| Quota | `state.phase.kind === 'quota_fulfilled'` | Terminal message |
|
|
19
|
+
| Active | `state.phase.kind === 'content'` | Questions + nav |
|
|
15
20
|
|
|
16
21
|
## Active Layout
|
|
17
22
|
|
|
@@ -22,7 +27,7 @@ main (max-w-4xl)
|
|
|
22
27
|
LanguageSelector (hidden if ≤1 language)
|
|
23
28
|
Questions (space-y-12)
|
|
24
29
|
Submit error (if any)
|
|
25
|
-
Nav: Back (if
|
|
30
|
+
Nav: Back (if canGoBack) + Next/Submit (primaryButtonType)
|
|
26
31
|
Footer
|
|
27
32
|
```
|
|
28
33
|
|
|
@@ -32,10 +37,10 @@ German when `survey.language.startsWith('de')`:
|
|
|
32
37
|
|
|
33
38
|
| Action | EN | DE |
|
|
34
39
|
|--------|----|----|
|
|
35
|
-
| Back |
|
|
36
|
-
| Next |
|
|
37
|
-
| Submit |
|
|
38
|
-
| Submitting |
|
|
40
|
+
| Back | Back | Zurück |
|
|
41
|
+
| Next | Next | Weiter |
|
|
42
|
+
| Submit | Submit | Absenden |
|
|
43
|
+
| Submitting | Submitting... | Wird gesendet... |
|
|
39
44
|
|
|
40
45
|
## Button Tokens
|
|
41
46
|
|
|
@@ -44,17 +49,56 @@ German when `survey.language.startsWith('de')`:
|
|
|
44
49
|
|
|
45
50
|
## ProgressBar
|
|
46
51
|
|
|
47
|
-
Pink fill bar
|
|
52
|
+
Pink fill bar. Bound to `state.progressPercentage` (path-scoped — see `00-integration/progress.md`).
|
|
48
53
|
|
|
49
54
|
## LanguageSelector
|
|
50
55
|
|
|
51
56
|
- Hidden when `languages.length <= 1`
|
|
52
57
|
- Focus ring: `#2563eb`
|
|
58
|
+
- Pass `language` in `useMemo` hook options — SDK remaps survey client-side (no refetch)
|
|
59
|
+
|
|
60
|
+
## Question numbering
|
|
61
|
+
|
|
62
|
+
Render ordinals from SDK on content pages:
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
{question.questionNumber != null && (
|
|
66
|
+
<span className="mr-1 font-semibold">{question.questionNumber}.</span>
|
|
67
|
+
)}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
See [`question-numbering.md`](../00-integration/question-numbering.md).
|
|
71
|
+
|
|
72
|
+
## Special pages
|
|
73
|
+
|
|
74
|
+
| Phase | Component | Action |
|
|
75
|
+
|-------|-----------|--------|
|
|
76
|
+
| `intro` | `IntroPage` | `page={state.specialPage}` · `onStart={() => onAction({ type: 'START' })}` |
|
|
77
|
+
| `paused` | `PausedPage` | `page={state.specialPage}` · read-only, no nav |
|
|
78
|
+
| `end` | `EndPage` | `page={state.specialPage}` · terminal thank-you |
|
|
79
|
+
|
|
80
|
+
## Terminal phases
|
|
81
|
+
|
|
82
|
+
Render a simple message (no question nav) for:
|
|
83
|
+
|
|
84
|
+
- `submitted` — already completed
|
|
85
|
+
- `expired` — survey expired
|
|
86
|
+
- `quota_fulfilled` — quota reached
|
|
87
|
+
|
|
88
|
+
Keep Header/Footer chrome consistent across all phases.
|
|
89
|
+
|
|
90
|
+
## Submit loading
|
|
91
|
+
|
|
92
|
+
Disable primary Next/Submit when `submitSurveyResults.isLoading`; label "Submitting…" / "Wird gesendet…".
|
|
53
93
|
|
|
54
94
|
## Agent Checklist
|
|
55
95
|
|
|
56
|
-
- [ ] All
|
|
57
|
-
- [ ] `onAction({ type: '
|
|
58
|
-
- [ ]
|
|
96
|
+
- [ ] All phase branches implemented
|
|
97
|
+
- [ ] `onAction({ type: 'START' })` on intro
|
|
98
|
+
- [ ] `onAction({ type: 'CHANGE' })` wired per question with `allAnswers` / `allQuestions`
|
|
99
|
+
- [ ] Back uses `state.canGoBack`; primary uses `state.primaryButtonType`
|
|
59
100
|
- [ ] ProgressBar receives `state.progressPercentage`
|
|
60
101
|
- [ ] Header/Footer on every state branch
|
|
102
|
+
- [ ] `questionNumber` prefix when present on content questions
|
|
103
|
+
- [ ] Intro / Paused / End / terminal phase branches
|
|
104
|
+
- [ ] Submit button disabled while `submitSurveyResults.isLoading`
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# UI Spec: Heatmap (`type: 'HEATMAP'`)
|
|
2
|
+
|
|
3
|
+
> **Implement from this spec** (npm installs). Monorepo reference: `apps/client/src/components/HeatmapScale.tsx`
|
|
4
|
+
> **SDK type:** `HeatmapQuestion` — `fetchSurvey/types/heatmap.ts`
|
|
5
|
+
> **Component blueprint:** `01-components/17-heatmap-scale.md`
|
|
6
|
+
|
|
7
|
+
## Config Inventory
|
|
8
|
+
|
|
9
|
+
| Field | Implemented | UI impact |
|
|
10
|
+
|-------|-------------|-----------|
|
|
11
|
+
| `questionText` | ✅ (shell) | Bold title + required asterisk |
|
|
12
|
+
| `questionDescription` | ✅ (shell) | Instruction text below title |
|
|
13
|
+
| `required` | ✅ (shell + SDK) | Asterisk + validation on NEXT |
|
|
14
|
+
| `imageUrl` | ✅ | Center-aligned interactive image |
|
|
15
|
+
| `maxClicksAllowed` | ✅ | Footer hint count; blocks new clicks at limit |
|
|
16
|
+
| `regions` | ❌ (respondent UI) | Logic only — not rendered on survey |
|
|
17
|
+
|
|
18
|
+
## Layout Anatomy
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
[Question shell: title *, description]
|
|
22
|
+
[Heatmap container — relative, inline-block, max-width 100%]
|
|
23
|
+
[<img> — object-contain, display block, crosshair cursor]
|
|
24
|
+
[Spot overlay — absolute inset-0, pointer-events-none]
|
|
25
|
+
[Orange circle markers at normalized x/y positions]
|
|
26
|
+
[Footer hint — info icon + max spots text]
|
|
27
|
+
[Validation error — from Question shell]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Spot Marker Visual Spec
|
|
31
|
+
|
|
32
|
+
| Property | Value |
|
|
33
|
+
|----------|-------|
|
|
34
|
+
| Shape | Circle |
|
|
35
|
+
| Diameter | 16px |
|
|
36
|
+
| Fill | `#F97316` |
|
|
37
|
+
| Border | 2px solid `#FFFFFF` |
|
|
38
|
+
| Shadow | `0 1px 3px rgba(0,0,0,0.25)` |
|
|
39
|
+
| Position | `left: x * 100%`, `top: y * 100%`, `transform: translate(-50%, -50%)` |
|
|
40
|
+
|
|
41
|
+
## Interactive States
|
|
42
|
+
|
|
43
|
+
| State | Behavior |
|
|
44
|
+
|-------|----------|
|
|
45
|
+
| Image loading | Spinner overlay; clicks disabled |
|
|
46
|
+
| Ready | `cursor: crosshair` on image |
|
|
47
|
+
| At max clicks | Block new adds; allow remove |
|
|
48
|
+
| Removing last spot | `onSelect(undefined)` |
|
|
49
|
+
|
|
50
|
+
## Footer Hint Text
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
ⓘ You can select up to {maxClicksAllowed} spot(s). To change your selection, simply click on the same spot again to remove it.
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Use singular "spot" when `maxClicksAllowed === 1`.
|
|
57
|
+
|
|
58
|
+
## Answer Mutation
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
onSelect(HeatmapClickPoint[] | undefined)
|
|
62
|
+
// HeatmapClickPoint = { id: string; x: number; y: number }
|
|
63
|
+
// x, y normalized 0–1 relative to rendered image bounds
|
|
64
|
+
onAction({ type: 'CHANGE', payload: { questionId, answerValue: spots } })
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Copy coord helpers from [`templates/heatmapCoords.ts`](../templates/heatmapCoords.ts): `getNormalizedClickFromImage`, `findSpotNearClick` (14px remove radius), `createNewSpot`.
|
|
68
|
+
|
|
69
|
+
## Logic fields
|
|
70
|
+
|
|
71
|
+
`HEATMAP` and `HEATMAP_REGION` — see [`logic-fields-catalog.md`](../00-integration/logic-fields-catalog.md).
|
|
72
|
+
|
|
73
|
+
## Agent Checklist
|
|
74
|
+
|
|
75
|
+
- [ ] Click handler on `<img>` element (not overlay) for precise coordinates
|
|
76
|
+
- [ ] `inline-block` wrapper so spots align with image bounds
|
|
77
|
+
- [ ] Toggle-remove within 14px hit radius
|
|
78
|
+
- [ ] Wait for `onLoad` before accepting clicks
|
|
79
|
+
- [ ] Percentage-positioned spots (survives resize)
|
|
80
|
+
- [ ] Do NOT render region overlays on respondent UI
|