@explorer02/cfm-survey-sdk 0.2.7 → 0.2.9

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.
Files changed (44) hide show
  1. package/dist/cli/index.js +38 -41
  2. package/dist/cli/index.mjs +36 -39
  3. package/package.json +1 -1
  4. package/templates/docs/00-integration/agent-execution-flow.md +7 -9
  5. package/templates/docs/00-integration/agent-operating-contract.md +2 -1
  6. package/templates/docs/00-integration/component-checklist.md +1 -0
  7. package/templates/docs/00-integration/ui-customization-wizard.md +29 -7
  8. package/templates/docs/00-integration/wizard-chrome-contract.md +11 -1
  9. package/templates/docs/00-integration/wizard-config-handoff.md +11 -9
  10. package/templates/docs/00-integration/wizard-post-customize-build.md +8 -2
  11. package/templates/docs/00-integration/wizard-preview-build-guide.md +0 -2
  12. package/templates/docs/00-integration/wizard-question-type-styling.md +7 -6
  13. package/templates/docs/00-integration/wizard-troubleshooting.md +20 -4
  14. package/templates/docs/03-ui-specs/README.md +1 -1
  15. package/templates/docs/MANIFEST.json +0 -3
  16. package/templates/docs/index.md +4 -6
  17. package/templates/docs/templates/CsatMatrixScale.tsx +56 -53
  18. package/templates/docs/templates/CustomSliderTrack.tsx +17 -8
  19. package/templates/docs/templates/FileUploadScale.tsx +11 -3
  20. package/templates/docs/templates/Header.tsx +32 -48
  21. package/templates/docs/templates/HeatmapScale.tsx +7 -4
  22. package/templates/docs/templates/LikertMatrixScale.tsx +58 -43
  23. package/templates/docs/templates/MatrixDropdown.tsx +12 -11
  24. package/templates/docs/templates/Question.tsx +4 -23
  25. package/templates/docs/templates/RankOrderScale.tsx +46 -6
  26. package/templates/docs/templates/RatingScale.tsx +38 -0
  27. package/templates/docs/templates/SliderMatrixScale.tsx +4 -3
  28. package/templates/docs/templates/verify-agent-build.sh +17 -0
  29. package/templates/preview-harness/preview-bridge.inline.js +10 -0
  30. package/templates/preview-harness/previewPages.ts +5 -7
  31. package/templates/preview-harness/vite-app/src/QuestionPreview.tsx +11 -6
  32. package/templates/preview-harness/vite-app/src/fixtures/questions.ts +2 -10
  33. package/templates/preview-harness/vite-app/src/preview-live-overrides.css +11 -6
  34. package/templates/previewBridge.ts +13 -0
  35. package/templates/survey-theme.css +17 -17
  36. package/templates/wizard-dist/assets/{PreviewMock-tTMvwuEy.js → PreviewMock-CysRws9G.js} +1 -1
  37. package/templates/wizard-dist/assets/TypePanel-DWX1LL3q.js +1 -0
  38. package/templates/wizard-dist/assets/index-DIuc7AU2.js +34 -0
  39. package/templates/wizard-dist/assets/index-pCM8n0FT.css +1 -0
  40. package/templates/wizard-dist/index.html +2 -2
  41. package/templates/docs/00-integration/wizard-customization-catalog.md +0 -391
  42. package/templates/wizard-dist/assets/TypePanel-B9Ls7BSq.js +0 -1
  43. package/templates/wizard-dist/assets/index-CjkIlOUI.css +0 -1
  44. package/templates/wizard-dist/assets/index-CoCrnnxt.js +0 -34
