@explorer02/cfm-survey-sdk 0.2.2 → 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/package.json +1 -1
- package/templates/AGENT.md +8 -4
- package/templates/docs/00-integration/analytics-events-catalog.md +54 -0
- package/templates/docs/00-integration/client-integration-guide.md +1 -1
- package/templates/docs/00-integration/component-checklist.md +10 -0
- package/templates/docs/00-integration/display-logic-and-navigation.md +16 -1
- package/templates/docs/00-integration/file-upload-aws.md +116 -0
- package/templates/docs/00-integration/logic-fields-catalog.md +105 -0
- package/templates/docs/00-integration/partial-save-and-recovery.md +24 -0
- package/templates/docs/00-integration/question-type-sdk-matrix.md +11 -9
- package/templates/docs/00-integration/setup.md +3 -3
- package/templates/docs/00-integration/skip-logic-and-navigation.md +1 -1
- package/templates/docs/00-integration/survey-lifecycle-analytics.md +1 -1
- package/templates/docs/01-components/06-likert-matrix-scale.md +2 -1
- package/templates/docs/01-components/08-file-upload-scale.md +15 -52
- package/templates/docs/01-components/13-matrix-dropdown.md +28 -33
- package/templates/docs/01-components/17-heatmap-scale.md +4 -4
- package/templates/docs/01-components/18-rank-order-scale.md +24 -31
- package/templates/docs/02-reference/question-types/11-file-upload.md +36 -21
- package/templates/docs/02-reference/question-types/README.md +11 -1
- package/templates/docs/03-ui-specs/01-rating.md +10 -2
- package/templates/docs/03-ui-specs/02-radio.md +80 -25
- package/templates/docs/03-ui-specs/04-csat.md +35 -5
- package/templates/docs/03-ui-specs/07-matrix-cfm.md +20 -4
- package/templates/docs/03-ui-specs/08-matrix-csat-rating.md +5 -1
- package/templates/docs/03-ui-specs/09-slider-matrix.md +17 -0
- package/templates/docs/03-ui-specs/10-file-upload.md +41 -20
- package/templates/docs/03-ui-specs/12-survey-chrome.md +38 -0
- package/templates/docs/03-ui-specs/13-heatmap.md +9 -2
- package/templates/docs/03-ui-specs/14-rank-order.md +98 -29
- package/templates/docs/03-ui-specs/README.md +7 -5
- package/templates/docs/03-ui-specs/shared/custom-slider-track.md +28 -35
- package/templates/docs/MANIFEST.json +28 -3
- package/templates/docs/index.md +5 -4
- package/templates/docs/templates/CustomSliderTrack.tsx +144 -0
- package/templates/docs/templates/MatrixDropdown.tsx +216 -0
- package/templates/docs/templates/RankOrderScale.tsx +353 -0
- package/templates/docs/templates/implementation_plan.md +20 -0
- package/templates/docs/templates/survey-inventory.schema.json +28 -23
- package/templates/docs/templates/surveyUiIcons.tsx +52 -0
- package/templates/docs/templates/verify-agent-build.sh +32 -0
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
# UI Spec: File Upload (`type: 'FILE_UPLOAD'`)
|
|
2
2
|
|
|
3
|
-
> **Implement from this spec** (npm installs). Monorepo reference: `apps/client/src/components/FileUploadScale.tsx`
|
|
4
3
|
> **SDK type:** `FileUploadQuestion` — `fetchSurvey/types/fileUpload.ts`
|
|
5
4
|
> **Component blueprint:** `01-components/08-file-upload-scale.md`
|
|
5
|
+
> **Upload API:** [`00-integration/file-upload-aws.md`](../00-integration/file-upload-aws.md)
|
|
6
6
|
|
|
7
7
|
## Config Inventory
|
|
8
8
|
|
|
9
|
-
| Field |
|
|
10
|
-
|
|
11
|
-
| `uploadMessage` |
|
|
12
|
-
| `supportedFileFormats` |
|
|
13
|
-
| `maxFileCount` |
|
|
14
|
-
| `fileSizeLimit` |
|
|
15
|
-
| `fileSizeLimitType` |
|
|
9
|
+
| Field | UI impact |
|
|
10
|
+
|-------|-----------|
|
|
11
|
+
| `uploadMessage` | HTML in dropzone (bold 16px) |
|
|
12
|
+
| `supportedFileFormats` | Validation + accept attribute |
|
|
13
|
+
| `maxFileCount` | Total file limit |
|
|
14
|
+
| `fileSizeLimit` | MB limit |
|
|
15
|
+
| `fileSizeLimitType` | `PER_FILE` or `IN_TOTAL` |
|
|
16
16
|
|
|
17
17
|
## Layout Anatomy
|
|
18
18
|
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
[uploadMessage HTML in dashed dropzone]
|
|
21
21
|
File limit: N; File size limit: XMB/file|in total; Supports: ...
|
|
22
22
|
[local error banner — red if validation fails]
|
|
23
|
-
[file list cards: icon, name, size, remove ×]
|
|
23
|
+
[file list cards: icon, name, size, uploading…, remove ×]
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
## Interactive States
|
|
@@ -31,24 +31,45 @@
|
|
|
31
31
|
| Hover | `#e20074` | `#fdf2f8` |
|
|
32
32
|
| Drag active | `#e20074` | `#fdf2f8` |
|
|
33
33
|
|
|
34
|
-
##
|
|
34
|
+
## Implementation Recipe
|
|
35
35
|
|
|
36
|
-
1
|
|
37
|
-
2. Unsupported extension
|
|
38
|
-
3. PER_FILE size exceeded
|
|
39
|
-
4. IN_TOTAL cumulative size exceeded
|
|
36
|
+
### Phase 1 — Local pick + validate
|
|
40
37
|
|
|
41
|
-
|
|
38
|
+
1. Hidden `<input type="file" multiple={maxFileCount > 1}>`
|
|
39
|
+
2. Drag enter/over/leave/drop on dropzone
|
|
40
|
+
3. Reject: wrong extension, count exceeded, size exceeded
|
|
41
|
+
|
|
42
|
+
### Phase 2 — Upload (required for submit)
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
User picks File
|
|
46
|
+
→ POST {uploadApi}/get-upload-url { fileName, contentType, questionId }
|
|
47
|
+
← { uploadUrl, fileId, mediaUrl }
|
|
48
|
+
→ PUT uploadUrl (binary body, file Content-Type)
|
|
49
|
+
→ onSelect([...existing, { id: fileId, mediaUrl, name, mimeType, size }])
|
|
50
|
+
→ onAction(CHANGE)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
See [`file-upload-aws.md`](../00-integration/file-upload-aws.md).
|
|
54
|
+
|
|
55
|
+
### Phase 3 — Remove
|
|
56
|
+
|
|
57
|
+
Remove from list → `onSelect(undefined)` when empty, else updated `UploadedFileAnswer[]`.
|
|
42
58
|
|
|
43
59
|
## Answer Mutation
|
|
44
60
|
|
|
45
61
|
```typescript
|
|
46
|
-
|
|
62
|
+
// After upload completes — NOT raw File[]
|
|
63
|
+
onSelect: (value: UploadedFileAnswer[] | undefined) => void
|
|
64
|
+
onAction({ type: 'CHANGE', payload: { questionId, answerValue: UploadedFileAnswer[] } })
|
|
47
65
|
```
|
|
48
66
|
|
|
67
|
+
**Non-submittable:** storing `File[]` only — SDK submit drops attachments.
|
|
68
|
+
|
|
49
69
|
## Agent Checklist
|
|
50
70
|
|
|
51
|
-
- [ ]
|
|
52
|
-
- [ ]
|
|
53
|
-
- [ ]
|
|
54
|
-
- [ ]
|
|
71
|
+
- [ ] Local validation before upload
|
|
72
|
+
- [ ] Presigned upload wired; progress/spinner per file
|
|
73
|
+
- [ ] Final answer shape: `UploadedFileAnswer[]` with `id` + `mediaUrl`
|
|
74
|
+
- [ ] Remove clears answer or passes updated array
|
|
75
|
+
- [ ] Matrix row: [`question-type-sdk-matrix.md#file_upload`](../00-integration/question-type-sdk-matrix.md#file_upload)
|
|
@@ -55,6 +55,41 @@ Pink fill bar. Bound to `state.progressPercentage` (path-scoped — see `00-inte
|
|
|
55
55
|
|
|
56
56
|
- Hidden when `languages.length <= 1`
|
|
57
57
|
- Focus ring: `#2563eb`
|
|
58
|
+
- Pass `language` in `useMemo` hook options — SDK remaps survey client-side (no refetch)
|
|
59
|
+
|
|
60
|
+
## Question numbering
|
|
61
|
+
|
|
62
|
+
Render ordinals from SDK on content pages:
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
{question.questionNumber != null && (
|
|
66
|
+
<span className="mr-1 font-semibold">{question.questionNumber}.</span>
|
|
67
|
+
)}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
See [`question-numbering.md`](../00-integration/question-numbering.md).
|
|
71
|
+
|
|
72
|
+
## Special pages
|
|
73
|
+
|
|
74
|
+
| Phase | Component | Action |
|
|
75
|
+
|-------|-----------|--------|
|
|
76
|
+
| `intro` | `IntroPage` | `page={state.specialPage}` · `onStart={() => onAction({ type: 'START' })}` |
|
|
77
|
+
| `paused` | `PausedPage` | `page={state.specialPage}` · read-only, no nav |
|
|
78
|
+
| `end` | `EndPage` | `page={state.specialPage}` · terminal thank-you |
|
|
79
|
+
|
|
80
|
+
## Terminal phases
|
|
81
|
+
|
|
82
|
+
Render a simple message (no question nav) for:
|
|
83
|
+
|
|
84
|
+
- `submitted` — already completed
|
|
85
|
+
- `expired` — survey expired
|
|
86
|
+
- `quota_fulfilled` — quota reached
|
|
87
|
+
|
|
88
|
+
Keep Header/Footer chrome consistent across all phases.
|
|
89
|
+
|
|
90
|
+
## Submit loading
|
|
91
|
+
|
|
92
|
+
Disable primary Next/Submit when `submitSurveyResults.isLoading`; label "Submitting…" / "Wird gesendet…".
|
|
58
93
|
|
|
59
94
|
## Agent Checklist
|
|
60
95
|
|
|
@@ -64,3 +99,6 @@ Pink fill bar. Bound to `state.progressPercentage` (path-scoped — see `00-inte
|
|
|
64
99
|
- [ ] Back uses `state.canGoBack`; primary uses `state.primaryButtonType`
|
|
65
100
|
- [ ] ProgressBar receives `state.progressPercentage`
|
|
66
101
|
- [ ] Header/Footer on every state branch
|
|
102
|
+
- [ ] `questionNumber` prefix when present on content questions
|
|
103
|
+
- [ ] Intro / Paused / End / terminal phase branches
|
|
104
|
+
- [ ] Submit button disabled while `submitSurveyResults.isLoading`
|
|
@@ -58,11 +58,18 @@ Use singular "spot" when `maxClicksAllowed === 1`.
|
|
|
58
58
|
## Answer Mutation
|
|
59
59
|
|
|
60
60
|
```typescript
|
|
61
|
-
onSelect(
|
|
62
|
-
//
|
|
61
|
+
onSelect(HeatmapClickPoint[] | undefined)
|
|
62
|
+
// HeatmapClickPoint = { id: string; x: number; y: number }
|
|
63
63
|
// x, y normalized 0–1 relative to rendered image bounds
|
|
64
|
+
onAction({ type: 'CHANGE', payload: { questionId, answerValue: spots } })
|
|
64
65
|
```
|
|
65
66
|
|
|
67
|
+
Copy coord helpers from [`templates/heatmapCoords.ts`](../templates/heatmapCoords.ts): `getNormalizedClickFromImage`, `findSpotNearClick` (14px remove radius), `createNewSpot`.
|
|
68
|
+
|
|
69
|
+
## Logic fields
|
|
70
|
+
|
|
71
|
+
`HEATMAP` and `HEATMAP_REGION` — see [`logic-fields-catalog.md`](../00-integration/logic-fields-catalog.md).
|
|
72
|
+
|
|
66
73
|
## Agent Checklist
|
|
67
74
|
|
|
68
75
|
- [ ] Click handler on `<img>` element (not overlay) for precise coordinates
|
|
@@ -2,58 +2,127 @@
|
|
|
2
2
|
|
|
3
3
|
> **Reference:** `02-reference/question-types/13-rank-order.md`
|
|
4
4
|
> **Blueprint:** `01-components/18-rank-order-scale.md`
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
> **Portable template:** copy [`templates/RankOrderScale.tsx`](../templates/RankOrderScale.tsx)
|
|
6
|
+
|
|
7
|
+
## Config gate
|
|
8
|
+
|
|
9
|
+
| Field | Values | UI branch |
|
|
10
|
+
|-------|--------|-----------|
|
|
11
|
+
| `interactionMode` | `'dropdown'` | Rank `<select>` per row |
|
|
12
|
+
| `interactionMode` | `'dragAndDrop'` | `@dnd-kit` sortable list |
|
|
13
|
+
| `optionDisplay` | `textOnly` / `imageOnly` / `textAndImage` | Option content layout |
|
|
14
|
+
| `requireRankAll` | boolean | Validation (SDK) |
|
|
15
|
+
| `shuffleOptions` | boolean | **SDK shuffles at fetch** — render `question.options` as-is; do not re-shuffle client-side |
|
|
16
|
+
|
|
17
|
+
## Dependencies
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Next.js: template includes `'use client'`. Optional in `Question.tsx`:
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
const RankOrderScale = dynamic(() => import('./RankOrderScale').then(m => ({ default: m.RankOrderScale })), {
|
|
27
|
+
ssr: false,
|
|
28
|
+
loading: () => <p>Loading rank order…</p>,
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## SDK wiring
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import {
|
|
36
|
+
getVisibleRankOrderOptionsForAnswers,
|
|
37
|
+
normalizeRankOrderAnswers,
|
|
38
|
+
assignRankWithoutDuplicates,
|
|
39
|
+
buildRankOrderFromOrderedOptionIds,
|
|
40
|
+
getOrderedOptionIdsFromRanks,
|
|
41
|
+
} from '@explorer02/cfm-survey-sdk';
|
|
42
|
+
|
|
43
|
+
// Question.tsx — filter options + normalize answer
|
|
44
|
+
const rankOrderQuestion = {
|
|
45
|
+
...question,
|
|
46
|
+
options: getVisibleRankOrderOptionsForAnswers(question, allAnswers, allQuestions, customFieldValues),
|
|
47
|
+
};
|
|
48
|
+
<RankOrderScale
|
|
49
|
+
question={rankOrderQuestion}
|
|
50
|
+
selectedValue={normalizeRankOrderAnswers(selectedValue)}
|
|
51
|
+
onSelect={onSelect}
|
|
52
|
+
/>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Answer shape: `RankOrderAnswers` = `Record<optionId, rank>` (1-based).
|
|
56
|
+
|
|
57
|
+
## Interaction modes
|
|
7
58
|
|
|
8
59
|
### Dropdown (`interactionMode: 'dropdown'`)
|
|
9
60
|
|
|
10
61
|
| Element | Behavior |
|
|
11
62
|
|---------|----------|
|
|
12
63
|
| Rank select | `-` (unassigned) or `1..N` per option row |
|
|
13
|
-
| Duplicate ranks |
|
|
14
|
-
| Layout | Rank select
|
|
64
|
+
| Duplicate ranks | `assignRankWithoutDuplicates` clears rank R from other options |
|
|
65
|
+
| Layout | Rank select left, option content right |
|
|
15
66
|
|
|
16
|
-
### Drag and
|
|
67
|
+
### Drag and drop (`interactionMode: 'dragAndDrop'`)
|
|
17
68
|
|
|
18
69
|
| Element | Behavior |
|
|
19
70
|
|---------|----------|
|
|
20
|
-
| Drag handle | Six-dot grip
|
|
71
|
+
| Drag handle | Six-dot grip; `PointerSensor` + `KeyboardSensor` |
|
|
21
72
|
| Rank badge | Circular badge showing 1-based position |
|
|
22
|
-
| Reorder | List order
|
|
23
|
-
| Up/Down
|
|
73
|
+
| Reorder | List order → `buildRankOrderFromOrderedOptionIds(orderedIds)` |
|
|
74
|
+
| Up/Down | Keyboard fallback; disabled on first/last |
|
|
75
|
+
|
|
76
|
+
## Implementation Recipe — dnd-kit
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
const sensors = useSensors(
|
|
80
|
+
useSensor(PointerSensor),
|
|
81
|
+
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const handleDragEnd = (event: DragEndEvent) => {
|
|
85
|
+
const { active, over } = event;
|
|
86
|
+
if (!over || active.id === over.id) return;
|
|
87
|
+
const oldIndex = orderedOptionIds.indexOf(String(active.id));
|
|
88
|
+
const newIndex = orderedOptionIds.indexOf(String(over.id));
|
|
89
|
+
if (oldIndex === -1 || newIndex === -1) return;
|
|
90
|
+
onSelect(buildRankOrderFromOrderedOptionIds(arrayMove(orderedOptionIds, oldIndex, newIndex)));
|
|
91
|
+
};
|
|
92
|
+
```
|
|
24
93
|
|
|
25
|
-
|
|
94
|
+
Wrap list in `<DndContext collisionDetection={closestCenter}>` + `<SortableContext strategy={verticalListSortingStrategy}>`.
|
|
95
|
+
|
|
96
|
+
Copy full implementation from [`templates/RankOrderScale.tsx`](../templates/RankOrderScale.tsx).
|
|
97
|
+
|
|
98
|
+
## Option display
|
|
26
99
|
|
|
27
100
|
| `optionDisplay` | Render |
|
|
28
101
|
|-----------------|--------|
|
|
29
|
-
| `textOnly` |
|
|
30
|
-
| `imageOnly` |
|
|
31
|
-
| `textAndImage` | Thumbnail +
|
|
32
|
-
|
|
33
|
-
Use `dangerouslySetInnerHTML` for option labels (HTML from builder).
|
|
102
|
+
| `textOnly` | Label via `dangerouslySetInnerHTML` |
|
|
103
|
+
| `imageOnly` | Square thumbnail; prefer `option.imageUrl` over `previewImageUrl` |
|
|
104
|
+
| `textAndImage` | Thumbnail + label side by side |
|
|
34
105
|
|
|
35
106
|
## Validation UX
|
|
36
107
|
|
|
37
|
-
- Required + `requireRankAll`: every visible option
|
|
108
|
+
- Required + `requireRankAll`: every visible option unique rank 1..N
|
|
38
109
|
- Required + not `requireRankAll`: at least one visible option ranked
|
|
39
|
-
-
|
|
110
|
+
- Show `validationError` from question shell
|
|
40
111
|
|
|
41
112
|
## Styling
|
|
42
113
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
- Border: `#e5e5e5` default, `#e20074` when actively ranked (optional highlight)
|
|
46
|
-
- Background hover: `hover:bg-gray-50/50`
|
|
47
|
-
- Rank badge: `#fdf2f8` background, `#e20074` text
|
|
114
|
+
- Border `#e5e5e5`, optional `#e20074` when ranked
|
|
115
|
+
- Rank badge: `#fdf2f8` bg, `#e20074` text
|
|
48
116
|
|
|
49
|
-
##
|
|
117
|
+
## Logic
|
|
50
118
|
|
|
51
|
-
|
|
52
|
-
- Drag handle: `aria-label="Drag to reorder {option label}"`
|
|
53
|
-
- Up/Down: disabled on first/last item
|
|
119
|
+
RANK_ORDER operators (ranked as/above/below) — see [`logic-fields-catalog.md`](../00-integration/logic-fields-catalog.md).
|
|
54
120
|
|
|
55
|
-
##
|
|
121
|
+
## Agent checklist
|
|
56
122
|
|
|
57
|
-
-
|
|
58
|
-
-
|
|
59
|
-
-
|
|
123
|
+
- [ ] Copied `templates/RankOrderScale.tsx` (or equivalent dnd-kit + dropdown)
|
|
124
|
+
- [ ] `@dnd-kit/*` in package.json when survey has RANK_ORDER
|
|
125
|
+
- [ ] `getVisibleRankOrderOptionsForAnswers` + `normalizeRankOrderAnswers`
|
|
126
|
+
- [ ] `option.imageUrl` before `previewImageUrl`
|
|
127
|
+
- [ ] No client-side re-shuffle when `shuffleOptions` is true
|
|
128
|
+
- [ ] No stub placeholder text
|
|
@@ -38,17 +38,19 @@ Each file in this folder follows the same structure:
|
|
|
38
38
|
| Spec file | Reference component | `question.type` |
|
|
39
39
|
|-----------|---------------------|-----------------|
|
|
40
40
|
| [00-question-shell.md](00-question-shell.md) | `Question.tsx` | All (wrapper) |
|
|
41
|
-
| [01-rating.md](01-rating.md) | `RatingScale.tsx` | `
|
|
42
|
-
| [02-radio.md](02-radio.md) | `Question.tsx` inline | `
|
|
43
|
-
| [03-text.md](03-text.md) | `Question.tsx` inline | `
|
|
44
|
-
| [04-csat.md](04-csat.md) | `CsatMatrixScale.tsx` | `
|
|
41
|
+
| [01-rating.md](01-rating.md) | `RatingScale.tsx` | `NPS_SCALE` |
|
|
42
|
+
| [02-radio.md](02-radio.md) | `Question.tsx` inline | `MCQ` |
|
|
43
|
+
| [03-text.md](03-text.md) | `Question.tsx` inline | `TEXTFIELD` |
|
|
44
|
+
| [04-csat.md](04-csat.md) | `CsatMatrixScale.tsx` | `CSAT_MATRIX` |
|
|
45
45
|
| [05-rating-scale.md](05-rating-scale.md) | `RatingScale.tsx` | `star_rating` |
|
|
46
46
|
| [06-slider.md](06-slider.md) | `CustomSliderTrack.tsx` | `slider` |
|
|
47
47
|
| [07-matrix-cfm.md](07-matrix-cfm.md) | `LikertMatrixScale.tsx` | `cfm_matrix` |
|
|
48
48
|
| [08-matrix-csat-rating.md](08-matrix-csat-rating.md) | `CsatMatrixScale.tsx` | `rating_matrix` |
|
|
49
49
|
| [09-slider-matrix.md](09-slider-matrix.md) | `SliderMatrixScale.tsx` | `slider_matrix` |
|
|
50
50
|
| [10-file-upload.md](10-file-upload.md) | `FileUploadScale.tsx` | `file_upload` |
|
|
51
|
-
| [11-text-and-media.md](11-text-and-media.md) | `Question.tsx` inline | `
|
|
51
|
+
| [11-text-and-media.md](11-text-and-media.md) | `Question.tsx` inline | `TEXT_AND_MEDIA` |
|
|
52
|
+
| [13-heatmap.md](13-heatmap.md) | `HeatmapScale.tsx` | `HEATMAP` |
|
|
53
|
+
| [14-rank-order.md](14-rank-order.md) | `RankOrderScale.tsx` | `RANK_ORDER` |
|
|
52
54
|
| [12-survey-chrome.md](12-survey-chrome.md) | `SurveyPage.tsx` + chrome | Shell |
|
|
53
55
|
| [shared/custom-slider-track.md](shared/custom-slider-track.md) | `CustomSliderTrack.tsx` | Primitive |
|
|
54
56
|
| [shared/matrix-dropdown.md](shared/matrix-dropdown.md) | `MatrixDropdown.tsx` | Primitive |
|
|
@@ -1,52 +1,45 @@
|
|
|
1
1
|
# UI Spec: Custom Slider Track (shared primitive)
|
|
2
2
|
|
|
3
|
-
> **
|
|
4
|
-
> **Used by:** `SliderMatrixScale
|
|
3
|
+
> **Portable template:** copy [`templates/CustomSliderTrack.tsx`](../templates/CustomSliderTrack.tsx) + [`templates/surveyUiIcons.tsx`](../templates/surveyUiIcons.tsx)
|
|
4
|
+
> **Used by:** `SliderMatrixScale`, `CsatMatrixScale` (graphics), matrix `displayStyle: graphics`
|
|
5
|
+
|
|
6
|
+
## Scale column labels (Day 19 — CRITICAL)
|
|
7
|
+
|
|
8
|
+
Matrix rows must pass **`tickLabels` from `scaleColumns[].label`** (filtered to visible columns):
|
|
9
|
+
|
|
10
|
+
```typescript
|
|
11
|
+
const tickLabels = visibleScaleColumns.map(col => col.label);
|
|
12
|
+
<CustomSliderTrack tickLabels={tickLabels} ticks={tickLabels.length} ... />
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Tooltip shows stripped HTML from `tickLabels[tickIndex]`; without labels, tooltips fall back to numeric value only.
|
|
5
16
|
|
|
6
17
|
## Layer Architecture
|
|
7
18
|
|
|
8
19
|
```
|
|
9
|
-
1. Gray inactive track (#e5e7eb, 4px
|
|
10
|
-
2. Magenta fill bar (
|
|
11
|
-
3. Tick marks
|
|
12
|
-
4. Invisible <input type="range"> overlay
|
|
13
|
-
5.
|
|
14
|
-
6. Tooltip
|
|
20
|
+
1. Gray inactive track (#e5e7eb, 4px)
|
|
21
|
+
2. Magenta fill bar (percentage%, #e20074 or #9ca3af if disabled)
|
|
22
|
+
3. Tick marks at column positions
|
|
23
|
+
4. Invisible <input type="range"> overlay
|
|
24
|
+
5. Thumb: circle OR emoji (sliderType === 'graphics')
|
|
25
|
+
6. Tooltip on hover/drag (#1f2937)
|
|
15
26
|
```
|
|
16
27
|
|
|
17
28
|
## Props
|
|
18
29
|
|
|
19
30
|
| Prop | Role |
|
|
20
31
|
|------|------|
|
|
21
|
-
| `min`, `max`, `htmlStep` | Range
|
|
32
|
+
| `min`, `max`, `htmlStep` | Range bounds |
|
|
22
33
|
| `value` | Current position |
|
|
23
|
-
| `disabled` | Gray theme, opacity 0.5
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
26
|
-
|
|
|
27
|
-
| `
|
|
28
|
-
| `reverseScaleOrder` | Flip emoji index calculation |
|
|
29
|
-
|
|
30
|
-
## Interactive States
|
|
31
|
-
|
|
32
|
-
| State | Thumb | Tooltip |
|
|
33
|
-
|-------|-------|---------|
|
|
34
|
-
| Default | White circle, 2.5px magenta border | Hidden |
|
|
35
|
-
| Hover/drag | Pink glow ring `rgba(226,0,116,0.2)` | Dark bubble with `Math.round(value)` |
|
|
36
|
-
| Disabled | Gray border, opacity 0.5 | Hidden |
|
|
37
|
-
| Graphics | Emoji at percentage position | Same tooltip |
|
|
38
|
-
|
|
39
|
-
## Emoji Index Formula
|
|
40
|
-
|
|
41
|
-
```typescript
|
|
42
|
-
const idx = Math.round(((value - min) / (max - min)) * ((ticks || 10) - 1));
|
|
43
|
-
const displayIdx = reverseScaleOrder ? (ticks || 10) - 1 - idx : idx;
|
|
44
|
-
// Map displayIdx to react-icons emoji — proportional mapping is client-implemented (not an SDK export)
|
|
45
|
-
```
|
|
34
|
+
| `disabled` | Gray theme, opacity 0.5 |
|
|
35
|
+
| `sliderType: 'graphics'` | Emoji thumb via `surveyUiIcons.getEmojiForIndex` |
|
|
36
|
+
| `ticks` | Discrete tick count |
|
|
37
|
+
| **`tickLabels`** | **Column labels for tooltip + graphics index** |
|
|
38
|
+
| `reverseScaleOrder` | Flip emoji index |
|
|
46
39
|
|
|
47
40
|
## Agent Checklist
|
|
48
41
|
|
|
49
|
-
- [ ]
|
|
42
|
+
- [ ] Copy portable template or match layer stack
|
|
43
|
+
- [ ] Always pass `tickLabels` from matrix `scaleColumns` when available
|
|
50
44
|
- [ ] Tooltip on hover AND drag
|
|
51
|
-
- [ ] Graphics
|
|
52
|
-
- [ ] Transition disabled while dragging
|
|
45
|
+
- [ ] Graphics emoji thumb with `reverseScaleOrder` support
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"docsSchemaVersion": "2.
|
|
2
|
+
"docsSchemaVersion": "2.4.0",
|
|
3
3
|
"sdkTypesVersion": "0.2.1",
|
|
4
4
|
"packageNames": {
|
|
5
5
|
"npm": "@explorer02/cfm-survey-sdk",
|
|
@@ -107,8 +107,27 @@
|
|
|
107
107
|
{
|
|
108
108
|
"path": "00-integration/survey-lifecycle-analytics.md",
|
|
109
109
|
"purpose": "Mark-as-started and question activity"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"path": "00-integration/logic-fields-catalog.md",
|
|
113
|
+
"purpose": "Logic field reference by question type for skip/display/answer/end"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"path": "00-integration/analytics-events-catalog.md",
|
|
117
|
+
"purpose": "Consolidated SDK analytics and client lifecycle events"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"path": "00-integration/file-upload-aws.md",
|
|
121
|
+
"purpose": "Presigned S3 upload flow for FILE_UPLOAD submit contract"
|
|
110
122
|
}
|
|
111
123
|
],
|
|
124
|
+
"templateScaleFiles": [
|
|
125
|
+
"templates/RankOrderScale.tsx",
|
|
126
|
+
"templates/MatrixDropdown.tsx",
|
|
127
|
+
"templates/CustomSliderTrack.tsx",
|
|
128
|
+
"templates/surveyUiIcons.tsx",
|
|
129
|
+
"templates/heatmapCoords.ts"
|
|
130
|
+
],
|
|
112
131
|
"deployTargets": {
|
|
113
132
|
"aws": {
|
|
114
133
|
"doc": "00-integration/aws-deploy.md",
|
|
@@ -216,7 +235,9 @@
|
|
|
216
235
|
"00-integration/partial-save-and-recovery.md",
|
|
217
236
|
"00-integration/survey-lifecycle-analytics.md",
|
|
218
237
|
"00-integration/end-page-logic.md",
|
|
219
|
-
"00-integration/progress.md"
|
|
238
|
+
"00-integration/progress.md",
|
|
239
|
+
"00-integration/logic-fields-catalog.md",
|
|
240
|
+
"00-integration/analytics-events-catalog.md"
|
|
220
241
|
]
|
|
221
242
|
},
|
|
222
243
|
{
|
|
@@ -225,6 +246,7 @@
|
|
|
225
246
|
"readAlways": [
|
|
226
247
|
"00-integration/component-checklist.md",
|
|
227
248
|
"00-integration/question-type-sdk-matrix.md",
|
|
249
|
+
"00-integration/logic-fields-catalog.md",
|
|
228
250
|
"03-ui-specs/00-question-shell.md",
|
|
229
251
|
"03-ui-specs/12-survey-chrome.md",
|
|
230
252
|
"01-components/01-survey-page.md",
|
|
@@ -271,7 +293,10 @@
|
|
|
271
293
|
"npmRunBuildPasses",
|
|
272
294
|
"nextStaticExportConfigured",
|
|
273
295
|
"outIndexHtmlExists",
|
|
274
|
-
"outFileCountUnder500"
|
|
296
|
+
"outFileCountUnder500",
|
|
297
|
+
"rankOrderScaleTemplateCopied",
|
|
298
|
+
"dndKitWhenRankOrder",
|
|
299
|
+
"fileUploadPresignedFlowWhenFileUpload"
|
|
275
300
|
]
|
|
276
301
|
},
|
|
277
302
|
{
|
package/templates/docs/index.md
CHANGED
|
@@ -71,7 +71,7 @@ Ask: *"Please provide your survey instance ID (JWT token)."*
|
|
|
71
71
|
|
|
72
72
|
### PHASE 3: SDK CONTRACT
|
|
73
73
|
|
|
74
|
-
Read: `00-integration/client-integration-guide.md` (start here), `00-integration/question-type-sdk-matrix.md`, `00-integration/useSurveySDK.md`, `02-reference/exports.md`, `00-integration/skip-logic-and-navigation.md`, `00-integration/display-logic-and-navigation.md`, `00-integration/question-display-variants.md`, `00-integration/question-numbering.md`, `00-integration/placeholders-and-tokens.md`, `00-integration/partial-save-and-recovery.md`, `00-integration/survey-lifecycle-analytics.md`, `00-integration/end-page-logic.md`, `00-integration/answer-logic-and-navigation.md`, `00-integration/custom-field-logic-and-navigation.md`, `00-integration/progress.md`, `02-reference/value-derivation.md`
|
|
74
|
+
Read: `00-integration/client-integration-guide.md` (start here), `00-integration/question-type-sdk-matrix.md`, `00-integration/useSurveySDK.md`, `02-reference/exports.md`, `00-integration/skip-logic-and-navigation.md`, `00-integration/display-logic-and-navigation.md`, `00-integration/question-display-variants.md`, `00-integration/question-numbering.md`, `00-integration/placeholders-and-tokens.md`, `00-integration/partial-save-and-recovery.md`, `00-integration/survey-lifecycle-analytics.md`, `00-integration/end-page-logic.md`, `00-integration/answer-logic-and-navigation.md`, `00-integration/custom-field-logic-and-navigation.md`, `00-integration/progress.md`, `02-reference/value-derivation.md`, `00-integration/logic-fields-catalog.md`, `00-integration/analytics-events-catalog.md`
|
|
75
75
|
|
|
76
76
|
Understand `useSurveySDK`, `onAction`, `state.answers`. Do **not** read internal mapper/source paths.
|
|
77
77
|
|
|
@@ -94,7 +94,7 @@ Optional: read `02-reference/routing-table.md` for type overview.
|
|
|
94
94
|
|
|
95
95
|
Read: `00-integration/constraints.md`, `00-integration/component-checklist.md`, `00-integration/question-type-sdk-matrix.md`
|
|
96
96
|
|
|
97
|
-
Create all required scale component files **before** wiring `Question.tsx`. For each question in the survey:
|
|
97
|
+
Create all required scale component files **before** wiring `Question.tsx`. **Copy portable templates** from `docs/templates/` when listed in `component-checklist.md` (`RankOrderScale.tsx`, `MatrixDropdown.tsx`, `CustomSliderTrack.tsx`, `surveyUiIcons.tsx`, `heatmapCoords.ts`). For each question in the survey:
|
|
98
98
|
|
|
99
99
|
1. Read the matching row in `question-type-sdk-matrix.md` (answer shape, visibility helper, performance)
|
|
100
100
|
2. Read `02-reference/question-types/` doc for data shape (MANIFEST `reference`)
|
|
@@ -166,8 +166,9 @@ export OUT_DIR="<absolute path to out/ or dist/>"
|
|
|
166
166
|
| When | Read |
|
|
167
167
|
|------|------|
|
|
168
168
|
| Always first | `index.md`, `00-integration/client-integration-guide.md`, `00-integration/question-type-sdk-matrix.md`, `00-integration/setup.md`, `00-integration/useSurveySDK.md`, `00-integration/constraints.md` |
|
|
169
|
-
| Lifecycle & placeholders | `
|
|
170
|
-
| Skip logic & progress | `
|
|
169
|
+
| Lifecycle & placeholders | `survey-lifecycle-analytics.md`, `placeholders-and-tokens.md`, `partial-save-and-recovery.md`, `analytics-events-catalog.md` |
|
|
170
|
+
| Skip logic & progress | `skip-logic-and-navigation.md`, `display-logic-and-navigation.md`, `question-display-variants.md`, `question-numbering.md`, `end-page-logic.md`, `answer-logic-and-navigation.md`, `custom-field-logic-and-navigation.md`, `progress.md`, `logic-fields-catalog.md` |
|
|
171
|
+
| File upload submit | `file-upload-aws.md`, `03-ui-specs/10-file-upload.md` |
|
|
171
172
|
| Before any React code | `component-checklist.md`, `templates/Question.tsx`, MANIFEST `readAlways` docs |
|
|
172
173
|
| After instanceId fetch | Per-type docs from MANIFEST `questionTypes` (blocking: `CFM_MATRIX`, `CSAT_MATRIX`, `RATING_MATRIX`, `SLIDER_MATRIX`, `FILE_UPLOAD`) |
|
|
173
174
|
| Config audit | `02-reference/config-field-index.md` |
|