@explorer02/cfm-survey-sdk 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/postinstall.js +30 -18
- package/templates/AGENT.md +10 -1906
- package/templates/docs/01-sdk-core/01-fetch-survey.md +68 -0
- package/templates/docs/01-sdk-core/02-survey-mapper.md +85 -0
- package/templates/docs/01-sdk-core/03-question-mappers.md +114 -0
- package/templates/docs/01-sdk-core/04-pagination.md +72 -0
- package/templates/docs/01-sdk-core/05-validation.md +66 -0
- package/templates/docs/01-sdk-core/06-submit-survey.md +90 -0
- package/templates/docs/01-sdk-core/07-language-handling.md +111 -0
- package/templates/docs/01-sdk-core/08-icons-and-emojis.md +88 -0
- package/templates/docs/01-sdk-core/README.md +53 -0
- package/templates/docs/02-question-types/01-rating.md +52 -0
- package/templates/docs/02-question-types/02-radio.md +26 -0
- package/templates/docs/02-question-types/03-text.md +26 -0
- package/templates/docs/02-question-types/04-csat.md +54 -0
- package/templates/docs/02-question-types/05-rating-scale.md +26 -0
- package/templates/docs/02-question-types/06-slider.md +30 -0
- package/templates/docs/02-question-types/07-matrix-cfm.md +43 -0
- package/templates/docs/02-question-types/08-matrix-csat.md +29 -0
- package/templates/docs/02-question-types/09-matrix-rating.md +28 -0
- package/templates/docs/02-question-types/10-slider-matrix.md +40 -0
- package/templates/docs/02-question-types/11-file-upload.md +34 -0
- package/templates/docs/02-question-types/12-text-and-media.md +35 -0
- package/templates/docs/02-question-types/README.md +74 -0
- package/templates/docs/03-client-components/01-survey-page.md +113 -0
- package/templates/docs/03-client-components/02-question.md +57 -0
- package/templates/docs/03-client-components/03-rating-scale.md +38 -0
- package/templates/docs/03-client-components/04-csat-scale.md +40 -0
- package/templates/docs/03-client-components/05-csat-matrix-scale.md +43 -0
- package/templates/docs/03-client-components/06-likert-matrix-scale.md +38 -0
- package/templates/docs/03-client-components/07-slider-matrix-scale.md +44 -0
- package/templates/docs/03-client-components/08-file-upload-scale.md +33 -0
- package/templates/docs/03-client-components/09-custom-slider-track.md +27 -0
- package/templates/docs/03-client-components/10-header-footer.md +46 -0
- package/templates/docs/03-client-components/11-progress-bar.md +31 -0
- package/templates/docs/03-client-components/12-language-selector.md +41 -0
- package/templates/docs/03-client-components/13-matrix-dropdown.md +42 -0
- package/templates/docs/03-client-components/README.md +63 -0
- package/templates/docs/04-critical-rules/01-import-rules.md +51 -0
- package/templates/docs/04-critical-rules/02-action-dispatching.md +56 -0
- package/templates/docs/04-critical-rules/03-scroll-navigation.md +37 -0
- package/templates/docs/04-critical-rules/04-logo-branding.md +42 -0
- package/templates/docs/04-critical-rules/05-troubleshooting.md +46 -0
- package/templates/docs/04-critical-rules/README.md +29 -0
- package/templates/docs/index.md +129 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Icons & Emojis — CSAT Visual System
|
|
2
|
+
|
|
3
|
+
> **Source**: `packages/sdk/src/ui/icons.tsx`
|
|
4
|
+
> **Exports**: `CsatEmojiSet`, `CsatEmojiMapping`, `CsatStarIcons`, `getEmojiForIndex`
|
|
5
|
+
|
|
6
|
+
## What It Provides
|
|
7
|
+
|
|
8
|
+
Pre-built React elements for CSAT satisfaction faces and star rating icons. These are used by `CsatScale`, `CsatMatrixScale`, and `CustomSliderTrack` components.
|
|
9
|
+
|
|
10
|
+
## Emoji Face System (11-Level)
|
|
11
|
+
|
|
12
|
+
The SDK ships an 11-level sentiment face scale using `react-icons/fa6`:
|
|
13
|
+
|
|
14
|
+
| Index | Face | Color | Icon |
|
|
15
|
+
|-------|------|-------|------|
|
|
16
|
+
| 1 | Very Angry | `#dc2626` | FaFaceAngry |
|
|
17
|
+
| 2 | Sad | `#e04832` | FaFaceSadTear |
|
|
18
|
+
| 3 | Frown | `#ea580c` | FaFaceFrown |
|
|
19
|
+
| 4 | Open Frown | `#f59e0b` | FaFaceFrownOpen |
|
|
20
|
+
| 5 | Meh | `#eab308` | FaFaceMeh |
|
|
21
|
+
| 6 | Neutral | `#a3a323` | FaFaceMeh |
|
|
22
|
+
| 7 | Smile | `#84cc16` | FaFaceSmile |
|
|
23
|
+
| 8 | Beam | `#65a30d` | FaFaceSmileBeam |
|
|
24
|
+
| 9 | Grin | `#22c55e` | FaFaceGrin |
|
|
25
|
+
| 10 | Wide Grin | `#16a34a` | FaFaceGrinWide |
|
|
26
|
+
| 11 | Star-Struck | `#059669` | FaFaceGrinStars |
|
|
27
|
+
|
|
28
|
+
## `getEmojiForIndex(index, total)` — Adaptive Mapping
|
|
29
|
+
|
|
30
|
+
Maps a 0-based column index within a scale of `total` items to the correct face from the 11-level set.
|
|
31
|
+
|
|
32
|
+
**Curated subsets for common scale lengths:**
|
|
33
|
+
- 3 options → indices [3, 6, 8] from the 11-set
|
|
34
|
+
- 4 options → indices [3, 4, 7, 9]
|
|
35
|
+
- 5 options → indices [3, 4, 6, 7, 9]
|
|
36
|
+
- 6 options → indices [3, 4, 6, 7, 9, 11]
|
|
37
|
+
|
|
38
|
+
**For other lengths:** Interpolates linearly across the 11-set:
|
|
39
|
+
```typescript
|
|
40
|
+
const percent = index / (total - 1);
|
|
41
|
+
const key = Math.min(11, Math.max(1, Math.round(percent * 10) + 1));
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## `CsatEmojiMapping` — Legacy 1–5 Mapping
|
|
45
|
+
|
|
46
|
+
For standalone 5-point CSAT scales:
|
|
47
|
+
```typescript
|
|
48
|
+
{ 1: Face[1], 2: Face[4], 3: Face[6], 4: Face[8], 5: Face[11] }
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## `CsatStarIcons` — Star Rating
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
{
|
|
55
|
+
filled: <FaStar style={{ color: '#e20074', fontSize: '28px' }} />,
|
|
56
|
+
empty: <FaRegStar style={{ color: '#d1d5db', fontSize: '28px' }} />,
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Usage in Components
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import { getEmojiForIndex, CsatStarIcons, CsatEmojiMapping } from '@explorer02/cfm-survey-sdk';
|
|
64
|
+
|
|
65
|
+
// CSAT Matrix — dynamic emoji per column
|
|
66
|
+
{columns.map((col, i) => getEmojiForIndex(i, columns.length))}
|
|
67
|
+
|
|
68
|
+
// CSAT Scale — star rendering
|
|
69
|
+
{options.map((opt, i) => (
|
|
70
|
+
i < selectedIndex ? CsatStarIcons.filled : CsatStarIcons.empty
|
|
71
|
+
))}
|
|
72
|
+
|
|
73
|
+
// Standalone CSAT — legacy 1-5 emoji
|
|
74
|
+
{CsatEmojiMapping[optionValue]}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Important: `buttonType` Determines Rendering
|
|
78
|
+
|
|
79
|
+
The `CsatQuestion.buttonType` and `MatrixQuestion.buttonType` fields control which icon system to use:
|
|
80
|
+
|
|
81
|
+
| `buttonType` | Render Style |
|
|
82
|
+
|-------------|-------------|
|
|
83
|
+
| `'emoji'` | Sentiment face icons from `getEmojiForIndex` |
|
|
84
|
+
| `'star'` | Star icons from `CsatStarIcons` |
|
|
85
|
+
| `'numbered'` | Numeric badge buttons (no icons) |
|
|
86
|
+
| `'radio'` | Standard radio circles |
|
|
87
|
+
| `'graphics'` | Continuous slider with emoji thumb |
|
|
88
|
+
| `'dropdown'` | Collapsed select dropdown |
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# SDK Core Architecture
|
|
2
|
+
|
|
3
|
+
> This folder documents the internal architecture of `@explorer02/cfm-survey-sdk`.
|
|
4
|
+
> Read these files **in order** to understand how the SDK processes a survey from network fetch to user submission.
|
|
5
|
+
|
|
6
|
+
## Pipeline Overview
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
┌─────────────┐ ┌──────────────┐ ┌──────────────────┐ ┌─────────────┐
|
|
10
|
+
│ Fetch API │ ──→ │ Map Survey │ ──→ │ Map Questions │ ──→ │ Paginate │
|
|
11
|
+
│ (network) │ │ (language) │ │ (per-type) │ │ (pages) │
|
|
12
|
+
└─────────────┘ └──────────────┘ └──────────────────┘ └─────────────┘
|
|
13
|
+
│
|
|
14
|
+
▼
|
|
15
|
+
┌─────────────┐ ┌──────────────┐ ┌──────────────────┐ ┌─────────────┐
|
|
16
|
+
│ Submit │ ←── │ Format Body │ ←── │ Validate │ ←── │ Render UI │
|
|
17
|
+
│ (network) │ │ (payload) │ │ (required) │ │ (React) │
|
|
18
|
+
└─────────────┘ └──────────────┘ └──────────────────┘ └─────────────┘
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Files in This Folder
|
|
22
|
+
|
|
23
|
+
| File | Topic | When to Read |
|
|
24
|
+
|------|-------|-------------|
|
|
25
|
+
| `01-fetch-survey.md` | Network fetching, API endpoint, instanceId | **Always** — first step |
|
|
26
|
+
| `02-survey-mapper.md` | Raw API → Survey object transformation | **Always** — understand data shape |
|
|
27
|
+
| `03-question-mappers.md` | Question type dispatch, normalizeType | **Always** — core routing logic |
|
|
28
|
+
| `04-pagination.md` | Page navigation, currentPageIndex | **Always** — state management |
|
|
29
|
+
| `05-validation.md` | Required field validation, scroll-to-error | **Always** — UX critical |
|
|
30
|
+
| `06-submit-survey.md` | Response submission, payload formatting | **Always** — submission pipeline |
|
|
31
|
+
| `07-language-handling.md` | Translations, placeholders, language switching | When survey is multi-language |
|
|
32
|
+
| `08-icons-and-emojis.md` | CSAT emoji faces, star icons | When CSAT/rating_scale types present |
|
|
33
|
+
|
|
34
|
+
## The Single Hook: `useSurveySDK()`
|
|
35
|
+
|
|
36
|
+
The entire SDK is consumed through **one hook**:
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { useSurveySDK } from '@explorer02/cfm-survey-sdk';
|
|
40
|
+
|
|
41
|
+
const { surveyQueryResults, submitSurveyResults, state, onAction } = useSurveySDK({
|
|
42
|
+
options: {
|
|
43
|
+
instanceId: string, // JWT token from client
|
|
44
|
+
language: string | undefined, // Active language code
|
|
45
|
+
placeholders: Record<string, string>, // Token replacements
|
|
46
|
+
debug: boolean, // Console logging
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
This hook internally composes: `useFetchSurvey` → `useSurveyPagination` → `useSubmitResponse` → `useSurveyProgress`.
|
|
52
|
+
|
|
53
|
+
The client component **never** needs to import or call these internal hooks directly.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Rating Question (NPS 0-10)
|
|
2
|
+
|
|
3
|
+
> **Type**: `'rating'` | **API Source**: `SCALE` with `SCALE_TYPE=TEN_POINT` or `NPS_SCALE`
|
|
4
|
+
> **Mapper**: `ratingMapper.ts` | **Component**: `RatingScale`
|
|
5
|
+
|
|
6
|
+
## TypeScript Type
|
|
7
|
+
|
|
8
|
+
```typescript
|
|
9
|
+
type RatingQuestion = QuestionBase & {
|
|
10
|
+
type: 'rating';
|
|
11
|
+
options: SurveyOption[]; // 0-10 with NPS traffic-light colors
|
|
12
|
+
minLabel?: string; // Left label (e.g. "Not Likely")
|
|
13
|
+
midLabel?: string; // Middle label (e.g. "Neutral")
|
|
14
|
+
maxLabel?: string; // Right label (e.g. "Extremely Likely")
|
|
15
|
+
midLabelIndex?: number; // Fractional position for mid-label CSS
|
|
16
|
+
reverseScaleOrder?: boolean; // Render high→low
|
|
17
|
+
buttonVariant?: 'radio' | 'numbered' | 'emoji';
|
|
18
|
+
};
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Answer Shape: `number`
|
|
22
|
+
Example: `7` (the selected badge value)
|
|
23
|
+
|
|
24
|
+
## Options Auto-Generation
|
|
25
|
+
If the API provides no explicit options, the SDK generates 0-10:
|
|
26
|
+
```typescript
|
|
27
|
+
Array.from({ length: 11 }, (_, i) => ({
|
|
28
|
+
id: String(i), label: String(i), value: i, color: getColor(i)
|
|
29
|
+
}));
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## NPS Traffic-Light Colors
|
|
33
|
+
```
|
|
34
|
+
0: #e2001a (red) 3: #f18b00 (orange) 6: #fcd900 (yellow)
|
|
35
|
+
1: #e4251b 4: #f7b200 7: #9fca00 (lime)
|
|
36
|
+
2: #ec610a 5: #fcd900 8: #6bb300 (green)
|
|
37
|
+
9: #339a00
|
|
38
|
+
10: #008000
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Mid-Label Positioning
|
|
42
|
+
```typescript
|
|
43
|
+
// CSS left percentage for mid-label
|
|
44
|
+
const left = (midLabelIndex / (options.length - 1)) * 100;
|
|
45
|
+
// For 11-point scale: midLabelIndex defaults to 7.5
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Rendering Guidance
|
|
49
|
+
- Display as horizontal row of numbered badge buttons
|
|
50
|
+
- Each badge uses `option.color` as background
|
|
51
|
+
- Selected badge: emphasized border/scale, all others dimmed
|
|
52
|
+
- Labels positioned: minLabel (left), midLabel (calculated %), maxLabel (right)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Radio Question (MCQ)
|
|
2
|
+
|
|
3
|
+
> **Type**: `'radio'` | **API Source**: `MCQ` (and any unrecognised answerType)
|
|
4
|
+
> **Mapper**: `radioMapper.ts` | **Component**: Inline in `Question.tsx`
|
|
5
|
+
|
|
6
|
+
## TypeScript Type
|
|
7
|
+
|
|
8
|
+
```typescript
|
|
9
|
+
type RadioQuestion = QuestionBase & {
|
|
10
|
+
type: 'radio';
|
|
11
|
+
options: SurveyOption[]; // Answer choices
|
|
12
|
+
isMultiSelect?: boolean; // If true, answer is (string|number)[]
|
|
13
|
+
};
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Answer Shape: `string | number`
|
|
17
|
+
Example: `"option_1"` or `3`
|
|
18
|
+
If `isMultiSelect: true` → `(string | number)[]`
|
|
19
|
+
|
|
20
|
+
## Rendering Guidance
|
|
21
|
+
- Render as card-style option list with radio circles
|
|
22
|
+
- Each option: border card with custom radio indicator + label
|
|
23
|
+
- Selected state: brand-color border + light tint background + filled dot
|
|
24
|
+
- Use `<input type="radio" className="sr-only">` for accessibility
|
|
25
|
+
- Labels use `dangerouslySetInnerHTML` (may contain HTML)
|
|
26
|
+
- `onChange={() => onSelect(option.value)}`
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Text Question (Free Text)
|
|
2
|
+
|
|
3
|
+
> **Type**: `'text'` | **API Source**: `TEXTFIELD`
|
|
4
|
+
> **Mapper**: `textMapper.ts` | **Component**: Inline in `Question.tsx`
|
|
5
|
+
|
|
6
|
+
## TypeScript Type
|
|
7
|
+
|
|
8
|
+
```typescript
|
|
9
|
+
type TextQuestion = QuestionBase & {
|
|
10
|
+
type: 'text';
|
|
11
|
+
maxCharacterCount?: number; // Character limit
|
|
12
|
+
placeholder?: string; // Input placeholder text
|
|
13
|
+
inputVariant?: 'short' | 'long'; // Single-line vs textarea
|
|
14
|
+
};
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Answer Shape: `string`
|
|
18
|
+
Example: `"Great customer service, very satisfied"`
|
|
19
|
+
|
|
20
|
+
## Rendering Guidance
|
|
21
|
+
- `inputVariant === 'short'` → `<input type="text">`, `inputVariant === 'long'` → `<textarea rows={4}>`
|
|
22
|
+
- Default to `<textarea>` if `inputVariant` is not specified
|
|
23
|
+
- Show character counter: `maxCharacterCount - currentLength` remaining
|
|
24
|
+
- `onChange={e => onSelect(e.target.value)}`
|
|
25
|
+
- Use `question.placeholder` or fallback: `"Type your response here..."`
|
|
26
|
+
- Focus state: brand-color border + ring
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# CSAT Question (Satisfaction Scale)
|
|
2
|
+
|
|
3
|
+
> **Type**: `'csat'` | **API Source**: `SCALE` with `SCALE_TYPE=CSAT` or ≤5 options
|
|
4
|
+
> **Mapper**: `ratingMapper.ts` (CSAT branch) | **Component**: `CsatScale`
|
|
5
|
+
|
|
6
|
+
## TypeScript Type
|
|
7
|
+
|
|
8
|
+
```typescript
|
|
9
|
+
type CsatQuestion = QuestionBase & {
|
|
10
|
+
type: 'csat';
|
|
11
|
+
options: SurveyOption[]; // Usually 1-5 satisfaction levels
|
|
12
|
+
minLabel?: string; // "Very Dissatisfied"
|
|
13
|
+
maxLabel?: string; // "Very Satisfied"
|
|
14
|
+
buttonType?: SurveyButtonType; // 'emoji' | 'star' | 'numbered' | 'radio'
|
|
15
|
+
hasNotApplicable?: boolean; // N/A option available
|
|
16
|
+
reverseScaleOrder?: boolean; // Render high→low
|
|
17
|
+
labelPosition?: 'top' | 'bottom' | 'hidden';
|
|
18
|
+
};
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Answer Shape: `number | null`
|
|
22
|
+
Example: `4` or `null` (N/A selected)
|
|
23
|
+
|
|
24
|
+
## Rendering by `buttonType`
|
|
25
|
+
|
|
26
|
+
| `buttonType` | Render As |
|
|
27
|
+
|-------------|-----------|
|
|
28
|
+
| `'emoji'` | Sentiment face icons from `CsatEmojiMapping` |
|
|
29
|
+
| `'star'` | Star icons from `CsatStarIcons` (filled/empty) |
|
|
30
|
+
| `'numbered'` | Numeric badge buttons |
|
|
31
|
+
| `'radio'` | Standard radio circles |
|
|
32
|
+
| `'graphics'` | Emoji slider (use `CustomSliderTrack`) |
|
|
33
|
+
|
|
34
|
+
## Star Rating Pattern
|
|
35
|
+
```tsx
|
|
36
|
+
{options.map((opt, i) => {
|
|
37
|
+
const numericValue = typeof opt.value === 'number' ? opt.value : i + 1;
|
|
38
|
+
const isActive = typeof selectedValue === 'number' && numericValue <= selectedValue;
|
|
39
|
+
return (
|
|
40
|
+
<button onClick={() => onSelect(opt.value)}>
|
|
41
|
+
{isActive ? CsatStarIcons.filled : CsatStarIcons.empty}
|
|
42
|
+
</button>
|
|
43
|
+
);
|
|
44
|
+
})}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Emoji Pattern
|
|
48
|
+
```tsx
|
|
49
|
+
{options.map((opt, i) => (
|
|
50
|
+
<button onClick={() => onSelect(opt.value)}>
|
|
51
|
+
{CsatEmojiMapping[typeof opt.value === 'number' ? opt.value : i + 1]}
|
|
52
|
+
</button>
|
|
53
|
+
))}
|
|
54
|
+
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Rating Scale Question (Stars/Emoji)
|
|
2
|
+
|
|
3
|
+
> **Type**: `'rating_scale'` | **API Source**: `SCALE` with `SCALE_TYPE=STAR/EMOJI/FIVE_POINT`
|
|
4
|
+
> **Mapper**: `ratingMapper.ts` (rating_scale branch) | **Component**: `RatingScale`
|
|
5
|
+
|
|
6
|
+
## TypeScript Type
|
|
7
|
+
|
|
8
|
+
```typescript
|
|
9
|
+
type RatingScaleQuestion = QuestionBase & {
|
|
10
|
+
type: 'rating_scale';
|
|
11
|
+
options: SurveyOption[]; // Typically 1-5 or 1-10 (no NPS colors)
|
|
12
|
+
minLabel?: string;
|
|
13
|
+
midLabel?: string;
|
|
14
|
+
maxLabel?: string;
|
|
15
|
+
midLabelIndex?: number;
|
|
16
|
+
reverseScaleOrder?: boolean;
|
|
17
|
+
scaleStyle?: 'star' | 'emoji';
|
|
18
|
+
};
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Answer Shape: `number`
|
|
22
|
+
|
|
23
|
+
## Key Difference from `'rating'`
|
|
24
|
+
- No NPS traffic-light colors on options
|
|
25
|
+
- Rendered as stars, emojis, or plain icons — not numbered badges
|
|
26
|
+
- Use `scaleStyle` to determine icon type
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Slider Question (Single Range)
|
|
2
|
+
|
|
3
|
+
> **Type**: `'slider'` | **API Source**: `SCALE` with `INPUT_TYPE=SLIDER`
|
|
4
|
+
> **Mapper**: `ratingMapper.ts` (slider branch) | **Component**: `CustomSliderTrack` or native `<input type="range">`
|
|
5
|
+
|
|
6
|
+
## TypeScript Type
|
|
7
|
+
|
|
8
|
+
```typescript
|
|
9
|
+
type SliderQuestion = QuestionBase & {
|
|
10
|
+
type: 'slider';
|
|
11
|
+
min: number; // Left end value
|
|
12
|
+
max: number; // Right end value
|
|
13
|
+
step: number; // Increment between positions
|
|
14
|
+
defaultValue?: number; // Initial handle position
|
|
15
|
+
minLabel?: string; // Left label
|
|
16
|
+
maxLabel?: string; // Right label
|
|
17
|
+
enableInputBox?: boolean; // Show numeric input alongside
|
|
18
|
+
displayValues?: boolean; // Show value tooltip on thumb
|
|
19
|
+
};
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Answer Shape: `number`
|
|
23
|
+
Example: `65`
|
|
24
|
+
|
|
25
|
+
## Rendering Guidance
|
|
26
|
+
- Use `<input type="range" min={min} max={max} step={step}>`
|
|
27
|
+
- Or use the `CustomSliderTrack` component for emoji-thumb variant
|
|
28
|
+
- Show labels at min/max ends
|
|
29
|
+
- If `enableInputBox` → show numeric `<input type="number">` alongside
|
|
30
|
+
- If `displayValues` → show floating tooltip showing current value
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Matrix CFM Question (Likert Grid)
|
|
2
|
+
|
|
3
|
+
> **Type**: `'matrix'` with `subType: 'CFM_MATRIX'` | **API Source**: `CFM_MATRIX`
|
|
4
|
+
> **Mapper**: `cfmMatrixMapper.ts` | **Component**: `LikertMatrixScale`
|
|
5
|
+
|
|
6
|
+
## TypeScript Type
|
|
7
|
+
|
|
8
|
+
```typescript
|
|
9
|
+
type MatrixQuestion = QuestionBase & {
|
|
10
|
+
type: 'matrix';
|
|
11
|
+
subType: 'CFM_MATRIX';
|
|
12
|
+
rows: MatrixRow[]; // Statements to rate
|
|
13
|
+
columns: MatrixColumn[]; // Shared answer options per row
|
|
14
|
+
buttonType?: SurveyButtonType;
|
|
15
|
+
hasNotApplicable?: boolean;
|
|
16
|
+
reverseScaleOrder?: boolean;
|
|
17
|
+
labels?: string[]; // Anchor labels above columns
|
|
18
|
+
matrixFormat?: 'standard' | 'carousel' | 'dropdown';
|
|
19
|
+
matrixType?: 'likert' | 'bipolar';
|
|
20
|
+
answerType?: 'single' | 'multiple';
|
|
21
|
+
transposeTable?: boolean;
|
|
22
|
+
repeatScale?: boolean;
|
|
23
|
+
enableScale?: boolean;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type MatrixRow = { id: string; text: string; rightText?: string; };
|
|
27
|
+
type MatrixColumn = { id: string; label: string; value: string | number | null; color?: string; };
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Answer Shape: `MatrixAnswerMap`
|
|
31
|
+
```typescript
|
|
32
|
+
{ "row1_id": "column_value", "row2_id": "column_value", "row3_id": null }
|
|
33
|
+
// null = N/A selected for that row
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Rendering Guidance
|
|
37
|
+
- **Standard format**: Grid table — rows on left, columns as headers
|
|
38
|
+
- **Carousel format**: Show one row at a time with prev/next
|
|
39
|
+
- **Dropdown format**: Each row has a dropdown selector for its column value
|
|
40
|
+
- Likert: unipolar text labels (e.g. "Strongly Disagree" → "Strongly Agree")
|
|
41
|
+
- Bipolar: `row.text` on left + `row.rightText` on right
|
|
42
|
+
- If `hasNotApplicable` → add N/A column with `value: null`
|
|
43
|
+
- `onSelect` receives the full `MatrixAnswerMap` object on each change
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Matrix CSAT Question (Satisfaction Grid)
|
|
2
|
+
|
|
3
|
+
> **Type**: `'matrix'` with `subType: 'CSAT_MATRIX'` | **API Source**: `CSAT_MATRIX`
|
|
4
|
+
> **Mapper**: `csatMatrixMapper.ts` | **Component**: `CsatMatrixScale`
|
|
5
|
+
|
|
6
|
+
## TypeScript Type
|
|
7
|
+
Same as `MatrixQuestion` (see `07-matrix-cfm.md`) but with `subType: 'CSAT_MATRIX'`.
|
|
8
|
+
|
|
9
|
+
## Key Difference from CFM_MATRIX
|
|
10
|
+
- Columns represent satisfaction levels (not text labels)
|
|
11
|
+
- Uses `buttonType` to determine visual: emoji faces, stars, numbered, etc.
|
|
12
|
+
- Uses `getEmojiForIndex(colIndex, totalCols)` for emoji rendering per cell
|
|
13
|
+
|
|
14
|
+
## Rendering by `buttonType`
|
|
15
|
+
|
|
16
|
+
| `buttonType` | Cell Rendering |
|
|
17
|
+
|-------------|---------------|
|
|
18
|
+
| `'emoji'` | Sentiment face icons via `getEmojiForIndex` |
|
|
19
|
+
| `'star'` | Star icons via `CsatStarIcons` |
|
|
20
|
+
| `'numbered'` | Numeric badge buttons |
|
|
21
|
+
| `'radio'` | Standard radio circles |
|
|
22
|
+
| `'dropdown'` | Dropdown per row (uses `MatrixDropdown`) |
|
|
23
|
+
| `'graphics'` | Emoji slider per row (uses `CustomSliderTrack`) |
|
|
24
|
+
|
|
25
|
+
## Answer Shape: `MatrixAnswerMap`
|
|
26
|
+
Same as CFM_MATRIX — `{ "row_id": column_value | null }`
|
|
27
|
+
|
|
28
|
+
## N/A Column
|
|
29
|
+
If `hasNotApplicable` is true, the last column has `value: null`. Selecting it records `null` for that row, which is a valid answer (passes validation).
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Rating Matrix Question
|
|
2
|
+
|
|
3
|
+
> **Type**: `'rating_matrix'` | **API Source**: `RATING_MATRIX`
|
|
4
|
+
> **Mapper**: `ratingMatrixMapper.ts` → routed to `mapMatrixQuestion` | **Component**: `CsatMatrixScale`
|
|
5
|
+
|
|
6
|
+
## TypeScript Type
|
|
7
|
+
|
|
8
|
+
```typescript
|
|
9
|
+
type RatingMatrixQuestion = QuestionBase & {
|
|
10
|
+
type: 'rating_matrix';
|
|
11
|
+
rows: MatrixRow[];
|
|
12
|
+
columns: MatrixColumn[]; // Rating columns with NPS-style colors
|
|
13
|
+
minLabel?: string;
|
|
14
|
+
maxLabel?: string;
|
|
15
|
+
buttonType?: SurveyButtonType;
|
|
16
|
+
hasNotApplicable?: boolean;
|
|
17
|
+
reverseScaleOrder?: boolean;
|
|
18
|
+
labels?: string[];
|
|
19
|
+
};
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Key Difference from CSAT_MATRIX
|
|
23
|
+
- Columns carry traffic-light `color` values (like NPS rating)
|
|
24
|
+
- Has `minLabel`/`maxLabel` endpoint labels
|
|
25
|
+
- Rendered by the same `CsatMatrixScale` component
|
|
26
|
+
|
|
27
|
+
## Answer Shape: `MatrixAnswerMap`
|
|
28
|
+
Same pattern: `{ "row_id": column_value | null }`
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Slider Matrix Question
|
|
2
|
+
|
|
3
|
+
> **Type**: `'slider_matrix'` | **API Source**: `SLIDER_MATRIX`
|
|
4
|
+
> **Mapper**: `sliderMatrixMapper.ts` | **Component**: `SliderMatrixScale`
|
|
5
|
+
|
|
6
|
+
## TypeScript Type
|
|
7
|
+
|
|
8
|
+
```typescript
|
|
9
|
+
type SliderMatrixQuestion = QuestionBase & {
|
|
10
|
+
type: 'slider_matrix';
|
|
11
|
+
rows: SliderMatrixRow[]; // Each row has independent slider config
|
|
12
|
+
labels?: string[]; // Anchor labels above track
|
|
13
|
+
ticks?: number; // Number of tick marks (default 10)
|
|
14
|
+
tickValues?: TickValue[]; // Pre-calculated tick values with colors
|
|
15
|
+
enableInputBox?: boolean;
|
|
16
|
+
displayValues?: boolean;
|
|
17
|
+
enableNotApplicable?: boolean;
|
|
18
|
+
sliderType?: 'graphics'; // Emoji thumb slider
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type SliderMatrixRow = MatrixRow & {
|
|
22
|
+
min: number; max: number; step: number; defaultValue?: number;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type TickValue = { value: number; label: string; color: string; };
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Answer Shape: `MatrixAnswerMap`
|
|
29
|
+
```typescript
|
|
30
|
+
{ "row1_id": 75, "row2_id": 30, "row3_id": null }
|
|
31
|
+
// number = slider position, null = N/A
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Rendering Guidance
|
|
35
|
+
- Each row: row label + independent range slider
|
|
36
|
+
- If `sliderType === 'graphics'` → use `CustomSliderTrack` (emoji thumb)
|
|
37
|
+
- If `tickValues` available → render tick marks with labels and colors
|
|
38
|
+
- If `enableInputBox` → show numeric input alongside slider
|
|
39
|
+
- If `enableNotApplicable` → add N/A checkbox per row
|
|
40
|
+
- `onSelect` receives the full `MatrixAnswerMap` with all row values
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# File Upload Question
|
|
2
|
+
|
|
3
|
+
> **Type**: `'file_upload'` | **API Source**: `FILE_UPLOAD`
|
|
4
|
+
> **Mapper**: `fileUploadMapper.ts` | **Component**: `FileUploadScale`
|
|
5
|
+
|
|
6
|
+
## TypeScript Type
|
|
7
|
+
|
|
8
|
+
```typescript
|
|
9
|
+
type FileUploadQuestion = QuestionBase & {
|
|
10
|
+
type: 'file_upload';
|
|
11
|
+
uploadMessage?: string; // Instruction text (HTML)
|
|
12
|
+
supportedFileFormats?: string[]; // ['PDF', 'PNG', 'DOCX'] (uppercase, no dots)
|
|
13
|
+
maxFileCount?: number; // Max files allowed
|
|
14
|
+
fileSizeLimit?: number; // Max size in MB
|
|
15
|
+
fileSizeLimitType?: 'PER_FILE' | 'IN_TOTAL';
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type UploadedFile = {
|
|
19
|
+
id: string; name: string; mediaUrl: string;
|
|
20
|
+
mimeType?: string; size?: number;
|
|
21
|
+
};
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Answer Shape: `UploadedFile[]`
|
|
25
|
+
Array of uploaded file descriptors. Submitted via `quesIdVsAttachmentDetails` (NOT `questionToAnswers`).
|
|
26
|
+
|
|
27
|
+
## Rendering Guidance
|
|
28
|
+
- Drag-and-drop zone + click-to-upload button
|
|
29
|
+
- Show accepted formats from `supportedFileFormats`
|
|
30
|
+
- Set `<input type="file" accept=".pdf,.png,.docx">`
|
|
31
|
+
- Enforce `maxFileCount` in UI
|
|
32
|
+
- Show file list with name, size, and remove button
|
|
33
|
+
- Enforce `fileSizeLimit` with appropriate error messages
|
|
34
|
+
- `onSelect` receives the full `UploadedFile[]` array
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Text and Media Question (Display-Only)
|
|
2
|
+
|
|
3
|
+
> **Type**: `'text_and_media'` | **API Source**: `TEXT_AND_MEDIA`
|
|
4
|
+
> **Mapper**: `textAndMediaMapper.ts` | **Component**: Inline in `Question.tsx`
|
|
5
|
+
|
|
6
|
+
## TypeScript Type
|
|
7
|
+
|
|
8
|
+
```typescript
|
|
9
|
+
type TextAndMediaQuestion = QuestionBase & {
|
|
10
|
+
type: 'text_and_media';
|
|
11
|
+
mediaUrl?: string; // Image or video URL
|
|
12
|
+
mediaMimeType?: string; // 'image/*' or 'video/*'
|
|
13
|
+
mediaTitle?: string; // Caption/alt text
|
|
14
|
+
mediaAlignment?: string; // Layout position
|
|
15
|
+
mediaSize?: number; // Width percentage
|
|
16
|
+
};
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Answer Shape: NONE
|
|
20
|
+
This is **display-only** — no answer is collected.
|
|
21
|
+
|
|
22
|
+
## ⚠️ Critical Rules
|
|
23
|
+
- **DO NOT** dispatch a `CHANGE` action for this question type
|
|
24
|
+
- **Excluded** from validation (always passes)
|
|
25
|
+
- **Excluded** from progress percentage counting
|
|
26
|
+
- **Skipped** in `buildSubmitRequestBody()` — nothing is submitted
|
|
27
|
+
|
|
28
|
+
## Rendering Guidance
|
|
29
|
+
- Show `question.text` as the heading
|
|
30
|
+
- If `mediaUrl` exists:
|
|
31
|
+
- `mediaMimeType` starts with `'video/'` → `<video controls src={mediaUrl}>`
|
|
32
|
+
- Otherwise → `<img src={mediaUrl} alt={mediaTitle}>`
|
|
33
|
+
- Position media according to `mediaAlignment`
|
|
34
|
+
- Size media according to `mediaSize` percentage
|
|
35
|
+
- Show `mediaTitle` as caption below media
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Question Types Overview
|
|
2
|
+
|
|
3
|
+
> This folder contains one detailed doc per question type. Each doc covers: TypeScript type, mapper logic, answer shape, properties, and rendering guidance.
|
|
4
|
+
|
|
5
|
+
## The `SurveyQuestion` Discriminated Union
|
|
6
|
+
|
|
7
|
+
The SDK maps API questions into a **discriminated union** using the `type` field:
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
type SurveyQuestion =
|
|
11
|
+
| RatingQuestion // type: 'rating' — NPS 0-10 numeric scale
|
|
12
|
+
| RadioQuestion // type: 'radio' — MCQ single/multi select
|
|
13
|
+
| TextQuestion // type: 'text' — Free text input
|
|
14
|
+
| CsatQuestion // type: 'csat' — 1-5 satisfaction (emoji/star)
|
|
15
|
+
| RatingScaleQuestion // type: 'rating_scale' — Star/emoji rating (1-5/1-10)
|
|
16
|
+
| SliderQuestion // type: 'slider' — Single range slider
|
|
17
|
+
| MatrixQuestion // type: 'matrix' — Grid (CFM/CSAT/RATING subTypes)
|
|
18
|
+
| RatingMatrixQuestion // type: 'rating_matrix' — NPS-style rating grid
|
|
19
|
+
| SliderMatrixQuestion // type: 'slider_matrix' — Grid of range sliders
|
|
20
|
+
| FileUploadQuestion // type: 'file_upload' — File attachment
|
|
21
|
+
| TextAndMediaQuestion; // type: 'text_and_media' — Display-only media card
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Question → Component Routing Table
|
|
25
|
+
|
|
26
|
+
| `question.type` | `question.subType` | Client Component | Answer Shape |
|
|
27
|
+
|-----------------|-------------------|-----------------|-------------|
|
|
28
|
+
| `'rating'` | — | `RatingScale` | `number` |
|
|
29
|
+
| `'radio'` | — | Inline radio cards | `string \| number` |
|
|
30
|
+
| `'text'` | — | Inline textarea | `string` |
|
|
31
|
+
| `'csat'` | — | `CsatScale` | `number \| null` |
|
|
32
|
+
| `'rating_scale'` | — | `RatingScale` (star/emoji variant) | `number` |
|
|
33
|
+
| `'slider'` | — | `CustomSliderTrack` | `number` |
|
|
34
|
+
| `'matrix'` | `'CFM_MATRIX'` | `LikertMatrixScale` | `MatrixAnswerMap` |
|
|
35
|
+
| `'matrix'` | `'CSAT_MATRIX'` | `CsatMatrixScale` | `MatrixAnswerMap` |
|
|
36
|
+
| `'matrix'` | `'RATING_MATRIX'` | `CsatMatrixScale` | `MatrixAnswerMap` |
|
|
37
|
+
| `'rating_matrix'` | — | `CsatMatrixScale` | `MatrixAnswerMap` |
|
|
38
|
+
| `'slider_matrix'` | — | `SliderMatrixScale` | `MatrixAnswerMap` |
|
|
39
|
+
| `'file_upload'` | — | `FileUploadScale` | `UploadedFile[]` |
|
|
40
|
+
| `'text_and_media'` | — | Inline media card | (no answer) |
|
|
41
|
+
|
|
42
|
+
## Shared Base Properties
|
|
43
|
+
|
|
44
|
+
All question types extend `QuestionBase`:
|
|
45
|
+
|
|
46
|
+
| Property | Type | Description |
|
|
47
|
+
|----------|------|-------------|
|
|
48
|
+
| `id` | `string` | ⚠️ MUST be DOM element id |
|
|
49
|
+
| `text` | `string` | Question text (may contain HTML) |
|
|
50
|
+
| `description` | `string?` | Supplementary instruction (HTML) |
|
|
51
|
+
| `required` | `boolean?` | Mandatory answer flag |
|
|
52
|
+
| `requiredErrorMessage` | `string?` | Custom validation error |
|
|
53
|
+
| `questionNumber` | `number?` | 1-indexed ordinal |
|
|
54
|
+
| `containerMediaUrl` | `string?` | Image/video alongside question |
|
|
55
|
+
| `containerMediaMimeType` | `string?` | MIME type for container media |
|
|
56
|
+
| `containerMediaAlignment` | `string?` | Media layout position |
|
|
57
|
+
| `containerMediaSize` | `number?` | Media width percentage |
|
|
58
|
+
|
|
59
|
+
## Files in This Folder
|
|
60
|
+
|
|
61
|
+
| File | Question Type | API Source |
|
|
62
|
+
|------|-------------|-----------|
|
|
63
|
+
| `01-rating.md` | `RatingQuestion` | `SCALE` (NPS/TEN_POINT) |
|
|
64
|
+
| `02-radio.md` | `RadioQuestion` | `MCQ` |
|
|
65
|
+
| `03-text.md` | `TextQuestion` | `TEXTFIELD` |
|
|
66
|
+
| `04-csat.md` | `CsatQuestion` | `SCALE` (CSAT, ≤5 options) |
|
|
67
|
+
| `05-rating-scale.md` | `RatingScaleQuestion` | `SCALE` (STAR/EMOJI) |
|
|
68
|
+
| `06-slider.md` | `SliderQuestion` | `SCALE` (INPUT_TYPE=SLIDER) |
|
|
69
|
+
| `07-matrix-cfm.md` | `MatrixQuestion` CFM | `CFM_MATRIX` |
|
|
70
|
+
| `08-matrix-csat.md` | `MatrixQuestion` CSAT | `CSAT_MATRIX` |
|
|
71
|
+
| `09-matrix-rating.md` | `RatingMatrixQuestion` | `RATING_MATRIX` |
|
|
72
|
+
| `10-slider-matrix.md` | `SliderMatrixQuestion` | `SLIDER_MATRIX` |
|
|
73
|
+
| `11-file-upload.md` | `FileUploadQuestion` | `FILE_UPLOAD` |
|
|
74
|
+
| `12-text-and-media.md` | `TextAndMediaQuestion` | `TEXT_AND_MEDIA` |
|