@explorer02/cfm-survey-sdk 0.2.9 → 0.3.0
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 +41 -38
- package/dist/cli/index.mjs +39 -36
- package/package.json +1 -1
- package/templates/docs/00-integration/agent-execution-flow.md +6 -6
- package/templates/docs/00-integration/agent-operating-contract.md +1 -2
- package/templates/docs/00-integration/component-checklist.md +0 -1
- package/templates/docs/00-integration/ui-customization-wizard.md +38 -29
- package/templates/docs/00-integration/wizard-chrome-contract.md +1 -11
- package/templates/docs/00-integration/wizard-config-handoff.md +19 -11
- package/templates/docs/00-integration/wizard-post-customize-build.md +0 -6
- package/templates/docs/00-integration/wizard-question-type-styling.md +3 -3
- package/templates/docs/00-integration/wizard-troubleshooting.md +4 -20
- package/templates/docs/index.md +1 -1
- package/templates/docs/templates/CsatMatrixScale.tsx +59 -53
- package/templates/docs/templates/CustomSliderTrack.tsx +8 -17
- package/templates/docs/templates/FileUploadScale.tsx +4 -12
- package/templates/docs/templates/Header.tsx +48 -32
- package/templates/docs/templates/HeatmapScale.tsx +2 -5
- package/templates/docs/templates/LikertMatrixScale.tsx +45 -57
- package/templates/docs/templates/MatrixDropdown.tsx +11 -12
- package/templates/docs/templates/Question.tsx +24 -4
- package/templates/docs/templates/RankOrderScale.tsx +9 -42
- package/templates/docs/templates/RatingScale.tsx +0 -38
- package/templates/docs/templates/SliderMatrixScale.tsx +3 -4
- package/templates/docs/templates/verify-agent-build.sh +0 -17
- package/templates/preview-harness/preview-bridge.inline.js +17 -14
- package/templates/preview-harness/previewPages.js +23 -15
- package/templates/preview-harness/previewPages.ts +7 -5
- package/templates/preview-harness/vite-app/src/QuestionPreview.tsx +22 -14
- package/templates/preview-harness/vite-app/src/fixtures/questions.ts +13 -2
- package/templates/preview-harness/vite-app/src/preview-live-overrides.css +6 -11
- package/templates/previewBridge.ts +5 -5
- package/templates/survey-theme.css +17 -17
- package/templates/wizard-dist/assets/{PreviewMock-CysRws9G.js → PreviewMock-Bax7oRAL.js} +1 -1
- package/templates/wizard-dist/assets/{TypePanel-DWX1LL3q.js → TypePanel-D2t4FPSd.js} +1 -1
- package/templates/wizard-dist/assets/index-BhWM50Yu.css +1 -0
- package/templates/wizard-dist/assets/index-c5lka74l.js +34 -0
- package/templates/wizard-dist/index.html +2 -2
- package/templates/wizard-dist/assets/index-DIuc7AU2.js +0 -34
- package/templates/wizard-dist/assets/index-pCM8n0FT.css +0 -1
|
@@ -2,6 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
> Optional **run** after Phase 6 verify. **Wizard-ready build** is mandatory in Phase 5 — see [`agent-execution-flow.md`](agent-execution-flow.md).
|
|
4
4
|
|
|
5
|
+
### Port selection (strict)
|
|
6
|
+
|
|
7
|
+
The CLI calls `findPort(4200)` — tries **4200**, then **4201**, **4202**, … until a port is free.
|
|
8
|
+
|
|
9
|
+
**Agent rules:**
|
|
10
|
+
|
|
11
|
+
1. **Do not** hardcode `localhost:4200` if the CLI printed a different port.
|
|
12
|
+
2. **Share only** the URL the CLI prints under `🔗 Client wizard link — share this URL only:`.
|
|
13
|
+
3. **Do not** give the client a second link, a file path, or `out/previews/` paths.
|
|
14
|
+
|
|
15
|
+
Example CLI output:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
🔗 Client wizard link — share this URL only:
|
|
19
|
+
http://localhost:4201
|
|
20
|
+
Port 4201 (auto-selected if 4200 was in use)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Client quit without save
|
|
24
|
+
|
|
25
|
+
If the client clicks **Quit** (no Review & Build):
|
|
26
|
+
|
|
27
|
+
- CLI runs preview GC (`out/previews/` deleted)
|
|
28
|
+
- CLI exits without `survey-ui-config.json` update
|
|
29
|
+
- **Agent must NOT** re-run `npx cfm-sdk customize` or send another wizard link
|
|
30
|
+
- **Agent must** ask deploy confirmation (INPUT 4) using the **existing** build from Phase 6
|
|
31
|
+
|
|
32
|
+
See [`wizard-config-handoff.md`](wizard-config-handoff.md) § Quit without save.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
5
36
|
## Default: local wizard (`customize`)
|
|
6
37
|
|
|
7
38
|
When the client says **yes** to customization in agent chat, **always run:**
|
|
@@ -12,8 +43,7 @@ npx cfm-sdk customize
|
|
|
12
43
|
|
|
13
44
|
This is the **recommended default** because it:
|
|
14
45
|
|
|
15
|
-
-
|
|
16
|
-
- Prints a machine-parseable line: **`WIZARD_LINK=http://localhost:{port}`** — agent shares **only** this URL with the client (never hardcode `:4200`)
|
|
46
|
+
- Serves wizard + component previews from **one local server** (`localhost:4200`)
|
|
17
47
|
- Auto-exports all **11** question-type previews from agent components
|
|
18
48
|
- Writes `survey-ui-config.json` **directly** on save — no S3, no session polling
|
|
19
49
|
- Cleans up local `out/previews/` when client quits without saving (`POST /api/wizard-quit`) or when CLI exits (Ctrl+C)
|
|
@@ -21,15 +51,9 @@ This is the **recommended default** because it:
|
|
|
21
51
|
|
|
22
52
|
**Agent script after client says yes:**
|
|
23
53
|
|
|
24
|
-
> *"I'll
|
|
54
|
+
> *"I'll open the customization wizard in your browser. Complete all steps and click **Review & Build** when done — I'll apply your changes automatically."*
|
|
25
55
|
|
|
26
|
-
**
|
|
27
|
-
|
|
28
|
-
1. Run `npx cfm-sdk customize` and wait for `WIZARD_LINK=http://localhost:…` in CLI output
|
|
29
|
-
2. Share **only** that URL with the client — do not guess port 4200
|
|
30
|
-
3. Keep the terminal open until CLI exits
|
|
31
|
-
|
|
32
|
-
**On successful save:**
|
|
56
|
+
The CLI **blocks** until the client saves. Wait for:
|
|
33
57
|
|
|
34
58
|
```
|
|
35
59
|
✅ UI configuration saved to survey-ui-config.json
|
|
@@ -37,8 +61,6 @@ This is the **recommended default** because it:
|
|
|
37
61
|
|
|
38
62
|
Then apply per [`apply-ui-config.md`](apply-ui-config.md).
|
|
39
63
|
|
|
40
|
-
**If client quits without saving:** CLI exits 0 after GC (`out/previews/` removed). **Do not** re-run the wizard. Skip Phase 6c. Ask deploy confirmation (Phase 7 / INPUT 4) with the existing Phase 6 build.
|
|
41
|
-
|
|
42
64
|
### Who can use it
|
|
43
65
|
|
|
44
66
|
| User | Can use `customize`? |
|
|
@@ -46,7 +68,7 @@ Then apply per [`apply-ui-config.md`](apply-ui-config.md).
|
|
|
46
68
|
| Client on **same machine** as agent (Cursor chat on dev laptop) | **Yes — default** |
|
|
47
69
|
| Client on **another device** | **No** — use EC2 fallback below |
|
|
48
70
|
|
|
49
|
-
|
|
71
|
+
`localhost:4200` is only reachable on the machine running the CLI.
|
|
50
72
|
|
|
51
73
|
---
|
|
52
74
|
|
|
@@ -60,7 +82,7 @@ npx cfm-sdk customize-ui
|
|
|
60
82
|
```
|
|
61
83
|
|
|
62
84
|
- Uploads previews to EC2 disk (local-server model on remote host)
|
|
63
|
-
- Prints
|
|
85
|
+
- Prints EC2 wizard link — agent sends link to client
|
|
64
86
|
- CLI **polls** until client submits → writes `.final.json` + `.diff.md`
|
|
65
87
|
- Requires deploy-api on EC2 + `CFM_INSTANCE_ID`
|
|
66
88
|
|
|
@@ -87,7 +109,7 @@ After Phase 6 verify + dev server:
|
|
|
87
109
|
```mermaid
|
|
88
110
|
sequenceDiagram
|
|
89
111
|
participant Agent as Agent CLI
|
|
90
|
-
participant Srv as localhost
|
|
112
|
+
participant Srv as localhost:4200
|
|
91
113
|
participant Wizard as Wizard SPA
|
|
92
114
|
participant Prev as out/previews/
|
|
93
115
|
|
|
@@ -120,24 +142,11 @@ sequenceDiagram
|
|
|
120
142
|
|
|
121
143
|
---
|
|
122
144
|
|
|
123
|
-
## Port probe + link-only handoff (mandatory)
|
|
124
|
-
|
|
125
|
-
| Step | Agent action |
|
|
126
|
-
|------|----------------|
|
|
127
|
-
| 1 | Run `npx cfm-sdk customize` (or `customize-ui` for remote) |
|
|
128
|
-
| 2 | Read `WIZARD_LINK=…` from CLI stdout — **never** assume `localhost:4200` |
|
|
129
|
-
| 3 | Share **only** that URL with the client |
|
|
130
|
-
| 4 | Wait until CLI exits (save → exit 0, quit → exit 0 with GC message) |
|
|
131
|
-
|
|
132
|
-
Optional: set `CFM_WIZARD_OPEN_BROWSER=1` to auto-open the wizard in the agent's browser.
|
|
133
|
-
|
|
134
|
-
---
|
|
135
|
-
|
|
136
145
|
## Agent checklist
|
|
137
146
|
|
|
138
147
|
- [ ] Phase 5: wizard-preview artifacts wired
|
|
139
148
|
- [ ] Client opted in (INPUT 3)
|
|
140
|
-
- [ ]
|
|
149
|
+
- [ ] Ran **`npx cfm-sdk customize`** (default) and waited for CLI exit 0
|
|
141
150
|
- [ ] Read `survey-ui-config.json` (or `.diff.md` if used EC2 fallback)
|
|
142
151
|
- [ ] Phase 6c: applied per [`apply-ui-config.md`](apply-ui-config.md)
|
|
143
152
|
- [ ] Re-ran verify + build
|
|
@@ -12,6 +12,7 @@ Pipeline: [`wizard-preview-build-guide.md`](wizard-preview-build-guide.md) · [`
|
|
|
12
12
|
| Wizard control / symptom | Required fix |
|
|
13
13
|
|--------------------------|--------------|
|
|
14
14
|
| Logo not updating in Branding / Header preview | `data-cfm-logo` img + `data-cfm-logo-text` fallback in `Header.tsx` |
|
|
15
|
+
| Logo shows colored pad behind image | Logo `<img>` must use `background: transparent` — **never** `var(--cfm-primary)` on logo box |
|
|
15
16
|
| Company name not updating | `data-cfm-company` span in `Header.tsx` |
|
|
16
17
|
| Show header works but progress bar toggle does nothing | Progress bar needs `data-cfm-progress` + class `cfm-progress`; header needs class `cfm-header` only — **not** on sticky wrapper |
|
|
17
18
|
| Show footer toggle does nothing | `footer` + class `cfm-footer` |
|
|
@@ -25,21 +26,10 @@ Pipeline: [`wizard-preview-build-guide.md`](wizard-preview-build-guide.md) · [`
|
|
|
25
26
|
| Text placeholder color not updating | `data-cfm-textfield` + `--cfm-input-placeholder` in CSS / `::placeholder` rule |
|
|
26
27
|
| Matrix 2nd row toggle | Harness adds row via `previewMultiStatement` — `LikertMatrixScale` must render all `statementRows` from props |
|
|
27
28
|
| Blank page after deploy | **Never** reference `UI_CONFIG` without import — use `src/lib/uiConfig.ts` |
|
|
28
|
-
| Logo pink/colored box behind image | Logo `background: var(--cfm-header-logo-bg, transparent)` — never `var(--cfm-primary)` on logo img |
|
|
29
|
-
| Logo + company placement | Locked: logo left, company name after logo in `.cfm-header-brand` flex row — no grid placement templates |
|
|
30
|
-
| Logo size | `--cfm-header-logo-width/height` from wizard max-dimension slider; `object-contain` |
|
|
31
29
|
| Logo wrong on CloudFront | Never change `global.logo.fileName` manually — use wizard file in `public/` |
|
|
32
30
|
|
|
33
31
|
---
|
|
34
32
|
|
|
35
|
-
## Agent link handoff (strict)
|
|
36
|
-
|
|
37
|
-
1. Run `npx cfm-sdk customize` — read **`WIZARD_LINK=http://localhost:{port}`** from CLI (port may be 4201+)
|
|
38
|
-
2. Share **only** that URL with the client
|
|
39
|
-
3. On quit without save: CLI exits 0, GC `out/previews/` — **do not** re-run wizard; ask deploy (Phase 7)
|
|
40
|
-
|
|
41
|
-
---
|
|
42
|
-
|
|
43
33
|
## Copy order (agents — do this in Phase 5)
|
|
44
34
|
|
|
45
35
|
```bash
|
|
@@ -30,18 +30,16 @@ npx cfm-sdk customize
|
|
|
30
30
|
The CLI will:
|
|
31
31
|
|
|
32
32
|
1. Export all 11 component preview pages (`export-previews` if needed)
|
|
33
|
-
2.
|
|
34
|
-
3.
|
|
35
|
-
4.
|
|
36
|
-
5.
|
|
33
|
+
2. Start wizard at `http://localhost:4200`
|
|
34
|
+
3. Open browser on the **agent machine**
|
|
35
|
+
4. **Block** until client clicks **Review & Build**
|
|
36
|
+
5. Write `survey-ui-config.json` and exit 0
|
|
37
37
|
|
|
38
38
|
**Agent must:**
|
|
39
39
|
|
|
40
|
-
- [ ]
|
|
41
|
-
- [ ]
|
|
42
|
-
- [ ]
|
|
43
|
-
- [ ] On save: apply per Phase 6c from `survey-ui-config.json`
|
|
44
|
-
- [ ] On quit: skip Phase 6c; ask deploy confirmation with existing Phase 6 build
|
|
40
|
+
- [ ] Client is on the **same machine** (can see `localhost:4200`)
|
|
41
|
+
- [ ] Keep terminal open until `✅ UI configuration saved`
|
|
42
|
+
- [ ] Apply per Phase 6c from `survey-ui-config.json`
|
|
45
43
|
|
|
46
44
|
**Artifact:** `survey-ui-config.json` — apply source for local flow.
|
|
47
45
|
|
|
@@ -56,10 +54,20 @@ When the client uploads a logo in the local wizard:
|
|
|
56
54
|
|
|
57
55
|
The logo is **not** fetched from wizard memory on CloudFront — only from the deploy bundle. See [`aws-deploy.md`](aws-deploy.md) § Logos & local assets.
|
|
58
56
|
|
|
59
|
-
**Local quit (preview GC):** If the client exits without saving, wizard calls `POST /api/wizard-quit` → CLI deletes `out/previews
|
|
57
|
+
**Local quit (preview GC):** If the client exits without saving, wizard calls `POST /api/wizard-quit` → CLI deletes `out/previews/`.
|
|
60
58
|
|
|
61
59
|
**After save:** `POST /api/save-config` also deletes `out/previews/` before CLI exits (preview garbage collection).
|
|
62
60
|
|
|
61
|
+
**Quit without save (no second wizard run):**
|
|
62
|
+
|
|
63
|
+
| Event | Agent action |
|
|
64
|
+
|-------|----------------|
|
|
65
|
+
| Client clicks **Quit** in wizard | Wait for CLI exit; previews GC'd automatically |
|
|
66
|
+
| CLI exits without `✅ UI configuration saved` | **Do not** run `customize` again or share another wizard URL |
|
|
67
|
+
| Build still valid from Phase 6 | Ask deploy confirmation (INPUT 4) — use existing `out/` from prior `npm run build` |
|
|
68
|
+
|
|
69
|
+
The agent does **not** re-open the wizard after quit. Garbage collection removes preview artifacts only; the production build in `out/` from Phase 6 remains unless the client requests a rebuild.
|
|
70
|
+
|
|
63
71
|
---
|
|
64
72
|
|
|
65
73
|
## Remote client flow — `customize-ui` (fallback only)
|
|
@@ -278,7 +286,7 @@ npx cfm-sdk fetch-ui-config
|
|
|
278
286
|
| Mistake | Fix |
|
|
279
287
|
|---------|-----|
|
|
280
288
|
| Killed CLI during poll | Re-run `customize-ui`; client re-submits |
|
|
281
|
-
| Client quit wizard | CLI exits
|
|
289
|
+
| Client quit wizard | CLI exits with abandon message; S3 GC done — re-run `customize-ui` for new session |
|
|
282
290
|
| Applied from `survey-ui-config.json` while still seed | Use `.final.json` or run `confirm-ui-config` after apply |
|
|
283
291
|
| Only changed primary color, ignored diff | Read **entire** `diff.md` — clients often change 20+ paths |
|
|
284
292
|
| Skipped `confirm-ui-config` | Active JSON stays seed; next wizard re-open is wrong |
|
|
@@ -121,12 +121,6 @@ Only after H passes → ask deploy (INPUT 4). See [`aws-deploy.md`](aws-deploy.m
|
|
|
121
121
|
|
|
122
122
|
**Do not re-run** `npx cfm-sdk export-previews` after wizard save — previews were deleted on purpose (GC). Export-previews is for **component structure changes** during Phase 5, not for applying saved config.
|
|
123
123
|
|
|
124
|
-
### If client quit without saving
|
|
125
|
-
|
|
126
|
-
CLI exits 0 with GC message. **Do not** re-run wizard or share a new link. Skip this runbook (no Phase 6c). Ask deploy (INPUT 4) with the Phase 6 build.
|
|
127
|
-
|
|
128
|
-
**Wizard link:** parse `WIZARD_LINK=…` from CLI — never hardcode `localhost:4200`.
|
|
129
|
-
|
|
130
124
|
---
|
|
131
125
|
|
|
132
126
|
## Wizard steps → live preview keys
|
|
@@ -102,11 +102,11 @@ Single-select radio: outer border = focus ring, annulus = cell selected, inner d
|
|
|
102
102
|
| Wizard section | Config path | CSS vars | Apply in |
|
|
103
103
|
|----------------|-------------|----------|----------|
|
|
104
104
|
| Button style | `questionTypes.NPS_SCALE.buttonStyle` | `data-cfm-nps-button-style` | `RatingScale.tsx` |
|
|
105
|
-
| Hint labels | `hintMinText`, `hintMaxText` + hint colors | `--cfm-hint-label-*` | `hintLabelStyle()` on
|
|
106
|
-
| Number labels | `numberLabelMode`, per-index colors | `--cfm-number
|
|
105
|
+
| Hint labels | `hintMinText`, `hintMaxText` + hint colors | `--cfm-hint-label-*` | `hintLabelStyle()` on anchor row in `Question.tsx` |
|
|
106
|
+
| Number labels | `numberLabelMode`, per-index colors | `--cfm-number-*` | `resolveNumberLabelColor()` / badge row in `RatingScale.tsx` |
|
|
107
107
|
| Selection | Theme → Question card | `--cfm-matrix-selected`, `--cfm-input-focus-ring` | `scaleRadioRingStyle` + `scaleRadioDotStyle` |
|
|
108
108
|
|
|
109
|
-
**Removed from wizard UI:**
|
|
109
|
+
**Removed from wizard UI:** selected fill, track bar, track highlight — NPS selection uses Theme → Question card tokens only.
|
|
110
110
|
|
|
111
111
|
### CFM_MATRIX (`CFM_matrix`, `CFM_dropdown`, `CFM_transpose`, `CFM_likert`, `CFM_bipolar`)
|
|
112
112
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
| | `customize` **(default)** | `customize-ui` **(fallback)** |
|
|
10
10
|
|--|---------------------------|--------------------------------|
|
|
11
11
|
| **When** | Client on same machine as agent | Client on different device |
|
|
12
|
-
| **URL** | `
|
|
12
|
+
| **URL** | `localhost:4200` | EC2 link with `session` + `secret` |
|
|
13
13
|
| **Previews** | `out/previews/` on agent disk | EC2 disk |
|
|
14
14
|
| **Save** | `survey-ui-config.json` immediately | Poll → `.final.json` + `.diff.md` |
|
|
15
15
|
| **GC** | CLI stops → no orphans | Session cleanup on EC2 |
|
|
@@ -34,7 +34,7 @@ Footer: **"Mock preview — run customize or customize-ui…"**
|
|
|
34
34
|
|
|
35
35
|
### Symptom
|
|
36
36
|
|
|
37
|
-
Client cannot open
|
|
37
|
+
Client cannot open `http://localhost:4200`
|
|
38
38
|
|
|
39
39
|
### Fix
|
|
40
40
|
|
|
@@ -46,7 +46,7 @@ export CFM_DEPLOY_API_BASE="http://43.204.26.213:3000"
|
|
|
46
46
|
npx cfm-sdk customize-ui
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
Send **only
|
|
49
|
+
Send **only the EC2 link** from CLI output.
|
|
50
50
|
|
|
51
51
|
---
|
|
52
52
|
|
|
@@ -72,25 +72,9 @@ Wire `data-cfm-*` hooks and `--cfm-*` CSS vars per [`wizard-preview-build-guide.
|
|
|
72
72
|
|
|
73
73
|
---
|
|
74
74
|
|
|
75
|
-
## Client quit without saving
|
|
76
|
-
|
|
77
|
-
### Symptom
|
|
78
|
-
|
|
79
|
-
CLI prints quit message and exits 0; `out/previews/` removed.
|
|
80
|
-
|
|
81
|
-
### Agent action (mandatory)
|
|
82
|
-
|
|
83
|
-
- **Do not** re-run `customize` or `customize-ui`
|
|
84
|
-
- **Do not** share a new wizard link
|
|
85
|
-
- Skip Phase 6c (no `survey-ui-config.json` update)
|
|
86
|
-
- Ask deploy confirmation (Phase 7 / INPUT 4) with the existing Phase 6 build
|
|
87
|
-
|
|
88
|
-
---
|
|
89
|
-
|
|
90
75
|
## Checklist
|
|
91
76
|
|
|
92
77
|
- [ ] Used **`customize`** as default when client said yes
|
|
93
|
-
- [ ]
|
|
94
|
-
- [ ] Waited for CLI exit 0 (save or quit)
|
|
78
|
+
- [ ] Waited for `✅ UI configuration saved`
|
|
95
79
|
- [ ] Phase 5 preview bridge + `data-cfm-*` wired
|
|
96
80
|
- [ ] EC2 only if client on remote device
|
package/templates/docs/index.md
CHANGED
|
@@ -131,7 +131,7 @@ Run: `bash templates/verify-agent-build.sh`
|
|
|
131
131
|
Only after Phase 6 passes and client says **yes**:
|
|
132
132
|
|
|
133
133
|
```bash
|
|
134
|
-
npx cfm-sdk customize # DEFAULT —
|
|
134
|
+
npx cfm-sdk customize # DEFAULT — opens localhost:4200, WAITS until client saves
|
|
135
135
|
```
|
|
136
136
|
|
|
137
137
|
**Phase 6b:** Client on same machine → `customize` writes `survey-ui-config.json` directly. Remote client → `customize-ui` + EC2 link (see `wizard-troubleshooting.md`). Apply per `apply-ui-config.md`.
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import type { CsatQuestion, RatingMatrixQuestion, MatrixRowAnswers, ScaleColumn } from '@explorer02/cfm-survey-sdk';
|
|
3
|
-
import {
|
|
3
|
+
import { getEmojiForIndex, CsatStarIcons } from './surveyUiIcons';
|
|
4
|
+
import { columnSubmitValue, nonNaScaleColumns } from './surveyUiScaleUtils';
|
|
5
|
+
import { CustomSliderTrack } from './CustomSliderTrack';
|
|
6
|
+
import MatrixDropdown from './MatrixDropdown';
|
|
4
7
|
import {
|
|
5
|
-
cellSelectedVar,
|
|
6
|
-
focusRingVar,
|
|
7
|
-
matrixRadioDotStyle,
|
|
8
8
|
matrixRadioRingStyle,
|
|
9
|
+
matrixRadioDotStyle,
|
|
9
10
|
scaleCellSelectedStyle,
|
|
10
|
-
emojiSizeStyle,
|
|
11
11
|
unselectedOpacityStyle,
|
|
12
|
+
emojiSizeStyle,
|
|
13
|
+
cellSelectedVar,
|
|
14
|
+
focusRingVar,
|
|
12
15
|
} from '@/lib/surveyUi/selectionStyles';
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
import MatrixDropdown from './MatrixDropdown';
|
|
16
|
+
import { csatColumnLabelStyle } from '@/lib/surveyUi/labelStyles';
|
|
17
|
+
|
|
18
|
+
const MATRIX_ROW_LABEL_WIDTH = 'var(--cfm-matrix-row-width, 180px)';
|
|
17
19
|
|
|
18
20
|
type CsatMatrixQuestion = CsatQuestion | RatingMatrixQuestion;
|
|
19
21
|
|
|
@@ -36,10 +38,10 @@ function GraphicsColumnLabelsRow({ scaleColumns }: { scaleColumns: ScaleColumn[]
|
|
|
36
38
|
textAlign: 'center',
|
|
37
39
|
fontSize: '12px',
|
|
38
40
|
fontWeight: 500,
|
|
41
|
+
color: '#4b5563',
|
|
39
42
|
lineHeight: 1.2,
|
|
40
43
|
wordBreak: 'break-word',
|
|
41
44
|
width: '64px',
|
|
42
|
-
...csatColumnLabelStyle(),
|
|
43
45
|
}}
|
|
44
46
|
>
|
|
45
47
|
<span dangerouslySetInnerHTML={{ __html: col.label }} />
|
|
@@ -104,10 +106,10 @@ function CsatMatrixVerticalList({
|
|
|
104
106
|
onClick={() => onCellSelect(row.id, submitValue)}
|
|
105
107
|
style={{
|
|
106
108
|
width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'flex-start',
|
|
107
|
-
borderRadius: '8px', border: `1px solid ${isSelected ?
|
|
109
|
+
borderRadius: '8px', border: `1px solid ${isSelected ? '#e20074' : '#d1d5db'}`,
|
|
108
110
|
padding: '14px 18px', cursor: 'pointer', transition: 'all 0.15s',
|
|
109
|
-
backgroundColor: isSelected ? '
|
|
110
|
-
color: isSelected ?
|
|
111
|
+
backgroundColor: isSelected ? '#fdf2f8' : '#fff',
|
|
112
|
+
color: isSelected ? '#e20074' : '#374151',
|
|
111
113
|
fontWeight: isSelected ? 600 : 400, fontSize: '15px', textAlign: 'left',
|
|
112
114
|
}}
|
|
113
115
|
>
|
|
@@ -126,9 +128,9 @@ function CsatMatrixVerticalList({
|
|
|
126
128
|
key={col.id}
|
|
127
129
|
style={{
|
|
128
130
|
display: 'flex', alignItems: 'center', gap: '16px',
|
|
129
|
-
borderRadius: '8px', border: `1px solid ${isSelected ?
|
|
131
|
+
borderRadius: '8px', border: `1px solid ${isSelected ? '#e20074' : '#e5e5e5'}`,
|
|
130
132
|
padding: '16px 20px', cursor: 'pointer', transition: 'all 0.15s',
|
|
131
|
-
backgroundColor: isSelected ? '
|
|
133
|
+
backgroundColor: isSelected ? '#fdf2f8' : '#fff',
|
|
132
134
|
}}
|
|
133
135
|
>
|
|
134
136
|
<input type="radio" name={`${question.id}-${row.id}`}
|
|
@@ -136,8 +138,13 @@ function CsatMatrixVerticalList({
|
|
|
136
138
|
onChange={() => onCellSelect(row.id, submitValue)}
|
|
137
139
|
style={{ position: 'absolute', width: 0, height: 0, opacity: 0 }}
|
|
138
140
|
/>
|
|
139
|
-
<div style={
|
|
140
|
-
|
|
141
|
+
<div style={{
|
|
142
|
+
display: 'flex', width: '20px', height: '20px', flexShrink: 0,
|
|
143
|
+
alignItems: 'center', justifyContent: 'center', borderRadius: '50%',
|
|
144
|
+
border: isSelected ? '2px solid #e20074' : '1.5px solid #9ca3af',
|
|
145
|
+
backgroundColor: '#fff',
|
|
146
|
+
}}>
|
|
147
|
+
{isSelected && <div style={{ width: '10px', height: '10px', borderRadius: '50%', backgroundColor: '#e20074' }} />}
|
|
141
148
|
</div>
|
|
142
149
|
<span style={{ fontSize: '15px', fontWeight: 500, color: '#111827' }}
|
|
143
150
|
dangerouslySetInnerHTML={{ __html: col.label }} />
|
|
@@ -151,7 +158,7 @@ function CsatMatrixVerticalList({
|
|
|
151
158
|
<label style={{ display: 'flex', alignItems: 'center', gap: '12px', cursor: 'pointer', userSelect: 'none' }}>
|
|
152
159
|
<input type="checkbox" checked={selectedValue[row.id] === null}
|
|
153
160
|
onChange={() => onCellSelect(row.id, selectedValue[row.id] === null ? undefined : null)}
|
|
154
|
-
style={{ width: '16px', height: '16px', accentColor:
|
|
161
|
+
style={{ width: '16px', height: '16px', accentColor: '#e20074', cursor: 'pointer' }} />
|
|
155
162
|
<span style={{ fontSize: '14px', fontWeight: 500, color: '#4b5563' }}>Not Applicable</span>
|
|
156
163
|
</label>
|
|
157
164
|
)}
|
|
@@ -195,7 +202,7 @@ function CsatMatrixGrid({
|
|
|
195
202
|
{/* Labels row */}
|
|
196
203
|
{showLabels && m > 0 && (
|
|
197
204
|
<div style={{ display: 'flex', alignItems: 'flex-end', marginBottom: '12px' }}>
|
|
198
|
-
<div style={{ width:
|
|
205
|
+
<div style={{ width: MATRIX_ROW_LABEL_WIDTH, flexShrink: 0 }} />
|
|
199
206
|
<div style={{ flex: 1, display: 'flex' }}>
|
|
200
207
|
<div style={{ flex: 1, position: 'relative', height: '24px', ...(isGraphics ? { margin: '0 40px' } : {}) }}>
|
|
201
208
|
{labels!.map((label, i) => {
|
|
@@ -209,8 +216,7 @@ function CsatMatrixGrid({
|
|
|
209
216
|
left: `${percent}%`,
|
|
210
217
|
transform: 'translateX(-50%)',
|
|
211
218
|
textAlign: 'center', fontSize: '13px', fontWeight: 600,
|
|
212
|
-
lineHeight: 1.3, whiteSpace: 'nowrap'
|
|
213
|
-
...csatColumnLabelStyle(),
|
|
219
|
+
color: '#4b5563', lineHeight: 1.3, whiteSpace: 'nowrap'
|
|
214
220
|
}}>
|
|
215
221
|
{label}
|
|
216
222
|
</div>
|
|
@@ -225,7 +231,7 @@ function CsatMatrixGrid({
|
|
|
225
231
|
{/* Column tick labels for graphics sliders (shown with or without anchor labels) */}
|
|
226
232
|
{isGraphics && m > 0 && (
|
|
227
233
|
<div style={{ display: 'flex', alignItems: 'flex-end', marginBottom: showLabels ? '8px' : '16px', minHeight: '24px' }}>
|
|
228
|
-
<div style={{ width:
|
|
234
|
+
<div style={{ width: MATRIX_ROW_LABEL_WIDTH, flexShrink: 0 }} />
|
|
229
235
|
<div style={{ flex: 1, display: 'flex' }}>
|
|
230
236
|
<GraphicsColumnLabelsRow scaleColumns={scaleColumns} />
|
|
231
237
|
{hasNotApplicable && (
|
|
@@ -238,7 +244,7 @@ function CsatMatrixGrid({
|
|
|
238
244
|
{/* Column Headers for non-graphics */}
|
|
239
245
|
{!isGraphics && !showLabels && m > 0 && (
|
|
240
246
|
<div style={{ display: 'flex', alignItems: 'flex-end', marginBottom: '16px', minHeight: '24px' }}>
|
|
241
|
-
<div style={{ width:
|
|
247
|
+
<div style={{ width: MATRIX_ROW_LABEL_WIDTH, flexShrink: 0 }} />
|
|
242
248
|
<div style={{ flex: 1, display: 'flex' }}>
|
|
243
249
|
<div style={{ flex: 1, display: 'grid', gridTemplateColumns: `repeat(${scaleColumns.length}, 1fr)` }}>
|
|
244
250
|
{scaleColumns.map(col => (
|
|
@@ -264,9 +270,9 @@ function CsatMatrixGrid({
|
|
|
264
270
|
{statementRows.map((row, rowIdx) => (
|
|
265
271
|
<div key={row.id} style={{
|
|
266
272
|
display: 'flex', alignItems: 'center', borderRadius: '8px',
|
|
267
|
-
padding: '12px 0', backgroundColor: rowIdx % 2 === 0 ? '
|
|
273
|
+
padding: '12px 0', backgroundColor: rowIdx % 2 === 0 ? 'rgba(249, 250, 251, 0.8)' : '#fff',
|
|
268
274
|
}}>
|
|
269
|
-
<div style={{ width:
|
|
275
|
+
<div style={{ width: MATRIX_ROW_LABEL_WIDTH, flexShrink: 0, padding: '0 16px' }}>
|
|
270
276
|
<span style={{ fontSize: '14px', fontWeight: 500, color: '#111827', lineHeight: 1.4 }}
|
|
271
277
|
dangerouslySetInnerHTML={{ __html: row.statementText }} />
|
|
272
278
|
</div>
|
|
@@ -305,7 +311,7 @@ function CsatMatrixGrid({
|
|
|
305
311
|
|
|
306
312
|
const RadioNode = (
|
|
307
313
|
<div style={matrixRadioRingStyle(isSelected)}>
|
|
308
|
-
|
|
314
|
+
<div style={matrixRadioDotStyle(isSelected)} />
|
|
309
315
|
</div>
|
|
310
316
|
);
|
|
311
317
|
|
|
@@ -328,23 +334,22 @@ function CsatMatrixGrid({
|
|
|
328
334
|
...emojiSizeStyle(),
|
|
329
335
|
transform: isSelected ? 'scale(1.1)' : isHovered ? 'scale(1.25)' : 'scale(1)',
|
|
330
336
|
...(isSelected ? {
|
|
331
|
-
|
|
332
|
-
|
|
337
|
+
outline: `2px solid ${focusRingVar}`,
|
|
338
|
+
backgroundColor: cellSelectedVar,
|
|
333
339
|
borderRadius: '50%',
|
|
334
|
-
} :
|
|
340
|
+
} : unselectedOpacityStyle()),
|
|
335
341
|
} : isStar ? {
|
|
336
342
|
padding: '4px',
|
|
337
343
|
transform: isHovered ? 'scale(1.1)' : 'scale(1)',
|
|
344
|
+
color: isSelected ? cellSelectedVar : focusRingVar,
|
|
338
345
|
...(isSelected ? {} : unselectedOpacityStyle()),
|
|
339
|
-
opacity: isSelected ? 1 : isHovered ? 1 : undefined,
|
|
340
346
|
} : isNumbered ? {
|
|
341
|
-
width: '
|
|
347
|
+
width: '40px', height: '40px', borderRadius: '8px',
|
|
342
348
|
fontSize: '14px', fontWeight: 500,
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
349
|
+
...(isSelected
|
|
350
|
+
? scaleCellSelectedStyle(true)
|
|
351
|
+
: { border: '1px solid #d1d5db', backgroundColor: '#fff', color: '#111827' }),
|
|
346
352
|
transition: 'all 0.15s',
|
|
347
|
-
...(isSelected ? scaleCellSelectedStyle(true) : {}),
|
|
348
353
|
} : { padding: '8px' }),
|
|
349
354
|
}}
|
|
350
355
|
>
|
|
@@ -382,7 +387,7 @@ function CsatMatrixGrid({
|
|
|
382
387
|
<label style={{ cursor: 'pointer' }}>
|
|
383
388
|
<input type="checkbox" checked={selectedValue[row.id] === null}
|
|
384
389
|
onChange={() => onCellSelect(row.id, selectedValue[row.id] === null ? undefined : null)}
|
|
385
|
-
style={{ width: '16px', height: '16px', accentColor:
|
|
390
|
+
style={{ width: '16px', height: '16px', accentColor: '#e20074', cursor: 'pointer' }} />
|
|
386
391
|
</label>
|
|
387
392
|
</div>
|
|
388
393
|
)}
|
|
@@ -441,8 +446,7 @@ function CsatMatrixCarousel({
|
|
|
441
446
|
<div key={i} style={{
|
|
442
447
|
position: 'absolute', left: `${percent}%`, transform: 'translateX(-50%)',
|
|
443
448
|
textAlign: 'center', fontSize: '13px', fontWeight: 600,
|
|
444
|
-
lineHeight: 1.3, whiteSpace: 'nowrap'
|
|
445
|
-
...csatColumnLabelStyle(),
|
|
449
|
+
color: '#4b5563', lineHeight: 1.3, whiteSpace: 'nowrap'
|
|
446
450
|
}}>
|
|
447
451
|
{label}
|
|
448
452
|
</div>
|
|
@@ -486,8 +490,13 @@ function CsatMatrixCarousel({
|
|
|
486
490
|
const EmojiNode = isEmoji ? getEmojiForIndex(displayIdx, scaleColumns.length) : null;
|
|
487
491
|
const StarNode = isStar ? (isSelected ? (CsatStarIcons.filled as any) : (CsatStarIcons.empty as any)) : null;
|
|
488
492
|
const RadioNode = (
|
|
489
|
-
<div style={
|
|
490
|
-
|
|
493
|
+
<div style={{
|
|
494
|
+
display: 'flex', width: '18px', height: '18px',
|
|
495
|
+
alignItems: 'center', justifyContent: 'center', borderRadius: '50%',
|
|
496
|
+
margin: '0 auto', border: isSelected ? '2px solid #e20074' : '2px solid #d1d5db',
|
|
497
|
+
backgroundColor: '#fff',
|
|
498
|
+
}}>
|
|
499
|
+
{isSelected && <div style={{ width: '8px', height: '8px', borderRadius: '50%', backgroundColor: '#e20074' }} />}
|
|
491
500
|
</div>
|
|
492
501
|
);
|
|
493
502
|
|
|
@@ -503,23 +512,19 @@ function CsatMatrixCarousel({
|
|
|
503
512
|
transition: 'all 0.15s', border: 'none', backgroundColor: 'transparent',
|
|
504
513
|
cursor: 'pointer', borderRadius: '6px', pointerEvents: 'none',
|
|
505
514
|
...(isEmoji ? {
|
|
506
|
-
padding: '6px',
|
|
507
|
-
|
|
508
|
-
...(isSelected ? { backgroundColor: 'var(--cfm-mcq-selected-bg)', boxShadow: `0 0 0 2px ${focusRingVar}`, borderRadius: '50%' } : {}),
|
|
515
|
+
padding: '6px', transform: isSelected ? 'scale(1.1)' : isHovered ? 'scale(1.25)' : 'scale(1)',
|
|
516
|
+
...(isSelected ? { backgroundColor: 'rgba(252, 231, 243, 0.6)', boxShadow: '0 0 0 2px rgba(226, 0, 116, 0.3)', borderRadius: '50%' } : {}),
|
|
509
517
|
} : isStar ? {
|
|
510
|
-
padding: '4px', transform: isHovered ? 'scale(1.1)' : 'scale(1)',
|
|
511
|
-
...(isSelected ? {} : unselectedOpacityStyle()),
|
|
512
|
-
opacity: isSelected ? 1 : isHovered ? 1 : undefined,
|
|
518
|
+
padding: '4px', transform: isHovered ? 'scale(1.1)' : 'scale(1)', opacity: isSelected ? 1 : isHovered ? 1 : 0.5,
|
|
513
519
|
} : isNumbered ? {
|
|
514
|
-
width: '
|
|
515
|
-
border: `1px solid ${isSelected ?
|
|
516
|
-
...(isSelected ? scaleCellSelectedStyle(true) : {}),
|
|
520
|
+
width: '40px', height: '40px', borderRadius: '8px', fontSize: '14px', fontWeight: 500,
|
|
521
|
+
border: `1px solid ${isSelected ? '#e20074' : '#d1d5db'}`, backgroundColor: isSelected ? '#e20074' : '#fff', color: isSelected ? '#fff' : '#111827',
|
|
517
522
|
} : { padding: '8px' }),
|
|
518
523
|
}}
|
|
519
524
|
>
|
|
520
525
|
{(EmojiNode ?? StarNode ?? (isNumbered ? (displayIdx + 1) : RadioNode)) as React.ReactNode}
|
|
521
526
|
</button>
|
|
522
|
-
<span style={{ fontSize: '13px', fontWeight: 500, textAlign: 'center', lineHeight: 1.2
|
|
527
|
+
<span style={{ fontSize: '13px', fontWeight: 500, color: '#4b5563', textAlign: 'center', lineHeight: 1.2 }}
|
|
523
528
|
dangerouslySetInnerHTML={{ __html: col.label }} />
|
|
524
529
|
</div>
|
|
525
530
|
);
|
|
@@ -532,7 +537,7 @@ function CsatMatrixCarousel({
|
|
|
532
537
|
<label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
|
|
533
538
|
<input type="checkbox" checked={selectedValue[row.id] === null}
|
|
534
539
|
onChange={() => onCellSelect(row.id, selectedValue[row.id] === null ? undefined : null)}
|
|
535
|
-
style={{ width: '16px', height: '16px', accentColor:
|
|
540
|
+
style={{ width: '16px', height: '16px', accentColor: '#e20074', cursor: 'pointer' }} />
|
|
536
541
|
<span style={{ fontSize: '14px', fontWeight: 500, color: '#4b5563' }}>N/A</span>
|
|
537
542
|
</label>
|
|
538
543
|
</div>
|
|
@@ -554,7 +559,7 @@ function CsatMatrixCarousel({
|
|
|
554
559
|
<button key={i} type="button" onClick={() => setActiveCarouselIndex(i)}
|
|
555
560
|
style={{
|
|
556
561
|
width: i === activeCarouselIndex ? '20px' : '8px', height: '8px', borderRadius: '4px',
|
|
557
|
-
backgroundColor: i === activeCarouselIndex ?
|
|
562
|
+
backgroundColor: i === activeCarouselIndex ? '#e20074' : '#d1d5db', border: 'none', padding: 0,
|
|
558
563
|
cursor: 'pointer', transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)'
|
|
559
564
|
}} />
|
|
560
565
|
))}
|
|
@@ -594,6 +599,7 @@ export function CsatMatrixScale({ question, selectedValue = {}, onSelect }: Csat
|
|
|
594
599
|
const isStar = displayStyle === 'star';
|
|
595
600
|
const isNumbered = displayStyle === 'numbered';
|
|
596
601
|
const isGraphics = displayStyle === 'graphics';
|
|
602
|
+
const isStandardRadio = displayStyle === 'standard';
|
|
597
603
|
|
|
598
604
|
const scaleColumns = nonNaScaleColumns(question.scaleColumns);
|
|
599
605
|
|
|
@@ -612,7 +618,7 @@ export function CsatMatrixScale({ question, selectedValue = {}, onSelect }: Csat
|
|
|
612
618
|
);
|
|
613
619
|
}
|
|
614
620
|
|
|
615
|
-
if (isDropdown || isSelectBox) {
|
|
621
|
+
if (isDropdown || isSelectBox || (isStandardRadio && question.type !== 'RATING_MATRIX')) {
|
|
616
622
|
return (
|
|
617
623
|
<CsatMatrixVerticalList
|
|
618
624
|
question={question}
|