@explorer02/cfm-survey-sdk 0.2.1 → 0.2.2
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 +14 -8
- 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 +45 -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 +49 -0
- package/templates/docs/00-integration/end-page-logic.md +64 -0
- package/templates/docs/00-integration/partial-save-and-recovery.md +74 -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 +242 -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 +12 -3
- package/templates/docs/01-components/07-slider-matrix-scale.md +11 -2
- package/templates/docs/01-components/08-file-upload-scale.md +10 -1
- 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 +3 -1
- 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 +100 -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 +13 -2
- 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 +86 -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 +12 -54
- package/templates/docs/03-ui-specs/02-radio.md +21 -33
- package/templates/docs/03-ui-specs/03-text.md +2 -2
- package/templates/docs/03-ui-specs/04-csat.md +38 -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 +24 -21
- package/templates/docs/03-ui-specs/08-matrix-csat-rating.md +17 -45
- package/templates/docs/03-ui-specs/09-slider-matrix.md +1 -1
- package/templates/docs/03-ui-specs/10-file-upload.md +1 -1
- package/templates/docs/03-ui-specs/11-text-and-media.md +29 -29
- package/templates/docs/03-ui-specs/12-survey-chrome.md +20 -14
- package/templates/docs/03-ui-specs/13-heatmap.md +73 -0
- package/templates/docs/03-ui-specs/14-rank-order.md +59 -0
- package/templates/docs/03-ui-specs/README.md +13 -13
- package/templates/docs/03-ui-specs/shared/custom-slider-track.md +2 -2
- package/templates/docs/03-ui-specs/shared/matrix-dropdown.md +5 -0
- package/templates/docs/MANIFEST.json +451 -69
- package/templates/docs/index.md +80 -29
- package/templates/docs/templates/Question.tsx +221 -154
- 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 +108 -69
- package/templates/docs/templates/survey-inventory.schema.json +72 -0
- package/templates/docs/templates/verify-agent-build.sh +53 -0
- package/templates/docs/01-components/04-csat-scale.md +0 -40
- package/templates/docs/02-question-types/01-rating.md +0 -44
|
@@ -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
|
|
|
@@ -44,3 +44,12 @@ To match enterprise standards, implement the following UI strictly:
|
|
|
44
44
|
- Every input MUST have `title={col.label}` so the user sees the semantic meaning when hovering the radio button.
|
|
45
45
|
4. **Header Typography**:
|
|
46
46
|
- `th` tags should be `text-xs font-semibold text-gray-500 uppercase tracking-wider p-4 border-b`.
|
|
47
|
+
|
|
48
|
+
## SDK Integration Checklist (agent)
|
|
49
|
+
|
|
50
|
+
- [ ] Type literal: `QUESTION_TYPE.CFM_MATRIX`
|
|
51
|
+
- [ ] Answer stored as: `MatrixRowAnswers` via `matrixColumnStoredValue`
|
|
52
|
+
- [ ] onAction CHANGE: `{ questionId, answerValue: MatrixRowAnswers }`
|
|
53
|
+
- [ ] Visibility helper: `getVisibleMatrixItemsForAnswers(..., customFieldValues?)`
|
|
54
|
+
- [ ] Handle `gridLayout`: standard / carousel / dropdown
|
|
55
|
+
- [ ] 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)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> **UI spec (authoritative):** `03-ui-specs/10-file-upload.md`
|
|
4
4
|
> **Reference implementation:** `apps/client/src/components/FileUploadScale.tsx`
|
|
5
5
|
> **Target Component**: `FileUploadScale`
|
|
6
|
-
> **Handles**: `type: '
|
|
6
|
+
> **Handles**: `type: 'FILE_UPLOAD'`
|
|
7
7
|
|
|
8
8
|
## Core Responsibility
|
|
9
9
|
Render a robust drag-and-drop zone and enforce complex file size, type limits, and maximum file count constraints BEFORE updating the state.
|
|
@@ -56,3 +56,12 @@ Before calling `onSelect`, your component MUST validate against these properties
|
|
|
56
56
|
|
|
57
57
|
## Hidden Input
|
|
58
58
|
- Include a hidden `<input type="file" multiple={question.maxFileCount > 1} accept={...} />` that is triggered via a `useRef` when the dropzone is clicked.
|
|
59
|
+
|
|
60
|
+
## SDK Integration Checklist (agent)
|
|
61
|
+
|
|
62
|
+
- [ ] Type literal: `QUESTION_TYPE.FILE_UPLOAD`
|
|
63
|
+
- [ ] Answer stored as: `UploadedFile[]`
|
|
64
|
+
- [ ] onAction CHANGE: `{ questionId, answerValue: UploadedFile[] }` after upload
|
|
65
|
+
- [ ] Visibility helper: N/A
|
|
66
|
+
- [ ] Enforce `maxFileCount` and `fileSizeLimit` client-side
|
|
67
|
+
- [ ] 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.
|
|
@@ -6,7 +6,9 @@
|
|
|
6
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 `
|
|
9
|
+
Renders a dropdown selector for a single row in a matrix question. Used when `gridLayout === 'dropdown'` or `displayStyle === 'dropdown'`.
|
|
10
|
+
|
|
11
|
+
Accepts `ScaleColumn[]`; emits stored column ids via `matrixColumnStoredValue(col)` — see `02-reference/value-derivation.md`.
|
|
10
12
|
|
|
11
13
|
## Props Interface
|
|
12
14
|
```typescript
|
|
@@ -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
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Paused Page
|
|
2
|
+
|
|
3
|
+
> **UI spec:** `03-ui-specs/12-survey-chrome.md`
|
|
4
|
+
> **Reference:** `apps/client/src/components/PausedPage.tsx`
|
|
5
|
+
|
|
6
|
+
## When to render
|
|
7
|
+
|
|
8
|
+
`state.phase.kind === 'paused'` when survey `phaseFlags.paused` is true.
|
|
9
|
+
|
|
10
|
+
## Props
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
type PausedPageProps = {
|
|
14
|
+
page: SpecialPage;
|
|
15
|
+
};
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Wiring
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
<PausedPage page={state.specialPage} />
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Content
|
|
25
|
+
|
|
26
|
+
- Read-only message from `page.header` / `page.description`
|
|
27
|
+
- No start or navigation actions
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Heatmap Architectural Blueprint
|
|
2
|
+
|
|
3
|
+
> **UI spec (authoritative):** `03-ui-specs/13-heatmap.md`
|
|
4
|
+
> **Reference implementation:** `apps/client/src/components/HeatmapScale.tsx`
|
|
5
|
+
> **Coordinate helpers:** `apps/client/src/lib/surveyUi/heatmapCoords.ts`
|
|
6
|
+
> **Target Component**: `HeatmapScale`
|
|
7
|
+
> **Handles**: `type: 'HEATMAP'`
|
|
8
|
+
|
|
9
|
+
## Core Responsibility
|
|
10
|
+
|
|
11
|
+
Render the heatmap image, capture click spots in normalized 0–1 coordinates, toggle spots on repeat click, enforce `maxClicksAllowed` client-side, and bubble answers via `onSelect`.
|
|
12
|
+
|
|
13
|
+
## Props Contract
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
type HeatmapScaleProps = {
|
|
17
|
+
question: HeatmapQuestion;
|
|
18
|
+
selectedValue?: HeatmapAnswer;
|
|
19
|
+
onSelect: (answer: HeatmapAnswer | undefined) => void;
|
|
20
|
+
};
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## State Management
|
|
24
|
+
|
|
25
|
+
- `selectedValue` from SDK is source of truth — do not duplicate answer state
|
|
26
|
+
- Local UI state: `isImageReady` only
|
|
27
|
+
|
|
28
|
+
## Click Interaction
|
|
29
|
+
|
|
30
|
+
1. User clicks image → compute normalized `{ x, y }` from `getBoundingClientRect()`
|
|
31
|
+
2. If click within 14px of existing spot → remove that spot
|
|
32
|
+
3. Else if under `maxClicksAllowed` → append `{ id: crypto.randomUUID(), x, y }`
|
|
33
|
+
4. Else → no-op
|
|
34
|
+
|
|
35
|
+
## Coordinate Helpers
|
|
36
|
+
|
|
37
|
+
Use `apps/client/src/lib/surveyUi/heatmapCoords.ts`:
|
|
38
|
+
|
|
39
|
+
- `getNormalizedClickFromImage(event, imageElement)`
|
|
40
|
+
- `findSpotNearClick(clickX, clickY, spots, imageRect)`
|
|
41
|
+
- `createNewSpot(x, y)`
|
|
42
|
+
|
|
43
|
+
## DOM Structure
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
<div className="relative inline-block max-w-full">
|
|
47
|
+
<img onClick={handleImageClick} ... />
|
|
48
|
+
<div className="absolute inset-0 pointer-events-none">
|
|
49
|
+
{spots.map(spot => <Spot />)}
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Wiring in Question.tsx
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
{question.type === QUESTION_TYPE.HEATMAP && (
|
|
58
|
+
<HeatmapScale
|
|
59
|
+
question={question as HeatmapQuestion}
|
|
60
|
+
selectedValue={Array.isArray(selectedValue) ? selectedValue : undefined}
|
|
61
|
+
onSelect={onSelect}
|
|
62
|
+
/>
|
|
63
|
+
)}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Question shell (title, description, validation) stays in `Question.tsx`.
|
|
67
|
+
|
|
68
|
+
## SDK Integration Checklist (agent)
|
|
69
|
+
|
|
70
|
+
- [ ] Type literal: `QUESTION_TYPE.HEATMAP`
|
|
71
|
+
- [ ] Answer stored as: `HeatmapClickPoint[]`
|
|
72
|
+
- [ ] onAction CHANGE: `{ questionId, answerValue: HeatmapClickPoint[] }`
|
|
73
|
+
- [ ] Copy coord helper from [`templates/heatmapCoords.ts`](../templates/heatmapCoords.ts)
|
|
74
|
+
- [ ] Respect `maxClicksAllowed`; normalize coords 0–1
|
|
75
|
+
- [ ] Matrix row: [`question-type-sdk-matrix.md#heatmap`](../00-integration/question-type-sdk-matrix.md#heatmap)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Rank Order Architectural Blueprint
|
|
2
|
+
|
|
3
|
+
> **UI spec (authoritative):** `03-ui-specs/14-rank-order.md`
|
|
4
|
+
> **Reference implementation:** `apps/client/src/components/RankOrderScale.tsx`
|
|
5
|
+
> **Target Component:** `RankOrderScale`
|
|
6
|
+
> **Handles:** `type: 'RANK_ORDER'`
|
|
7
|
+
|
|
8
|
+
## Core Responsibility
|
|
9
|
+
|
|
10
|
+
Render rankable options in either **dropdown** or **drag-and-drop** mode, support three option display variants (text only, image only, text and image), and bubble rank assignments via `onSelect`.
|
|
11
|
+
|
|
12
|
+
## Props Contract
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
type RankOrderScaleProps = {
|
|
16
|
+
question: RankOrderQuestion;
|
|
17
|
+
selectedValue?: RankOrderAnswers;
|
|
18
|
+
onSelect: (value: RankOrderAnswers) => void;
|
|
19
|
+
};
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## State Management
|
|
23
|
+
|
|
24
|
+
- `selectedValue` from SDK is source of truth — do not duplicate answer state locally
|
|
25
|
+
- Dropdown mode: use `assignRankWithoutDuplicates` from SDK when a rank changes
|
|
26
|
+
- Drag-and-drop mode: use `buildRankOrderFromOrderedOptionIds` after reorder
|
|
27
|
+
|
|
28
|
+
## Layout Orchestrator
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
export function RankOrderScale({ question, selectedValue, onSelect }: RankOrderScaleProps) {
|
|
32
|
+
if (question.interactionMode === 'dragAndDrop') {
|
|
33
|
+
return <DragDropRankLayout ... />;
|
|
34
|
+
}
|
|
35
|
+
return <DropdownRankLayout ... />;
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Dropdown Mode
|
|
40
|
+
|
|
41
|
+
- One row per visible option
|
|
42
|
+
- Left: rank `<select>` with `-` (unassigned) and `1..N`
|
|
43
|
+
- Selecting rank `R` clears `R` from any other option (SDK helper)
|
|
44
|
+
- Option content depends on `question.optionDisplay`
|
|
45
|
+
- **Image URLs:** prefer `option.imageUrl` over `option.previewImageUrl` — preview `*_p.*` URLs often return 403 on CloudFront
|
|
46
|
+
|
|
47
|
+
## Drag-and-Drop Mode
|
|
48
|
+
|
|
49
|
+
- Use `@dnd-kit/core` + `@dnd-kit/sortable` for smooth reorder
|
|
50
|
+
- List order maps to ranks: position 0 → rank 1
|
|
51
|
+
- Show rank badge on each row
|
|
52
|
+
- Provide Up/Down buttons for keyboard accessibility
|
|
53
|
+
|
|
54
|
+
## Wiring in Question.tsx
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import {
|
|
58
|
+
QUESTION_TYPE,
|
|
59
|
+
getVisibleRankOrderOptionsForAnswers,
|
|
60
|
+
} from '@explorer02/cfm-survey-sdk';
|
|
61
|
+
|
|
62
|
+
const rankOrderQuestion = useMemo(() => {
|
|
63
|
+
if (question.type !== QUESTION_TYPE.RANK_ORDER) return null;
|
|
64
|
+
return {
|
|
65
|
+
...question,
|
|
66
|
+
options: getVisibleRankOrderOptionsForAnswers(
|
|
67
|
+
question,
|
|
68
|
+
allAnswers,
|
|
69
|
+
allQuestions,
|
|
70
|
+
customFieldValues
|
|
71
|
+
),
|
|
72
|
+
};
|
|
73
|
+
}, [question, allAnswers, allQuestions, customFieldValues]);
|
|
74
|
+
|
|
75
|
+
{question.type === QUESTION_TYPE.RANK_ORDER && rankOrderQuestion && (
|
|
76
|
+
<RankOrderScale
|
|
77
|
+
question={rankOrderQuestion}
|
|
78
|
+
selectedValue={...}
|
|
79
|
+
onSelect={onSelect}
|
|
80
|
+
/>
|
|
81
|
+
)}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Dependencies
|
|
85
|
+
|
|
86
|
+
Add to client `package.json`:
|
|
87
|
+
|
|
88
|
+
- `@dnd-kit/core`
|
|
89
|
+
- `@dnd-kit/sortable`
|
|
90
|
+
- `@dnd-kit/utilities`
|
|
91
|
+
|
|
92
|
+
## SDK Integration Checklist (agent)
|
|
93
|
+
|
|
94
|
+
- [ ] Type literal: `QUESTION_TYPE.RANK_ORDER`
|
|
95
|
+
- [ ] Answer stored as: `RankOrderAnswers`
|
|
96
|
+
- [ ] onAction CHANGE: `{ questionId, answerValue: RankOrderAnswers }`
|
|
97
|
+
- [ ] Visibility helper: `getVisibleRankOrderOptionsForAnswers(..., customFieldValues?)`
|
|
98
|
+
- [ ] Rank utils: `assignRankWithoutDuplicates`, `buildRankOrderFromOrderedOptionIds`, `normalizeRankOrderAnswers`
|
|
99
|
+
- [ ] Prefer `option.imageUrl` over `previewImageUrl`
|
|
100
|
+
- [ ] Matrix row: [`question-type-sdk-matrix.md#rank_order`](../00-integration/question-type-sdk-matrix.md#rank_order)
|
|
@@ -1,50 +1,51 @@
|
|
|
1
1
|
# Client Components Overview
|
|
2
2
|
|
|
3
|
-
> Wiring blueprints for React components. **Authoritative UI details** are in [`03-ui-specs/`](../03-ui-specs/README.md)
|
|
3
|
+
> Wiring blueprints for React components. **Authoritative UI details** are in [`03-ui-specs/`](../03-ui-specs/README.md).
|
|
4
4
|
> **Required files gate:** [`component-checklist.md`](../00-integration/component-checklist.md)
|
|
5
5
|
> **Dispatcher template:** [`templates/Question.tsx`](../templates/Question.tsx)
|
|
6
|
-
>
|
|
6
|
+
> Read blueprints **per type** from `MANIFEST.json` after survey inventory — not all files are needed for every survey.
|
|
7
7
|
|
|
8
8
|
## Component Architecture
|
|
9
9
|
|
|
10
|
-
The UI is driven by
|
|
10
|
+
The UI is driven by `SurveyPage`, which routes by `state.phase` and renders `Question` for each item in `currentQuestions`.
|
|
11
11
|
|
|
12
12
|
```
|
|
13
|
-
SurveyPage (
|
|
13
|
+
SurveyPage (phase router)
|
|
14
14
|
│
|
|
15
|
-
├──
|
|
16
|
-
├── ProgressBar
|
|
17
|
-
├── LanguageSelector
|
|
18
|
-
├── Footer
|
|
15
|
+
├── IntroPage / PausedPage / EndPage (special phases)
|
|
19
16
|
│
|
|
20
|
-
└──
|
|
21
|
-
|
|
22
|
-
├──
|
|
23
|
-
├──
|
|
24
|
-
├──
|
|
25
|
-
├──
|
|
26
|
-
|
|
27
|
-
├── FileUploadScale (handles 'file_upload')
|
|
28
|
-
│
|
|
29
|
-
├── (Inline UI for 'radio', 'text', 'text_and_media')
|
|
30
|
-
│
|
|
31
|
-
└── Shared Sub-Components:
|
|
32
|
-
├── CustomSliderTrack (used by csat/matrix sliders)
|
|
33
|
-
└── MatrixDropdown (used by matrix dropdowns)
|
|
17
|
+
└── SurveyContent (phase.kind === 'content')
|
|
18
|
+
├── Header
|
|
19
|
+
├── ProgressBar
|
|
20
|
+
├── LanguageSelector
|
|
21
|
+
├── Question × N (dispatcher)
|
|
22
|
+
├── Back / Next|Submit nav
|
|
23
|
+
└── Footer
|
|
34
24
|
```
|
|
35
25
|
|
|
36
|
-
##
|
|
26
|
+
## Question dispatcher (11 types)
|
|
37
27
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
28
|
+
`Question` switches on `question.type` / `QUESTION_TYPE` (no `subType`):
|
|
29
|
+
|
|
30
|
+
| Type | Component |
|
|
31
|
+
|------|-----------|
|
|
32
|
+
| `NPS_SCALE` | `RatingScale` |
|
|
33
|
+
| `MCQ`, `TEXTFIELD`, `TEXT_AND_MEDIA` | Inline in `Question` |
|
|
34
|
+
| `CFM_MATRIX` | `LikertMatrixScale` |
|
|
35
|
+
| `CSAT_MATRIX`, `RATING_MATRIX` | `CsatMatrixScale` |
|
|
36
|
+
| `SLIDER_MATRIX` | `SliderMatrixScale` |
|
|
37
|
+
| `FILE_UPLOAD` | `FileUploadScale` |
|
|
38
|
+
| `HEATMAP` | `HeatmapScale` |
|
|
39
|
+
| `RANK_ORDER` | `RankOrderScale` |
|
|
42
40
|
|
|
43
|
-
|
|
41
|
+
Shared: `CustomSliderTrack`, `MatrixDropdown`
|
|
44
42
|
|
|
45
|
-
|
|
43
|
+
## Styling Approach
|
|
46
44
|
|
|
47
|
-
|
|
45
|
+
- Built entirely with **Tailwind CSS**.
|
|
46
|
+
- **No external UI libraries** (no Material UI, Chakra, etc.).
|
|
47
|
+
- Default brand colour: Telekom Magenta `#e20074`.
|
|
48
|
+
- Card-based layout, subtle borders, pink accents on selection.
|
|
48
49
|
|
|
49
50
|
## Files in This Folder
|
|
50
51
|
|
|
@@ -52,14 +53,19 @@ See `02-question.md` for details on the dispatcher logic.
|
|
|
52
53
|
|------|-----------|
|
|
53
54
|
| `01-survey-page.md` | `SurveyPage` orchestrator |
|
|
54
55
|
| `02-question.md` | `Question` dispatcher & inline types |
|
|
55
|
-
| `03-rating-scale.md` | `RatingScale` (NPS
|
|
56
|
-
| `04-
|
|
57
|
-
| `05-csat-matrix-scale.md` | `CsatMatrixScale`
|
|
58
|
-
| `06-likert-matrix-scale.md` | `LikertMatrixScale`
|
|
59
|
-
| `07-slider-matrix-scale.md` | `SliderMatrixScale`
|
|
56
|
+
| `03-rating-scale.md` | `RatingScale` (NPS) |
|
|
57
|
+
| `04-star-rating-scale.md` | Legacy star patterns (deprecated) |
|
|
58
|
+
| `05-csat-matrix-scale.md` | `CsatMatrixScale` |
|
|
59
|
+
| `06-likert-matrix-scale.md` | `LikertMatrixScale` |
|
|
60
|
+
| `07-slider-matrix-scale.md` | `SliderMatrixScale` |
|
|
60
61
|
| `08-file-upload-scale.md` | `FileUploadScale` |
|
|
61
|
-
| `09-custom-slider-track.md` | `CustomSliderTrack`
|
|
62
|
+
| `09-custom-slider-track.md` | `CustomSliderTrack` |
|
|
62
63
|
| `10-header-footer.md` | `Header` & `Footer` |
|
|
63
64
|
| `11-progress-bar.md` | `ProgressBar` |
|
|
64
65
|
| `12-language-selector.md` | `LanguageSelector` |
|
|
65
66
|
| `13-matrix-dropdown.md` | `MatrixDropdown` |
|
|
67
|
+
| `14-intro-page.md` | `IntroPage` |
|
|
68
|
+
| `15-end-page.md` | `EndPage` |
|
|
69
|
+
| `16-paused-page.md` | `PausedPage` |
|
|
70
|
+
| `17-heatmap-scale.md` | `HeatmapScale` |
|
|
71
|
+
| `18-rank-order-scale.md` | `RankOrderScale` |
|