@@ -25,10 +25,21 @@ Pipeline: [`wizard-preview-build-guide.md`](wizard-preview-build-guide.md) · [`
25
25
  | Text placeholder color not updating | `data-cfm-textfield` + `--cfm-input-placeholder` in CSS / `::placeholder` rule |
26
26
  | Matrix 2nd row toggle | Harness adds row via `previewMultiStatement` — `LikertMatrixScale` must render all `statementRows` from props |
27
27
  | 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` |
28
31
  | Logo wrong on CloudFront | Never change `global.logo.fileName` manually — use wizard file in `public/` |
29
32
 
30
33
  ---
31
34
 
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
+
32
43
  ## Copy order (agents — do this in Phase 5)
33
44
 
34
45
  ```bash
@@ -276,7 +287,6 @@ EC2 `customize-ui`: quit/save uses disk session GC — see [`wizard-config-hando
276
287
 
277
288
  | Doc | Role |
278
289
  |-----|------|
279
- | [`wizard-customization-catalog.md`](wizard-customization-catalog.md) | Every wizard control → CSS var → component |
280
290
  | [`wizard-preview-build-guide.md`](wizard-preview-build-guide.md) | Full build guide |
281
291
  | [`wizard-post-customize-build.md`](wizard-post-customize-build.md) | After wizard save — apply all formats + build |
282
292
  | [`wizard-question-type-styling.md`](wizard-question-type-styling.md) | Per-type CSS vars |
@@ -30,16 +30,18 @@ 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. 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
33
+ 2. Probe first free port from 4200 and start wizard CLI prints **`WIZARD_LINK=http://localhost:{port}`**
34
+ 3. **Block** until client clicks **Review & Build** or quits
35
+ 4. On save: write `survey-ui-config.json` and exit 0
36
+ 5. On quit: GC `out/previews/` and exit 0 — **do not re-run wizard**; ask deploy (Phase 7)
37
37
 
38
38
  **Agent must:**
39
39
 
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`
40
+ - [ ] Parse `WIZARD_LINK` from CLI and share **only** that URL with client (never hardcode `:4200`)
41
+ - [ ] Client is on the **same machine** (can open the `WIZARD_LINK` host)
42
+ - [ ] Keep terminal open until CLI exits
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
43
45
 
44
46
  **Artifact:** `survey-ui-config.json` — apply source for local flow.
45
47
 
@@ -54,7 +56,7 @@ When the client uploads a logo in the local wizard:
54
56
 
55
57
  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.
56
58
 
57
- **Local quit (preview GC):** If the client exits without saving, wizard calls `POST /api/wizard-quit` → CLI deletes `out/previews/`.
59
+ **Local quit (preview GC):** If the client exits without saving, wizard calls `POST /api/wizard-quit` → CLI deletes `out/previews/` → exits 0. Agent **must not** re-run the wizard or share a new link — proceed to Phase 7 deploy ask.
58
60
 
59
61
  **After save:** `POST /api/save-config` also deletes `out/previews/` before CLI exits (preview garbage collection).
60
62
 
@@ -276,7 +278,7 @@ npx cfm-sdk fetch-ui-config
276
278
  | Mistake | Fix |
277
279
  |---------|-----|
278
280
  | Killed CLI during poll | Re-run `customize-ui`; client re-submits |
279
- | Client quit wizard | CLI exits with abandon message; S3 GC done re-run `customize-ui` for new session |
281
+ | Client quit wizard | CLI exits 0; GC done (`out/previews/` removed). **Do not** re-run wizard skip Phase 6c, ask deploy (INPUT 4) |
280
282
  | Applied from `survey-ui-config.json` while still seed | Use `.final.json` or run `confirm-ui-config` after apply |
281
283
  | Only changed primary color, ignored diff | Read **entire** `diff.md` — clients often change 20+ paths |
282
284
  | Skipped `confirm-ui-config` | Active JSON stays seed; next wizard re-open is wrong |
@@ -1,7 +1,7 @@
1
1
  # Wizard Post-Customize Build — All Types, All Formats
2
2
 
3
3
  > **Phase 6c runbook.** Read after the client clicks **Review & Build** and the CLI prints `✅ UI configuration saved`.
4
- > Companion docs: [`wizard-config-handoff.md`](wizard-config-handoff.md) · [`wizard-customization-catalog.md`](wizard-customization-catalog.md) · [`apply-ui-config.md`](apply-ui-config.md) · [`wizard-question-type-styling.md`](wizard-question-type-styling.md) · [`wizard-chrome-contract.md`](wizard-chrome-contract.md)
4
+ > Companion docs: [`wizard-config-handoff.md`](wizard-config-handoff.md) · [`apply-ui-config.md`](apply-ui-config.md) · [`wizard-question-type-styling.md`](wizard-question-type-styling.md) · [`wizard-chrome-contract.md`](wizard-chrome-contract.md)
5
5
 
6
6
  This doc answers two agent questions:
7
7
 
@@ -87,7 +87,7 @@ test -z "$LOGO" || test -f "public/$LOGO" || echo "FAIL: logo file missing in pu
87
87
 
88
88
  The wizard **Question Types** step exposes **10 type families** (TEXT_AND_MEDIA uses theme vars only). Apply `questionTypes.{TYPE}` for **every** type block in saved JSON — even if the client's survey does not include that type. This keeps `survey-theme.css` complete and future-proof.
89
89
 
90
- Use the format matrix below. Detailed token tables: [`wizard-customization-catalog.md`](wizard-customization-catalog.md) and [`wizard-question-type-styling.md`](wizard-question-type-styling.md) and [`apply-ui-config.md`](apply-ui-config.md).
90
+ Use the format matrix below. Detailed token tables: [`wizard-question-type-styling.md`](wizard-question-type-styling.md) and [`apply-ui-config.md`](apply-ui-config.md).
91
91
 
92
92
  ### F. Hook options
93
93
 
@@ -121,6 +121,12 @@ 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
+
124
130
  ---
125
131
 
126
132
  ## Wizard steps → live preview keys
@@ -2,12 +2,10 @@
2
2
 
3
3
  > **Read during Phase 5 (BUILD) — mandatory.** Wizard **run** (Phase 6b) is optional; this contract is required for every agent build.
4
4
  > **Authoritative hook list + copy commands:** [`wizard-chrome-contract.md`](wizard-chrome-contract.md) — fixes logo, layout toggles, question card, and per-type preview issues.
5
- > **Complete control catalog (all steps, types, formats):** [`wizard-customization-catalog.md`](wizard-customization-catalog.md).
6
5
  > Pipeline: [`agent-execution-flow.md`](agent-execution-flow.md).
7
6
 
8
7
  Related docs:
9
8
 
10
- - [`wizard-customization-catalog.md`](wizard-customization-catalog.md) — **every wizard control** → config → CSS var → component
11
9
  - [`wizard-chrome-contract.md`](wizard-chrome-contract.md) — **copy-first templates** + symptom → fix table
12
10
 
13
11
  - [`ui-customization-wizard.md`](ui-customization-wizard.md) — wizard flow and CLI
@@ -1,6 +1,6 @@
1
1
  # Wizard Question-Type Styling
2
2
 
3
- > **Read during Phase 5 (BUILD) — mandatory.** Alongside [`wizard-customization-catalog.md`](wizard-customization-catalog.md) (complete control index), [`wizard-preview-build-guide.md`](wizard-preview-build-guide.md) and [`apply-ui-config.md`](apply-ui-config.md).
3
+ > **Read during Phase 5 (BUILD) — mandatory.** Alongside [`wizard-preview-build-guide.md`](wizard-preview-build-guide.md) and [`apply-ui-config.md`](apply-ui-config.md).
4
4
  > **After client saves wizard:** [`wizard-post-customize-build.md`](wizard-post-customize-build.md) — full format matrix + Phase 6c runbook.
5
5
  > Pipeline: [`agent-execution-flow.md`](agent-execution-flow.md).
6
6
 
@@ -102,11 +102,12 @@ 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
- | Track | `selectedFill`, `trackBar`, `trackHighlight` | `--cfm-nps-selected-fill`, `--cfm-nps-track-bar`, `--cfm-nps-track-highlight` | Track bar background |
106
- | Hint labels | `hintMinText`, `hintMaxText` + hint colors | `--cfm-hint-label-*` | Min/max anchor row |
107
- | Number labels | `numberLabelMode`, per-index colors | `--cfm-number-label-mode`, `--cfm-number-mono-color`, `--cfm-number-color-{n}` | Badge row above radios |
105
+ | Hint labels | `hintMinText`, `hintMaxText` + hint colors | `--cfm-hint-label-*` | `hintLabelStyle()` on min/max anchor row |
106
+ | Number labels | `numberLabelMode`, per-index colors | `--cfm-number-label-mode`, `--cfm-number-mono-color`, `--cfm-number-color-{n}` | Badge row; `resolveNumberLabelColor()` for unselected |
108
107
  | Selection | Theme → Question card | `--cfm-matrix-selected`, `--cfm-input-focus-ring` | `scaleRadioRingStyle` + `scaleRadioDotStyle` |
109
108
 
109
+ **Removed from wizard UI:** NPS selected fill, track bar, track highlight — use Theme → Question card tokens only.
110
+
110
111
  ### CFM_MATRIX (`CFM_matrix`, `CFM_dropdown`, `CFM_transpose`, `CFM_likert`, `CFM_bipolar`)
111
112
 
112
113
  | Wizard section | CSS vars | Apply in |
@@ -193,9 +194,9 @@ For production builds after wizard: syncing `survey-theme.css` is sufficient —
193
194
 
194
195
  **Wizard customization** exposes all **11** question types regardless of fetched survey inventory. Wire styling for every scale template you copy — not only types in the client's mockup image.
195
196
 
196
- **Format-level matrix** (every preview tab → component): [`wizard-customization-catalog.md`](wizard-customization-catalog.md) § Question Types step.
197
+ **Format-level matrix** (every preview tab → component): [`wizard-post-customize-build.md`](wizard-post-customize-build.md) § Question type format matrix.
197
198
 
198
- For each type in **wizard export** (all types in `surveyTypes[]`):
199
+ For each type in **wizard export** (all 11):
199
200
 
200
201
  - [ ] Scale component uses `var(--cfm-*)` for every wizard-exposed token (no hardcoded brand hex)
201
202
  - [ ] Radio/scale selections use `selectionStyles.ts` pattern (focus ring + cell selected)
@@ -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** | `localhost:4200` | EC2 link with `session` + `secret` |
12
+ | **URL** | `WIZARD_LINK` from CLI (dynamic port) | `WIZARD_LINK` from CLI (EC2) |
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 `http://localhost:4200`
37
+ Client cannot open the `WIZARD_LINK` URL (wrong port or wrong machine)
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 the EC2 link** from CLI output.
49
+ Send **only** the `WIZARD_LINK=…` line from CLI output.
50
50
 
51
51
  ---
52
52
 
@@ -72,9 +72,25 @@ 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
+
75
90
  ## Checklist
76
91
 
77
92
  - [ ] Used **`customize`** as default when client said yes
78
- - [ ] Waited for `✅ UI configuration saved`
93
+ - [ ] Parsed **`WIZARD_LINK`** from CLI and shared only that URL
94
+ - [ ] Waited for CLI exit 0 (save or quit)
79
95
  - [ ] Phase 5 preview bridge + `data-cfm-*` wired
80
96
  - [ ] EC2 only if client on remote device
@@ -4,7 +4,7 @@
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). **Full control catalog (all formats):** [`wizard-customization-catalog.md`](../00-integration/wizard-customization-catalog.md).
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
8
 
