@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
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
> **UI spec (authoritative):** `03-ui-specs/00-question-shell.md`
|
|
4
4
|
> **Canonical template:** [`templates/Question.tsx`](../templates/Question.tsx) — copy verbatim, then create imported components
|
|
5
|
-
> **Monorepo reference:** `apps/client/src/components/Question.tsx`
|
|
5
|
+
> **Monorepo reference:** `apps/client/src/components/Question.tsx`
|
|
6
6
|
> **Target Component**: `Question`
|
|
7
|
-
> **Role**: Wrapper card component that routes to the correct scale component based on `question.type`.
|
|
8
7
|
|
|
9
8
|
## Start Here (CRITICAL)
|
|
10
9
|
|
|
@@ -12,71 +11,40 @@
|
|
|
12
11
|
2. Create every imported scale component — see [`component-checklist.md`](../00-integration/component-checklist.md).
|
|
13
12
|
3. Read `03-ui-specs/` for each type in your fetched survey **before** editing the dispatcher.
|
|
14
13
|
|
|
15
|
-
**Do NOT** write
|
|
14
|
+
**Do NOT** write partial skeletons with `default` fallbacks or `"not yet implemented"` messages.
|
|
16
15
|
|
|
17
|
-
##
|
|
18
|
-
Render a visually separated container (card) for each question. Handle rich text parsing, required asterisks, description text, and error validation messages. Most importantly, it MUST act as an exhaustive switch statement dispatching to ALL supported question types.
|
|
16
|
+
## Dispatcher (11 type literals)
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
Use `QUESTION_TYPE` from the SDK:
|
|
21
19
|
|
|
22
|
-
|
|
20
|
+
| `question.type` | Renders |
|
|
21
|
+
|-----------------|---------|
|
|
22
|
+
| `NPS_SCALE` | `RatingScale` (NPS) |
|
|
23
|
+
| `MCQ` | Inline radio/checkbox |
|
|
24
|
+
| `TEXTFIELD` | Inline textarea/input |
|
|
25
|
+
| `CFM_MATRIX` | `LikertMatrixScale` |
|
|
26
|
+
| `CSAT_MATRIX`, `RATING_MATRIX` | `CsatMatrixScale` |
|
|
27
|
+
| `SLIDER_MATRIX` | `SliderMatrixScale` |
|
|
28
|
+
| `FILE_UPLOAD` | `FileUploadScale` |
|
|
29
|
+
| `TEXT_AND_MEDIA` | Media only (no answer) |
|
|
30
|
+
| `HEATMAP` | `HeatmapScale` |
|
|
31
|
+
| `RANK_ORDER` | `RankOrderScale` |
|
|
23
32
|
|
|
24
|
-
|
|
25
|
-
2. **`csat`**: Renders `CsatScale`. This covers CSAT specific faces/icons.
|
|
26
|
-
3. **`radio`**: Renders inline MCQ radio buttons.
|
|
27
|
-
4. **`text`**: Renders an inline `<textarea>` or `<input>`.
|
|
28
|
-
5. **`matrix`** — **requires `subType`**:
|
|
29
|
-
- If `subType === 'CFM_MATRIX'`, render `LikertMatrixScale`
|
|
30
|
-
- If `subType === 'CSAT_MATRIX'` or `'RATING_MATRIX'`, render `CsatMatrixScale`
|
|
31
|
-
- Routing on `type === 'matrix'` alone renders **nothing**
|
|
32
|
-
6. **`slider_matrix`**: Renders `SliderMatrixScale`
|
|
33
|
-
7. **`file_upload`**: Renders `FileUploadScale`
|
|
34
|
-
8. **`text_and_media`**: Renders inline `<video>` or `<img>` based on `question.mediaUrl`. No answer input.
|
|
35
|
-
9. **`rating_scale`**: Star/emoji scale — see `03-ui-specs/05-rating-scale.md`
|
|
36
|
-
10. **`slider`**: Standalone slider — see `03-ui-specs/06-slider.md`
|
|
37
|
-
|
|
38
|
-
## Forbidden Anti-Patterns
|
|
33
|
+
**Never** branch on `subType`.
|
|
39
34
|
|
|
40
35
|
```tsx
|
|
41
|
-
|
|
42
|
-
default:
|
|
43
|
-
return <p>Question type "{question.type}" is not yet implemented in this client.</p>;
|
|
44
|
-
|
|
45
|
-
// ❌ FORBIDDEN — matrix without subType
|
|
46
|
-
{question.type === 'matrix' && <SomeMatrix />}
|
|
36
|
+
import { QUESTION_TYPE } from '@explorer02/cfm-survey-sdk';
|
|
47
37
|
|
|
48
|
-
|
|
49
|
-
{question.type === 'matrix' && question.subType === 'CFM_MATRIX' && <LikertMatrixScale ... />}
|
|
50
|
-
{question.type === 'matrix' && (question.subType === 'CSAT_MATRIX' || question.subType === 'RATING_MATRIX') && <CsatMatrixScale ... />}
|
|
38
|
+
{question.type === QUESTION_TYPE.CFM_MATRIX && <LikertMatrixScale ... />}
|
|
51
39
|
```
|
|
52
40
|
|
|
53
|
-
##
|
|
54
|
-
|
|
55
|
-
Every question MUST be wrapped in a standard HTML `<section>` to visually separate it.
|
|
56
|
-
|
|
57
|
-
1. **Wrapper styling**:
|
|
58
|
-
- Border: `border border-gray-200`
|
|
59
|
-
- Radius: `rounded-xl`
|
|
60
|
-
- Background: `bg-white`
|
|
61
|
-
- Padding: `p-6 md:p-8`
|
|
62
|
-
- Shadow & Hover: `shadow-sm transition-all hover:shadow-md`
|
|
63
|
-
- **Crucial**: ID must be set to `question.id` (`id={question.id}`) so scroll-to-error works!
|
|
64
|
-
|
|
65
|
-
2. **Heading (`question.text`)**:
|
|
66
|
-
- MUST use `dangerouslySetInnerHTML={{ __html: question.text }}` because text from Sprinklr contains rich HTML (bold, links, italics).
|
|
67
|
-
- If `question.required === true`, append a red asterisk `*`.
|
|
41
|
+
## Placeholder resolution
|
|
68
42
|
|
|
69
|
-
|
|
70
|
-
- Render conditionally if `question.description` exists.
|
|
71
|
-
- MUST use `dangerouslySetInnerHTML`.
|
|
72
|
-
- Style with slightly smaller text, greyish color (e.g., `text-sm text-gray-600 mt-2`).
|
|
43
|
+
Pass `allAnswers` and `allQuestions` so question text can reference prior answers:
|
|
73
44
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
## Hover Tooltips & Micro-interactions
|
|
78
|
-
- Focus states on text inputs MUST use the brand primary color (e.g., magenta `#e20074` for Telekom).
|
|
79
|
-
- Radio button wrappers should have a subtle background hover effect (e.g., `hover:bg-pink-50`).
|
|
45
|
+
```tsx
|
|
46
|
+
resolveSurveyQuestionPlaceholders(question.questionText, allAnswers, questionsById)
|
|
47
|
+
```
|
|
80
48
|
|
|
81
49
|
## Props Contract
|
|
82
50
|
|
|
@@ -85,8 +53,43 @@ type QuestionProps = {
|
|
|
85
53
|
question: SurveyQuestion;
|
|
86
54
|
selectedValue?: AnswerValue;
|
|
87
55
|
validationError?: string;
|
|
88
|
-
|
|
56
|
+
allAnswers: SurveyAnswers;
|
|
57
|
+
allQuestions: SurveyQuestion[];
|
|
58
|
+
onSelect: (answerValue: AnswerValue) => void;
|
|
89
59
|
};
|
|
90
60
|
```
|
|
91
61
|
|
|
92
|
-
Wire `onSelect` → `onAction({ type: 'CHANGE', payload: { questionId,
|
|
62
|
+
Wire `onSelect` → `onAction({ type: 'CHANGE', payload: { questionId, answerValue } })`.
|
|
63
|
+
|
|
64
|
+
## Container UI Structure
|
|
65
|
+
|
|
66
|
+
- Wrapper: `<section id={question.id} ...>` — **required** for scroll-to-error
|
|
67
|
+
- `questionText` / `questionDescription`: `dangerouslySetInnerHTML`
|
|
68
|
+
- Required asterisk when `question.required`
|
|
69
|
+
- Validation error banner when `validationError` is set
|
|
70
|
+
|
|
71
|
+
## Matrix value storage
|
|
72
|
+
|
|
73
|
+
Store matrix cell values as column **`id`** via `matrixColumnStoredValue(col)` — see [`value-derivation.md`](../02-reference/value-derivation.md).
|
|
74
|
+
|
|
75
|
+
## SDK Integration Checklist — inline types (agent)
|
|
76
|
+
|
|
77
|
+
### MCQ (`QUESTION_TYPE.MCQ`)
|
|
78
|
+
|
|
79
|
+
- [ ] Filter options with `getVisibleMcqOptionsForAnswers` before render
|
|
80
|
+
- [ ] Store `option.id`; support `isMultiSelect` array shape when configured
|
|
81
|
+
- [ ] onAction CHANGE: `{ questionId, answerValue: optionId | optionId[] }`
|
|
82
|
+
|
|
83
|
+
### TEXTFIELD (`QUESTION_TYPE.TEXTFIELD`)
|
|
84
|
+
|
|
85
|
+
- [ ] Render `inputVariant` (`short` single-line vs `long` textarea)
|
|
86
|
+
- [ ] onAction CHANGE: `{ questionId, answerValue: string }`
|
|
87
|
+
- [ ] Debounce long text if partial save enabled
|
|
88
|
+
|
|
89
|
+
### TEXT_AND_MEDIA (`QUESTION_TYPE.TEXT_AND_MEDIA`)
|
|
90
|
+
|
|
91
|
+
- [ ] No answer input — no CHANGE action
|
|
92
|
+
- [ ] Render media block when `question.media?.url` present
|
|
93
|
+
- [ ] User advances with NEXT
|
|
94
|
+
|
|
95
|
+
See [`question-type-sdk-matrix.md`](../00-integration/question-type-sdk-matrix.md) for all 11 types.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> **UI spec (authoritative):** `03-ui-specs/01-rating.md` (NPS) · `03-ui-specs/05-rating-scale.md` (star/emoji gap)
|
|
4
4
|
> **Reference implementation:** `apps/client/src/components/RatingScale.tsx`
|
|
5
5
|
> **Target Component**: `RatingScale`
|
|
6
|
-
> **Handles**: `type: '
|
|
6
|
+
> **Handles**: `type: 'NPS_SCALE'` (via `QUESTION_TYPE.NPS_SCALE`)
|
|
7
7
|
|
|
8
8
|
## Core Responsibility
|
|
9
9
|
Render a horizontal sequence of numbered badges.
|
|
@@ -28,3 +28,13 @@ The UI must exactly match enterprise standards:
|
|
|
28
28
|
3. **Selected State**: The selected badge MUST pop out prominently: `border-2 border-[#e20074] shadow-md scale-105 opacity-100 font-bold`.
|
|
29
29
|
4. **Hover State**: `hover:scale-105 hover:border-[#e20074] transition-all cursor-pointer`.
|
|
30
30
|
5. **Tooltips**: Every badge MUST have `title={option.label}` applied for accessibility and semantic context (e.g. hovering '10' shows 'Extremely Likely').
|
|
31
|
+
|
|
32
|
+
## SDK Integration Checklist (agent)
|
|
33
|
+
|
|
34
|
+
- [ ] Type literal: `QUESTION_TYPE.NPS_SCALE`
|
|
35
|
+
- [ ] Answer stored as: `number`
|
|
36
|
+
- [ ] onAction CHANGE: `{ questionId, answerValue: number }`
|
|
37
|
+
- [ ] Visibility helper: `getVisibleScaleColumnsForAnswers` when scale points have answer logic
|
|
38
|
+
- [ ] Custom fields: 4th arg on scale visibility helper when needed
|
|
39
|
+
- [ ] Logic features: skip ✓ display ✓ answer ✓ variants ✓ numbering ✓
|
|
40
|
+
- [ ] Matrix row: [`question-type-sdk-matrix.md`](../00-integration/question-type-sdk-matrix.md#nps_scale)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Star Rating Scale Architectural Blueprint
|
|
2
|
+
|
|
3
|
+
> **DEPRECATED:** API RATING pages map to `NPS_SCALE` or matrix display styles — not a separate `question.type`. Keep for historical reference only.
|
|
4
|
+
>
|
|
5
|
+
> **UI spec (authoritative):** `03-ui-specs/05-rating-scale.md`
|
|
6
|
+
> **SDK type:** `StarRatingQuestion` — `type: 'star_rating'`
|
|
7
|
+
> **Target Component**: `RatingScale` (star/emoji variant)
|
|
8
|
+
> **Handles**: `type: 'star_rating'` only — not `CSAT_MATRIX` (that is a matrix grid)
|
|
9
|
+
|
|
10
|
+
## Core Responsibility
|
|
11
|
+
|
|
12
|
+
Render star or emoji rating scales ensuring that active selections are unmistakably highlighted using bounding boxes or strong border accents, NEVER just by changing the SVG fill color.
|
|
13
|
+
|
|
14
|
+
## State Management & Mapping
|
|
15
|
+
|
|
16
|
+
- **Emojis**: `scaleStyle === 'emoji'`
|
|
17
|
+
- Map values using client-side emoji icons (react-icons) proportional to scale length.
|
|
18
|
+
- **Stars**: `scaleStyle === 'star'`
|
|
19
|
+
- Active stars (index <= selectedValue) use filled star icons from react-icons.
|
|
20
|
+
- **Numbers**: Derive display/submit value from `optionLabel` (see `02-reference/value-derivation.md`).
|
|
21
|
+
|
|
22
|
+
## UX Bounding Box Rules (CRITICAL)
|
|
23
|
+
|
|
24
|
+
### For Emojis & Stars:
|
|
25
|
+
|
|
26
|
+
- **Unselected**: `inline-flex p-2 border-2 border-transparent opacity-40 hover:opacity-100 hover:scale-110 transition-all cursor-pointer`.
|
|
27
|
+
- **Selected**: The specific selected icon MUST be wrapped in a prominent square box with rounded corners.
|
|
28
|
+
- **CSS**: `border-2 border-[#e20074] rounded-lg p-2 bg-pink-50 shadow-sm scale-110 opacity-100`.
|
|
29
|
+
|
|
30
|
+
### For Numbered Badges:
|
|
31
|
+
|
|
32
|
+
- **Shape**: MUST be square with rounded corners (`rounded-lg w-12 h-12`). Do not use full circles.
|
|
33
|
+
- **Selected**: `border-2 border-[#e20074] opacity-100 font-bold scale-105 shadow-md`.
|
|
34
|
+
- **Unselected**: `opacity-30 border-transparent`.
|
|
35
|
+
|
|
36
|
+
## Bounding Boxes inside Matrices
|
|
37
|
+
|
|
38
|
+
When emojis or stars are rendered inside `CsatMatrixScale`, the exact same bounding box logic applies. See `05-csat-matrix-scale.md`.
|
|
39
|
+
|
|
40
|
+
## Tooltips & Label Positions
|
|
41
|
+
|
|
42
|
+
- **Tooltips**: Every emoji, star, and badge MUST have `title={optionLabel}` on hover.
|
|
43
|
+
- **Labels (`minLabel`, `midLabel`, `maxLabel`)**:
|
|
44
|
+
- Render in `Question.tsx` above the scale (same pattern as `nps`).
|
|
45
|
+
- `midLabel` position: `style={{ left: \`\${(midLabelIndex / (options.length - 1)) * 100}%\` }}`.
|
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
# CSAT Matrix Architectural Blueprint
|
|
2
2
|
|
|
3
|
-
> **UI spec (authoritative):** `03-ui-specs/08-matrix-csat-rating.md`
|
|
3
|
+
> **UI spec (authoritative):** `03-ui-specs/04-csat.md` (for `CSAT_MATRIX`) and `03-ui-specs/08-matrix-csat-rating.md` (for `RATING_MATRIX`)
|
|
4
4
|
> **Reference implementation:** `apps/client/src/components/CsatMatrixScale.tsx`
|
|
5
5
|
> **Target Component**: `CsatMatrixScale`
|
|
6
|
-
> **Handles**: `type: '
|
|
6
|
+
> **Handles**: `type: 'CSAT_MATRIX'` and `type: 'RATING_MATRIX'`
|
|
7
7
|
|
|
8
8
|
## Core Responsibility
|
|
9
|
+
|
|
9
10
|
Render a grid where columns represent satisfaction scales (Emojis, Stars, or Rating Badges).
|
|
10
11
|
|
|
11
12
|
## Data Mapping & State Management
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
13
|
+
|
|
14
|
+
- `selectedValue`: `MatrixRowAnswers` — keys are `statementRows[].id`, values are column **`id`** (via `matrixColumnStoredValue`)
|
|
15
|
+
- Iterate over `question.statementRows`. Within each row, iterate over `question.scaleColumns`.
|
|
16
|
+
- Render the correct icon/badge in the `<td>` based on `question.displayStyle`.
|
|
15
17
|
|
|
16
18
|
## Configuration Matrix
|
|
19
|
+
|
|
17
20
|
You MUST handle:
|
|
18
|
-
|
|
19
|
-
- **`
|
|
21
|
+
|
|
22
|
+
- **`hasNotApplicableOption`**: If true, the last column might be `null`. Render a distinct UI for the N/A column.
|
|
23
|
+
- **`reverseScaleOrder`**: Reverse column order before render when true.
|
|
24
|
+
- **`gridLayout`**: `'standard'`, `'carousel'`, or `'dropdown'` — see ui-spec decision tree.
|
|
20
25
|
|
|
21
26
|
## UI/UX Bounding Box Rules (CRITICAL)
|
|
22
27
|
|
|
@@ -28,8 +33,12 @@ Matrices are difficult to read and click accurately. You MUST implement these st
|
|
|
28
33
|
2. **The "Active Bounding Box"**:
|
|
29
34
|
- For Emojis and Stars inside the matrix, do NOT just change the SVG fill.
|
|
30
35
|
- The interactive wrapper inside the `<td>` MUST toggle a pink bounding box.
|
|
31
|
-
- **Selected Cell**: `border-2 border-[#e20074] rounded-lg p-2 bg-pink-50 shadow-sm scale-110 opacity-100 cursor-pointer`.
|
|
32
|
-
- **Unselected Cell**: `border-2 border-transparent p-2 opacity-40 hover:opacity-100 hover:scale-110 cursor-pointer`.
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
## SDK Integration Checklist (agent)
|
|
38
|
+
|
|
39
|
+
- [ ] Type literals: `QUESTION_TYPE.CSAT_MATRIX` and `QUESTION_TYPE.RATING_MATRIX`
|
|
40
|
+
- [ ] Answer stored as: `MatrixRowAnswers` via `matrixColumnStoredValue`
|
|
41
|
+
- [ ] onAction CHANGE: `{ questionId, answerValue: MatrixRowAnswers }`
|
|
42
|
+
- [ ] Visibility helper: `getVisibleMatrixItemsForAnswers(..., customFieldValues?)`
|
|
43
|
+
- [ ] Emoji/star UI: client-side `react-icons` — not SDK exports
|
|
44
|
+
- [ ] Matrix row: [`question-type-sdk-matrix.md`](../00-integration/question-type-sdk-matrix.md)
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
> **UI spec (authoritative):** `03-ui-specs/07-matrix-cfm.md`
|
|
4
4
|
> **Reference implementation:** `apps/client/src/components/LikertMatrixScale.tsx`
|
|
5
5
|
> **Target Component**: `LikertMatrixScale`
|
|
6
|
-
> **Handles**: `type: '
|
|
6
|
+
> **Handles**: `type: 'CFM_MATRIX'`
|
|
7
7
|
|
|
8
8
|
## Core Responsibility
|
|
9
9
|
Render a grid with text-label columns. However, this component must be highly dynamic to support complex layouts, transpositions, and long-form matrix readability.
|
|
10
10
|
|
|
11
11
|
## Data Mapping & State Management
|
|
12
|
-
- `selectedValue`:
|
|
13
|
-
- **Mutation**: When
|
|
12
|
+
- `selectedValue`: `MatrixRowAnswers` — store column **`id`** via `matrixColumnStoredValue(col)`
|
|
13
|
+
- **Mutation**: When a cell is selected for `row.id`, dispatch `onSelect({ ...selectedValue, [row.id]: columnId })`
|
|
14
14
|
|
|
15
15
|
## Configuration Matrix (CRITICAL FOR PLANNING)
|
|
16
16
|
|
|
@@ -23,9 +23,10 @@ If `true`, the standard Row-by-Column matrix is inverted:
|
|
|
23
23
|
- **Architectural Requirement**: Your `<tbody>` map must iterate over `question.columns` first, and render `<td>` cells mapped from `question.rows`.
|
|
24
24
|
- Ensure `onSelect` still correctly pairs the original `row.id` with the original `col.value`.
|
|
25
25
|
|
|
26
|
-
### 2. `
|
|
26
|
+
### 2. `repeatColumnHeaders`
|
|
27
27
|
If `true`, the `<thead>` containing column labels must be re-injected periodically to prevent users from losing track of columns on long matrices.
|
|
28
28
|
- **Implementation Strategy**: In your row `map()`, if `index > 0 && index % 5 === 0`, return a React Fragment containing a cloned `<tr>` header row followed by the actual data `<tr>`.
|
|
29
|
+
- **Note:** API/config field is `repeatColumnHeaders` (not `repeatScale`).
|
|
29
30
|
|
|
30
31
|
### 3. `matrixType === 'bipolar'`
|
|
31
32
|
Standard matrices (`'likert'`) only show statement text on the left. Bipolar scales show text on BOTH sides of the radio buttons.
|
|
@@ -44,3 +45,12 @@ To match enterprise standards, implement the following UI strictly:
|
|
|
44
45
|
- Every input MUST have `title={col.label}` so the user sees the semantic meaning when hovering the radio button.
|
|
45
46
|
4. **Header Typography**:
|
|
46
47
|
- `th` tags should be `text-xs font-semibold text-gray-500 uppercase tracking-wider p-4 border-b`.
|
|
48
|
+
|
|
49
|
+
## SDK Integration Checklist (agent)
|
|
50
|
+
|
|
51
|
+
- [ ] Type literal: `QUESTION_TYPE.CFM_MATRIX`
|
|
52
|
+
- [ ] Answer stored as: `MatrixRowAnswers` via `matrixColumnStoredValue`
|
|
53
|
+
- [ ] onAction CHANGE: `{ questionId, answerValue: MatrixRowAnswers }`
|
|
54
|
+
- [ ] Visibility helper: `getVisibleMatrixItemsForAnswers(..., customFieldValues?)`
|
|
55
|
+
- [ ] Handle `gridLayout`: standard / carousel / dropdown
|
|
56
|
+
- [ ] Matrix row: [`question-type-sdk-matrix.md#cfm_matrix`](../00-integration/question-type-sdk-matrix.md#cfm_matrix)
|
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
> **UI spec (authoritative):** `03-ui-specs/09-slider-matrix.md`
|
|
4
4
|
> **Reference implementation:** `apps/client/src/components/SliderMatrixScale.tsx`
|
|
5
5
|
> **Target Component**: `SliderMatrixScale`
|
|
6
|
-
> **Handles**: `type: '
|
|
6
|
+
> **Handles**: `type: 'SLIDER_MATRIX'`
|
|
7
7
|
|
|
8
8
|
## Core Responsibility
|
|
9
9
|
Render an independent range slider for every row in the matrix. Sliders must be highly visible, utilizing a "thick track" UI, dynamic fill based on the current value, and exact value badges.
|
|
10
10
|
|
|
11
11
|
## State Management
|
|
12
|
-
- `selectedValue`: Expects `
|
|
12
|
+
- `selectedValue`: Expects `MatrixRowAnswers` shape.
|
|
13
13
|
- `row.min`, `row.max`, and `row.step` must dictate the boundaries of the slider logic.
|
|
14
14
|
- **Mutation**: `onChange` of the input must parse the value as a Number before dispatching `onSelect`.
|
|
15
15
|
|
|
@@ -45,3 +45,12 @@ Your component must account for:
|
|
|
45
45
|
- If `true`, render a checkbox at the end of the row.
|
|
46
46
|
- Checking it sets the value in the map to `null`.
|
|
47
47
|
- When `value === null`, gray out the slider track and disable the input.
|
|
48
|
+
|
|
49
|
+
## SDK Integration Checklist (agent)
|
|
50
|
+
|
|
51
|
+
- [ ] Type literal: `QUESTION_TYPE.SLIDER_MATRIX`
|
|
52
|
+
- [ ] Answer stored as: `MatrixRowAnswers` — number or `null` per row
|
|
53
|
+
- [ ] onAction CHANGE: `{ questionId, answerValue: MatrixRowAnswers }`
|
|
54
|
+
- [ ] Visibility helper: `getVisibleStatementRowsForAnswers(..., customFieldValues?)`
|
|
55
|
+
- [ ] Use `CustomSliderTrack` when `sliderType === 'graphics'`
|
|
56
|
+
- [ ] Matrix row: [`question-type-sdk-matrix.md#slider_matrix`](../00-integration/question-type-sdk-matrix.md#slider_matrix)
|
|
@@ -1,58 +1,30 @@
|
|
|
1
1
|
# File Upload Architectural Blueprint
|
|
2
2
|
|
|
3
3
|
> **UI spec (authoritative):** `03-ui-specs/10-file-upload.md`
|
|
4
|
-
> **
|
|
5
|
-
> **
|
|
6
|
-
> **
|
|
4
|
+
> **Upload API:** [`00-integration/file-upload-aws.md`](../00-integration/file-upload-aws.md)
|
|
5
|
+
> **Portable template:** implement per ui-spec (upload wiring is project-specific)
|
|
6
|
+
> **Target Component:** `FileUploadScale`
|
|
7
|
+
> **Handles:** `type: 'FILE_UPLOAD'`
|
|
7
8
|
|
|
8
9
|
## Core Responsibility
|
|
9
|
-
|
|
10
|
+
|
|
11
|
+
Render drag-and-drop zone, validate files locally, **upload via presigned URL**, then bubble `UploadedFileAnswer[]` via `onSelect`.
|
|
10
12
|
|
|
11
13
|
## State Management
|
|
12
|
-
|
|
13
|
-
- **
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
|
|
29
|
-
### 2. `maxFileCount`
|
|
30
|
-
- Prevent uploads if `currentFiles.length + newFiles.length > maxFileCount`.
|
|
31
|
-
- Provide a clear local error message indicating the max limit.
|
|
32
|
-
|
|
33
|
-
### 3. `fileSizeLimit` & `fileSizeLimitType`
|
|
34
|
-
- `fileSizeLimit` is a number representing megabytes (MB).
|
|
35
|
-
- If `fileSizeLimitType === 'PER_FILE'`, validate that EVERY incoming file's `size` (in bytes) is `<= fileSizeLimit * 1024 * 1024`.
|
|
36
|
-
- If `fileSizeLimitType === 'IN_TOTAL'`, validate that the combined sum of all existing `selectedValue` sizes plus the incoming files' sizes is `<= fileSizeLimit * 1024 * 1024`.
|
|
37
|
-
|
|
38
|
-
## UI/UX Specifications
|
|
39
|
-
|
|
40
|
-
1. **Dropzone**:
|
|
41
|
-
- Wrap the main area with `onDragEnter`, `onDragOver`, `onDragLeave`, `onDrop`.
|
|
42
|
-
- **Inactive State**: `border-2 border-dashed border-gray-300 rounded-xl bg-gray-50 p-8 text-center cursor-pointer transition-all`.
|
|
43
|
-
- **Drag/Hover State**: `border-[#e20074] bg-[#fdf2f8]` (Using the brand primary color).
|
|
44
|
-
- Display a Cloud SVG icon (use gray, switch to magenta on hover).
|
|
45
|
-
- Render `question.uploadMessage` (which can contain HTML, so use `dangerouslySetInnerHTML`).
|
|
46
|
-
- Dynamically render the limits text (e.g., "Max 3 files, 10MB per file").
|
|
47
|
-
|
|
48
|
-
2. **File List**:
|
|
49
|
-
- Render existing files as distinct cards underneath the dropzone.
|
|
50
|
-
- Styling: `flex items-center justify-between p-3 mt-3 border border-gray-200 rounded-lg bg-white shadow-sm`.
|
|
51
|
-
- Display the filename (truncated if long) and the formatted file size (e.g., "1.2 MB").
|
|
52
|
-
- Include a distinct, circular Trash/Remove button that removes the file from the state and calls `onSelect` with the updated array.
|
|
53
|
-
|
|
54
|
-
3. **Validation Errors**:
|
|
55
|
-
- Local validation errors (e.g. "File too large") must be displayed in a highly visible red banner (`bg-red-50 text-red-700 p-3 rounded-md`) right below the dropzone.
|
|
56
|
-
|
|
57
|
-
## Hidden Input
|
|
58
|
-
- Include a hidden `<input type="file" multiple={question.maxFileCount > 1} accept={...} />` that is triggered via a `useRef` when the dropzone is clicked.
|
|
14
|
+
|
|
15
|
+
- `selectedValue`: `UploadedFileAnswer[] | undefined` — **not** raw `File[]` after upload completes
|
|
16
|
+
- **Mutation pattern:**
|
|
17
|
+
1. User selects files → local validation
|
|
18
|
+
2. For each file: request presigned URL → PUT to S3 → append `{ id, mediaUrl, name, mimeType, size }`
|
|
19
|
+
3. `onSelect(uploadedAnswers)` → `onAction(CHANGE)`
|
|
20
|
+
|
|
21
|
+
## Local validation (before upload)
|
|
22
|
+
|
|
23
|
+
Same as ui-spec: `supportedFileFormats`, `maxFileCount`, `fileSizeLimit` + `fileSizeLimitType`.
|
|
24
|
+
|
|
25
|
+
## SDK Integration Checklist (agent)
|
|
26
|
+
|
|
27
|
+
- [ ] Type literal: `QUESTION_TYPE.FILE_UPLOAD`
|
|
28
|
+
- [ ] Final answer: `UploadedFileAnswer[]` with `id` + `mediaUrl`
|
|
29
|
+
- [ ] Wire upload API per [`file-upload-aws.md`](../00-integration/file-upload-aws.md)
|
|
30
|
+
- [ ] Matrix row: [`question-type-sdk-matrix.md#file_upload`](../00-integration/question-type-sdk-matrix.md#file_upload)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> **UI spec (authoritative):** `03-ui-specs/shared/custom-slider-track.md`
|
|
4
4
|
> **Reference implementation:** `apps/client/src/components/CustomSliderTrack.tsx`
|
|
5
5
|
> **Target Component**: `CustomSliderTrack`
|
|
6
|
-
> **Used By**: `SliderMatrixScale`, `CsatMatrixScale`, `
|
|
6
|
+
> **Used By**: `SliderMatrixScale`, `CsatMatrixScale`, standalone `slider`
|
|
7
7
|
|
|
8
8
|
## Core Responsibility
|
|
9
9
|
Native browser `<input type="range">` elements are impossible to style consistently across browsers, especially when trying to render dynamic emoji handles. This component MUST implement an invisible overlay architecture to provide total CSS control over the slider's appearance.
|
|
@@ -24,7 +24,7 @@ A dynamic `<div>` representing the portion of the slider up to the current value
|
|
|
24
24
|
### Layer 3: The Custom Thumb
|
|
25
25
|
A visual element that floats along the track.
|
|
26
26
|
- **For Standard Sliders**: A pink circle. `w-5 h-5 bg-[#e20074] border-2 border-white rounded-full shadow-md pointer-events-none`.
|
|
27
|
-
- **For Graphics Sliders (`sliderType === 'graphics'`)**: A larger white circle containing an emoji mapped to the current value using
|
|
27
|
+
- **For Graphics Sliders (`sliderType === 'graphics'`)**: A larger white circle containing an emoji mapped to the current value using client-side react-icons. `w-10 h-10 bg-white shadow-md border flex items-center justify-center text-2xl`.
|
|
28
28
|
- Inline Style: `style={{ left: \`\${percentage}%\` }}`.
|
|
29
29
|
|
|
30
30
|
### Layer 4: The Native Input (The Engine)
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
# Progress Bar
|
|
2
2
|
|
|
3
3
|
> **UI spec (authoritative):** `03-ui-specs/12-survey-chrome.md`
|
|
4
|
+
> **Progress calculation:** `00-integration/progress.md`
|
|
4
5
|
> **Reference implementation:** `apps/client/src/components/ProgressBar.tsx`
|
|
5
6
|
> **Component**: `ProgressBar`
|
|
6
|
-
> **Role**: Displays completion status at the top of the survey.
|
|
7
7
|
|
|
8
8
|
## Props Interface
|
|
9
|
+
|
|
9
10
|
```typescript
|
|
10
11
|
type ProgressBarProps = {
|
|
11
|
-
progressPercentage: number; // 0 to 100
|
|
12
|
+
progressPercentage: number; // 0 to 100, path-scoped
|
|
12
13
|
};
|
|
13
14
|
```
|
|
14
15
|
|
|
@@ -16,7 +17,6 @@ type ProgressBarProps = {
|
|
|
16
17
|
|
|
17
18
|
```tsx
|
|
18
19
|
export default function ProgressBar({ progressPercentage }: ProgressBarProps) {
|
|
19
|
-
// Ensure it's bounded between 0 and 100
|
|
20
20
|
const width = Math.min(100, Math.max(0, progressPercentage));
|
|
21
21
|
|
|
22
22
|
return (
|
|
@@ -30,4 +30,10 @@ export default function ProgressBar({ progressPercentage }: ProgressBarProps) {
|
|
|
30
30
|
}
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
## Wiring
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
<ProgressBar progressPercentage={state.progressPercentage} />
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The SDK computes percentage from **questions on the active skip path**, not total survey pages. Skip logic and answer changes update the bar automatically.
|
|
@@ -1,44 +1,41 @@
|
|
|
1
1
|
# Matrix Dropdown
|
|
2
2
|
|
|
3
|
-
> **UI spec (authoritative):** `03-ui-specs/shared/matrix-dropdown.md`
|
|
4
|
-
> **
|
|
5
|
-
> **Component
|
|
6
|
-
> **Used by
|
|
3
|
+
> **UI spec (authoritative):** [`03-ui-specs/shared/matrix-dropdown.md`](../03-ui-specs/shared/matrix-dropdown.md) — implement the **custom panel** (not a native `<select>`).
|
|
4
|
+
> **Portable template:** copy [`templates/MatrixDropdown.tsx`](../templates/MatrixDropdown.tsx) when available.
|
|
5
|
+
> **Component:** `MatrixDropdown`
|
|
6
|
+
> **Used by:** `CsatMatrixScale`, `LikertMatrixScale`
|
|
7
7
|
|
|
8
8
|
## Role
|
|
9
|
-
Renders a dropdown selector for a single row in a matrix question. Used when `matrixFormat === 'dropdown'` or `buttonType === 'dropdown'`.
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
Renders a custom dropdown selector for a single matrix row. Used when `gridLayout === 'dropdown'` or `displayStyle` is `'dropdown'` / `'selectbox'`.
|
|
11
|
+
|
|
12
|
+
Emits stored column **`id`** values via `matrixColumnStoredValue(col)` — see [`02-reference/value-derivation.md`](../02-reference/value-derivation.md).
|
|
13
|
+
|
|
14
|
+
## Props (wiring only)
|
|
15
|
+
|
|
12
16
|
```typescript
|
|
13
17
|
type MatrixDropdownProps = {
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
options: ScaleColumn[]; // visible columns for this row
|
|
19
|
+
value: string | number | null | undefined;
|
|
16
20
|
onChange: (value: string | number | null) => void;
|
|
21
|
+
multiple?: boolean;
|
|
17
22
|
placeholder?: string;
|
|
18
23
|
};
|
|
19
24
|
```
|
|
20
25
|
|
|
21
|
-
##
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
<option value="" disabled>Select an option...</option>
|
|
38
|
-
{columns.map(col => (
|
|
39
|
-
<option key={String(col.value)} value={String(col.value)}>
|
|
40
|
-
{col.label}
|
|
41
|
-
</option>
|
|
42
|
-
))}
|
|
43
|
-
</select>
|
|
44
|
-
```
|
|
26
|
+
## Implementation
|
|
27
|
+
|
|
28
|
+
Do **not** use a native `<select>`. Follow the shared ui-spec for:
|
|
29
|
+
|
|
30
|
+
- Click-to-open panel with click-outside dismiss
|
|
31
|
+
- Single-select: one choice; multi-select: tag chips + toggle
|
|
32
|
+
- N/A mutual exclusion when `hasNotApplicableOption` applies
|
|
33
|
+
- Placeholder text: `"Select..."` (dropdown) or full-width cards (selectbox)
|
|
34
|
+
|
|
35
|
+
See [`03-ui-specs/shared/matrix-dropdown.md`](../03-ui-specs/shared/matrix-dropdown.md) for layout, states, and agent checklist.
|
|
36
|
+
|
|
37
|
+
## SDK Integration Checklist
|
|
38
|
+
|
|
39
|
+
- [ ] Copy `templates/MatrixDropdown.tsx` or implement per shared ui-spec
|
|
40
|
+
- [ ] `onChange` stores column **`id`** via `matrixColumnStoredValue(col)`
|
|
41
|
+
- [ ] Filter columns with `getVisibleMatrixItemsForAnswers` before passing to dropdown
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Intro Page
|
|
2
|
+
|
|
3
|
+
> **UI spec:** `03-ui-specs/12-survey-chrome.md`
|
|
4
|
+
> **Reference:** `apps/client/src/components/IntroPage.tsx`
|
|
5
|
+
|
|
6
|
+
## When to render
|
|
7
|
+
|
|
8
|
+
`state.phase.kind === 'intro'` and `state.specialPage` is set.
|
|
9
|
+
|
|
10
|
+
## Props
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
type IntroPageProps = {
|
|
14
|
+
page: SpecialPage;
|
|
15
|
+
language?: string;
|
|
16
|
+
onStart: () => void;
|
|
17
|
+
};
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Wiring
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
<IntroPage
|
|
24
|
+
page={state.specialPage}
|
|
25
|
+
language={survey.language}
|
|
26
|
+
onStart={() => onAction({ type: 'START' })}
|
|
27
|
+
/>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Content
|
|
31
|
+
|
|
32
|
+
- Render `page.header` and optional `page.description` as HTML
|
|
33
|
+
- CTA label: `page.ctaLabel` or default "Start Survey"
|
|
34
|
+
- Header + Footer chrome on full-page layout
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# End Page
|
|
2
|
+
|
|
3
|
+
> **UI spec:** `03-ui-specs/12-survey-chrome.md`
|
|
4
|
+
> **Reference:** `apps/client/src/components/EndPage.tsx`
|
|
5
|
+
> **Logic:** `00-integration/end-page-logic.md`
|
|
6
|
+
|
|
7
|
+
## When to render
|
|
8
|
+
|
|
9
|
+
`state.phase.kind === 'end'` after successful submit, with `state.specialPage` from `survey.endPages`.
|
|
10
|
+
|
|
11
|
+
When the survey has **multiple end pages**, the SDK picks the page at submit using display logic (see [`end-page-logic.md`](../00-integration/end-page-logic.md)). The client only renders the resolved `state.specialPage`.
|
|
12
|
+
|
|
13
|
+
## Props
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
type EndPageProps = {
|
|
17
|
+
page: SpecialPage;
|
|
18
|
+
};
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Wiring
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
<EndPage page={state.specialPage} />
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Content
|
|
28
|
+
|
|
29
|
+
- Render `page.header` and optional `page.description` as HTML
|
|
30
|
+
- No navigation buttons — terminal thank-you screen
|
|
31
|
+
- Header + Footer chrome
|