@explorer02/cfm-survey-sdk 0.3.4 → 0.3.6
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 -38
- package/dist/cli/index.mjs +46 -46
- package/package.json +1 -1
- package/templates/docs/00-integration/component-checklist.md +0 -1
- package/templates/docs/templates/CsatMatrixScale.tsx +67 -92
- package/templates/docs/templates/CustomSliderTrack.tsx +4 -6
- package/templates/docs/templates/Footer.tsx +42 -72
- package/templates/docs/templates/Header.tsx +59 -110
- package/templates/docs/templates/LikertMatrixScale.tsx +3 -3
- package/templates/docs/templates/SliderMatrixScale.tsx +6 -4
- package/templates/docs/templates/labelStyles.ts +40 -50
- package/templates/docs/templates/surveyUiScaleUtils.ts +17 -0
- package/templates/preview-harness/preview-bridge.inline.js +58 -5
- package/templates/preview-harness/vite-app/src/PreviewConfigContext.tsx +8 -2
- package/templates/preview-harness/vite-app/src/fixtures/questions.ts +9 -18
- package/templates/survey-theme.css +18 -37
- package/templates/wizard-dist/assets/{PreviewMock-CC1UlWe-.js → PreviewMock-AeUxzA3b.js} +1 -1
- package/templates/wizard-dist/assets/TypePanel-B4UpxOhr.js +1 -0
- package/templates/wizard-dist/assets/index-CsragJbi.css +1 -0
- package/templates/wizard-dist/assets/index-Z7mePXvc.js +34 -0
- package/templates/wizard-dist/index.html +2 -2
- package/templates/wizard-dist/assets/TypePanel-DISCXm0z.js +0 -1
- package/templates/wizard-dist/assets/index-Blpq4QjK.js +0 -34
- package/templates/wizard-dist/assets/index-CVBd54V0.css +0 -1
package/package.json
CHANGED
|
@@ -117,7 +117,6 @@ Read: [`wizard-chrome-contract.md`](wizard-chrome-contract.md), [`agent-executio
|
|
|
117
117
|
- [ ] Draft `./survey-ui-config.json` synced with mockup branding
|
|
118
118
|
- [ ] `previewBridge.ts` + `PreviewBridgeInit` in root layout
|
|
119
119
|
- [ ] Header/Footer/ProgressBar/SurveyPage use `var(--cfm-*)` — no hardcoded brand hex for wizard-controlled tokens
|
|
120
|
-
- [ ] **Footer logo:** `Footer.tsx` must call `getLogoSrc()` (same `global.logo.fileName` as header) — never hardcode `./logo.svg` or a separate footer upload path
|
|
121
120
|
- [ ] `data-cfm-logo` / `data-cfm-logo-text`, `data-cfm-company`, `data-cfm-survey-title`, `data-cfm-btn-next`, `data-cfm-btn-back`, `data-cfm-thank-you` on chrome
|
|
122
121
|
- [ ] **Layout toggles:** `data-cfm-progress`, `data-cfm-question-number`, `data-cfm-required`, `cfm-footer`, `cfm-header` — see [`wizard-chrome-contract.md`](wizard-chrome-contract.md)
|
|
123
122
|
- [ ] Per-type scale vars per `wizard-question-type-styling.md` for **all 11 types** (wizard exports all; survey inventory may be smaller)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import type { CsatQuestion, RatingMatrixQuestion, MatrixRowAnswers, ScaleColumn } from '@explorer02/cfm-survey-sdk';
|
|
3
3
|
import { getEmojiForIndex, CsatStarIcons } from './surveyUiIcons';
|
|
4
|
-
import { columnSubmitValue, nonNaScaleColumns } from './surveyUiScaleUtils';
|
|
4
|
+
import { columnSubmitValue, nonNaScaleColumns, anchorLabelPercent } from './surveyUiScaleUtils';
|
|
5
5
|
import { CustomSliderTrack } from './CustomSliderTrack';
|
|
6
6
|
import MatrixDropdown from './MatrixDropdown';
|
|
7
7
|
import {
|
|
@@ -16,20 +16,34 @@ import {
|
|
|
16
16
|
mcqSelectedAccentVar,
|
|
17
17
|
mcqSelectedBgVar,
|
|
18
18
|
} from '@/lib/surveyUi/selectionStyles';
|
|
19
|
-
import {
|
|
19
|
+
import { csatColumnLabelStyle, ratingColumnLabelStyle } from '@/lib/surveyUi/labelStyles';
|
|
20
20
|
|
|
21
21
|
const MATRIX_ROW_LABEL_WIDTH = 'var(--cfm-matrix-row-width, 180px)';
|
|
22
22
|
|
|
23
23
|
type CsatMatrixQuestion = CsatQuestion | RatingMatrixQuestion;
|
|
24
24
|
|
|
25
|
+
function anchorLabelStyle(question: CsatMatrixQuestion) {
|
|
26
|
+
return question.type === 'RATING_MATRIX' ? ratingColumnLabelStyle() : csatColumnLabelStyle();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function columnHeaderLabelStyle(question: CsatMatrixQuestion) {
|
|
30
|
+
if (question.type === 'RATING_MATRIX') {
|
|
31
|
+
return { fontSize: '13px', fontWeight: 500, textAlign: 'center' as const, lineHeight: 1.2, color: '#4b5563' };
|
|
32
|
+
}
|
|
33
|
+
return { fontSize: '13px', fontWeight: 500, textAlign: 'center' as const, lineHeight: 1.2, ...csatColumnLabelStyle() };
|
|
34
|
+
}
|
|
35
|
+
|
|
25
36
|
function GraphicsColumnLabelsRow({
|
|
26
37
|
scaleColumns,
|
|
27
|
-
|
|
38
|
+
question,
|
|
28
39
|
}: {
|
|
29
40
|
scaleColumns: ScaleColumn[];
|
|
30
|
-
|
|
41
|
+
question: CsatMatrixQuestion;
|
|
31
42
|
}) {
|
|
32
43
|
if (scaleColumns.length === 0) return null;
|
|
44
|
+
const labelStyle = question.type === 'RATING_MATRIX'
|
|
45
|
+
? { fontSize: '12px', fontWeight: 500, lineHeight: 1.2, wordBreak: 'break-word' as const, color: '#4b5563' }
|
|
46
|
+
: { fontSize: '12px', fontWeight: 500, lineHeight: 1.2, wordBreak: 'break-word' as const, ...csatColumnLabelStyle() };
|
|
33
47
|
|
|
34
48
|
return (
|
|
35
49
|
<div style={{ flex: 1, padding: '0 24px' }}>
|
|
@@ -48,10 +62,7 @@ function GraphicsColumnLabelsRow({
|
|
|
48
62
|
width: '64px',
|
|
49
63
|
}}
|
|
50
64
|
>
|
|
51
|
-
<span
|
|
52
|
-
style={{ fontSize: '12px', fontWeight: 500, ...columnLabelPillStyle(pillScope) }}
|
|
53
|
-
dangerouslySetInnerHTML={{ __html: col.label }}
|
|
54
|
-
/>
|
|
65
|
+
<span style={labelStyle} dangerouslySetInnerHTML={{ __html: col.label }} />
|
|
55
66
|
</div>
|
|
56
67
|
);
|
|
57
68
|
})}
|
|
@@ -193,35 +204,33 @@ function CsatMatrixGrid({
|
|
|
193
204
|
isGraphics
|
|
194
205
|
}: GridProps) {
|
|
195
206
|
const { statementRows, scaleAnchorLabels: labels, hasNotApplicableOption: hasNotApplicable, reverseScaleOrder } = question;
|
|
196
|
-
const isRating = question.type === 'RATING_MATRIX';
|
|
197
207
|
const [hoveredCell, setHoveredCell] = useState<string | null>(null);
|
|
198
208
|
|
|
199
|
-
const
|
|
209
|
+
const showLabels = labels && labels.length > 0;
|
|
200
210
|
const m = scaleColumns.length;
|
|
201
211
|
const n = labels?.length || 0;
|
|
202
212
|
|
|
203
213
|
return (
|
|
204
214
|
<div style={{ width: '100%' }}>
|
|
205
|
-
{/*
|
|
206
|
-
{
|
|
215
|
+
{/* Labels row */}
|
|
216
|
+
{showLabels && m > 0 && (
|
|
207
217
|
<div style={{ display: 'flex', alignItems: 'flex-end', marginBottom: '12px' }}>
|
|
208
218
|
<div style={{ width: MATRIX_ROW_LABEL_WIDTH, flexShrink: 0 }} />
|
|
209
219
|
<div style={{ flex: 1, display: 'flex' }}>
|
|
210
|
-
<div style={{ flex: 1, position: 'relative', height: '
|
|
220
|
+
<div style={{ flex: 1, position: 'relative', height: '24px', ...(isGraphics ? { margin: '0 40px' } : {}) }}>
|
|
211
221
|
{labels!.map((label, i) => {
|
|
212
|
-
const
|
|
213
|
-
const rangePercent = isGraphics ? 1 : (m - 1) / m;
|
|
214
|
-
const percent = n === 1 ? 50 : (startPercent + (i / (n - 1)) * rangePercent) * 100;
|
|
222
|
+
const percent = anchorLabelPercent(i, n, m, isGraphics);
|
|
215
223
|
|
|
216
224
|
return (
|
|
217
225
|
<div key={i} style={{
|
|
218
226
|
position: 'absolute',
|
|
219
227
|
left: `${percent}%`,
|
|
220
228
|
transform: 'translateX(-50%)',
|
|
229
|
+
textAlign: 'center', fontSize: '13px', fontWeight: 600,
|
|
230
|
+
lineHeight: 1.3, whiteSpace: 'nowrap',
|
|
231
|
+
...anchorLabelStyle(question),
|
|
221
232
|
}}>
|
|
222
|
-
|
|
223
|
-
{label}
|
|
224
|
-
</span>
|
|
233
|
+
{label}
|
|
225
234
|
</div>
|
|
226
235
|
);
|
|
227
236
|
})}
|
|
@@ -231,12 +240,12 @@ function CsatMatrixGrid({
|
|
|
231
240
|
</div>
|
|
232
241
|
)}
|
|
233
242
|
|
|
234
|
-
{/* Column tick labels for graphics sliders */}
|
|
243
|
+
{/* Column tick labels for graphics sliders (shown with or without anchor labels) */}
|
|
235
244
|
{isGraphics && m > 0 && (
|
|
236
|
-
<div style={{ display: 'flex', alignItems: 'flex-end', marginBottom:
|
|
245
|
+
<div style={{ display: 'flex', alignItems: 'flex-end', marginBottom: showLabels ? '8px' : '16px', minHeight: '24px' }}>
|
|
237
246
|
<div style={{ width: MATRIX_ROW_LABEL_WIDTH, flexShrink: 0 }} />
|
|
238
247
|
<div style={{ flex: 1, display: 'flex' }}>
|
|
239
|
-
<GraphicsColumnLabelsRow scaleColumns={scaleColumns}
|
|
248
|
+
<GraphicsColumnLabelsRow scaleColumns={scaleColumns} question={question} />
|
|
240
249
|
{hasNotApplicable && (
|
|
241
250
|
<div style={{ width: '56px', flexShrink: 0 }} />
|
|
242
251
|
)}
|
|
@@ -244,8 +253,8 @@ function CsatMatrixGrid({
|
|
|
244
253
|
</div>
|
|
245
254
|
)}
|
|
246
255
|
|
|
247
|
-
{/* Column
|
|
248
|
-
{!isGraphics && m > 0 && (
|
|
256
|
+
{/* Column Headers for non-graphics — star format always shows labels above stars */}
|
|
257
|
+
{!isGraphics && (!showLabels || isStar) && m > 0 && (
|
|
249
258
|
<div style={{ display: 'flex', alignItems: 'flex-end', marginBottom: '16px', minHeight: '24px' }}>
|
|
250
259
|
<div style={{ width: MATRIX_ROW_LABEL_WIDTH, flexShrink: 0 }} />
|
|
251
260
|
<div style={{ flex: 1, display: 'flex' }}>
|
|
@@ -253,13 +262,7 @@ function CsatMatrixGrid({
|
|
|
253
262
|
{scaleColumns.map(col => (
|
|
254
263
|
<div key={col.id} style={{ display: 'flex', justifyContent: 'center', padding: '0 4px' }}>
|
|
255
264
|
<span
|
|
256
|
-
style={
|
|
257
|
-
fontSize: '13px',
|
|
258
|
-
fontWeight: 500,
|
|
259
|
-
...(isRating
|
|
260
|
-
? { color: 'var(--cfm-text, #374151)', textAlign: 'center' as const }
|
|
261
|
-
: columnLabelPillStyle('csat')),
|
|
262
|
-
}}
|
|
265
|
+
style={columnHeaderLabelStyle(question)}
|
|
263
266
|
dangerouslySetInnerHTML={{ __html: col.label }}
|
|
264
267
|
/>
|
|
265
268
|
</div>
|
|
@@ -303,16 +306,6 @@ function CsatMatrixGrid({
|
|
|
303
306
|
ticks={scaleColumns.length}
|
|
304
307
|
tickLabels={scaleColumns.map(col => col.label)}
|
|
305
308
|
reverseScaleOrder={reverseScaleOrder}
|
|
306
|
-
trackVar={
|
|
307
|
-
isRating
|
|
308
|
-
? 'var(--cfm-rating-track, var(--cfm-slider-track, #e5e7eb))'
|
|
309
|
-
: 'var(--cfm-csat-track, var(--cfm-slider-track, #e5e7eb))'
|
|
310
|
-
}
|
|
311
|
-
thumbVar={
|
|
312
|
-
isRating
|
|
313
|
-
? 'var(--cfm-rating-thumb, var(--cfm-slider-thumb, var(--cfm-primary)))'
|
|
314
|
-
: 'var(--cfm-csat-thumb, var(--cfm-slider-thumb, var(--cfm-primary)))'
|
|
315
|
-
}
|
|
316
309
|
onChange={v => onCellSelect(row.id, columnSubmitValue(scaleColumns[v]))}
|
|
317
310
|
/>
|
|
318
311
|
</div>
|
|
@@ -367,9 +360,17 @@ function CsatMatrixGrid({
|
|
|
367
360
|
fontSize: '14px', fontWeight: 500,
|
|
368
361
|
...(isSelected
|
|
369
362
|
? scaleCellSelectedStyle(true)
|
|
370
|
-
: {
|
|
363
|
+
: {
|
|
364
|
+
border: '1px solid #d1d5db',
|
|
365
|
+
backgroundColor: '#fff',
|
|
366
|
+
color: '#111827',
|
|
367
|
+
...unselectedOpacityStyle(),
|
|
368
|
+
}),
|
|
371
369
|
transition: 'all 0.15s',
|
|
372
|
-
} : {
|
|
370
|
+
} : {
|
|
371
|
+
padding: '8px',
|
|
372
|
+
...(isSelected ? {} : unselectedOpacityStyle()),
|
|
373
|
+
}),
|
|
373
374
|
}}
|
|
374
375
|
>
|
|
375
376
|
{(EmojiNode ?? StarNode ?? (isNumbered ? (displayIdx + 1) : RadioNode)) as React.ReactNode}
|
|
@@ -440,64 +441,42 @@ function CsatMatrixCarousel({
|
|
|
440
441
|
isGraphics
|
|
441
442
|
}: CarouselProps) {
|
|
442
443
|
const { statementRows, scaleAnchorLabels: labels, hasNotApplicableOption: hasNotApplicable, reverseScaleOrder } = question;
|
|
443
|
-
const isRating = question.type === 'RATING_MATRIX';
|
|
444
444
|
const [activeCarouselIndex, setActiveCarouselIndex] = useState(0);
|
|
445
445
|
const [hoveredCell, setHoveredCell] = useState<string | null>(null);
|
|
446
446
|
|
|
447
447
|
const row = statementRows[activeCarouselIndex];
|
|
448
448
|
if (!row) return null;
|
|
449
|
-
|
|
450
|
-
const
|
|
451
|
-
const trackVar = isRating
|
|
452
|
-
? 'var(--cfm-rating-track, var(--cfm-slider-track, #e5e7eb))'
|
|
453
|
-
: 'var(--cfm-csat-track, var(--cfm-slider-track, #e5e7eb))';
|
|
454
|
-
const thumbVar = isRating
|
|
455
|
-
? 'var(--cfm-rating-thumb, var(--cfm-slider-thumb, var(--cfm-primary)))'
|
|
456
|
-
: 'var(--cfm-csat-thumb, var(--cfm-slider-thumb, var(--cfm-primary)))';
|
|
449
|
+
|
|
450
|
+
const showLabels = labels && labels.length > 0;
|
|
457
451
|
|
|
458
452
|
return (
|
|
459
453
|
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', backgroundColor: '#fff', borderRadius: '12px', border: '1px solid #e5e5e5', padding: '32px', boxShadow: '0 4px 6px -1px rgba(0,0,0,0.05)', width: '100%', boxSizing: 'border-box' }}>
|
|
460
454
|
<h3 style={{ fontSize: '18px', fontWeight: 500, color: '#111827', marginBottom: '32px', textAlign: 'center' }} dangerouslySetInnerHTML={{ __html: row.statementText }} />
|
|
461
455
|
|
|
462
|
-
{
|
|
463
|
-
<div style={{ width: '100%', maxWidth: '800px', display: 'flex', position: 'relative', height: '
|
|
456
|
+
{showLabels && scaleColumns.length > 0 && (
|
|
457
|
+
<div style={{ width: '100%', maxWidth: '800px', display: 'flex', position: 'relative', height: '24px', marginBottom: '16px' }}>
|
|
464
458
|
{labels!.map((label, i) => {
|
|
465
459
|
const m = scaleColumns.length;
|
|
466
460
|
const n = labels!.length;
|
|
467
|
-
const
|
|
468
|
-
const rangePercent = isGraphics ? 1 : (m - 1) / m;
|
|
469
|
-
const percent = n === 1 ? 50 : (startPercent + (i / (n - 1)) * rangePercent) * 100;
|
|
461
|
+
const percent = anchorLabelPercent(i, n, m, isGraphics);
|
|
470
462
|
|
|
471
463
|
return (
|
|
472
464
|
<div key={i} style={{
|
|
473
465
|
position: 'absolute', left: `${percent}%`, transform: 'translateX(-50%)',
|
|
474
|
-
textAlign: 'center',
|
|
466
|
+
textAlign: 'center', fontSize: '13px', fontWeight: 600,
|
|
467
|
+
lineHeight: 1.3, whiteSpace: 'nowrap',
|
|
468
|
+
...anchorLabelStyle(question),
|
|
475
469
|
}}>
|
|
476
|
-
|
|
477
|
-
{label}
|
|
478
|
-
</span>
|
|
470
|
+
{label}
|
|
479
471
|
</div>
|
|
480
472
|
);
|
|
481
473
|
})}
|
|
482
474
|
</div>
|
|
483
475
|
)}
|
|
484
476
|
|
|
485
|
-
{!isGraphics && !isRating && scaleColumns.length > 0 && (
|
|
486
|
-
<div style={{ width: '100%', maxWidth: '800px', display: 'grid', gridTemplateColumns: `repeat(${scaleColumns.length}, 1fr)`, gap: '8px', marginBottom: '16px' }}>
|
|
487
|
-
{scaleColumns.map(col => (
|
|
488
|
-
<div key={col.id} style={{ display: 'flex', justifyContent: 'center' }}>
|
|
489
|
-
<span
|
|
490
|
-
style={{ fontSize: '13px', fontWeight: 500, ...columnLabelPillStyle('csat') }}
|
|
491
|
-
dangerouslySetInnerHTML={{ __html: col.label }}
|
|
492
|
-
/>
|
|
493
|
-
</div>
|
|
494
|
-
))}
|
|
495
|
-
</div>
|
|
496
|
-
)}
|
|
497
|
-
|
|
498
477
|
{isGraphics && scaleColumns.length > 0 && (
|
|
499
|
-
<div style={{ width: '100%', maxWidth: '800px', marginBottom:
|
|
500
|
-
<GraphicsColumnLabelsRow scaleColumns={scaleColumns}
|
|
478
|
+
<div style={{ width: '100%', maxWidth: '800px', marginBottom: showLabels ? '8px' : '16px' }}>
|
|
479
|
+
<GraphicsColumnLabelsRow scaleColumns={scaleColumns} question={question} />
|
|
501
480
|
</div>
|
|
502
481
|
)}
|
|
503
482
|
|
|
@@ -515,8 +494,6 @@ function CsatMatrixCarousel({
|
|
|
515
494
|
sliderType="graphics" ticks={scaleColumns.length}
|
|
516
495
|
tickLabels={scaleColumns.map(col => col.label)}
|
|
517
496
|
reverseScaleOrder={reverseScaleOrder}
|
|
518
|
-
trackVar={trackVar}
|
|
519
|
-
thumbVar={thumbVar}
|
|
520
497
|
onChange={v => onCellSelect(row.id, columnSubmitValue(scaleColumns[v]))}
|
|
521
498
|
/>
|
|
522
499
|
</div>
|
|
@@ -550,29 +527,27 @@ function CsatMatrixCarousel({
|
|
|
550
527
|
cursor: 'pointer', borderRadius: '6px', pointerEvents: 'none',
|
|
551
528
|
...(isEmoji ? {
|
|
552
529
|
padding: '6px', transform: isSelected ? 'scale(1.1)' : isHovered ? 'scale(1.25)' : 'scale(1)',
|
|
553
|
-
...(isSelected ? { backgroundColor: 'rgba(252, 231, 243, 0.6)', boxShadow: '0 0 0 2px rgba(226, 0, 116, 0.3)', borderRadius: '50%' } :
|
|
530
|
+
...(isSelected ? { backgroundColor: 'rgba(252, 231, 243, 0.6)', boxShadow: '0 0 0 2px rgba(226, 0, 116, 0.3)', borderRadius: '50%' } : unselectedOpacityStyle()),
|
|
554
531
|
} : isStar ? {
|
|
555
|
-
padding: '4px', transform: isHovered ? 'scale(1.1)' : 'scale(1)',
|
|
532
|
+
padding: '4px', transform: isHovered ? 'scale(1.1)' : 'scale(1)', color: isSelected ? cellSelectedVar : focusRingVar,
|
|
533
|
+
...(isSelected ? {} : unselectedOpacityStyle()),
|
|
556
534
|
} : isNumbered ? {
|
|
557
535
|
width: '40px', height: '40px', borderRadius: '8px', fontSize: '14px', fontWeight: 500,
|
|
558
536
|
...(isSelected
|
|
559
537
|
? scaleCellSelectedStyle(true)
|
|
560
|
-
: {
|
|
561
|
-
|
|
538
|
+
: {
|
|
539
|
+
border: '1px solid #d1d5db',
|
|
540
|
+
backgroundColor: '#fff',
|
|
541
|
+
color: '#111827',
|
|
542
|
+
...unselectedOpacityStyle(),
|
|
543
|
+
}),
|
|
544
|
+
} : { padding: '8px', ...(isSelected ? {} : unselectedOpacityStyle()) }),
|
|
562
545
|
}}
|
|
563
546
|
>
|
|
564
547
|
{(EmojiNode ?? StarNode ?? (isNumbered ? (displayIdx + 1) : RadioNode)) as React.ReactNode}
|
|
565
548
|
</button>
|
|
566
|
-
<span
|
|
567
|
-
|
|
568
|
-
fontSize: '13px',
|
|
569
|
-
fontWeight: 500,
|
|
570
|
-
...(isRating
|
|
571
|
-
? { color: 'var(--cfm-text, #374151)', textAlign: 'center' as const }
|
|
572
|
-
: columnLabelPillStyle('csat')),
|
|
573
|
-
}}
|
|
574
|
-
dangerouslySetInnerHTML={{ __html: col.label }}
|
|
575
|
-
/>
|
|
549
|
+
<span style={columnHeaderLabelStyle(question)}
|
|
550
|
+
dangerouslySetInnerHTML={{ __html: col.label }} />
|
|
576
551
|
</div>
|
|
577
552
|
);
|
|
578
553
|
})}
|
|
@@ -22,8 +22,6 @@ type CustomSliderTrackProps = {
|
|
|
22
22
|
/** Pass scaleColumns.map(c => c.label) for visible column labels under slider */
|
|
23
23
|
tickLabels?: string[];
|
|
24
24
|
reverseScaleOrder?: boolean;
|
|
25
|
-
trackVar?: string;
|
|
26
|
-
thumbVar?: string;
|
|
27
25
|
onChange: (v: number) => void;
|
|
28
26
|
};
|
|
29
27
|
|
|
@@ -37,8 +35,6 @@ export function CustomSliderTrack({
|
|
|
37
35
|
ticks,
|
|
38
36
|
tickLabels,
|
|
39
37
|
reverseScaleOrder,
|
|
40
|
-
trackVar = 'var(--cfm-slider-track, #e5e7eb)',
|
|
41
|
-
thumbVar = 'var(--cfm-slider-thumb, var(--cfm-input-focus-ring, var(--cfm-primary)))',
|
|
42
38
|
onChange,
|
|
43
39
|
}: CustomSliderTrackProps) {
|
|
44
40
|
const [isHovered, setIsHovered] = React.useState(false);
|
|
@@ -54,7 +50,9 @@ export function CustomSliderTrack({
|
|
|
54
50
|
? tooltipLabel.replace(/<[^>]*>/g, '')
|
|
55
51
|
: String(Math.round(value));
|
|
56
52
|
|
|
57
|
-
const themeColor = disabled
|
|
53
|
+
const themeColor = disabled
|
|
54
|
+
? '#9ca3af'
|
|
55
|
+
: 'var(--cfm-slider-thumb, var(--cfm-input-focus-ring, var(--cfm-primary)))';
|
|
58
56
|
|
|
59
57
|
return (
|
|
60
58
|
<div
|
|
@@ -64,7 +62,7 @@ export function CustomSliderTrack({
|
|
|
64
62
|
>
|
|
65
63
|
<div style={{
|
|
66
64
|
position: 'absolute', top: '50%', left: 0, right: 0, transform: 'translateY(-50%)',
|
|
67
|
-
height: '4px', borderRadius: '2px', backgroundColor:
|
|
65
|
+
height: '4px', borderRadius: '2px', backgroundColor: 'var(--cfm-slider-track, #e5e7eb)',
|
|
68
66
|
}} />
|
|
69
67
|
|
|
70
68
|
<div style={{
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Wizard-ready Footer — copy to src/components/Footer.tsx
|
|
3
|
-
*
|
|
4
|
-
* Uses the same logo as Header via getLogoSrc(). Layout, alignment, and spacing
|
|
5
|
-
* are driven entirely by --cfm-footer-* CSS vars from survey-ui-config.
|
|
3
|
+
* Required: footer.cfm-footer, data-cfm-copyright, data-cfm-footer-links, data-cfm-footer-logo
|
|
6
4
|
*/
|
|
7
5
|
'use client';
|
|
8
6
|
|
|
@@ -31,84 +29,56 @@ export default function Footer() {
|
|
|
31
29
|
}}
|
|
32
30
|
>
|
|
33
31
|
<div
|
|
34
|
-
className="cfm-footer-inner mx-auto w-full
|
|
35
|
-
style={{
|
|
36
|
-
maxWidth: 'var(--cfm-max-width, 48rem)',
|
|
37
|
-
display: 'var(--cfm-footer-inner-display, grid)',
|
|
38
|
-
flexDirection: 'var(--cfm-footer-inner-direction, row)' as React.CSSProperties['flexDirection'],
|
|
39
|
-
gridTemplateColumns: '1fr auto 1fr',
|
|
40
|
-
gap: 'var(--cfm-footer-inner-gap, 24px)',
|
|
41
|
-
}}
|
|
32
|
+
className="cfm-footer-inner mx-auto w-full"
|
|
33
|
+
style={{ maxWidth: 'var(--cfm-max-width, 48rem)' }}
|
|
42
34
|
>
|
|
43
|
-
|
|
44
|
-
className="cfm-footer-logo-
|
|
45
|
-
style={{
|
|
46
|
-
gridColumn: 'var(--cfm-footer-logo-col, 1)',
|
|
47
|
-
justifySelf: 'var(--cfm-footer-logo-justify, start)',
|
|
48
|
-
display: 'flex',
|
|
49
|
-
flexDirection: 'column',
|
|
50
|
-
alignItems: 'var(--cfm-footer-logo-justify, start)',
|
|
51
|
-
gap: 'var(--cfm-footer-logo-copyright-gap, 12px)',
|
|
52
|
-
}}
|
|
53
|
-
>
|
|
54
|
-
{showLogo ? (
|
|
55
|
-
<div
|
|
56
|
-
className="cfm-footer-logo-slot"
|
|
57
|
-
style={{
|
|
58
|
-
width: 'var(--cfm-footer-logo-width, 120px)',
|
|
59
|
-
height: 'var(--cfm-footer-logo-height, 32px)',
|
|
60
|
-
display: 'flex',
|
|
61
|
-
alignItems: 'center',
|
|
62
|
-
justifyContent: 'var(--cfm-footer-logo-justify, start)',
|
|
63
|
-
}}
|
|
64
|
-
>
|
|
65
|
-
<img
|
|
66
|
-
data-cfm-footer-logo
|
|
67
|
-
data-cfm-logo
|
|
68
|
-
src={seedLogoSrc!}
|
|
69
|
-
alt=""
|
|
70
|
-
className="h-full w-full object-contain"
|
|
71
|
-
/>
|
|
72
|
-
</div>
|
|
73
|
-
) : (
|
|
35
|
+
{showLogo ? (
|
|
36
|
+
<div className="cfm-footer-logo-slot">
|
|
74
37
|
<img
|
|
75
38
|
data-cfm-footer-logo
|
|
76
39
|
data-cfm-logo
|
|
77
|
-
src=
|
|
40
|
+
src={seedLogoSrc!}
|
|
78
41
|
alt=""
|
|
79
|
-
|
|
42
|
+
className="object-contain"
|
|
43
|
+
style={{
|
|
44
|
+
width: 'var(--cfm-footer-logo-width, 120px)',
|
|
45
|
+
height: 'var(--cfm-footer-logo-height, 32px)',
|
|
46
|
+
}}
|
|
80
47
|
/>
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
48
|
+
</div>
|
|
49
|
+
) : (
|
|
50
|
+
<img
|
|
51
|
+
data-cfm-footer-logo
|
|
52
|
+
data-cfm-logo
|
|
53
|
+
src=""
|
|
54
|
+
alt=""
|
|
55
|
+
style={{ display: 'none' }}
|
|
56
|
+
/>
|
|
57
|
+
)}
|
|
58
|
+
|
|
59
|
+
{seedCopyright ? (
|
|
60
|
+
<p data-cfm-copyright className="cfm-footer-copyright text-xs">
|
|
87
61
|
{seedCopyright}
|
|
88
62
|
</p>
|
|
89
|
-
|
|
63
|
+
) : (
|
|
64
|
+
<p data-cfm-copyright className="cfm-footer-copyright text-xs" style={{ display: 'none' }} />
|
|
65
|
+
)}
|
|
90
66
|
|
|
91
|
-
|
|
92
|
-
data-cfm-footer-links
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
style={{ color: 'var(--cfm-footer-link, #9ca3af)' }}
|
|
107
|
-
>
|
|
108
|
-
{link.label}
|
|
109
|
-
</a>
|
|
110
|
-
))}
|
|
111
|
-
</nav>
|
|
67
|
+
{seedLinks.length > 0 ? (
|
|
68
|
+
<nav data-cfm-footer-links className="cfm-footer-links flex flex-wrap gap-x-6 gap-y-2 text-xs">
|
|
69
|
+
{seedLinks.map((link, i) => (
|
|
70
|
+
<a
|
|
71
|
+
key={`${link.label}-${i}`}
|
|
72
|
+
href={link.url || '#'}
|
|
73
|
+
className="cfm-footer-link transition-colors hover:opacity-80"
|
|
74
|
+
>
|
|
75
|
+
{link.label}
|
|
76
|
+
</a>
|
|
77
|
+
))}
|
|
78
|
+
</nav>
|
|
79
|
+
) : (
|
|
80
|
+
<nav data-cfm-footer-links className="cfm-footer-links" style={{ display: 'none' }} />
|
|
81
|
+
)}
|
|
112
82
|
</div>
|
|
113
83
|
</footer>
|
|
114
84
|
);
|