@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
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import React from 'react';
|
|
10
|
-
import { focusRingVar, cellSelectedVar } from '@/lib/surveyUi/selectionStyles';
|
|
11
10
|
import { getEmojiForIndex } from './surveyUiIcons';
|
|
12
11
|
|
|
13
12
|
type CustomSliderTrackProps = {
|
|
@@ -51,8 +50,7 @@ export function CustomSliderTrack({
|
|
|
51
50
|
? tooltipLabel.replace(/<[^>]*>/g, '')
|
|
52
51
|
: String(Math.round(value));
|
|
53
52
|
|
|
54
|
-
const
|
|
55
|
-
const thumbBorderColor = disabled ? 'var(--cfm-slider-tick, #9ca3af)' : focusRingVar;
|
|
53
|
+
const themeColor = disabled ? '#9ca3af' : 'var(--cfm-input-focus-ring, var(--cfm-primary))';
|
|
56
54
|
|
|
57
55
|
return (
|
|
58
56
|
<div
|
|
@@ -62,12 +60,12 @@ export function CustomSliderTrack({
|
|
|
62
60
|
>
|
|
63
61
|
<div style={{
|
|
64
62
|
position: 'absolute', top: '50%', left: 0, right: 0, transform: 'translateY(-50%)',
|
|
65
|
-
height: '4px', borderRadius: '2px', backgroundColor: 'var(--cfm-slider-
|
|
63
|
+
height: '4px', borderRadius: '2px', backgroundColor: 'var(--cfm-slider-track, #e5e7eb)',
|
|
66
64
|
}} />
|
|
67
65
|
|
|
68
66
|
<div style={{
|
|
69
67
|
position: 'absolute', top: '50%', left: 0, width: `${percentage}%`, transform: 'translateY(-50%)',
|
|
70
|
-
height: '4px', borderRadius: '2px', backgroundColor:
|
|
68
|
+
height: '4px', borderRadius: '2px', backgroundColor: themeColor,
|
|
71
69
|
transition: isDragging ? 'none' : 'width 0.15s ease-in-out',
|
|
72
70
|
}} />
|
|
73
71
|
|
|
@@ -78,7 +76,7 @@ export function CustomSliderTrack({
|
|
|
78
76
|
key={idx}
|
|
79
77
|
style={{
|
|
80
78
|
position: 'absolute', top: '50%', left: `${tickPct}%`, transform: 'translate(-50%, -50%)',
|
|
81
|
-
width: '1px', height: '8px', backgroundColor: '
|
|
79
|
+
width: '1px', height: '8px', backgroundColor: '#d1d5db', pointerEvents: 'none',
|
|
82
80
|
}}
|
|
83
81
|
/>
|
|
84
82
|
);
|
|
@@ -115,22 +113,15 @@ export function CustomSliderTrack({
|
|
|
115
113
|
) : (
|
|
116
114
|
<div style={{
|
|
117
115
|
position: 'absolute', top: '50%', left: `${percentage}%`, transform: 'translate(-50%, -50%)',
|
|
118
|
-
width: '18px', height: '18px', borderRadius: '50%',
|
|
119
|
-
border: `2.5px solid ${
|
|
120
|
-
backgroundColor: cellSelectedVar,
|
|
121
|
-
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
116
|
+
width: '18px', height: '18px', borderRadius: '50%', backgroundColor: '#ffffff',
|
|
117
|
+
border: `2.5px solid ${themeColor}`,
|
|
122
118
|
boxShadow: (isHovered || isDragging) && !disabled
|
|
123
|
-
? '0 0 0 6px
|
|
119
|
+
? '0 0 0 6px rgba(226, 0, 116, 0.2), 0 1px 3px rgba(0,0,0,0.2)'
|
|
124
120
|
: '0 1px 3px rgba(0,0,0,0.2)',
|
|
125
121
|
pointerEvents: 'none',
|
|
126
122
|
transition: isDragging ? 'none' : 'left 0.15s ease-in-out, box-shadow 0.15s, border-color 0.2s',
|
|
127
123
|
boxSizing: 'border-box',
|
|
128
|
-
}}
|
|
129
|
-
<div style={{
|
|
130
|
-
width: '33%', height: '33%', borderRadius: '50%',
|
|
131
|
-
backgroundColor: 'var(--cfm-slider-thumb, var(--cfm-primary))',
|
|
132
|
-
}} />
|
|
133
|
-
</div>
|
|
124
|
+
}} />
|
|
134
125
|
)}
|
|
135
126
|
|
|
136
127
|
{(isHovered || isDragging) && !disabled && (
|
|
@@ -8,7 +8,6 @@
|
|
|
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';
|
|
12
11
|
|
|
13
12
|
type UploadedFileAnswer = {
|
|
14
13
|
id: string;
|
|
@@ -87,15 +86,8 @@ function FileUploadDropzone({
|
|
|
87
86
|
onDrag: (e: React.DragEvent) => void;
|
|
88
87
|
onDrop: (e: React.DragEvent) => void;
|
|
89
88
|
}) {
|
|
90
|
-
const dropzoneStyleAttr =
|
|
91
|
-
typeof document !== 'undefined'
|
|
92
|
-
? document.documentElement.getAttribute('data-cfm-dropzone-style') ?? undefined
|
|
93
|
-
: undefined;
|
|
94
|
-
|
|
95
89
|
return (
|
|
96
90
|
<div
|
|
97
|
-
className="cfm-dropzone"
|
|
98
|
-
{...(dropzoneStyleAttr ? { 'data-cfm-dropzone-style': dropzoneStyleAttr } : {})}
|
|
99
91
|
onDragEnter={onDrag}
|
|
100
92
|
onDragOver={onDrag}
|
|
101
93
|
onDragLeave={onDrag}
|
|
@@ -106,10 +98,10 @@ function FileUploadDropzone({
|
|
|
106
98
|
flexDirection: 'column',
|
|
107
99
|
alignItems: 'center',
|
|
108
100
|
justifyContent: 'center',
|
|
109
|
-
border: `2px var(--cfm-upload-border-style, dashed)
|
|
110
|
-
borderRadius: '12px',
|
|
111
|
-
backgroundColor: isDragActive ? 'var(--cfm-mcq-selected-bg)' : 'var(--cfm-upload-fill)',
|
|
112
|
-
padding: 'var(--cfm-upload-padding)',
|
|
101
|
+
border: `2px ${isDragActive ? 'solid' : 'var(--cfm-upload-border-style, dashed)'} var(--cfm-upload-border-color, #d1d5db)`,
|
|
102
|
+
borderRadius: 'var(--cfm-border-radius, 12px)',
|
|
103
|
+
backgroundColor: isDragActive ? 'var(--cfm-mcq-selected-bg, #fdf2f8)' : 'var(--cfm-upload-fill, #f9fafb)',
|
|
104
|
+
padding: 'var(--cfm-upload-padding, 40px 24px)',
|
|
113
105
|
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
114
106
|
opacity: disabled ? 0.6 : 1,
|
|
115
107
|
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
|
+
* When no logo file: text fallback via data-cfm-logo-text (company name or empty).
|
|
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,30 +33,42 @@ export default function Header({ embedded = false }: HeaderProps) {
|
|
|
33
33
|
}}
|
|
34
34
|
>
|
|
35
35
|
<div
|
|
36
|
-
className="cfm-header-inner
|
|
36
|
+
className="cfm-header-inner grid h-full w-full items-center"
|
|
37
37
|
style={{
|
|
38
|
+
gridTemplateColumns: '1fr 1fr 1fr',
|
|
38
39
|
padding: `0 var(--cfm-header-padding-x, 40px)`,
|
|
39
40
|
}}
|
|
40
41
|
>
|
|
41
42
|
<div
|
|
42
|
-
className="cfm-header-brand flex
|
|
43
|
+
className="cfm-header-brand flex items-center"
|
|
43
44
|
style={{
|
|
45
|
+
gridColumn: 'var(--cfm-header-logo-col, 1)',
|
|
46
|
+
justifySelf: 'var(--cfm-header-logo-justify, start)',
|
|
44
47
|
gap: 'var(--cfm-header-brand-gap, 16px)',
|
|
45
48
|
}}
|
|
46
49
|
>
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
50
|
+
{hasLogoFile ? (
|
|
51
|
+
<img
|
|
52
|
+
data-cfm-logo
|
|
53
|
+
src={seedLogoSrc!}
|
|
54
|
+
alt={seedCompany || 'Logo'}
|
|
55
|
+
className="shrink-0 object-contain"
|
|
56
|
+
style={{
|
|
57
|
+
width: 'var(--cfm-header-logo-width, 120px)',
|
|
58
|
+
height: 'var(--cfm-header-logo-height, 120px)',
|
|
59
|
+
padding: 'var(--cfm-header-logo-padding, 10px)',
|
|
60
|
+
background: 'transparent',
|
|
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
|
+
)}
|
|
60
72
|
|
|
61
73
|
<span
|
|
62
74
|
data-cfm-logo-text
|
|
@@ -71,22 +83,26 @@ export default function Header({ embedded = false }: HeaderProps) {
|
|
|
71
83
|
{seedCompany}
|
|
72
84
|
</span>
|
|
73
85
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
<span
|
|
87
|
+
data-cfm-company
|
|
88
|
+
className="font-semibold"
|
|
89
|
+
style={{
|
|
90
|
+
display:
|
|
91
|
+
seedShowCompany && (hasLogoFile || seedCompany)
|
|
92
|
+
? hasLogoFile
|
|
93
|
+
? ''
|
|
94
|
+
: 'none'
|
|
95
|
+
: 'none',
|
|
96
|
+
marginTop: 'var(--cfm-header-company-margin-top, 0)',
|
|
97
|
+
marginLeft: 'var(--cfm-header-company-margin-left, 0)',
|
|
98
|
+
marginRight: 'var(--cfm-header-company-margin-right, 0)',
|
|
99
|
+
color: 'var(--cfm-header-company-color, var(--cfm-text))',
|
|
100
|
+
fontSize: 'var(--cfm-header-company-size, 18px)',
|
|
101
|
+
fontWeight: 'var(--cfm-header-company-weight, 600)',
|
|
102
|
+
}}
|
|
103
|
+
>
|
|
104
|
+
{seedCompany}
|
|
105
|
+
</span>
|
|
90
106
|
</div>
|
|
91
107
|
</div>
|
|
92
108
|
</header>
|
|
@@ -29,8 +29,8 @@ function HeatmapSpot({ spot }: { spot: { id: string; x: number; y: number } }) {
|
|
|
29
29
|
width: 'var(--cfm-heatmap-pin-size, 16px)',
|
|
30
30
|
height: 'var(--cfm-heatmap-pin-size, 16px)',
|
|
31
31
|
transform: 'translate(-50%, -50%)',
|
|
32
|
-
backgroundColor: 'var(--cfm-heatmap-pin)',
|
|
33
|
-
border: '2px solid var(--cfm-heatmap-pin-border,
|
|
32
|
+
backgroundColor: 'var(--cfm-heatmap-pin, #F97316)',
|
|
33
|
+
border: '2px solid var(--cfm-heatmap-pin-border, #fff)',
|
|
34
34
|
boxShadow: '0 1px 3px rgba(0,0,0,0.25)',
|
|
35
35
|
}}
|
|
36
36
|
/>
|
|
@@ -63,9 +63,6 @@ export function HeatmapScale({ question, selectedValue = [], onSelect }: Heatmap
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
onSelect([...spots, createNewSpot(x, y)]);
|
|
66
|
-
if (typeof window !== 'undefined' && window.parent !== window) {
|
|
67
|
-
window.parent.postMessage({ type: 'CFM_HEATMAP_PIN', pin: { x, y } }, '*');
|
|
68
|
-
}
|
|
69
66
|
}
|
|
70
67
|
|
|
71
68
|
function handleImageLoad() {
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import type { CfmMatrixQuestion, MatrixRowAnswers } from '@explorer02/cfm-survey-sdk';
|
|
3
|
-
import { matrixColumnLabelStyle } from '@/lib/surveyUi/labelStyles';
|
|
4
|
-
import {
|
|
5
|
-
cellSelectedVar,
|
|
6
|
-
focusRingVar,
|
|
7
|
-
matrixRadioDotStyle,
|
|
8
|
-
matrixRadioRingStyle,
|
|
9
|
-
} from '@/lib/surveyUi/selectionStyles';
|
|
10
3
|
import { columnSubmitValue } from './surveyUiScaleUtils';
|
|
11
4
|
import MatrixDropdown from './MatrixDropdown';
|
|
5
|
+
import { matrixColumnLabelStyle } from '@/lib/surveyUi/labelStyles';
|
|
6
|
+
|
|
7
|
+
const MATRIX_ROW_LABEL_WIDTH = 'var(--cfm-matrix-row-width, 180px)';
|
|
12
8
|
|
|
13
9
|
type LikertMatrixScaleProps = {
|
|
14
10
|
question: CfmMatrixQuestion;
|
|
@@ -118,8 +114,8 @@ function LikertMatrixCarouselLayout({
|
|
|
118
114
|
<div style={{
|
|
119
115
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
120
116
|
width: '18px', height: '18px', borderRadius: '4px',
|
|
121
|
-
border: selected ?
|
|
122
|
-
backgroundColor: selected ?
|
|
117
|
+
border: selected ? '2px solid #e20074' : '2px solid #d1d5db',
|
|
118
|
+
backgroundColor: selected ? '#e20074' : '#fff',
|
|
123
119
|
flexShrink: 0, transition: 'all 0.15s'
|
|
124
120
|
}}>
|
|
125
121
|
{selected && (
|
|
@@ -129,8 +125,13 @@ function LikertMatrixCarouselLayout({
|
|
|
129
125
|
)}
|
|
130
126
|
</div>
|
|
131
127
|
) : (
|
|
132
|
-
<div style={
|
|
133
|
-
|
|
128
|
+
<div style={{
|
|
129
|
+
display: 'flex', width: '18px', height: '18px', flexShrink: 0,
|
|
130
|
+
alignItems: 'center', justifyContent: 'center', borderRadius: '50%',
|
|
131
|
+
border: selected ? '2px solid #e20074' : '1.5px solid #9ca3af',
|
|
132
|
+
backgroundColor: '#fff',
|
|
133
|
+
}}>
|
|
134
|
+
{selected && <div style={{ width: '10px', height: '10px', borderRadius: '50%', backgroundColor: '#e20074' }} />}
|
|
134
135
|
</div>
|
|
135
136
|
)}
|
|
136
137
|
<span style={{ fontSize: '15px', fontWeight: 400, color: '#374151', userSelect: 'none' }} dangerouslySetInnerHTML={{ __html: col.label }} />
|
|
@@ -208,7 +209,7 @@ function LikertMatrixGridLayout({
|
|
|
208
209
|
<div style={{ display: 'flex', flexDirection: 'column', marginBottom: '16px' }}>
|
|
209
210
|
{hasLabels && (
|
|
210
211
|
<div style={{ display: 'flex', alignItems: 'flex-end', minHeight: '24px', marginBottom: '8px' }}>
|
|
211
|
-
<div style={{ flex:
|
|
212
|
+
<div style={{ flex: '0 0 25%', paddingRight: '16px', boxSizing: 'border-box' }} />
|
|
212
213
|
<div style={{ flex: 1, position: 'relative', height: '20px' }}>
|
|
213
214
|
{labels.map((lbl, idx) => {
|
|
214
215
|
let leftPos = '50%';
|
|
@@ -226,12 +227,12 @@ function LikertMatrixGridLayout({
|
|
|
226
227
|
);
|
|
227
228
|
})}
|
|
228
229
|
</div>
|
|
229
|
-
<div style={{ flex:
|
|
230
|
+
<div style={{ flex: '0 0 25%', paddingLeft: '16px', boxSizing: 'border-box' }} />
|
|
230
231
|
</div>
|
|
231
232
|
)}
|
|
232
233
|
{showColumnHeaders && (
|
|
233
234
|
<div style={{ display: 'flex', alignItems: 'flex-end', minHeight: '24px' }}>
|
|
234
|
-
<div style={{ flex: isBipolar ? '0 0
|
|
235
|
+
<div style={{ flex: isBipolar ? '0 0 25%' : `0 0 ${MATRIX_ROW_LABEL_WIDTH}`, paddingRight: '16px', boxSizing: 'border-box' }} />
|
|
235
236
|
<div style={{ flex: 1, display: 'flex' }}>
|
|
236
237
|
<div style={{ flex: 1, display: 'grid', gridTemplateColumns: `repeat(${gridCols.length}, 1fr)` }}>
|
|
237
238
|
{gridCols.map((col) => (
|
|
@@ -242,7 +243,7 @@ function LikertMatrixGridLayout({
|
|
|
242
243
|
))}
|
|
243
244
|
</div>
|
|
244
245
|
</div>
|
|
245
|
-
{isBipolar && <div style={{ flex:
|
|
246
|
+
{isBipolar && <div style={{ flex: '0 0 25%', paddingLeft: '16px', boxSizing: 'border-box' }} />}
|
|
246
247
|
</div>
|
|
247
248
|
)}
|
|
248
249
|
</div>
|
|
@@ -260,10 +261,10 @@ function LikertMatrixGridLayout({
|
|
|
260
261
|
{repeatColumnHeaders && renderHeader()}
|
|
261
262
|
<div style={{
|
|
262
263
|
display: 'flex', alignItems: 'center', borderRadius: '8px',
|
|
263
|
-
padding: '12px 0', backgroundColor: !repeatColumnHeaders && rowIdx % 2 === 0 ? '
|
|
264
|
+
padding: '12px 0', backgroundColor: !repeatColumnHeaders && rowIdx % 2 === 0 ? 'rgba(249, 250, 251, 0.8)' : '#fff',
|
|
264
265
|
boxSizing: 'border-box'
|
|
265
266
|
}}>
|
|
266
|
-
<div style={{ flex: isBipolar ? '0 0
|
|
267
|
+
<div style={{ flex: isBipolar ? '0 0 25%' : `0 0 ${MATRIX_ROW_LABEL_WIDTH}`, padding: '0 16px', boxSizing: 'border-box' }}>
|
|
267
268
|
<span style={{ fontSize: '14px', fontWeight: 500, color: '#111827', lineHeight: 1.4, wordWrap: 'break-word', display: 'block' }}
|
|
268
269
|
dangerouslySetInnerHTML={{ __html: transposeTable ? ('label' in rowItem ? rowItem.label : '') : ('statementText' in rowItem ? rowItem.statementText : '') }} />
|
|
269
270
|
</div>
|
|
@@ -281,45 +282,32 @@ function LikertMatrixGridLayout({
|
|
|
281
282
|
|
|
282
283
|
return (
|
|
283
284
|
<div key={colItem.id} style={{ display: 'flex', justifyContent: 'center' }}>
|
|
284
|
-
<
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
}}>
|
|
311
|
-
{selected && (
|
|
312
|
-
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round">
|
|
313
|
-
<polyline points="20 6 9 17 4 12" />
|
|
314
|
-
</svg>
|
|
315
|
-
)}
|
|
316
|
-
</div>
|
|
317
|
-
) : (
|
|
318
|
-
<div style={matrixRadioRingStyle(selected)}>
|
|
319
|
-
{selected && <div style={matrixRadioDotStyle(selected)} />}
|
|
320
|
-
</div>
|
|
321
|
-
)}
|
|
322
|
-
</button>
|
|
285
|
+
<label style={{
|
|
286
|
+
cursor: isDisabled ? 'not-allowed' : 'pointer',
|
|
287
|
+
opacity: isDisabled ? 0.4 : 1,
|
|
288
|
+
padding: '8px', display: 'flex', alignItems: 'center', justifyContent: 'center'
|
|
289
|
+
}}>
|
|
290
|
+
<input
|
|
291
|
+
type={isMultiple ? "checkbox" : "radio"}
|
|
292
|
+
checked={selected}
|
|
293
|
+
disabled={isDisabled}
|
|
294
|
+
onClick={(e) => {
|
|
295
|
+
if (!isMultiple && selected) {
|
|
296
|
+
onCellSelect(actualRowId, undefined);
|
|
297
|
+
}
|
|
298
|
+
}}
|
|
299
|
+
onChange={(e) => {
|
|
300
|
+
if (isMultiple || !selected) {
|
|
301
|
+
onCellSelect(actualRowId, actualColValue);
|
|
302
|
+
}
|
|
303
|
+
}}
|
|
304
|
+
style={{
|
|
305
|
+
width: '18px', height: '18px', accentColor: 'var(--cfm-input-focus-ring)',
|
|
306
|
+
cursor: isDisabled ? 'not-allowed' : 'pointer',
|
|
307
|
+
...(isMultiple ? { borderRadius: '4px' } : {})
|
|
308
|
+
}}
|
|
309
|
+
/>
|
|
310
|
+
</label>
|
|
323
311
|
</div>
|
|
324
312
|
);
|
|
325
313
|
})}
|
|
@@ -327,7 +315,7 @@ function LikertMatrixGridLayout({
|
|
|
327
315
|
</div>
|
|
328
316
|
|
|
329
317
|
{isBipolar && (
|
|
330
|
-
<div style={{ flex: '0 0
|
|
318
|
+
<div style={{ flex: '0 0 25%', padding: '0 16px', textAlign: 'right', boxSizing: 'border-box' }}>
|
|
331
319
|
<span style={{ fontSize: '14px', fontWeight: 500, color: '#111827', lineHeight: 1.4, wordWrap: 'break-word', display: 'block' }}
|
|
332
320
|
dangerouslySetInnerHTML={{ __html: transposeTable ? '' : ('oppositeStatementText' in rowItem ? rowItem.oppositeStatementText ?? '' : '') }} />
|
|
333
321
|
</div>
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
import React, { useState, useRef, useEffect } from 'react';
|
|
10
10
|
import type { ScaleColumn } from '@explorer02/cfm-survey-sdk';
|
|
11
11
|
import { matrixColumnStoredValue } from '@explorer02/cfm-survey-sdk';
|
|
12
|
-
import { cellSelectedVar, focusRingVar } from '@/lib/surveyUi/selectionStyles';
|
|
13
12
|
|
|
14
13
|
type MatrixDropdownProps = {
|
|
15
14
|
options: ScaleColumn[];
|
|
@@ -73,9 +72,9 @@ export default function MatrixDropdown({
|
|
|
73
72
|
style={{
|
|
74
73
|
display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: '8px',
|
|
75
74
|
width: '100%', padding: '10px 14px', backgroundColor: '#fff',
|
|
76
|
-
border: `1px solid ${isOpen ?
|
|
75
|
+
border: `1px solid ${isOpen ? '#e20074' : '#d1d5db'}`, borderRadius: '8px',
|
|
77
76
|
cursor: 'pointer', transition: 'all 0.2s', minHeight: '44px',
|
|
78
|
-
boxShadow: isOpen ? '0 0 0 3px
|
|
77
|
+
boxShadow: isOpen ? '0 0 0 3px rgba(226, 0, 116, 0.1)' : '0 1px 2px rgba(0,0,0,0.05)',
|
|
79
78
|
}}
|
|
80
79
|
>
|
|
81
80
|
{multiValues.length === 0 ? (
|
|
@@ -125,9 +124,9 @@ export default function MatrixDropdown({
|
|
|
125
124
|
style={{
|
|
126
125
|
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
|
127
126
|
width: '100%', padding: '14px 16px', backgroundColor: '#fff',
|
|
128
|
-
border: `1px solid ${isOpen ?
|
|
127
|
+
border: `1px solid ${isOpen ? '#e20074' : '#d1d5db'}`, borderRadius: '8px',
|
|
129
128
|
cursor: 'pointer', transition: 'all 0.2s',
|
|
130
|
-
boxShadow: isOpen ? '0 0 0 3px
|
|
129
|
+
boxShadow: isOpen ? '0 0 0 3px rgba(226, 0, 116, 0.1)' : '0 1px 2px rgba(0,0,0,0.05)',
|
|
131
130
|
}}
|
|
132
131
|
>
|
|
133
132
|
<span
|
|
@@ -166,12 +165,12 @@ export default function MatrixDropdown({
|
|
|
166
165
|
key={col.id}
|
|
167
166
|
style={{
|
|
168
167
|
padding: '12px 16px', cursor: isDisabled ? 'not-allowed' : 'pointer', transition: 'background-color 0.15s',
|
|
169
|
-
backgroundColor: isSelected ? '
|
|
168
|
+
backgroundColor: isSelected ? '#fdf2f8' : '#fff',
|
|
170
169
|
opacity: isDisabled ? 0.5 : 1,
|
|
171
170
|
display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '12px',
|
|
172
171
|
}}
|
|
173
|
-
onMouseEnter={e => { if (!isDisabled) e.currentTarget.style.backgroundColor = '
|
|
174
|
-
onMouseLeave={e => { if (!isDisabled) e.currentTarget.style.backgroundColor = isSelected ? '
|
|
172
|
+
onMouseEnter={e => { if (!isDisabled) e.currentTarget.style.backgroundColor = '#fdf2f8'; }}
|
|
173
|
+
onMouseLeave={e => { if (!isDisabled) e.currentTarget.style.backgroundColor = isSelected ? '#fdf2f8' : '#fff'; }}
|
|
175
174
|
onClick={e => {
|
|
176
175
|
e.preventDefault();
|
|
177
176
|
if (isDisabled) return;
|
|
@@ -188,8 +187,8 @@ export default function MatrixDropdown({
|
|
|
188
187
|
<div style={{
|
|
189
188
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
190
189
|
width: '18px', height: '18px', borderRadius: '4px',
|
|
191
|
-
border: isSelected ?
|
|
192
|
-
backgroundColor: isSelected ?
|
|
190
|
+
border: isSelected ? '2px solid #e20074' : '2px solid #d1d5db',
|
|
191
|
+
backgroundColor: isSelected ? '#e20074' : '#fff',
|
|
193
192
|
flexShrink: 0, transition: 'all 0.15s',
|
|
194
193
|
}}>
|
|
195
194
|
{isSelected && (
|
|
@@ -199,11 +198,11 @@ export default function MatrixDropdown({
|
|
|
199
198
|
)}
|
|
200
199
|
</div>
|
|
201
200
|
)}
|
|
202
|
-
<span style={{ fontSize: '15px', fontWeight: isSelected ? 600 : 400, color: isSelected ?
|
|
201
|
+
<span style={{ fontSize: '15px', fontWeight: isSelected ? 600 : 400, color: isSelected ? '#e20074' : '#374151', userSelect: 'none' }}
|
|
203
202
|
dangerouslySetInnerHTML={{ __html: col.label }} />
|
|
204
203
|
</div>
|
|
205
204
|
{!multiple && isSelected && (
|
|
206
|
-
<svg style={{ width: '18px', height: '18px', color:
|
|
205
|
+
<svg style={{ width: '18px', height: '18px', color: '#e20074', flexShrink: 0 }} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
207
206
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" />
|
|
208
207
|
</svg>
|
|
209
208
|
)}
|
|
@@ -19,6 +19,7 @@ import { FileUploadScale } from './FileUploadScale';
|
|
|
19
19
|
import { HeatmapScale } from './HeatmapScale';
|
|
20
20
|
import { RankOrderScale } from './RankOrderScale';
|
|
21
21
|
import RatingScale from './RatingScale';
|
|
22
|
+
import { hintLabelStyle } from '@/lib/surveyUi/labelStyles';
|
|
22
23
|
|
|
23
24
|
type QuestionProps = {
|
|
24
25
|
question: SurveyQuestion;
|
|
@@ -140,6 +141,29 @@ export default function Question({
|
|
|
140
141
|
|
|
141
142
|
{question.type === QUESTION_TYPE.NPS_SCALE && (
|
|
142
143
|
<div className="space-y-3">
|
|
144
|
+
{(question.minLabel || question.midLabel || question.maxLabel) && (
|
|
145
|
+
<div className="relative w-full h-6 text-xs font-semibold" style={hintLabelStyle()}>
|
|
146
|
+
<span
|
|
147
|
+
className="absolute left-0 top-0 max-w-[30%] text-left leading-tight"
|
|
148
|
+
dangerouslySetInnerHTML={{ __html: question.minLabel ?? '' }}
|
|
149
|
+
/>
|
|
150
|
+
{question.midLabel && (
|
|
151
|
+
<span
|
|
152
|
+
className="absolute top-0 -translate-x-1/2 text-center max-w-[40%] leading-tight"
|
|
153
|
+
style={{
|
|
154
|
+
left: `${question.midLabelIndex !== undefined && question.options.length > 1
|
|
155
|
+
? (question.midLabelIndex / (question.options.length - 1)) * 100
|
|
156
|
+
: 50}%`,
|
|
157
|
+
}}
|
|
158
|
+
dangerouslySetInnerHTML={{ __html: question.midLabel }}
|
|
159
|
+
/>
|
|
160
|
+
)}
|
|
161
|
+
<span
|
|
162
|
+
className="absolute right-0 top-0 max-w-[30%] text-right leading-tight"
|
|
163
|
+
dangerouslySetInnerHTML={{ __html: question.maxLabel ?? '' }}
|
|
164
|
+
/>
|
|
165
|
+
</div>
|
|
166
|
+
)}
|
|
143
167
|
<RatingScale
|
|
144
168
|
questionId={question.id}
|
|
145
169
|
options={question.options}
|
|
@@ -147,10 +171,6 @@ export default function Question({
|
|
|
147
171
|
onSelect={onSelect}
|
|
148
172
|
variant="nps"
|
|
149
173
|
buttonStyle={question.buttonStyle}
|
|
150
|
-
minLabel={question.minLabel}
|
|
151
|
-
midLabel={question.midLabel}
|
|
152
|
-
maxLabel={question.maxLabel}
|
|
153
|
-
midLabelIndex={question.midLabelIndex}
|
|
154
174
|
/>
|
|
155
175
|
</div>
|
|
156
176
|
)}
|
|
@@ -25,7 +25,6 @@ import {
|
|
|
25
25
|
verticalListSortingStrategy,
|
|
26
26
|
} from '@dnd-kit/sortable';
|
|
27
27
|
import { CSS } from '@dnd-kit/utilities';
|
|
28
|
-
import { focusRingVar } from '@/lib/surveyUi/selectionStyles';
|
|
29
28
|
import type {
|
|
30
29
|
RankOrderAnswers,
|
|
31
30
|
RankOrderOption,
|
|
@@ -121,35 +120,15 @@ function RankOrderOptionRow({
|
|
|
121
120
|
}: RankOrderOptionRowProps) {
|
|
122
121
|
return (
|
|
123
122
|
<div
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
alignItems: 'center',
|
|
127
|
-
gap: 'var(--cfm-rank-item-gap, 12px)',
|
|
128
|
-
borderRadius: '8px',
|
|
129
|
-
border: '1px solid #e5e5e5',
|
|
130
|
-
backgroundColor: 'var(--cfm-rank-item-bg, #fff)',
|
|
131
|
-
padding: 'var(--cfm-rank-item-padding, 12px 16px)',
|
|
132
|
-
transition: 'background-color 0.15s',
|
|
133
|
-
}}
|
|
134
|
-
className="hover:bg-gray-50/50"
|
|
123
|
+
className="flex items-center gap-3 rounded-lg border border-[#e5e5e5] px-4 py-3 transition-colors"
|
|
124
|
+
style={{ backgroundColor: 'var(--cfm-rank-item-bg, #fff)', padding: 'var(--cfm-rank-item-padding, 12px 16px)', marginBottom: 'var(--cfm-rank-item-gap, 12px)' }}
|
|
135
125
|
>
|
|
136
126
|
{rankSelect}
|
|
137
127
|
{dragHandle}
|
|
138
128
|
{rankBadge !== undefined && (
|
|
139
129
|
<span
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
height: '28px',
|
|
143
|
-
width: '28px',
|
|
144
|
-
flexShrink: 0,
|
|
145
|
-
alignItems: 'center',
|
|
146
|
-
justifyContent: 'center',
|
|
147
|
-
borderRadius: '9999px',
|
|
148
|
-
backgroundColor: 'var(--cfm-rank-badge)',
|
|
149
|
-
fontSize: '14px',
|
|
150
|
-
fontWeight: 600,
|
|
151
|
-
color: '#fff',
|
|
152
|
-
}}
|
|
130
|
+
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-sm font-semibold text-white"
|
|
131
|
+
style={{ backgroundColor: 'var(--cfm-rank-badge, var(--cfm-primary))' }}
|
|
153
132
|
>
|
|
154
133
|
{rankBadge}
|
|
155
134
|
</span>
|
|
@@ -174,7 +153,7 @@ function DropdownRankLayout({
|
|
|
174
153
|
};
|
|
175
154
|
|
|
176
155
|
return (
|
|
177
|
-
<div className="space-y-3"
|
|
156
|
+
<div className="space-y-3">
|
|
178
157
|
{question.options.map(option => {
|
|
179
158
|
const currentRank = rankAnswers[option.id];
|
|
180
159
|
|
|
@@ -188,19 +167,8 @@ function DropdownRankLayout({
|
|
|
188
167
|
aria-label={`Rank for ${option.optionLabel || option.id}`}
|
|
189
168
|
value={currentRank ?? ''}
|
|
190
169
|
onChange={event => handleRankChange(option.id, event.target.value)}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
width: '64px',
|
|
194
|
-
flexShrink: 0,
|
|
195
|
-
borderRadius: '4px',
|
|
196
|
-
border: '1px solid var(--cfm-rank-select-border, #d1d5db)',
|
|
197
|
-
backgroundColor: '#fff',
|
|
198
|
-
padding: '0 8px',
|
|
199
|
-
fontSize: '14px',
|
|
200
|
-
outline: 'none',
|
|
201
|
-
}}
|
|
202
|
-
onFocus={e => { e.currentTarget.style.borderColor = focusRingVar; }}
|
|
203
|
-
onBlur={e => { e.currentTarget.style.borderColor = 'var(--cfm-rank-select-border, #d1d5db)'; }}
|
|
170
|
+
className="h-10 w-16 shrink-0 rounded border bg-white px-2 text-sm outline-none"
|
|
171
|
+
style={{ borderColor: 'var(--cfm-rank-select-border, #d1d5db)' }}
|
|
204
172
|
>
|
|
205
173
|
<option value="">-</option>
|
|
206
174
|
{rankChoices.map(rank => (
|
|
@@ -257,8 +225,7 @@ function SortableRankItem({
|
|
|
257
225
|
<button
|
|
258
226
|
type="button"
|
|
259
227
|
aria-label={`Drag to reorder ${option.optionLabel || option.id}`}
|
|
260
|
-
className="flex h-10 w-8 shrink-0 cursor-grab items-center justify-center rounded active:cursor-grabbing"
|
|
261
|
-
style={{ color: 'var(--cfm-rank-handle, #6b7280)' }}
|
|
228
|
+
className="flex h-10 w-8 shrink-0 cursor-grab items-center justify-center rounded text-gray-500 hover:bg-gray-100 active:cursor-grabbing"
|
|
262
229
|
{...attributes}
|
|
263
230
|
{...listeners}
|
|
264
231
|
>
|
|
@@ -356,7 +323,7 @@ function DragDropRankLayout({
|
|
|
356
323
|
return (
|
|
357
324
|
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
|
|
358
325
|
<SortableContext items={orderedOptionIds} strategy={verticalListSortingStrategy}>
|
|
359
|
-
<div className="space-y-3"
|
|
326
|
+
<div className="space-y-3">
|
|
360
327
|
{orderedOptionIds.map((optionId, index) => {
|
|
361
328
|
const option = question.options.find(item => item.id === optionId);
|
|
362
329
|
if (!option) {
|