@explorer02/cfm-survey-sdk 0.2.2 → 0.2.4
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 +140 -33
- package/dist/cli/index.mjs +140 -33
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +8 -1
- package/postinstall.js +8 -5
- package/templates/AGENT.md +62 -17
- package/templates/deploy-wizard-to-s3.sh +52 -0
- package/templates/docs/00-integration/agent-execution-flow.md +281 -0
- package/templates/docs/00-integration/agent-operating-contract.md +260 -0
- package/templates/docs/00-integration/analytics-events-catalog.md +54 -0
- package/templates/docs/00-integration/apply-ui-config.md +365 -0
- package/templates/docs/00-integration/canonical-survey-fixtures.md +94 -0
- package/templates/docs/00-integration/client-integration-guide.md +10 -8
- package/templates/docs/00-integration/client-lib-folder.md +115 -0
- package/templates/docs/00-integration/component-checklist.md +90 -54
- package/templates/docs/00-integration/constraints.md +13 -5
- package/templates/docs/00-integration/custom-field-logic-and-navigation.md +56 -0
- package/templates/docs/00-integration/display-logic-and-navigation.md +16 -1
- 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 +24 -0
- package/templates/docs/00-integration/progress.md +4 -2
- package/templates/docs/00-integration/question-type-sdk-matrix.md +11 -9
- package/templates/docs/00-integration/setup.md +15 -3
- package/templates/docs/00-integration/skip-logic-and-navigation.md +1 -1
- package/templates/docs/00-integration/survey-lifecycle-analytics.md +1 -1
- package/templates/docs/00-integration/ui-customization-wizard.md +144 -0
- package/templates/docs/00-integration/useSurveySDK.md +35 -1
- package/templates/docs/00-integration/wizard-api.md +134 -0
- package/templates/docs/00-integration/wizard-config-handoff.md +246 -0
- package/templates/docs/00-integration/wizard-preview-build-guide.md +400 -0
- package/templates/docs/00-integration/wizard-question-type-styling.md +195 -0
- package/templates/docs/01-components/01-survey-page.md +18 -8
- package/templates/docs/01-components/02-question.md +2 -2
- package/templates/docs/01-components/03-rating-scale.md +12 -18
- package/templates/docs/01-components/05-csat-matrix-scale.md +18 -26
- package/templates/docs/01-components/06-likert-matrix-scale.md +19 -38
- package/templates/docs/01-components/07-slider-matrix-scale.md +48 -33
- package/templates/docs/01-components/08-file-upload-scale.md +15 -52
- package/templates/docs/01-components/09-custom-slider-track.md +15 -25
- package/templates/docs/01-components/10-header-footer.md +23 -4
- package/templates/docs/01-components/11-progress-bar.md +14 -6
- package/templates/docs/01-components/13-matrix-dropdown.md +28 -33
- package/templates/docs/01-components/17-heatmap-scale.md +4 -4
- package/templates/docs/01-components/18-rank-order-scale.md +24 -31
- package/templates/docs/01-components/19-survey-sticky-chrome.md +79 -0
- package/templates/docs/01-components/README.md +2 -2
- package/templates/docs/02-reference/question-types/10-slider-matrix.md +38 -26
- package/templates/docs/02-reference/question-types/11-file-upload.md +36 -21
- package/templates/docs/02-reference/question-types/README.md +11 -1
- package/templates/docs/02-reference/routing-table.md +1 -0
- package/templates/docs/02-reference/value-derivation.md +1 -1
- package/templates/docs/03-ui-specs/00-question-shell.md +10 -0
- package/templates/docs/03-ui-specs/01-rating.md +28 -2
- package/templates/docs/03-ui-specs/02-radio.md +94 -24
- package/templates/docs/03-ui-specs/03-text.md +12 -1
- package/templates/docs/03-ui-specs/04-csat.md +90 -26
- package/templates/docs/03-ui-specs/06-slider.md +3 -1
- package/templates/docs/03-ui-specs/07-matrix-cfm.md +74 -35
- package/templates/docs/03-ui-specs/08-matrix-csat-rating.md +50 -16
- package/templates/docs/03-ui-specs/09-slider-matrix.md +70 -26
- package/templates/docs/03-ui-specs/10-file-upload.md +52 -20
- package/templates/docs/03-ui-specs/11-text-and-media.md +4 -0
- package/templates/docs/03-ui-specs/12-survey-chrome.md +92 -5
- package/templates/docs/03-ui-specs/13-heatmap.md +21 -3
- package/templates/docs/03-ui-specs/14-rank-order.md +108 -28
- package/templates/docs/03-ui-specs/README.md +11 -7
- package/templates/docs/03-ui-specs/shared/custom-slider-track.md +28 -35
- package/templates/docs/03-ui-specs/shared/matrix-dropdown.md +1 -1
- package/templates/docs/MANIFEST.json +187 -6
- package/templates/docs/index.md +116 -116
- package/templates/docs/templates/CsatMatrixScale.tsx +637 -0
- package/templates/docs/templates/CustomSliderTrack.tsx +144 -0
- package/templates/docs/templates/FileUploadScale.tsx +262 -0
- package/templates/docs/templates/HeatmapScale.tsx +114 -0
- package/templates/docs/templates/LikertMatrixScale.tsx +414 -0
- package/templates/docs/templates/MatrixDropdown.tsx +216 -0
- package/templates/docs/templates/Question.tsx +3 -0
- package/templates/docs/templates/RankOrderScale.tsx +353 -0
- package/templates/docs/templates/RatingScale.tsx +129 -0
- package/templates/docs/templates/SliderMatrixScale.tsx +248 -0
- package/templates/docs/templates/SurveyStickyChrome.tsx +24 -0
- package/templates/docs/templates/customFieldValues.ts +36 -0
- package/templates/docs/templates/implementation_plan.md +75 -8
- package/templates/docs/templates/labelStyles.ts +33 -0
- package/templates/docs/templates/selectionStyles.ts +100 -0
- package/templates/docs/templates/survey-inventory.schema.json +28 -23
- package/templates/docs/templates/surveyUiIcons.tsx +63 -0
- package/templates/docs/templates/surveyUiScaleUtils.ts +51 -0
- package/templates/docs/templates/verify-agent-build.sh +151 -0
- package/templates/preview-harness/preview-bridge.inline.js +201 -0
- package/templates/preview-harness/previewPages.js +108 -0
- package/templates/preview-harness/previewPages.ts +298 -0
- package/templates/preview-harness/vite-app/src/PreviewConfigContext.tsx +67 -0
- package/templates/preview-harness/vite-app/src/QuestionPreview.tsx +122 -0
- package/templates/preview-harness/vite-app/src/SurveyPagePreview.tsx +75 -0
- package/templates/preview-harness/vite-app/src/fixtures/questions.ts +311 -0
- package/templates/preview-harness/vite-app/src/globals.css +16 -0
- package/templates/preview-harness/vite-app/src/mount.tsx +9 -0
- package/templates/preview-harness/vite-app/src/preview-live-overrides.css +101 -0
- package/templates/preview-harness/vite-app/stubs/next-dynamic.tsx +25 -0
- package/templates/preview-harness/vite-app/stubs/next-image.tsx +29 -0
- package/templates/previewBridge.ts +153 -0
- package/templates/survey-theme.css +295 -0
- package/templates/survey-ui-config.schema.json +213 -0
- package/templates/wizard-dist/assets/PreviewMock-DgHfrVeb.js +1 -0
- package/templates/wizard-dist/assets/TypePanel-CFVC3Ptn.js +1 -0
- package/templates/wizard-dist/assets/index-BhWM50Yu.css +1 -0
- package/templates/wizard-dist/assets/index-DYK3X1e5.js +34 -0
- package/templates/wizard-dist/assets/vendor-BwkXDkd3.js +17 -0
- package/templates/wizard-dist/index.html +20 -0
- package/templates/wizard.html +13 -1129
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# UI Spec: CSAT Matrix Grid (`type: 'CSAT_MATRIX'`)
|
|
2
2
|
|
|
3
|
-
> **
|
|
3
|
+
> **COPY TEMPLATE FIRST:** [`templates/CsatMatrixScale.tsx`](../templates/CsatMatrixScale.tsx) + [`surveyUiIcons.tsx`](../templates/surveyUiIcons.tsx) + [`surveyUiScaleUtils.ts`](../templates/surveyUiScaleUtils.ts) + [`CustomSliderTrack.tsx`](../templates/CustomSliderTrack.tsx) + [`MatrixDropdown.tsx`](../templates/MatrixDropdown.tsx)
|
|
4
|
+
>
|
|
4
5
|
> **SDK type:** `CsatQuestion` — `fetchSurvey/types/csat.ts`
|
|
5
6
|
> **Component blueprint:** `01-components/05-csat-matrix-scale.md`
|
|
6
7
|
|
|
@@ -9,11 +10,56 @@
|
|
|
9
10
|
| Field | Implemented | UI impact |
|
|
10
11
|
|-------|-------------|-----------|
|
|
11
12
|
| `statementRows[]`, `scaleColumns[]` | ✅ | Grid / carousel / dropdown data |
|
|
12
|
-
| `displayStyle` | ✅ |
|
|
13
|
+
| `displayStyle` | ✅ | emoji / star / numbered / graphics / standard |
|
|
13
14
|
| `gridLayout` | ✅ | carousel vs standard vs dropdown |
|
|
14
|
-
| `hasNotApplicableOption` | ✅ |
|
|
15
|
+
| `hasNotApplicableOption` | ✅ | N/A column (56px) or vertical checkbox |
|
|
15
16
|
| `reverseScaleOrder` | ✅ | Flip emoji/star/number index |
|
|
16
|
-
| `scaleAnchorLabels[]` | ✅ | Anchor row above columns |
|
|
17
|
+
| `scaleAnchorLabels[]` | ✅ | Anchor row above columns (replaces column headers when set) |
|
|
18
|
+
|
|
19
|
+
## Statement & label visibility (CRITICAL)
|
|
20
|
+
|
|
21
|
+
Agents often render inputs with **no row labels** and **no column headers**. Every matrix grid MUST include:
|
|
22
|
+
|
|
23
|
+
### 1. Row labels — `statementRows[].statementText`
|
|
24
|
+
|
|
25
|
+
Fixed **180px** left column on every data row:
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
<div style={{ width: '180px', flexShrink: 0, padding: '0 16px' }}>
|
|
29
|
+
<span
|
|
30
|
+
style={{ fontSize: '14px', fontWeight: 500, color: '#111827' }}
|
|
31
|
+
dangerouslySetInnerHTML={{ __html: row.statementText }}
|
|
32
|
+
/>
|
|
33
|
+
</div>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 2. Column labels — two modes (mutually exclusive header row)
|
|
37
|
+
|
|
38
|
+
| Condition | What to render above cells |
|
|
39
|
+
|-----------|---------------------------|
|
|
40
|
+
| `scaleAnchorLabels.length > 0` | Anchor label row positioned over columns (see formula below) |
|
|
41
|
+
| No anchor labels | Column header row from `scaleColumns[].label` |
|
|
42
|
+
|
|
43
|
+
**When `scaleAnchorLabels` is set**, still render anchor row even if column text labels are long (emoji mode Page 9 fixture):
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
const m = scaleColumns.length;
|
|
47
|
+
const n = scaleAnchorLabels.length;
|
|
48
|
+
const percent = n === 1 ? 50 : (0.5 / m + (i / (n - 1)) * ((m - 1) / m)) * 100;
|
|
49
|
+
// position label i at left: `${percent}%`, transform: translateX(-50%)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**When no anchor labels**, render column headers:
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
{scaleColumns.map(col => (
|
|
56
|
+
<span dangerouslySetInnerHTML={{ __html: col.label }} />
|
|
57
|
+
))}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 3. N/A column header
|
|
61
|
+
|
|
62
|
+
When `hasNotApplicableOption`, reserve **56px** right column with "N/A" header text.
|
|
17
63
|
|
|
18
64
|
## Config Decision Tree
|
|
19
65
|
|
|
@@ -21,33 +67,32 @@
|
|
|
21
67
|
CSAT_MATRIX
|
|
22
68
|
├── gridLayout === 'carousel' → CsatMatrixCarousel
|
|
23
69
|
├── displayStyle in (dropdown, selectbox) → CsatMatrixVerticalList
|
|
24
|
-
│ ├── dropdown → MatrixDropdown placeholder "Select..."
|
|
25
|
-
│ ├── selectbox → full-width cards #fdf2f8 selected
|
|
26
|
-
│ └── else → card radio list
|
|
27
70
|
└── else → CsatMatrixGrid
|
|
28
|
-
├── displayStyle === 'emoji' →
|
|
29
|
-
├── displayStyle === 'star' →
|
|
30
|
-
├── displayStyle === 'numbered' → 40×40
|
|
31
|
-
|
|
71
|
+
├── displayStyle === 'emoji' → getEmojiForIndex(colIdx, m) — NOT radio circles
|
|
72
|
+
├── displayStyle === 'star' → CsatStarIcons filled/empty
|
|
73
|
+
├── displayStyle === 'numbered' → 40×40 magenta badges (colIdx+1)
|
|
74
|
+
├── displayStyle === 'graphics' → CustomSliderTrack per row
|
|
75
|
+
└── displayStyle === 'standard' → custom radio circles #e20074
|
|
32
76
|
```
|
|
33
77
|
|
|
34
|
-
##
|
|
78
|
+
## Emoji + anchor labels (Page 9 fixture)
|
|
79
|
+
|
|
80
|
+
- 5 `scaleAnchorLabels` over 6 columns (5 emoji + N/A)
|
|
81
|
+
- Emoji cells: pink ring when selected; tooltip shows `col.label` on hover
|
|
82
|
+
- Do NOT replace emojis with plain radio buttons
|
|
35
83
|
|
|
36
|
-
|
|
37
|
-
- N/A column: `56px` when `hasNotApplicableOption`
|
|
38
|
-
- Alternating row backgrounds
|
|
39
|
-
- Column headers OR `scaleAnchorLabels[]` anchor row with percentage positioning
|
|
84
|
+
## Star mode (Page 14 fixture)
|
|
40
85
|
|
|
41
|
-
|
|
86
|
+
- `CsatStarIcons` from `surveyUiIcons.tsx`
|
|
87
|
+
- Unselected opacity 0.5; selected filled star `#e20074`
|
|
42
88
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
-
|
|
46
|
-
- Graphics mode: slider maps column index
|
|
89
|
+
## Graphics / numbered (RATING_MATRIX reuse)
|
|
90
|
+
|
|
91
|
+
See [`08-matrix-csat-rating.md`](08-matrix-csat-rating.md) — same `CsatMatrixScale` component.
|
|
47
92
|
|
|
48
93
|
## Answer Mutation
|
|
49
94
|
|
|
50
|
-
Store **`column.id`** via `matrixColumnStoredValue(col)
|
|
95
|
+
Store **`column.id`** via `matrixColumnStoredValue(col)`:
|
|
51
96
|
|
|
52
97
|
```typescript
|
|
53
98
|
onSelect(MatrixRowAnswers)
|
|
@@ -56,7 +101,26 @@ onAction({ type: 'CHANGE', payload: { questionId, answerValue: MatrixRowAnswers
|
|
|
56
101
|
|
|
57
102
|
## Agent Checklist
|
|
58
103
|
|
|
59
|
-
- [ ]
|
|
60
|
-
- [ ]
|
|
61
|
-
- [ ]
|
|
62
|
-
- [ ]
|
|
104
|
+
- [ ] Copied `CsatMatrixScale.tsx` + dependencies
|
|
105
|
+
- [ ] **180px statement column visible** on every row
|
|
106
|
+
- [ ] Column headers OR `scaleAnchorLabels` row visible (never both missing)
|
|
107
|
+
- [ ] `displayStyle` branch matches survey (emoji ≠ star ≠ numbered ≠ radio)
|
|
108
|
+
- [ ] N/A column when `hasNotApplicableOption`
|
|
109
|
+
- [ ] `react-icons` in package.json
|
|
110
|
+
|
|
111
|
+
## Wizard customization (Phase 5 — required)
|
|
112
|
+
|
|
113
|
+
> Per-format controls in [`wizard-question-type-styling.md`](../00-integration/wizard-question-type-styling.md). Copy [`selectionStyles.ts`](../templates/selectionStyles.ts) + [`labelStyles.ts`](../templates/labelStyles.ts).
|
|
114
|
+
|
|
115
|
+
| Format | Key tokens | Pattern |
|
|
116
|
+
|--------|------------|---------|
|
|
117
|
+
| emoji / star | `--cfm-input-focus-ring`, `cellSelectedVar` | Ring on select; star fill = cell selected |
|
|
118
|
+
| numbered | `--cfm-number-*`, focus ring + cell selected | Selected cell border + fill |
|
|
119
|
+
| radio grid | `--cfm-csat-column-label-*`, matrix radio styles | `csatColumnLabelStyle()` + `matrixRadioRingStyle` |
|
|
120
|
+
| graphics | `--cfm-slider-tick-label-*` | `CustomSliderTrack` thumb ring + annulus + dot |
|
|
121
|
+
| carousel | `--cfm-matrix-selected` | Active dot = cell selected |
|
|
122
|
+
|
|
123
|
+
### Wizard checklist
|
|
124
|
+
|
|
125
|
+
- [ ] Column headers use `csatColumnLabelStyle()` — not hint labels
|
|
126
|
+
- [ ] No per-type accent hex overrides — use Question card tokens only
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# UI Spec: Standalone Slider (`type: 'slider'`)
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> **⛔ DEPRECATED — DO NOT IMPLEMENT.** API SLIDER maps to `SLIDER_MATRIX` (`09-slider-matrix.md`) or RATING/CSAT `displayStyle: 'graphics'` (`08-matrix-csat-rating.md`).
|
|
4
|
+
>
|
|
5
|
+
> If you see slider-like UI, copy [`templates/SliderMatrixScale.tsx`](../templates/SliderMatrixScale.tsx) or use `CsatMatrixScale` with `CustomSliderTrack` — not this spec.
|
|
4
6
|
>
|
|
5
7
|
> **Reference:** Standalone slider is not routed in current `Question.tsx`
|
|
6
8
|
> **SDK type:** `SliderQuestion` — `fetchSurvey/types/slider.ts`
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# UI Spec: CFM Matrix (`type: 'CFM_MATRIX'`)
|
|
2
2
|
|
|
3
|
-
> **
|
|
3
|
+
> **COPY TEMPLATE FIRST:** [`templates/LikertMatrixScale.tsx`](../templates/LikertMatrixScale.tsx) + [`MatrixDropdown.tsx`](../templates/MatrixDropdown.tsx)
|
|
4
|
+
>
|
|
4
5
|
> **SDK type:** `CfmMatrixQuestion` — `fetchSurvey/types/cfmMatrix.ts`
|
|
5
6
|
> **Component blueprint:** `01-components/06-likert-matrix-scale.md`
|
|
6
7
|
|
|
@@ -18,52 +19,90 @@
|
|
|
18
19
|
| `showColumnHeaders` | ✅ | Show/hide column header row |
|
|
19
20
|
| `hasNotApplicableOption` | ✅ | N/A column with mutex |
|
|
20
21
|
|
|
21
|
-
##
|
|
22
|
+
## Statement & label visibility (CRITICAL)
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
```
|
|
24
|
+
### Row labels
|
|
25
|
+
|
|
26
|
+
| Layout | Left column content | Width |
|
|
27
|
+
|--------|---------------------|-------|
|
|
28
|
+
| `statementLayout: 'likert'` | `statementRows[].statementText` | 180px |
|
|
29
|
+
| `statementLayout: 'bipolar'` | `statementRows[].statementText` | 25% |
|
|
30
|
+
| `transposeTable: true` | `scaleColumns[].label` (rows become scale items) | 180px |
|
|
31
|
+
|
|
32
|
+
Always use `dangerouslySetInnerHTML` for HTML statement text.
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
### Bipolar right column
|
|
35
35
|
|
|
36
|
-
|
|
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 | selectionMode multiple | Sets `[null]` exclusively |
|
|
41
|
-
| Multi toggle | selectionMode multiple | Add/remove from array |
|
|
36
|
+
When `statementLayout === 'bipolar'`, render **`oppositeStatementText`** in 25% right column (unless transposed):
|
|
42
37
|
|
|
43
|
-
|
|
38
|
+
```tsx
|
|
39
|
+
<span dangerouslySetInnerHTML={{ __html: row.oppositeStatementText ?? '' }} />
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Column / anchor labels
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
- Zebra rows: even rows `rgba(249,250,251,0.8)`
|
|
47
|
-
- Native inputs: `accentColor: #e20074`, 18×18px
|
|
44
|
+
Header renders when `scaleAnchorLabels` OR `showColumnHeaders`:
|
|
48
45
|
|
|
49
|
-
|
|
46
|
+
**Bipolar + anchor labels (Page 7 fixture)** — `showColumnHeaders: false`:
|
|
50
47
|
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
```
|
|
49
|
+
[25% spacer] [Label1 at col2] [Label2 at col6] [Label3 at col10] [25% spacer]
|
|
50
|
+
[25% Left Stmt 1] [10 radios] [25% Right Stmt 1]
|
|
51
|
+
```
|
|
53
52
|
|
|
54
|
-
|
|
53
|
+
Anchor positioning:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
const numCols = gridCols.length;
|
|
57
|
+
if (idx === 0) left = `${100 / (2 * numCols)}%`;
|
|
58
|
+
else if (idx === labels.length - 1) left = `${100 - 100 / (2 * numCols)}%`;
|
|
59
|
+
else if (labels.length === 3 && idx === 1) left = '50%';
|
|
60
|
+
```
|
|
55
61
|
|
|
56
|
-
|
|
62
|
+
**Transposed + column headers (Page 10 fixture)** — `transposeTable: true`, `showColumnHeaders: true`:
|
|
57
63
|
|
|
58
64
|
```typescript
|
|
59
|
-
|
|
60
|
-
|
|
65
|
+
gridRows = scaleColumns; // row labels = Scale Item 1, 2, 3
|
|
66
|
+
gridCols = statementRows; // column headers = Statement Item 1, 2, 3
|
|
67
|
+
// Header cell HTML: col.statementText
|
|
68
|
+
// Row label HTML: row.label
|
|
69
|
+
input type = selectionMode === 'multiple' ? 'checkbox' : 'radio';
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Answer keys **always** use `statementRows[].id` as row id (`actualRowId` logic in template).
|
|
73
|
+
|
|
74
|
+
## Config Decision Tree
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
CFM_MATRIX
|
|
78
|
+
├── gridLayout === 'dropdown' → MatrixDropdown per statement row
|
|
79
|
+
├── gridLayout === 'carousel' → one row visible + blue dot nav
|
|
80
|
+
└── else → LikertMatrixGridLayout
|
|
81
|
+
├── transposeTable swaps gridRows/gridCols
|
|
82
|
+
├── showColumnHeaders controls column label row
|
|
83
|
+
└── statementLayout bipolar adds oppositeStatementText column
|
|
61
84
|
```
|
|
62
85
|
|
|
63
86
|
## Agent Checklist
|
|
64
87
|
|
|
65
|
-
- [ ]
|
|
66
|
-
- [ ]
|
|
67
|
-
- [ ] Bipolar
|
|
68
|
-
- [ ]
|
|
69
|
-
- [ ]
|
|
88
|
+
- [ ] Copied `LikertMatrixScale.tsx` + `MatrixDropdown.tsx`
|
|
89
|
+
- [ ] Row labels visible (statementText or transposed scale label)
|
|
90
|
+
- [ ] Bipolar: oppositeStatementText on right when not transposed
|
|
91
|
+
- [ ] Anchor labels OR column headers — never both missing when configured
|
|
92
|
+
- [ ] `selectionMode: multiple` uses checkboxes, not radios
|
|
93
|
+
- [ ] `transposeTable` swaps rows/cols correctly
|
|
94
|
+
|
|
95
|
+
## Wizard customization (Phase 5 — required)
|
|
96
|
+
|
|
97
|
+
> CFM column headers are **label items** only. See [`wizard-question-type-styling.md`](../00-integration/wizard-question-type-styling.md).
|
|
98
|
+
|
|
99
|
+
| Control | CSS vars | Apply in |
|
|
100
|
+
|---------|----------|----------|
|
|
101
|
+
| Row / bipolar width | `--cfm-matrix-row-width`, `--cfm-matrix-bipolar-width` | Statement columns |
|
|
102
|
+
| Label items | `--cfm-matrix-column-label-color`, `--cfm-matrix-column-label-bg` | Column header row — `matrixColumnLabelStyle()` |
|
|
103
|
+
| Selection | `--cfm-matrix-selected`, `--cfm-input-focus-ring` | `matrixRadioRingStyle` / dropdown option ring |
|
|
104
|
+
|
|
105
|
+
### Wizard checklist
|
|
106
|
+
|
|
107
|
+
- [ ] `labelStyles.ts` imported; column headers never use hint-label vars
|
|
108
|
+
- [ ] No CFM-specific accent overrides — Question card tokens only
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# UI Spec: Rating Matrix Grid (`type: 'RATING_MATRIX'`)
|
|
2
2
|
|
|
3
|
-
> **
|
|
3
|
+
> **COPY TEMPLATE FIRST:** [`templates/CsatMatrixScale.tsx`](../templates/CsatMatrixScale.tsx) — **same component as CSAT_MATRIX**
|
|
4
|
+
>
|
|
4
5
|
> **SDK type:** `RatingMatrixQuestion` — `fetchSurvey/types/ratingMatrix.ts`
|
|
5
6
|
> **Component blueprint:** `01-components/05-csat-matrix-scale.md`
|
|
6
7
|
|
|
@@ -8,30 +9,63 @@
|
|
|
8
9
|
|
|
9
10
|
| Field | Implemented | UI impact |
|
|
10
11
|
|-------|-------------|-----------|
|
|
11
|
-
| `statementRows[]`, `scaleColumns[]` | ✅ | Grid
|
|
12
|
-
| `displayStyle` | ✅ |
|
|
12
|
+
| `statementRows[]`, `scaleColumns[]` | ✅ | Grid data |
|
|
13
|
+
| `displayStyle` | ✅ | **numbered / graphics / standard** — NOT generic radio grid |
|
|
13
14
|
| `gridLayout` | ✅ | carousel vs standard vs dropdown |
|
|
14
|
-
| `hasNotApplicableOption` | ✅ | N/A column or checkbox |
|
|
15
|
-
| `reverseScaleOrder` | ✅ | Flip emoji/star/number index |
|
|
16
15
|
| `scaleAnchorLabels[]` | ✅ | Anchor row above columns |
|
|
17
|
-
| `minLabel` / `maxLabel` | ✅ | Rendered in Question.tsx above scale |
|
|
18
|
-
| `
|
|
16
|
+
| `minLabel` / `maxLabel` | ✅ | Rendered in `Question.tsx` above scale |
|
|
17
|
+
| `hasNotApplicableOption` | ✅ | N/A column |
|
|
18
|
+
| `reverseScaleOrder` | ✅ | Flip column index |
|
|
19
19
|
|
|
20
|
-
##
|
|
20
|
+
## Statement & label visibility (CRITICAL)
|
|
21
21
|
|
|
22
|
-
Same as `
|
|
22
|
+
Same rules as [`04-csat.md`](04-csat.md):
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
1. **180px row column** — `statementRows[].statementText` via `dangerouslySetInnerHTML`
|
|
25
|
+
2. **Header row** — `scaleAnchorLabels` OR `scaleColumns[].label` (never both missing)
|
|
26
|
+
3. **Page 14 numbered fixture** — 10 column headers "1"–"10" above radio/badge row
|
|
27
|
+
|
|
28
|
+
## displayStyle branches (do NOT collapse to generic radios)
|
|
29
|
+
|
|
30
|
+
| displayStyle | UI | Page fixture |
|
|
31
|
+
|--------------|-----|--------------|
|
|
32
|
+
| `numbered` | 40×40 magenta badges showing `colIdx+1`; tooltip = `col.label` | Page 14 Q8 |
|
|
33
|
+
| `graphics` | One `CustomSliderTrack` per row; `min=0`, `max=cols-1`, `tickLabels=scaleColumns.map(c=>c.label)` | Page 11 |
|
|
34
|
+
| `standard` | Custom 18px radio circles, `#e20074` accent | — |
|
|
35
|
+
| `emoji` / `star` | Same as CSAT — see [`04-csat.md`](04-csat.md) | — |
|
|
36
|
+
|
|
37
|
+
**graphics mode is NOT `SliderMatrixScale`** — it uses `CsatMatrixScale` with `CustomSliderTrack` per statement row.
|
|
38
|
+
|
|
39
|
+
## minLabel / maxLabel (Page 11)
|
|
40
|
+
|
|
41
|
+
`Question.tsx` template renders these above `CsatMatrixScale` when set on the question.
|
|
25
42
|
|
|
26
|
-
|
|
43
|
+
## Answer Mutation
|
|
27
44
|
|
|
28
45
|
```typescript
|
|
29
|
-
onSelect(MatrixRowAnswers)
|
|
30
|
-
|
|
46
|
+
onSelect(MatrixRowAnswers) // keys = statementRows[].id, values = scaleColumns[].id
|
|
47
|
+
matrixColumnStoredValue(col) // returns col.id or null for N/A
|
|
31
48
|
```
|
|
32
49
|
|
|
33
50
|
## Agent Checklist
|
|
34
51
|
|
|
35
|
-
- [ ] Reuse
|
|
36
|
-
- [ ]
|
|
37
|
-
- [ ]
|
|
52
|
+
- [ ] Reuse copied `CsatMatrixScale` — do not create separate RatingMatrixScale
|
|
53
|
+
- [ ] Statement text visible in 180px column
|
|
54
|
+
- [ ] Column headers or anchor labels visible
|
|
55
|
+
- [ ] `displayStyle: numbered` uses badges, not native radios
|
|
56
|
+
- [ ] `displayStyle: graphics` uses CustomSliderTrack per row
|
|
57
|
+
|
|
58
|
+
## Wizard customization (Phase 5 — required)
|
|
59
|
+
|
|
60
|
+
> Reuses `CsatMatrixScale` patterns — see [`wizard-question-type-styling.md`](../00-integration/wizard-question-type-styling.md) § RATING_MATRIX.
|
|
61
|
+
|
|
62
|
+
| Format | Key tokens |
|
|
63
|
+
|--------|------------|
|
|
64
|
+
| radio | Horizontal grid + `matrixRadioRingStyle` |
|
|
65
|
+
| star / emoji / numbered | Same as CSAT formats |
|
|
66
|
+
| graphics / slider | `CustomSliderTrack` thumb ring + annulus + dot |
|
|
67
|
+
|
|
68
|
+
### Wizard checklist
|
|
69
|
+
|
|
70
|
+
- [ ] Radio format is horizontal grid (not vertical list)
|
|
71
|
+
- [ ] `csatColumnLabelStyle()` on column headers
|
|
@@ -1,61 +1,105 @@
|
|
|
1
1
|
# UI Spec: Slider Matrix (`type: 'SLIDER_MATRIX'`)
|
|
2
2
|
|
|
3
|
-
> **
|
|
3
|
+
> **COPY TEMPLATE FIRST:** [`templates/SliderMatrixScale.tsx`](../templates/SliderMatrixScale.tsx) + [`CustomSliderTrack.tsx`](../templates/CustomSliderTrack.tsx) + [`surveyUiScaleUtils.ts`](../templates/surveyUiScaleUtils.ts)
|
|
4
|
+
>
|
|
5
|
+
> **Anti-pattern:** Do NOT build custom range overlay sliders. Lone pink circle with no track = broken implementation.
|
|
6
|
+
>
|
|
4
7
|
> **SDK type:** `SliderMatrixQuestion` — `fetchSurvey/types/sliderMatrix.ts`
|
|
5
8
|
> **Component blueprint:** `01-components/07-slider-matrix-scale.md`
|
|
6
|
-
> **Shared:** [`shared/custom-slider-track.md`](shared/custom-slider-track.md)
|
|
7
9
|
|
|
8
10
|
## Config Inventory
|
|
9
11
|
|
|
10
12
|
| Field | Implemented | UI impact |
|
|
11
13
|
|-------|-------------|-----------|
|
|
12
|
-
| `
|
|
13
|
-
| `
|
|
14
|
-
| `
|
|
15
|
-
| `
|
|
16
|
-
| `sliderType: 'graphics'` | ✅ | Plain text tick labels; emoji thumb |
|
|
14
|
+
| `statementRows[]` (min/max/step) | ✅ | Per-row slider + **25% statement column** |
|
|
15
|
+
| `scaleAnchorLabels?` | ✅ | Text labels above tick badge row |
|
|
16
|
+
| `ticks.count` + `ticks.marks[]` | ✅ | Colored header badges (`tickPosition`, `tickLabel`) |
|
|
17
|
+
| `sliderStyle: 'graphics'` | ✅ | Plain text tick labels; emoji thumb |
|
|
17
18
|
| `enableInputBox` | ✅ | 80px input + stepper buttons |
|
|
18
19
|
| `displayValues` | ✅ | Passed to CustomSliderTrack |
|
|
19
|
-
| `
|
|
20
|
+
| `hasNotApplicableOption` | ✅ | Checkbox; answer value **`'N/A'`** string |
|
|
20
21
|
|
|
21
22
|
## Layout Anatomy
|
|
22
23
|
|
|
23
24
|
```
|
|
24
|
-
[header:
|
|
25
|
+
[header: scaleAnchorLabels? + colored tick badges from ticks.marks]
|
|
25
26
|
┌─ row band (#f3f4f6, rounded-xl) ─────────────────────────────┐
|
|
26
|
-
│ 25%
|
|
27
|
+
│ 25% statementText │ CustomSliderTrack │ 80px input? │ N/A? │
|
|
27
28
|
└───────────────────────────────────────────────────────────────┘
|
|
28
29
|
```
|
|
29
30
|
|
|
30
|
-
##
|
|
31
|
+
## Statement text (CRITICAL)
|
|
32
|
+
|
|
33
|
+
Every row MUST show `statementRows[].statementText` in the left 25% column:
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
<div style={{ flex: '0 0 25%', paddingRight: '16px' }}>
|
|
37
|
+
{row.statementText && row.statementText !== 'Question' && (
|
|
38
|
+
<span dangerouslySetInnerHTML={{ __html: row.statementText }} />
|
|
39
|
+
)}
|
|
40
|
+
</div>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Agents that hide statement text or merge it into the question title break multi-row sliders.
|
|
44
|
+
|
|
45
|
+
## Header tick badges
|
|
31
46
|
|
|
32
47
|
```typescript
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
48
|
+
tickValues = ticks.marks.map((mark, i) => ({
|
|
49
|
+
value: mark.tickPosition,
|
|
50
|
+
label: mark.tickLabel,
|
|
51
|
+
color: tickColorFromIndex(i, ticks.count),
|
|
52
|
+
}));
|
|
53
|
+
percentage = ((tv.value - min) / (max - min)) * 100;
|
|
54
|
+
htmlStep = ticks.count > 1 ? (max - min) / (ticks.count - 1) : row.step;
|
|
37
55
|
```
|
|
38
56
|
|
|
39
|
-
##
|
|
57
|
+
## CustomSliderTrack per row
|
|
40
58
|
|
|
41
|
-
|
|
42
|
-
|
|
59
|
+
```typescript
|
|
60
|
+
<CustomSliderTrack
|
|
61
|
+
min={row.min}
|
|
62
|
+
max={row.max}
|
|
63
|
+
htmlStep={htmlStep}
|
|
64
|
+
value={rowVal as number}
|
|
65
|
+
disabled={rowVal === 'N/A'}
|
|
66
|
+
hasSelectedValue={typeof rowVal === 'number'}
|
|
67
|
+
displayValues={displayValues}
|
|
68
|
+
sliderType={question.sliderStyle === 'graphics' ? 'graphics' : undefined}
|
|
69
|
+
ticks={ticks.count}
|
|
70
|
+
onChange={v => updateRow(row.id, v)}
|
|
71
|
+
/>
|
|
72
|
+
```
|
|
43
73
|
|
|
44
|
-
##
|
|
74
|
+
## N/A Behavior
|
|
45
75
|
|
|
46
|
-
-
|
|
47
|
-
-
|
|
76
|
+
- Checked → `rowVal === 'N/A'`; slider disabled
|
|
77
|
+
- Unchecked → reset to `defaultValue ?? min`
|
|
48
78
|
|
|
49
79
|
## Answer Mutation
|
|
50
80
|
|
|
51
81
|
```typescript
|
|
52
|
-
onSelect(Record<string, number |
|
|
82
|
+
onSelect(Record<string, number | 'N/A'>)
|
|
83
|
+
onAction({ type: 'CHANGE', payload: { questionId, answerValue: MatrixRowAnswers } })
|
|
53
84
|
```
|
|
54
85
|
|
|
55
86
|
## Agent Checklist
|
|
56
87
|
|
|
57
|
-
- [ ]
|
|
58
|
-
- [ ]
|
|
88
|
+
- [ ] Copied portable templates — did not build slider from scratch
|
|
89
|
+
- [ ] Shared header with colored 0–10 badges (even single-row questions)
|
|
90
|
+
- [ ] Gray row bands with **visible** statement text in 25% column
|
|
59
91
|
- [ ] CustomSliderTrack per row with correct htmlStep
|
|
60
|
-
- [ ]
|
|
61
|
-
|
|
92
|
+
- [ ] N/A uses `'N/A'` string, not null
|
|
93
|
+
|
|
94
|
+
## Wizard customization (Phase 5 — required)
|
|
95
|
+
|
|
96
|
+
| Control | CSS vars | Apply in |
|
|
97
|
+
|---------|----------|----------|
|
|
98
|
+
| Tick label items | `--cfm-slider-tick-label-color`, `--cfm-slider-tick-label-bg` | Header badge row — `sliderTickLabelStyle()` |
|
|
99
|
+
| Row band | `--cfm-slider-row-bg` | Gray statement band |
|
|
100
|
+
| Thumb selection | `--cfm-matrix-selected`, `--cfm-input-focus-ring` | `CustomSliderTrack` thumb ring + annulus + dot |
|
|
101
|
+
|
|
102
|
+
### Wizard checklist
|
|
103
|
+
|
|
104
|
+
- [ ] Colored tick badges use `--cfm-number-*` when traffic-light mode applies
|
|
105
|
+
- [ ] `labelStyles.ts` imported for tick label row
|
|
@@ -1,18 +1,18 @@
|
|
|
1
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,56 @@
|
|
|
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)
|
|
76
|
+
|
|
77
|
+
## Wizard customization (Phase 5 — required)
|
|
78
|
+
|
|
79
|
+
| Control | CSS vars | Apply in |
|
|
80
|
+
|---------|----------|----------|
|
|
81
|
+
| Dropzone border | `--cfm-upload-border-color` | Dashed border on upload area |
|
|
82
|
+
| Focus / drag-over | `--cfm-input-focus-ring` | Active dropzone outline |
|
|
83
|
+
|
|
84
|
+
### Wizard checklist
|
|
85
|
+
|
|
86
|
+
- [ ] Dropzone border uses `var(--cfm-upload-border-color)` not hardcoded hex
|
|
@@ -47,3 +47,7 @@ Container-level media (survey chrome) is separate — see `03-ui-specs/12-survey
|
|
|
47
47
|
|
|
48
48
|
- [ ] No CHANGE action — display-only
|
|
49
49
|
- [ ] Matrix row: [`question-type-sdk-matrix.md#text_and_media`](../00-integration/question-type-sdk-matrix.md#text_and_media)
|
|
50
|
+
|
|
51
|
+
## Wizard customization (Phase 5 — required)
|
|
52
|
+
|
|
53
|
+
Display-only — no selection tokens. Media caption may use `--cfm-question-title-color` for consistency.
|