@explorer02/cfm-survey-sdk 0.2.8 → 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.
- package/dist/cli/index.js +38 -41
- package/dist/cli/index.mjs +36 -39
- 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 +2 -1
- package/templates/docs/00-integration/component-checklist.md +1 -0
- package/templates/docs/00-integration/ui-customization-wizard.md +29 -7
- package/templates/docs/00-integration/wizard-chrome-contract.md +11 -0
- package/templates/docs/00-integration/wizard-config-handoff.md +11 -9
- package/templates/docs/00-integration/wizard-post-customize-build.md +6 -0
- package/templates/docs/00-integration/wizard-question-type-styling.md +4 -3
- package/templates/docs/00-integration/wizard-troubleshooting.md +20 -4
- package/templates/docs/index.md +1 -1
- package/templates/docs/templates/CsatMatrixScale.tsx +56 -53
- package/templates/docs/templates/CustomSliderTrack.tsx +17 -8
- package/templates/docs/templates/FileUploadScale.tsx +11 -3
- package/templates/docs/templates/Header.tsx +32 -48
- package/templates/docs/templates/HeatmapScale.tsx +7 -4
- package/templates/docs/templates/LikertMatrixScale.tsx +58 -43
- package/templates/docs/templates/MatrixDropdown.tsx +12 -11
- package/templates/docs/templates/Question.tsx +4 -23
- package/templates/docs/templates/RankOrderScale.tsx +46 -6
- package/templates/docs/templates/RatingScale.tsx +38 -0
- package/templates/docs/templates/SliderMatrixScale.tsx +4 -3
- package/templates/docs/templates/verify-agent-build.sh +17 -0
- package/templates/preview-harness/preview-bridge.inline.js +10 -0
- package/templates/preview-harness/previewPages.ts +5 -7
- package/templates/preview-harness/vite-app/src/QuestionPreview.tsx +11 -6
- package/templates/preview-harness/vite-app/src/fixtures/questions.ts +2 -10
- package/templates/preview-harness/vite-app/src/preview-live-overrides.css +11 -6
- package/templates/previewBridge.ts +13 -0
- package/templates/survey-theme.css +17 -17
- package/templates/wizard-dist/assets/{PreviewMock-tTMvwuEy.js → PreviewMock-CysRws9G.js} +1 -1
- package/templates/wizard-dist/assets/TypePanel-DWX1LL3q.js +1 -0
- package/templates/wizard-dist/assets/index-DIuc7AU2.js +34 -0
- package/templates/wizard-dist/assets/index-pCM8n0FT.css +1 -0
- package/templates/wizard-dist/index.html +2 -2
- package/templates/wizard-dist/assets/TypePanel-B9Ls7BSq.js +0 -1
- package/templates/wizard-dist/assets/index-CjkIlOUI.css +0 -1
- package/templates/wizard-dist/assets/index-CoCrnnxt.js +0 -34
|
@@ -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.
|
|
34
|
-
3.
|
|
35
|
-
4.
|
|
36
|
-
5.
|
|
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
|
-
- [ ]
|
|
41
|
-
- [ ]
|
|
42
|
-
- [ ]
|
|
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
|
|
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 |
|
|
@@ -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
|
|
@@ -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
|
-
|
|
|
106
|
-
|
|
|
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 |
|
|
@@ -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** | `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 `
|
|
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
|
|
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
|
-
- [ ]
|
|
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
|
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 — prints WIZARD_LINK, WAITS until client saves or quits
|
|
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,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 ?
|
|
107
|
+
borderRadius: '8px', border: `1px solid ${isSelected ? focusRingVar : '#d1d5db'}`,
|
|
98
108
|
padding: '14px 18px', cursor: 'pointer', transition: 'all 0.15s',
|
|
99
|
-
backgroundColor: isSelected ? '
|
|
100
|
-
color: isSelected ?
|
|
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 ?
|
|
129
|
+
borderRadius: '8px', border: `1px solid ${isSelected ? focusRingVar : '#e5e5e5'}`,
|
|
120
130
|
padding: '16px 20px', cursor: 'pointer', transition: 'all 0.15s',
|
|
121
|
-
backgroundColor: isSelected ? '
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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,
|
|
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 ? '
|
|
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
|
-
|
|
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: '
|
|
330
|
-
boxShadow:
|
|
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
|
-
|
|
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 ?
|
|
341
|
-
backgroundColor: isSelected ?
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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',
|
|
507
|
-
|
|
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)',
|
|
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 ?
|
|
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,
|
|
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:
|
|
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 ?
|
|
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
|
|
615
|
+
if (isDropdown || isSelectBox) {
|
|
613
616
|
return (
|
|
614
617
|
<CsatMatrixVerticalList
|
|
615
618
|
question={question}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import React from 'react';
|
|
10
|
+
import { focusRingVar, cellSelectedVar } from '@/lib/surveyUi/selectionStyles';
|
|
10
11
|
import { getEmojiForIndex } from './surveyUiIcons';
|
|
11
12
|
|
|
12
13
|
type CustomSliderTrackProps = {
|
|
@@ -50,7 +51,8 @@ export function CustomSliderTrack({
|
|
|
50
51
|
? tooltipLabel.replace(/<[^>]*>/g, '')
|
|
51
52
|
: String(Math.round(value));
|
|
52
53
|
|
|
53
|
-
const
|
|
54
|
+
const trackColor = disabled ? 'var(--cfm-slider-tick, #9ca3af)' : 'var(--cfm-slider-track, var(--cfm-primary))';
|
|
55
|
+
const thumbBorderColor = disabled ? 'var(--cfm-slider-tick, #9ca3af)' : focusRingVar;
|
|
54
56
|
|
|
55
57
|
return (
|
|
56
58
|
<div
|
|
@@ -60,12 +62,12 @@ export function CustomSliderTrack({
|
|
|
60
62
|
>
|
|
61
63
|
<div style={{
|
|
62
64
|
position: 'absolute', top: '50%', left: 0, right: 0, transform: 'translateY(-50%)',
|
|
63
|
-
height: '4px', borderRadius: '2px', backgroundColor: '#e5e7eb',
|
|
65
|
+
height: '4px', borderRadius: '2px', backgroundColor: 'var(--cfm-slider-tick, #e5e7eb)',
|
|
64
66
|
}} />
|
|
65
67
|
|
|
66
68
|
<div style={{
|
|
67
69
|
position: 'absolute', top: '50%', left: 0, width: `${percentage}%`, transform: 'translateY(-50%)',
|
|
68
|
-
height: '4px', borderRadius: '2px', backgroundColor:
|
|
70
|
+
height: '4px', borderRadius: '2px', backgroundColor: trackColor,
|
|
69
71
|
transition: isDragging ? 'none' : 'width 0.15s ease-in-out',
|
|
70
72
|
}} />
|
|
71
73
|
|
|
@@ -76,7 +78,7 @@ export function CustomSliderTrack({
|
|
|
76
78
|
key={idx}
|
|
77
79
|
style={{
|
|
78
80
|
position: 'absolute', top: '50%', left: `${tickPct}%`, transform: 'translate(-50%, -50%)',
|
|
79
|
-
width: '1px', height: '8px', backgroundColor: '#d1d5db', pointerEvents: 'none',
|
|
81
|
+
width: '1px', height: '8px', backgroundColor: 'var(--cfm-slider-tick, #d1d5db)', pointerEvents: 'none',
|
|
80
82
|
}}
|
|
81
83
|
/>
|
|
82
84
|
);
|
|
@@ -113,15 +115,22 @@ export function CustomSliderTrack({
|
|
|
113
115
|
) : (
|
|
114
116
|
<div style={{
|
|
115
117
|
position: 'absolute', top: '50%', left: `${percentage}%`, transform: 'translate(-50%, -50%)',
|
|
116
|
-
width: '18px', height: '18px', borderRadius: '50%',
|
|
117
|
-
border: `2.5px solid ${
|
|
118
|
+
width: '18px', height: '18px', borderRadius: '50%',
|
|
119
|
+
border: `2.5px solid ${thumbBorderColor}`,
|
|
120
|
+
backgroundColor: cellSelectedVar,
|
|
121
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
118
122
|
boxShadow: (isHovered || isDragging) && !disabled
|
|
119
|
-
? '0 0 0 6px rgba(226, 0, 116, 0.2), 0 1px 3px rgba(0,0,0,0.2)'
|
|
123
|
+
? '0 0 0 6px var(--cfm-mcq-selected-bg, rgba(226, 0, 116, 0.2)), 0 1px 3px rgba(0,0,0,0.2)'
|
|
120
124
|
: '0 1px 3px rgba(0,0,0,0.2)',
|
|
121
125
|
pointerEvents: 'none',
|
|
122
126
|
transition: isDragging ? 'none' : 'left 0.15s ease-in-out, box-shadow 0.15s, border-color 0.2s',
|
|
123
127
|
boxSizing: 'border-box',
|
|
124
|
-
}}
|
|
128
|
+
}}>
|
|
129
|
+
<div style={{
|
|
130
|
+
width: '33%', height: '33%', borderRadius: '50%',
|
|
131
|
+
backgroundColor: 'var(--cfm-slider-thumb, var(--cfm-primary))',
|
|
132
|
+
}} />
|
|
133
|
+
</div>
|
|
125
134
|
)}
|
|
126
135
|
|
|
127
136
|
{(isHovered || isDragging) && !disabled && (
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import React, { useState, useRef } from 'react';
|
|
10
10
|
import type { FileUploadQuestion } from '@explorer02/cfm-survey-sdk';
|
|
11
|
+
import { focusRingVar } from '@/lib/surveyUi/selectionStyles';
|
|
11
12
|
|
|
12
13
|
type UploadedFileAnswer = {
|
|
13
14
|
id: string;
|
|
@@ -86,8 +87,15 @@ function FileUploadDropzone({
|
|
|
86
87
|
onDrag: (e: React.DragEvent) => void;
|
|
87
88
|
onDrop: (e: React.DragEvent) => void;
|
|
88
89
|
}) {
|
|
90
|
+
const dropzoneStyleAttr =
|
|
91
|
+
typeof document !== 'undefined'
|
|
92
|
+
? document.documentElement.getAttribute('data-cfm-dropzone-style') ?? undefined
|
|
93
|
+
: undefined;
|
|
94
|
+
|
|
89
95
|
return (
|
|
90
96
|
<div
|
|
97
|
+
className="cfm-dropzone"
|
|
98
|
+
{...(dropzoneStyleAttr ? { 'data-cfm-dropzone-style': dropzoneStyleAttr } : {})}
|
|
91
99
|
onDragEnter={onDrag}
|
|
92
100
|
onDragOver={onDrag}
|
|
93
101
|
onDragLeave={onDrag}
|
|
@@ -98,10 +106,10 @@ function FileUploadDropzone({
|
|
|
98
106
|
flexDirection: 'column',
|
|
99
107
|
alignItems: 'center',
|
|
100
108
|
justifyContent: 'center',
|
|
101
|
-
border: `2px dashed ${isDragActive ?
|
|
109
|
+
border: `2px var(--cfm-upload-border-style, dashed) ${isDragActive ? focusRingVar : 'var(--cfm-upload-border-color)'}`,
|
|
102
110
|
borderRadius: '12px',
|
|
103
|
-
backgroundColor: isDragActive ? '
|
|
104
|
-
padding: '
|
|
111
|
+
backgroundColor: isDragActive ? 'var(--cfm-mcq-selected-bg)' : 'var(--cfm-upload-fill)',
|
|
112
|
+
padding: 'var(--cfm-upload-padding)',
|
|
105
113
|
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
106
114
|
opacity: disabled ? 0.6 : 1,
|
|
107
115
|
textAlign: 'center',
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Wizard-ready Header — copy to src/components/Header.tsx
|
|
3
3
|
*
|
|
4
4
|
* Brand row: [logo image] [company name] — company sits after logo when both exist.
|
|
5
|
-
*
|
|
5
|
+
* Layout locked: logo left, name after (flex row only).
|
|
6
6
|
*
|
|
7
7
|
* After wizard save, logo src MUST come from survey-ui-config.json global.logo.fileName
|
|
8
8
|
* via uiConfig.ts — never hardcode telekom-logo.svg or change fileName manually.
|
|
@@ -33,42 +33,30 @@ export default function Header({ embedded = false }: HeaderProps) {
|
|
|
33
33
|
}}
|
|
34
34
|
>
|
|
35
35
|
<div
|
|
36
|
-
className="cfm-header-inner
|
|
36
|
+
className="cfm-header-inner flex h-full w-full items-center"
|
|
37
37
|
style={{
|
|
38
|
-
gridTemplateColumns: '1fr 1fr 1fr',
|
|
39
38
|
padding: `0 var(--cfm-header-padding-x, 40px)`,
|
|
40
39
|
}}
|
|
41
40
|
>
|
|
42
41
|
<div
|
|
43
|
-
className="cfm-header-brand flex items-center"
|
|
42
|
+
className="cfm-header-brand flex min-w-0 items-center"
|
|
44
43
|
style={{
|
|
45
|
-
gridColumn: 'var(--cfm-header-logo-col, 1)',
|
|
46
|
-
justifySelf: 'var(--cfm-header-logo-justify, start)',
|
|
47
44
|
gap: 'var(--cfm-header-brand-gap, 16px)',
|
|
48
45
|
}}
|
|
49
46
|
>
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
) : (
|
|
64
|
-
<img
|
|
65
|
-
data-cfm-logo
|
|
66
|
-
src=""
|
|
67
|
-
alt="Logo"
|
|
68
|
-
className="shrink-0 object-contain"
|
|
69
|
-
style={{ display: 'none' }}
|
|
70
|
-
/>
|
|
71
|
-
)}
|
|
47
|
+
<img
|
|
48
|
+
data-cfm-logo
|
|
49
|
+
src={hasLogoFile ? seedLogoSrc! : ''}
|
|
50
|
+
alt={seedCompany || 'Logo'}
|
|
51
|
+
className="shrink-0 object-contain"
|
|
52
|
+
style={{
|
|
53
|
+
display: hasLogoFile ? '' : 'none',
|
|
54
|
+
width: 'var(--cfm-header-logo-width, 120px)',
|
|
55
|
+
height: 'var(--cfm-header-logo-height, 120px)',
|
|
56
|
+
padding: 'var(--cfm-header-logo-padding, 0px)',
|
|
57
|
+
background: 'var(--cfm-header-logo-bg, transparent)',
|
|
58
|
+
}}
|
|
59
|
+
/>
|
|
72
60
|
|
|
73
61
|
<span
|
|
74
62
|
data-cfm-logo-text
|
|
@@ -83,26 +71,22 @@ export default function Header({ embedded = false }: HeaderProps) {
|
|
|
83
71
|
{seedCompany}
|
|
84
72
|
</span>
|
|
85
73
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}}
|
|
103
|
-
>
|
|
104
|
-
{seedCompany}
|
|
105
|
-
</span>
|
|
74
|
+
{seedShowCompany && seedCompany ? (
|
|
75
|
+
<span
|
|
76
|
+
data-cfm-company
|
|
77
|
+
className="truncate font-semibold"
|
|
78
|
+
style={{
|
|
79
|
+
display: hasLogoFile ? '' : 'none',
|
|
80
|
+
color: 'var(--cfm-header-company-color, var(--cfm-text))',
|
|
81
|
+
fontSize: 'var(--cfm-header-company-size, 18px)',
|
|
82
|
+
fontWeight: 'var(--cfm-header-company-weight, 600)',
|
|
83
|
+
}}
|
|
84
|
+
>
|
|
85
|
+
{seedCompany}
|
|
86
|
+
</span>
|
|
87
|
+
) : (
|
|
88
|
+
<span data-cfm-company className="hidden" aria-hidden />
|
|
89
|
+
)}
|
|
106
90
|
</div>
|
|
107
91
|
</div>
|
|
108
92
|
</header>
|