9
9
  ## How to Read a UI Spec
10
10
 
@@ -368,10 +368,8 @@
368
368
  "00-integration/agent-execution-flow.md",
369
369
  "00-integration/constraints.md",
370
370
  "00-integration/component-checklist.md",
371
- "00-integration/wizard-customization-catalog.md",
372
371
  "00-integration/wizard-preview-build-guide.md",
373
372
  "00-integration/wizard-question-type-styling.md",
374
- "00-integration/wizard-chrome-contract.md",
375
373
  "02-reference/routing-table.md"
376
374
  ],
377
375
  "onDemand": "02-reference/question-types"
@@ -428,7 +426,6 @@
428
426
  "read": [
429
427
  "00-integration/wizard-config-handoff.md",
430
428
  "00-integration/ui-customization-wizard.md",
431
- "00-integration/wizard-customization-catalog.md",
432
429
  "00-integration/wizard-preview-build-guide.md",
433
430
  "00-integration/wizard-question-type-styling.md",
434
431
  "00-integration/wizard-api.md",
@@ -29,7 +29,6 @@ templates/docs/
29
29
  ├── 00-integration/
30
30
  │ ├── agent-operating-contract.md ← Anti-hallucination — read FIRST
31
31
  │ ├── agent-execution-flow.md ← Pipeline, client questions, wizard-ready build
32
- │ ├── wizard-customization-catalog.md ← All wizard controls × types × formats
33
32
  │ ├── wizard-preview-build-guide.md
34
33
  │ ├── wizard-post-customize-build.md ← Phase 6c — all formats + build gate
35
34
  │ ├── wizard-question-type-styling.md
@@ -79,7 +78,7 @@ Full step-by-step: [`agent-execution-flow.md`](00-integration/agent-execution-fl
79
78
  | **2** Instance ID | Get JWT | `useSurveySDK.md` — **ask client** |
80
79
  | **3** SDK contract | Hook + logic | `client-integration-guide.md`, `question-type-sdk-matrix.md`, logic docs |
81
80
  | **4** Plan | Inventory + copy dispatcher | `component-checklist.md`, per-type MANIFEST docs, `implementation_plan.md` |
82
- | **5** Build | Wizard-ready UI | `wizard-customization-catalog.md`, `wizard-chrome-contract.md`, `wizard-preview-build-guide.md`, `wizard-question-type-styling.md`, `03-ui-specs/*` |
81
+ | **5** Build | Wizard-ready UI | `wizard-chrome-contract.md`, `wizard-preview-build-guide.md`, `wizard-question-type-styling.md`, `03-ui-specs/*` |
83
82
  | **6** Verify | Build + dev server | `verify-agent-build.sh` — **ask wizard** |
84
83
  | **6b** Wizard | Optional customize | **`npx cfm-sdk customize`** (default) — EC2 `customize-ui` only if client on another device |
85
84
  | **6c** Apply | Sync final config | `wizard-post-customize-build.md`, `apply-ui-config.md` |
@@ -132,7 +131,7 @@ Run: `bash templates/verify-agent-build.sh`
132
131
  Only after Phase 6 passes and client says **yes**:
133
132
 
134
133
  ```bash
135
- npx cfm-sdk customize # DEFAULT — opens localhost:4200, WAITS until client saves
134
+ npx cfm-sdk customize # DEFAULT — prints WIZARD_LINK, WAITS until client saves or quits
136
135
  ```
137
136
 
138
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`.
@@ -154,7 +153,7 @@ Re-run verify script + build.
154
153
  | **Always first** | `agent-operating-contract.md`, `agent-execution-flow.md`, `mockup-ui-extraction.md`, `index.md`, `client-integration-guide.md`, `constraints.md` |
155
154
  | Phase 3 contract | `question-type-sdk-matrix.md`, `useSurveySDK.md`, logic/navigation docs |
156
155
  | Phase 4 plan | `mockup-ui-extraction.md`, `placeholders-and-custom-fields-wiring.md`, `component-checklist.md`, per-type `03-ui-specs/*` |
157
- | Wizard build | `wizard-customization-catalog.md`, `wizard-preview-build-guide.md`, `wizard-question-type-styling.md` |
156
+ | Wizard build | `wizard-preview-build-guide.md`, `wizard-question-type-styling.md` |
158
157
  | Wizard run | `wizard-config-handoff.md`, `ui-customization-wizard.md`, `wizard-api.md`, `wizard-troubleshooting.md` |
159
158
  | After wizard (apply all paths) | `wizard-post-customize-build.md`, `apply-ui-config.md` |
160
159
  | Deploy | `aws-deploy.md` (§ Logos & local assets), `setup.md#nextjs-aws-static-export` |
@@ -172,5 +171,4 @@ Re-run verify script + build.
172
171
  | Hint labels | `--cfm-hint-label-*` | NPS min/max anchors |
173
172
  | Number labels | `--cfm-number-*` | Traffic-light badges (unselected only) |
174
173
 
175
- Full per-type table: [`wizard-question-type-styling.md`](00-integration/wizard-question-type-styling.md).
176
- Complete wizard control index (all formats): [`wizard-customization-catalog.md`](00-integration/wizard-customization-catalog.md).
174
+ Full per-type table: [`wizard-question-type-styling.md`](00-integration/wizard-question-type-styling.md).
@@ -1,5 +1,15 @@
1
1
  import React, { useState } from 'react';
2
2
  import type { CsatQuestion, RatingMatrixQuestion, MatrixRowAnswers, ScaleColumn } from '@explorer02/cfm-survey-sdk';
3
+ import { csatColumnLabelStyle } from '@/lib/surveyUi/labelStyles';
4
+ import {
5
+ cellSelectedVar,
6
+ focusRingVar,
7
+ matrixRadioDotStyle,
8
+ matrixRadioRingStyle,
9
+ scaleCellSelectedStyle,
10
+ emojiSizeStyle,
11
+ unselectedOpacityStyle,
12
+ } from '@/lib/surveyUi/selectionStyles';
3
13
  import { getEmojiForIndex, CsatStarIcons } from './surveyUiIcons';
4
14
  import { columnSubmitValue, nonNaScaleColumns } from './surveyUiScaleUtils';
5
15
  import { CustomSliderTrack } from './CustomSliderTrack';
@@ -26,10 +36,10 @@ function GraphicsColumnLabelsRow({ scaleColumns }: { scaleColumns: ScaleColumn[]
26
36
  textAlign: 'center',
27
37
  fontSize: '12px',
28
38
  fontWeight: 500,
29
- color: '#4b5563',
30
39
  lineHeight: 1.2,
31
40
  wordBreak: 'break-word',
32
41
  width: '64px',
42
+ ...csatColumnLabelStyle(),
33
43
  }}
34
44
  >
35
45
  <span dangerouslySetInnerHTML={{ __html: col.label }} />
@@ -94,10 +104,10 @@ function CsatMatrixVerticalList({
94
104
  onClick={() => onCellSelect(row.id, submitValue)}
95
105
  style={{
96
106
  width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'flex-start',
97
- borderRadius: '8px', border: `1px solid ${isSelected ? '#e20074' : '#d1d5db'}`,
107
+ borderRadius: '8px', border: `1px solid ${isSelected ? focusRingVar : '#d1d5db'}`,
98
108
  padding: '14px 18px', cursor: 'pointer', transition: 'all 0.15s',
99
- backgroundColor: isSelected ? '#fdf2f8' : '#fff',
100
- color: isSelected ? '#e20074' : '#374151',
109
+ backgroundColor: isSelected ? 'var(--cfm-mcq-selected-bg)' : '#fff',
110
+ color: isSelected ? focusRingVar : '#374151',
101
111
  fontWeight: isSelected ? 600 : 400, fontSize: '15px', textAlign: 'left',
102
112
  }}
103
113
  >
@@ -116,9 +126,9 @@ function CsatMatrixVerticalList({
116
126
  key={col.id}
117
127
  style={{
118
128
  display: 'flex', alignItems: 'center', gap: '16px',
119
- borderRadius: '8px', border: `1px solid ${isSelected ? '#e20074' : '#e5e5e5'}`,
129
+ borderRadius: '8px', border: `1px solid ${isSelected ? focusRingVar : '#e5e5e5'}`,
120
130
  padding: '16px 20px', cursor: 'pointer', transition: 'all 0.15s',
121
- backgroundColor: isSelected ? '#fdf2f8' : '#fff',
131
+ backgroundColor: isSelected ? 'var(--cfm-mcq-selected-bg)' : '#fff',
122
132
  }}
123
133
  >
124
134
  <input type="radio" name={`${question.id}-${row.id}`}
@@ -126,13 +136,8 @@ function CsatMatrixVerticalList({
126
136
  onChange={() => onCellSelect(row.id, submitValue)}
127
137
  style={{ position: 'absolute', width: 0, height: 0, opacity: 0 }}
128
138
  />
129
- <div style={{
130
- display: 'flex', width: '20px', height: '20px', flexShrink: 0,
131
- alignItems: 'center', justifyContent: 'center', borderRadius: '50%',
132
- border: isSelected ? '2px solid #e20074' : '1.5px solid #9ca3af',
133
- backgroundColor: '#fff',
134
- }}>
135
- {isSelected && <div style={{ width: '10px', height: '10px', borderRadius: '50%', backgroundColor: '#e20074' }} />}
139
+ <div style={matrixRadioRingStyle(isSelected, '20px')}>
140
+ {isSelected && <div style={matrixRadioDotStyle(isSelected, '10px')} />}
136
141
  </div>
137
142
  <span style={{ fontSize: '15px', fontWeight: 500, color: '#111827' }}
138
143
  dangerouslySetInnerHTML={{ __html: col.label }} />
@@ -146,7 +151,7 @@ function CsatMatrixVerticalList({
146
151
  <label style={{ display: 'flex', alignItems: 'center', gap: '12px', cursor: 'pointer', userSelect: 'none' }}>
147
152
  <input type="checkbox" checked={selectedValue[row.id] === null}
148
153
  onChange={() => onCellSelect(row.id, selectedValue[row.id] === null ? undefined : null)}
149
- style={{ width: '16px', height: '16px', accentColor: '#e20074', cursor: 'pointer' }} />
154
+ style={{ width: '16px', height: '16px', accentColor: focusRingVar, cursor: 'pointer' }} />
150
155
  <span style={{ fontSize: '14px', fontWeight: 500, color: '#4b5563' }}>Not Applicable</span>
151
156
  </label>
152
157
  )}
@@ -190,7 +195,7 @@ function CsatMatrixGrid({
190
195
  {/* Labels row */}
191
196
  {showLabels && m > 0 && (
192
197
  <div style={{ display: 'flex', alignItems: 'flex-end', marginBottom: '12px' }}>
193
- <div style={{ width: '180px', flexShrink: 0 }} />
198
+ <div style={{ width: 'var(--cfm-matrix-row-width, 180px)', flexShrink: 0 }} />
194
199
  <div style={{ flex: 1, display: 'flex' }}>
195
200
  <div style={{ flex: 1, position: 'relative', height: '24px', ...(isGraphics ? { margin: '0 40px' } : {}) }}>
196
201
  {labels!.map((label, i) => {
@@ -204,7 +209,8 @@ function CsatMatrixGrid({
204
209
  left: `${percent}%`,
205
210
  transform: 'translateX(-50%)',
206
211
  textAlign: 'center', fontSize: '13px', fontWeight: 600,
207
- color: '#4b5563', lineHeight: 1.3, whiteSpace: 'nowrap'
212
+ lineHeight: 1.3, whiteSpace: 'nowrap',
213
+ ...csatColumnLabelStyle(),
208
214
  }}>
209
215
  {label}
210
216
  </div>
@@ -219,7 +225,7 @@ function CsatMatrixGrid({
219
225
  {/* Column tick labels for graphics sliders (shown with or without anchor labels) */}
220
226
  {isGraphics && m > 0 && (
221
227
  <div style={{ display: 'flex', alignItems: 'flex-end', marginBottom: showLabels ? '8px' : '16px', minHeight: '24px' }}>
222
- <div style={{ width: '180px', flexShrink: 0 }} />
228
+ <div style={{ width: 'var(--cfm-matrix-row-width, 180px)', flexShrink: 0 }} />
223
229
  <div style={{ flex: 1, display: 'flex' }}>
224
230
  <GraphicsColumnLabelsRow scaleColumns={scaleColumns} />
225
231
  {hasNotApplicable && (
@@ -232,13 +238,13 @@ function CsatMatrixGrid({
232
238
  {/* Column Headers for non-graphics */}
233
239
  {!isGraphics && !showLabels && m > 0 && (
234
240
  <div style={{ display: 'flex', alignItems: 'flex-end', marginBottom: '16px', minHeight: '24px' }}>
235
- <div style={{ width: '180px', flexShrink: 0 }} />
241
+ <div style={{ width: 'var(--cfm-matrix-row-width, 180px)', flexShrink: 0 }} />
236
242
  <div style={{ flex: 1, display: 'flex' }}>
237
243
  <div style={{ flex: 1, display: 'grid', gridTemplateColumns: `repeat(${scaleColumns.length}, 1fr)` }}>
238
244
  {scaleColumns.map(col => (
239
245
  <div key={col.id} style={{ display: 'flex', justifyContent: 'center', padding: '0 4px' }}>
240
246
  <span
241
- style={{ fontSize: '13px', fontWeight: 500, color: '#4b5563', textAlign: 'center', lineHeight: 1.2 }}
247
+ style={{ fontSize: '13px', fontWeight: 500, textAlign: 'center', lineHeight: 1.2, ...csatColumnLabelStyle() }}
242
248
  dangerouslySetInnerHTML={{ __html: col.label }}
243
249
  />
244
250
  </div>
@@ -258,9 +264,9 @@ function CsatMatrixGrid({
258
264
  {statementRows.map((row, rowIdx) => (
259
265
  <div key={row.id} style={{
260
266
  display: 'flex', alignItems: 'center', borderRadius: '8px',
261
- padding: '12px 0', backgroundColor: rowIdx % 2 === 0 ? 'rgba(249, 250, 251, 0.8)' : '#fff',
267
+ padding: '12px 0', backgroundColor: rowIdx % 2 === 0 ? 'var(--cfm-zebra-row)' : '#fff',
262
268
  }}>
263
- <div style={{ width: '180px', flexShrink: 0, padding: '0 16px' }}>
269
+ <div style={{ width: 'var(--cfm-matrix-row-width, 180px)', flexShrink: 0, padding: '0 16px' }}>
264
270
  <span style={{ fontSize: '14px', fontWeight: 500, color: '#111827', lineHeight: 1.4 }}
265
271
  dangerouslySetInnerHTML={{ __html: row.statementText }} />
266
272
  </div>
@@ -298,13 +304,8 @@ function CsatMatrixGrid({
298
304
  const StarNode = isStar ? (isSelected ? (CsatStarIcons.filled as any) : (CsatStarIcons.empty as any)) : null;
299
305
 
300
306
  const RadioNode = (
301
- <div style={{
302
- display: 'flex', width: '18px', height: '18px',
303
- alignItems: 'center', justifyContent: 'center', borderRadius: '50%',
304
- margin: '0 auto', border: isSelected ? '2px solid #e20074' : '2px solid #d1d5db',
305
- backgroundColor: '#fff',
306
- }}>
307
- {isSelected && <div style={{ width: '8px', height: '8px', borderRadius: '50%', backgroundColor: '#e20074' }} />}
307
+ <div style={matrixRadioRingStyle(isSelected)}>
308
+ {isSelected && <div style={matrixRadioDotStyle(isSelected)} />}
308
309
  </div>
309
310
  );
310
311
 
@@ -324,23 +325,26 @@ function CsatMatrixGrid({
324
325
  cursor: 'pointer', borderRadius: '6px',
325
326
  ...(isEmoji ? {
326
327
  padding: '6px',
328
+ ...emojiSizeStyle(),
327
329
  transform: isSelected ? 'scale(1.1)' : isHovered ? 'scale(1.25)' : 'scale(1)',
328
330
  ...(isSelected ? {
329
- backgroundColor: 'rgba(252, 231, 243, 0.6)',
330
- boxShadow: '0 0 0 2px rgba(226, 0, 116, 0.3)',
331
+ backgroundColor: 'var(--cfm-mcq-selected-bg)',
332
+ boxShadow: `0 0 0 2px ${focusRingVar}`,
331
333
  borderRadius: '50%',
332
334
  } : {}),
333
335
  } : isStar ? {
334
336
  padding: '4px',
335
337
  transform: isHovered ? 'scale(1.1)' : 'scale(1)',
336
- opacity: isSelected ? 1 : isHovered ? 1 : 0.5,
338
+ ...(isSelected ? {} : unselectedOpacityStyle()),
339
+ opacity: isSelected ? 1 : isHovered ? 1 : undefined,
337
340
  } : isNumbered ? {
338
- width: '40px', height: '40px', borderRadius: '8px',
341
+ width: 'var(--cfm-csat-cell-size, 40px)', height: 'var(--cfm-csat-cell-size, 40px)', borderRadius: '8px',
339
342
  fontSize: '14px', fontWeight: 500,
340
- border: `1px solid ${isSelected ? '#e20074' : '#d1d5db'}`,
341
- backgroundColor: isSelected ? '#e20074' : '#fff',
343
+ border: `1px solid ${isSelected ? focusRingVar : '#d1d5db'}`,
344
+ backgroundColor: isSelected ? cellSelectedVar : '#fff',
342
345
  color: isSelected ? '#fff' : '#111827',
343
346
  transition: 'all 0.15s',
347
+ ...(isSelected ? scaleCellSelectedStyle(true) : {}),
344
348
  } : { padding: '8px' }),
345
349
  }}
346
350
  >
@@ -378,7 +382,7 @@ function CsatMatrixGrid({
378
382
  <label style={{ cursor: 'pointer' }}>
379
383
  <input type="checkbox" checked={selectedValue[row.id] === null}
380
384
  onChange={() => onCellSelect(row.id, selectedValue[row.id] === null ? undefined : null)}
381
- style={{ width: '16px', height: '16px', accentColor: '#e20074', cursor: 'pointer' }} />
385
+ style={{ width: '16px', height: '16px', accentColor: focusRingVar, cursor: 'pointer' }} />
382
386
  </label>
383
387
  </div>
384
388
  )}
@@ -437,7 +441,8 @@ function CsatMatrixCarousel({
437
441
  <div key={i} style={{
438
442
  position: 'absolute', left: `${percent}%`, transform: 'translateX(-50%)',
439
443
  textAlign: 'center', fontSize: '13px', fontWeight: 600,
440
- color: '#4b5563', lineHeight: 1.3, whiteSpace: 'nowrap'
444
+ lineHeight: 1.3, whiteSpace: 'nowrap',
445
+ ...csatColumnLabelStyle(),
441
446
  }}>
442
447
  {label}
443
448
  </div>
@@ -481,13 +486,8 @@ function CsatMatrixCarousel({
481
486
  const EmojiNode = isEmoji ? getEmojiForIndex(displayIdx, scaleColumns.length) : null;
482
487
  const StarNode = isStar ? (isSelected ? (CsatStarIcons.filled as any) : (CsatStarIcons.empty as any)) : null;
483
488
  const RadioNode = (
484
- <div style={{
485
- display: 'flex', width: '18px', height: '18px',
486
- alignItems: 'center', justifyContent: 'center', borderRadius: '50%',
487
- margin: '0 auto', border: isSelected ? '2px solid #e20074' : '2px solid #d1d5db',
488
- backgroundColor: '#fff',
489
- }}>
490
- {isSelected && <div style={{ width: '8px', height: '8px', borderRadius: '50%', backgroundColor: '#e20074' }} />}
489
+ <div style={matrixRadioRingStyle(isSelected)}>
490
+ {isSelected && <div style={matrixRadioDotStyle(isSelected)} />}
491
491
  </div>
492
492
  );
493
493
 
@@ -503,19 +503,23 @@ function CsatMatrixCarousel({
503
503
  transition: 'all 0.15s', border: 'none', backgroundColor: 'transparent',
504
504
  cursor: 'pointer', borderRadius: '6px', pointerEvents: 'none',
505
505
  ...(isEmoji ? {
506
- padding: '6px', transform: isSelected ? 'scale(1.1)' : isHovered ? 'scale(1.25)' : 'scale(1)',
507
- ...(isSelected ? { backgroundColor: 'rgba(252, 231, 243, 0.6)', boxShadow: '0 0 0 2px rgba(226, 0, 116, 0.3)', borderRadius: '50%' } : {}),
506
+ padding: '6px', ...emojiSizeStyle(),
507
+ transform: isSelected ? 'scale(1.1)' : isHovered ? 'scale(1.25)' : 'scale(1)',
508
+ ...(isSelected ? { backgroundColor: 'var(--cfm-mcq-selected-bg)', boxShadow: `0 0 0 2px ${focusRingVar}`, borderRadius: '50%' } : {}),
508
509
  } : isStar ? {
509
- padding: '4px', transform: isHovered ? 'scale(1.1)' : 'scale(1)', opacity: isSelected ? 1 : isHovered ? 1 : 0.5,
510
+ padding: '4px', transform: isHovered ? 'scale(1.1)' : 'scale(1)',
511
+ ...(isSelected ? {} : unselectedOpacityStyle()),
512
+ opacity: isSelected ? 1 : isHovered ? 1 : undefined,
510
513
  } : isNumbered ? {
511
- width: '40px', height: '40px', borderRadius: '8px', fontSize: '14px', fontWeight: 500,
512
- border: `1px solid ${isSelected ? '#e20074' : '#d1d5db'}`, backgroundColor: isSelected ? '#e20074' : '#fff', color: isSelected ? '#fff' : '#111827',
514
+ width: 'var(--cfm-csat-cell-size, 40px)', height: 'var(--cfm-csat-cell-size, 40px)', borderRadius: '8px', fontSize: '14px', fontWeight: 500,
515
+ border: `1px solid ${isSelected ? focusRingVar : '#d1d5db'}`, backgroundColor: isSelected ? cellSelectedVar : '#fff', color: isSelected ? '#fff' : '#111827',
516
+ ...(isSelected ? scaleCellSelectedStyle(true) : {}),
513
517
  } : { padding: '8px' }),
514
518
  }}
515
519
  >
516
520
  {(EmojiNode ?? StarNode ?? (isNumbered ? (displayIdx + 1) : RadioNode)) as React.ReactNode}
517
521
  </button>
518
- <span style={{ fontSize: '13px', fontWeight: 500, color: '#4b5563', textAlign: 'center', lineHeight: 1.2 }}
522
+ <span style={{ fontSize: '13px', fontWeight: 500, textAlign: 'center', lineHeight: 1.2, ...csatColumnLabelStyle() }}
519
523
  dangerouslySetInnerHTML={{ __html: col.label }} />
520
524
  </div>
521
525
  );
@@ -528,7 +532,7 @@ function CsatMatrixCarousel({
528
532
  <label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
529
533
  <input type="checkbox" checked={selectedValue[row.id] === null}
530
534
  onChange={() => onCellSelect(row.id, selectedValue[row.id] === null ? undefined : null)}
531
- style={{ width: '16px', height: '16px', accentColor: '#e20074', cursor: 'pointer' }} />
535
+ style={{ width: '16px', height: '16px', accentColor: focusRingVar, cursor: 'pointer' }} />
532
536
  <span style={{ fontSize: '14px', fontWeight: 500, color: '#4b5563' }}>N/A</span>
533
537
  </label>
534
538
  </div>
@@ -550,7 +554,7 @@ function CsatMatrixCarousel({
550
554
  <button key={i} type="button" onClick={() => setActiveCarouselIndex(i)}
551
555
  style={{
552
556
  width: i === activeCarouselIndex ? '20px' : '8px', height: '8px', borderRadius: '4px',
553
- backgroundColor: i === activeCarouselIndex ? '#e20074' : '#d1d5db', border: 'none', padding: 0,
557
+ backgroundColor: i === activeCarouselIndex ? cellSelectedVar : '#d1d5db', border: 'none', padding: 0,
554
558
  cursor: 'pointer', transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)'
555
559
  }} />
556
560
  ))}
@@ -590,7 +594,6 @@ export function CsatMatrixScale({ question, selectedValue = {}, onSelect }: Csat
590
594
  const isStar = displayStyle === 'star';
591
595
  const isNumbered = displayStyle === 'numbered';
592
596
  const isGraphics = displayStyle === 'graphics';
593
- const isStandardRadio = displayStyle === 'standard';
594
597
 
595
598
  const scaleColumns = nonNaScaleColumns(question.scaleColumns);
596
599
 
@@ -609,7 +612,7 @@ export function CsatMatrixScale({ question, selectedValue = {}, onSelect }: Csat
609
612
  );
610
613
  }
611
614
 
612
- if (isDropdown || isSelectBox || isStandardRadio) {
615
+ if (isDropdown || isSelectBox) {
613
616
  return (
614
617
  <CsatMatrixVerticalList
615
618
  question={question}