@explorer02/cfm-survey-sdk 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +140 -33
- package/dist/cli/index.mjs +140 -33
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +8 -1
- package/postinstall.js +8 -5
- package/templates/AGENT.md +62 -17
- package/templates/deploy-wizard-to-s3.sh +52 -0
- package/templates/docs/00-integration/agent-execution-flow.md +281 -0
- package/templates/docs/00-integration/agent-operating-contract.md +260 -0
- package/templates/docs/00-integration/analytics-events-catalog.md +54 -0
- package/templates/docs/00-integration/apply-ui-config.md +365 -0
- package/templates/docs/00-integration/canonical-survey-fixtures.md +94 -0
- package/templates/docs/00-integration/client-integration-guide.md +10 -8
- package/templates/docs/00-integration/client-lib-folder.md +115 -0
- package/templates/docs/00-integration/component-checklist.md +90 -54
- package/templates/docs/00-integration/constraints.md +13 -5
- package/templates/docs/00-integration/custom-field-logic-and-navigation.md +56 -0
- package/templates/docs/00-integration/display-logic-and-navigation.md +16 -1
- package/templates/docs/00-integration/file-upload-aws.md +116 -0
- package/templates/docs/00-integration/logic-fields-catalog.md +105 -0
- package/templates/docs/00-integration/partial-save-and-recovery.md +24 -0
- package/templates/docs/00-integration/progress.md +4 -2
- package/templates/docs/00-integration/question-type-sdk-matrix.md +11 -9
- package/templates/docs/00-integration/setup.md +15 -3
- package/templates/docs/00-integration/skip-logic-and-navigation.md +1 -1
- package/templates/docs/00-integration/survey-lifecycle-analytics.md +1 -1
- package/templates/docs/00-integration/ui-customization-wizard.md +144 -0
- package/templates/docs/00-integration/useSurveySDK.md +35 -1
- package/templates/docs/00-integration/wizard-api.md +134 -0
- package/templates/docs/00-integration/wizard-config-handoff.md +246 -0
- package/templates/docs/00-integration/wizard-preview-build-guide.md +400 -0
- package/templates/docs/00-integration/wizard-question-type-styling.md +195 -0
- package/templates/docs/01-components/01-survey-page.md +18 -8
- package/templates/docs/01-components/02-question.md +2 -2
- package/templates/docs/01-components/03-rating-scale.md +12 -18
- package/templates/docs/01-components/05-csat-matrix-scale.md +18 -26
- package/templates/docs/01-components/06-likert-matrix-scale.md +19 -38
- package/templates/docs/01-components/07-slider-matrix-scale.md +48 -33
- package/templates/docs/01-components/08-file-upload-scale.md +15 -52
- package/templates/docs/01-components/09-custom-slider-track.md +15 -25
- package/templates/docs/01-components/10-header-footer.md +23 -4
- package/templates/docs/01-components/11-progress-bar.md +14 -6
- package/templates/docs/01-components/13-matrix-dropdown.md +28 -33
- package/templates/docs/01-components/17-heatmap-scale.md +4 -4
- package/templates/docs/01-components/18-rank-order-scale.md +24 -31
- package/templates/docs/01-components/19-survey-sticky-chrome.md +79 -0
- package/templates/docs/01-components/README.md +2 -2
- package/templates/docs/02-reference/question-types/10-slider-matrix.md +38 -26
- package/templates/docs/02-reference/question-types/11-file-upload.md +36 -21
- package/templates/docs/02-reference/question-types/README.md +11 -1
- package/templates/docs/02-reference/routing-table.md +1 -0
- package/templates/docs/02-reference/value-derivation.md +1 -1
- package/templates/docs/03-ui-specs/00-question-shell.md +10 -0
- package/templates/docs/03-ui-specs/01-rating.md +28 -2
- package/templates/docs/03-ui-specs/02-radio.md +94 -24
- package/templates/docs/03-ui-specs/03-text.md +12 -1
- package/templates/docs/03-ui-specs/04-csat.md +90 -26
- package/templates/docs/03-ui-specs/06-slider.md +3 -1
- package/templates/docs/03-ui-specs/07-matrix-cfm.md +74 -35
- package/templates/docs/03-ui-specs/08-matrix-csat-rating.md +50 -16
- package/templates/docs/03-ui-specs/09-slider-matrix.md +70 -26
- package/templates/docs/03-ui-specs/10-file-upload.md +52 -20
- package/templates/docs/03-ui-specs/11-text-and-media.md +4 -0
- package/templates/docs/03-ui-specs/12-survey-chrome.md +92 -5
- package/templates/docs/03-ui-specs/13-heatmap.md +21 -3
- package/templates/docs/03-ui-specs/14-rank-order.md +108 -28
- package/templates/docs/03-ui-specs/README.md +11 -7
- package/templates/docs/03-ui-specs/shared/custom-slider-track.md +28 -35
- package/templates/docs/03-ui-specs/shared/matrix-dropdown.md +1 -1
- package/templates/docs/MANIFEST.json +187 -6
- package/templates/docs/index.md +116 -116
- package/templates/docs/templates/CsatMatrixScale.tsx +637 -0
- package/templates/docs/templates/CustomSliderTrack.tsx +144 -0
- package/templates/docs/templates/FileUploadScale.tsx +262 -0
- package/templates/docs/templates/HeatmapScale.tsx +114 -0
- package/templates/docs/templates/LikertMatrixScale.tsx +414 -0
- package/templates/docs/templates/MatrixDropdown.tsx +216 -0
- package/templates/docs/templates/Question.tsx +3 -0
- package/templates/docs/templates/RankOrderScale.tsx +353 -0
- package/templates/docs/templates/RatingScale.tsx +129 -0
- package/templates/docs/templates/SliderMatrixScale.tsx +248 -0
- package/templates/docs/templates/SurveyStickyChrome.tsx +24 -0
- package/templates/docs/templates/customFieldValues.ts +36 -0
- package/templates/docs/templates/implementation_plan.md +75 -8
- package/templates/docs/templates/labelStyles.ts +33 -0
- package/templates/docs/templates/selectionStyles.ts +100 -0
- package/templates/docs/templates/survey-inventory.schema.json +28 -23
- package/templates/docs/templates/surveyUiIcons.tsx +63 -0
- package/templates/docs/templates/surveyUiScaleUtils.ts +51 -0
- package/templates/docs/templates/verify-agent-build.sh +151 -0
- package/templates/preview-harness/preview-bridge.inline.js +201 -0
- package/templates/preview-harness/previewPages.js +108 -0
- package/templates/preview-harness/previewPages.ts +298 -0
- package/templates/preview-harness/vite-app/src/PreviewConfigContext.tsx +67 -0
- package/templates/preview-harness/vite-app/src/QuestionPreview.tsx +122 -0
- package/templates/preview-harness/vite-app/src/SurveyPagePreview.tsx +75 -0
- package/templates/preview-harness/vite-app/src/fixtures/questions.ts +311 -0
- package/templates/preview-harness/vite-app/src/globals.css +16 -0
- package/templates/preview-harness/vite-app/src/mount.tsx +9 -0
- package/templates/preview-harness/vite-app/src/preview-live-overrides.css +101 -0
- package/templates/preview-harness/vite-app/stubs/next-dynamic.tsx +25 -0
- package/templates/preview-harness/vite-app/stubs/next-image.tsx +29 -0
- package/templates/previewBridge.ts +153 -0
- package/templates/survey-theme.css +295 -0
- package/templates/survey-ui-config.schema.json +213 -0
- package/templates/wizard-dist/assets/PreviewMock-DgHfrVeb.js +1 -0
- package/templates/wizard-dist/assets/TypePanel-CFVC3Ptn.js +1 -0
- package/templates/wizard-dist/assets/index-BhWM50Yu.css +1 -0
- package/templates/wizard-dist/assets/index-DYK3X1e5.js +34 -0
- package/templates/wizard-dist/assets/vendor-BwkXDkd3.js +17 -0
- package/templates/wizard-dist/index.html +20 -0
- package/templates/wizard.html +13 -1129
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# UI Spec: Survey Chrome
|
|
2
2
|
|
|
3
|
-
> **Reference:** `SurveyPage.tsx`, `Header.tsx`, `Footer.tsx`, `ProgressBar.tsx`, `LanguageSelector.tsx`, `IntroPage.tsx`, `EndPage.tsx`, `PausedPage.tsx`
|
|
4
|
-
> **Component blueprints:** `01-components/01-survey-page.md`, `10-header-footer.md`, `11-progress-bar.md`, `12-language-selector.md`, `14-intro-page.md`, `15-end-page.md`, `16-paused-page.md`
|
|
3
|
+
> **Reference:** `SurveyPage.tsx`, `SurveyStickyChrome.tsx`, `Header.tsx`, `Footer.tsx`, `ProgressBar.tsx`, `LanguageSelector.tsx`, `IntroPage.tsx`, `EndPage.tsx`, `PausedPage.tsx`
|
|
4
|
+
> **Component blueprints:** `01-components/01-survey-page.md`, `10-header-footer.md`, `11-progress-bar.md`, `19-survey-sticky-chrome.md`, `12-language-selector.md`, `14-intro-page.md`, `15-end-page.md`, `16-paused-page.md`
|
|
5
5
|
|
|
6
6
|
## SurveyPage — Phase Router
|
|
7
7
|
|
|
@@ -21,9 +21,8 @@
|
|
|
21
21
|
## Active Layout
|
|
22
22
|
|
|
23
23
|
```
|
|
24
|
-
Header
|
|
24
|
+
SurveyStickyChrome (sticky top-0 — Header + ProgressBar)
|
|
25
25
|
main (max-w-4xl)
|
|
26
|
-
ProgressBar(progressPercentage)
|
|
27
26
|
LanguageSelector (hidden if ≤1 language)
|
|
28
27
|
Questions (space-y-12)
|
|
29
28
|
Submit error (if any)
|
|
@@ -31,6 +30,25 @@ main (max-w-4xl)
|
|
|
31
30
|
Footer
|
|
32
31
|
```
|
|
33
32
|
|
|
33
|
+
### Sticky chrome (required on content phase)
|
|
34
|
+
|
|
35
|
+
Long pages push the last question below the fold. Wrap **Header + ProgressBar** in one sticky container so progress stays visible while answering.
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
<div className="sticky top-0 z-30 bg-white shadow-[0_2px_8px_rgba(0,0,0,0.06)]">
|
|
39
|
+
<Header embedded />
|
|
40
|
+
<div className="mx-auto w-full max-w-4xl px-4 pb-5 pt-4 sm:px-6 lg:px-8">
|
|
41
|
+
<ProgressBar progressPercentage={state.progressPercentage} />
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Spacing:** `pt-4` (16px) between logo block and progress track; `pb-5` (20px) below the track before scrollable content. Do not flush the bar against the logo square.
|
|
47
|
+
|
|
48
|
+
See [`01-components/19-survey-sticky-chrome.md`](../01-components/19-survey-sticky-chrome.md). Copy [`templates/SurveyStickyChrome.tsx`](../templates/SurveyStickyChrome.tsx).
|
|
49
|
+
|
|
50
|
+
Use standalone `Header` (no sticky wrapper) on intro, end, paused, loading, and terminal phases.
|
|
51
|
+
|
|
34
52
|
## Navigation Labels
|
|
35
53
|
|
|
36
54
|
German when `survey.language.startsWith('de')`:
|
|
@@ -51,10 +69,53 @@ German when `survey.language.startsWith('de')`:
|
|
|
51
69
|
|
|
52
70
|
Pink fill bar. Bound to `state.progressPercentage` (path-scoped — see `00-integration/progress.md`).
|
|
53
71
|
|
|
72
|
+
Render inside **`SurveyStickyChrome`** (sticky header + progress) on the content phase — not inside scrollable `<main>`. Clamp width to `0–100`.
|
|
73
|
+
|
|
74
|
+
| Token | Value | Role |
|
|
75
|
+
|-------|-------|------|
|
|
76
|
+
| Gap above track | `pt-4` (16px) | Space between logo block and progress bar |
|
|
77
|
+
| Gap below track | `pb-5` (20px) | Breathing room before question content |
|
|
78
|
+
| Track height | `h-3` (12px) | Full-width rounded pill |
|
|
79
|
+
|
|
54
80
|
## LanguageSelector
|
|
55
81
|
|
|
56
82
|
- Hidden when `languages.length <= 1`
|
|
57
83
|
- Focus ring: `#2563eb`
|
|
84
|
+
- Pass `language` in `useMemo` hook options — SDK remaps survey client-side (no refetch)
|
|
85
|
+
|
|
86
|
+
## Question numbering
|
|
87
|
+
|
|
88
|
+
Render ordinals from SDK on content pages:
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
{question.questionNumber != null && (
|
|
92
|
+
<span className="mr-1 font-semibold">{question.questionNumber}.</span>
|
|
93
|
+
)}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
See [`question-numbering.md`](../00-integration/question-numbering.md).
|
|
97
|
+
|
|
98
|
+
## Special pages
|
|
99
|
+
|
|
100
|
+
| Phase | Component | Action |
|
|
101
|
+
|-------|-----------|--------|
|
|
102
|
+
| `intro` | `IntroPage` | `page={state.specialPage}` · `onStart={() => onAction({ type: 'START' })}` |
|
|
103
|
+
| `paused` | `PausedPage` | `page={state.specialPage}` · read-only, no nav |
|
|
104
|
+
| `end` | `EndPage` | `page={state.specialPage}` · terminal thank-you |
|
|
105
|
+
|
|
106
|
+
## Terminal phases
|
|
107
|
+
|
|
108
|
+
Render a simple message (no question nav) for:
|
|
109
|
+
|
|
110
|
+
- `submitted` — already completed
|
|
111
|
+
- `expired` — survey expired
|
|
112
|
+
- `quota_fulfilled` — quota reached
|
|
113
|
+
|
|
114
|
+
Keep Header/Footer chrome consistent across all phases.
|
|
115
|
+
|
|
116
|
+
## Submit loading
|
|
117
|
+
|
|
118
|
+
Disable primary Next/Submit when `submitSurveyResults.isLoading`; label "Submitting…" / "Wird gesendet…".
|
|
58
119
|
|
|
59
120
|
## Agent Checklist
|
|
60
121
|
|
|
@@ -62,5 +123,31 @@ Pink fill bar. Bound to `state.progressPercentage` (path-scoped — see `00-inte
|
|
|
62
123
|
- [ ] `onAction({ type: 'START' })` on intro
|
|
63
124
|
- [ ] `onAction({ type: 'CHANGE' })` wired per question with `allAnswers` / `allQuestions`
|
|
64
125
|
- [ ] Back uses `state.canGoBack`; primary uses `state.primaryButtonType`
|
|
65
|
-
- [ ] ProgressBar receives `state.progressPercentage`
|
|
126
|
+
- [ ] ProgressBar receives `state.progressPercentage` inside sticky chrome (`SurveyStickyChrome`)
|
|
127
|
+
- [ ] Sticky `top-0 z-30` wrapper keeps progress visible on long pages
|
|
128
|
+
- [ ] `pt-4` / `pb-5` spacing between logo, progress track, and question content
|
|
66
129
|
- [ ] Header/Footer on every state branch
|
|
130
|
+
- [ ] `questionNumber` prefix when present on content questions
|
|
131
|
+
- [ ] Intro / Paused / End / terminal phase branches
|
|
132
|
+
- [ ] Submit button disabled while `submitSurveyResults.isLoading`
|
|
133
|
+
|
|
134
|
+
## Wizard customization (Phase 5 — required)
|
|
135
|
+
|
|
136
|
+
> Full chrome contract: [`wizard-preview-build-guide.md`](../00-integration/wizard-preview-build-guide.md).
|
|
137
|
+
|
|
138
|
+
| Element | `data-cfm-*` / CSS vars |
|
|
139
|
+
|---------|-------------------------|
|
|
140
|
+
| Logo | `data-cfm-logo` or `data-cfm-logo-text` |
|
|
141
|
+
| Company | `data-cfm-company` |
|
|
142
|
+
| Survey title | `data-cfm-survey-title` |
|
|
143
|
+
| Thank you | `data-cfm-thank-you` |
|
|
144
|
+
| Next / Back | `data-cfm-btn-next`, `data-cfm-btn-back` |
|
|
145
|
+
| Footer | `data-cfm-copyright`, `data-cfm-footer-links` |
|
|
146
|
+
| Progress | `--cfm-progress-track`, `--cfm-progress-fill` |
|
|
147
|
+
| Header bg | `--cfm-header-bg`, `--cfm-header-text` |
|
|
148
|
+
|
|
149
|
+
### Wizard checklist
|
|
150
|
+
|
|
151
|
+
- [ ] All editable chrome strings use `data-cfm-*` attributes
|
|
152
|
+
- [ ] Buttons and progress use `var(--cfm-*)` — not hardcoded Telekom hex
|
|
153
|
+
- [ ] Logo: text/CSS **or** local `public/` image — never external URL (see `constraints.md` §4)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# UI Spec: Heatmap (`type: 'HEATMAP'`)
|
|
2
2
|
|
|
3
|
-
> **Implement from this spec
|
|
3
|
+
> **Implement from this spec.** Portable template: `docs/templates/HeatmapScale.tsx`
|
|
4
4
|
> **SDK type:** `HeatmapQuestion` — `fetchSurvey/types/heatmap.ts`
|
|
5
5
|
> **Component blueprint:** `01-components/17-heatmap-scale.md`
|
|
6
6
|
|
|
@@ -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
|
|
@@ -71,3 +78,14 @@ onSelect(HeatmapAnswer | undefined)
|
|
|
71
78
|
- [ ] Wait for `onLoad` before accepting clicks
|
|
72
79
|
- [ ] Percentage-positioned spots (survives resize)
|
|
73
80
|
- [ ] Do NOT render region overlays on respondent UI
|
|
81
|
+
|
|
82
|
+
## Wizard customization (Phase 5 — required)
|
|
83
|
+
|
|
84
|
+
| Control | CSS vars | Apply in |
|
|
85
|
+
|---------|----------|----------|
|
|
86
|
+
| Spot color | `--cfm-heatmap-spot-color` | Click markers |
|
|
87
|
+
| Spot border | `--cfm-heatmap-spot-border` | Marker outline |
|
|
88
|
+
|
|
89
|
+
### Wizard checklist
|
|
90
|
+
|
|
91
|
+
- [ ] Spot markers use `--cfm-heatmap-spot-*` vars
|
|
@@ -2,58 +2,138 @@
|
|
|
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
|
+
```
|
|
93
|
+
|
|
94
|
+
Wrap list in `<DndContext collisionDetection={closestCenter}>` + `<SortableContext strategy={verticalListSortingStrategy}>`.
|
|
24
95
|
|
|
25
|
-
|
|
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
|
-
|
|
114
|
+
- Border `#e5e5e5`, optional `#e20074` when ranked
|
|
115
|
+
- Rank badge: `#fdf2f8` bg, `#e20074` text
|
|
116
|
+
|
|
117
|
+
## Logic
|
|
118
|
+
|
|
119
|
+
RANK_ORDER operators (ranked as/above/below) — see [`logic-fields-catalog.md`](../00-integration/logic-fields-catalog.md).
|
|
120
|
+
|
|
121
|
+
## Agent checklist
|
|
44
122
|
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
-
|
|
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
|
|
48
129
|
|
|
49
|
-
##
|
|
130
|
+
## Wizard customization (Phase 5 — required)
|
|
50
131
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
-
|
|
132
|
+
| Control | CSS vars | Apply in |
|
|
133
|
+
|---------|----------|----------|
|
|
134
|
+
| Rank badge | `--cfm-rank-badge-bg`, `--cfm-rank-badge-color` | Drag handle / rank number |
|
|
135
|
+
| Option border | `--cfm-rank-option-border` | List item cards |
|
|
54
136
|
|
|
55
|
-
|
|
137
|
+
### Wizard checklist
|
|
56
138
|
|
|
57
|
-
-
|
|
58
|
-
- Submit option labels — submit rank numbers via SDK formatter only
|
|
59
|
-
- Hardcode logic operators in the client — all conditions are SDK-evaluated
|
|
139
|
+
- [ ] Rank badges and option borders use `--cfm-rank-*` vars
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# UI Specifications — Reference Implementation Guide
|
|
2
2
|
|
|
3
3
|
> **For npm installs:** implement from these specs + [`templates/Question.tsx`](../templates/Question.tsx).
|
|
4
|
-
> **
|
|
4
|
+
> **Shared lib utilities** (NPS colors, custom fields, heatmap coords) live in [`templates/`](../templates/) — see [`client-lib-folder.md`](../00-integration/client-lib-folder.md), not in this ui-specs folder.
|
|
5
5
|
> Read the spec for **each question type present** in the fetched survey before writing React code.
|
|
6
6
|
|
|
7
|
+
> **Wizard customization:** When the client may use the web wizard, hardcoded hex in § Design Tokens below are **defaults only**. Wire `var(--cfm-*)` per [`wizard-question-type-styling.md`](../00-integration/wizard-question-type-styling.md) — especially `--cfm-matrix-selected` (cell annulus) and `--cfm-input-focus-ring` (outer border + inner dot). Portable scale templates in `docs/templates/` are layout/logic baselines; extend with `selectionStyles.ts` + `labelStyles.ts` for live preview.
|
|
8
|
+
|
|
7
9
|
## How to Read a UI Spec
|
|
8
10
|
|
|
9
11
|
Each file in this folder follows the same structure:
|
|
@@ -38,17 +40,19 @@ Each file in this folder follows the same structure:
|
|
|
38
40
|
| Spec file | Reference component | `question.type` |
|
|
39
41
|
|-----------|---------------------|-----------------|
|
|
40
42
|
| [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` | `
|
|
43
|
+
| [01-rating.md](01-rating.md) | `RatingScale.tsx` | `NPS_SCALE` |
|
|
44
|
+
| [02-radio.md](02-radio.md) | `Question.tsx` inline | `MCQ` |
|
|
45
|
+
| [03-text.md](03-text.md) | `Question.tsx` inline | `TEXTFIELD` |
|
|
46
|
+
| [04-csat.md](04-csat.md) | `CsatMatrixScale.tsx` | `CSAT_MATRIX` |
|
|
45
47
|
| [05-rating-scale.md](05-rating-scale.md) | `RatingScale.tsx` | `star_rating` |
|
|
46
48
|
| [06-slider.md](06-slider.md) | `CustomSliderTrack.tsx` | `slider` |
|
|
47
49
|
| [07-matrix-cfm.md](07-matrix-cfm.md) | `LikertMatrixScale.tsx` | `cfm_matrix` |
|
|
48
50
|
| [08-matrix-csat-rating.md](08-matrix-csat-rating.md) | `CsatMatrixScale.tsx` | `rating_matrix` |
|
|
49
51
|
| [09-slider-matrix.md](09-slider-matrix.md) | `SliderMatrixScale.tsx` | `slider_matrix` |
|
|
50
52
|
| [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 | `
|
|
53
|
+
| [11-text-and-media.md](11-text-and-media.md) | `Question.tsx` inline | `TEXT_AND_MEDIA` |
|
|
54
|
+
| [13-heatmap.md](13-heatmap.md) | `HeatmapScale.tsx` | `HEATMAP` |
|
|
55
|
+
| [14-rank-order.md](14-rank-order.md) | `RankOrderScale.tsx` | `RANK_ORDER` |
|
|
52
56
|
| [12-survey-chrome.md](12-survey-chrome.md) | `SurveyPage.tsx` + chrome | Shell |
|
|
53
57
|
| [shared/custom-slider-track.md](shared/custom-slider-track.md) | `CustomSliderTrack.tsx` | Primitive |
|
|
54
58
|
| [shared/matrix-dropdown.md](shared/matrix-dropdown.md) | `MatrixDropdown.tsx` | Primitive |
|
|
@@ -73,7 +77,7 @@ Use accent colors from SDK options — never invent colors for NPS:
|
|
|
73
77
|
|
|
74
78
|
## When to Read
|
|
75
79
|
|
|
76
|
-
After Phase 3 in [`index.md`](../index.md), for each question in the fetched survey:
|
|
80
|
+
After Phase 3 in [`index.md`](../index.md) and [`agent-execution-flow.md`](../00-integration/agent-execution-flow.md), for each question in the fetched survey:
|
|
77
81
|
|
|
78
82
|
1. Read [`02-reference/question-types/`](../02-reference/question-types/) for data shape
|
|
79
83
|
2. Read matching spec from this folder (see [`MANIFEST.json`](../MANIFEST.json) `questionTypes.{type}.uiSpec`)
|
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
# UI Spec: Matrix Dropdown (shared primitive)
|
|
2
2
|
|
|
3
|
-
> **Implement from this spec
|
|
3
|
+
> **Implement from this spec.** Portable template: `docs/templates/MatrixDropdown.tsx`
|
|
4
4
|
> **Used by:** `LikertMatrixScale`, `CsatMatrixScale` vertical list
|
|
5
5
|
|
|
6
6
|
## Modes
|