@explorer02/cfm-survey-sdk 0.3.4 → 0.3.5
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 +39 -39
- package/dist/cli/index.mjs +44 -44
- package/package.json +1 -1
- package/templates/docs/00-integration/component-checklist.md +0 -1
- package/templates/docs/templates/CsatMatrixScale.tsx +44 -79
- package/templates/docs/templates/CustomSliderTrack.tsx +4 -6
- package/templates/docs/templates/Footer.tsx +42 -72
- package/templates/docs/templates/Header.tsx +58 -110
- package/templates/docs/templates/LikertMatrixScale.tsx +3 -3
- package/templates/docs/templates/SliderMatrixScale.tsx +2 -2
- package/templates/docs/templates/labelStyles.ts +40 -50
- package/templates/preview-harness/vite-app/src/fixtures/questions.ts +9 -18
- package/templates/survey-theme.css +6 -37
- package/templates/wizard-dist/assets/{PreviewMock-CC1UlWe-.js → PreviewMock-CD4Euaen.js} +1 -1
- package/templates/wizard-dist/assets/TypePanel-BjEnhbSM.js +1 -0
- package/templates/wizard-dist/assets/index-CsragJbi.css +1 -0
- package/templates/wizard-dist/assets/index-DWWkuRUU.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)
|
|
@@ -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,21 +204,20 @@ 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
222
|
const startPercent = isGraphics ? 0 : 0.5 / m;
|
|
213
223
|
const rangePercent = isGraphics ? 1 : (m - 1) / m;
|
|
@@ -218,10 +228,11 @@ function CsatMatrixGrid({
|
|
|
218
228
|
position: 'absolute',
|
|
219
229
|
left: `${percent}%`,
|
|
220
230
|
transform: 'translateX(-50%)',
|
|
231
|
+
textAlign: 'center', fontSize: '13px', fontWeight: 600,
|
|
232
|
+
lineHeight: 1.3, whiteSpace: 'nowrap',
|
|
233
|
+
...anchorLabelStyle(question),
|
|
221
234
|
}}>
|
|
222
|
-
|
|
223
|
-
{label}
|
|
224
|
-
</span>
|
|
235
|
+
{label}
|
|
225
236
|
</div>
|
|
226
237
|
);
|
|
227
238
|
})}
|
|
@@ -231,12 +242,12 @@ function CsatMatrixGrid({
|
|
|
231
242
|
</div>
|
|
232
243
|
)}
|
|
233
244
|
|
|
234
|
-
{/* Column tick labels for graphics sliders */}
|
|
245
|
+
{/* Column tick labels for graphics sliders (shown with or without anchor labels) */}
|
|
235
246
|
{isGraphics && m > 0 && (
|
|
236
|
-
<div style={{ display: 'flex', alignItems: 'flex-end', marginBottom:
|
|
247
|
+
<div style={{ display: 'flex', alignItems: 'flex-end', marginBottom: showLabels ? '8px' : '16px', minHeight: '24px' }}>
|
|
237
248
|
<div style={{ width: MATRIX_ROW_LABEL_WIDTH, flexShrink: 0 }} />
|
|
238
249
|
<div style={{ flex: 1, display: 'flex' }}>
|
|
239
|
-
<GraphicsColumnLabelsRow scaleColumns={scaleColumns}
|
|
250
|
+
<GraphicsColumnLabelsRow scaleColumns={scaleColumns} question={question} />
|
|
240
251
|
{hasNotApplicable && (
|
|
241
252
|
<div style={{ width: '56px', flexShrink: 0 }} />
|
|
242
253
|
)}
|
|
@@ -244,8 +255,8 @@ function CsatMatrixGrid({
|
|
|
244
255
|
</div>
|
|
245
256
|
)}
|
|
246
257
|
|
|
247
|
-
{/* Column
|
|
248
|
-
{!isGraphics && m > 0 && (
|
|
258
|
+
{/* Column Headers for non-graphics — star format always shows labels above stars */}
|
|
259
|
+
{!isGraphics && (!showLabels || isStar) && m > 0 && (
|
|
249
260
|
<div style={{ display: 'flex', alignItems: 'flex-end', marginBottom: '16px', minHeight: '24px' }}>
|
|
250
261
|
<div style={{ width: MATRIX_ROW_LABEL_WIDTH, flexShrink: 0 }} />
|
|
251
262
|
<div style={{ flex: 1, display: 'flex' }}>
|
|
@@ -253,13 +264,7 @@ function CsatMatrixGrid({
|
|
|
253
264
|
{scaleColumns.map(col => (
|
|
254
265
|
<div key={col.id} style={{ display: 'flex', justifyContent: 'center', padding: '0 4px' }}>
|
|
255
266
|
<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
|
-
}}
|
|
267
|
+
style={columnHeaderLabelStyle(question)}
|
|
263
268
|
dangerouslySetInnerHTML={{ __html: col.label }}
|
|
264
269
|
/>
|
|
265
270
|
</div>
|
|
@@ -303,16 +308,6 @@ function CsatMatrixGrid({
|
|
|
303
308
|
ticks={scaleColumns.length}
|
|
304
309
|
tickLabels={scaleColumns.map(col => col.label)}
|
|
305
310
|
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
311
|
onChange={v => onCellSelect(row.id, columnSubmitValue(scaleColumns[v]))}
|
|
317
312
|
/>
|
|
318
313
|
</div>
|
|
@@ -440,27 +435,20 @@ function CsatMatrixCarousel({
|
|
|
440
435
|
isGraphics
|
|
441
436
|
}: CarouselProps) {
|
|
442
437
|
const { statementRows, scaleAnchorLabels: labels, hasNotApplicableOption: hasNotApplicable, reverseScaleOrder } = question;
|
|
443
|
-
const isRating = question.type === 'RATING_MATRIX';
|
|
444
438
|
const [activeCarouselIndex, setActiveCarouselIndex] = useState(0);
|
|
445
439
|
const [hoveredCell, setHoveredCell] = useState<string | null>(null);
|
|
446
440
|
|
|
447
441
|
const row = statementRows[activeCarouselIndex];
|
|
448
442
|
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)))';
|
|
443
|
+
|
|
444
|
+
const showLabels = labels && labels.length > 0;
|
|
457
445
|
|
|
458
446
|
return (
|
|
459
447
|
<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
448
|
<h3 style={{ fontSize: '18px', fontWeight: 500, color: '#111827', marginBottom: '32px', textAlign: 'center' }} dangerouslySetInnerHTML={{ __html: row.statementText }} />
|
|
461
449
|
|
|
462
|
-
{
|
|
463
|
-
<div style={{ width: '100%', maxWidth: '800px', display: 'flex', position: 'relative', height: '
|
|
450
|
+
{showLabels && scaleColumns.length > 0 && (
|
|
451
|
+
<div style={{ width: '100%', maxWidth: '800px', display: 'flex', position: 'relative', height: '24px', marginBottom: '16px' }}>
|
|
464
452
|
{labels!.map((label, i) => {
|
|
465
453
|
const m = scaleColumns.length;
|
|
466
454
|
const n = labels!.length;
|
|
@@ -471,33 +459,20 @@ function CsatMatrixCarousel({
|
|
|
471
459
|
return (
|
|
472
460
|
<div key={i} style={{
|
|
473
461
|
position: 'absolute', left: `${percent}%`, transform: 'translateX(-50%)',
|
|
474
|
-
textAlign: 'center',
|
|
462
|
+
textAlign: 'center', fontSize: '13px', fontWeight: 600,
|
|
463
|
+
lineHeight: 1.3, whiteSpace: 'nowrap',
|
|
464
|
+
...anchorLabelStyle(question),
|
|
475
465
|
}}>
|
|
476
|
-
|
|
477
|
-
{label}
|
|
478
|
-
</span>
|
|
466
|
+
{label}
|
|
479
467
|
</div>
|
|
480
468
|
);
|
|
481
469
|
})}
|
|
482
470
|
</div>
|
|
483
471
|
)}
|
|
484
472
|
|
|
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
473
|
{isGraphics && scaleColumns.length > 0 && (
|
|
499
|
-
<div style={{ width: '100%', maxWidth: '800px', marginBottom:
|
|
500
|
-
<GraphicsColumnLabelsRow scaleColumns={scaleColumns}
|
|
474
|
+
<div style={{ width: '100%', maxWidth: '800px', marginBottom: showLabels ? '8px' : '16px' }}>
|
|
475
|
+
<GraphicsColumnLabelsRow scaleColumns={scaleColumns} question={question} />
|
|
501
476
|
</div>
|
|
502
477
|
)}
|
|
503
478
|
|
|
@@ -515,8 +490,6 @@ function CsatMatrixCarousel({
|
|
|
515
490
|
sliderType="graphics" ticks={scaleColumns.length}
|
|
516
491
|
tickLabels={scaleColumns.map(col => col.label)}
|
|
517
492
|
reverseScaleOrder={reverseScaleOrder}
|
|
518
|
-
trackVar={trackVar}
|
|
519
|
-
thumbVar={thumbVar}
|
|
520
493
|
onChange={v => onCellSelect(row.id, columnSubmitValue(scaleColumns[v]))}
|
|
521
494
|
/>
|
|
522
495
|
</div>
|
|
@@ -563,16 +536,8 @@ function CsatMatrixCarousel({
|
|
|
563
536
|
>
|
|
564
537
|
{(EmojiNode ?? StarNode ?? (isNumbered ? (displayIdx + 1) : RadioNode)) as React.ReactNode}
|
|
565
538
|
</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
|
-
/>
|
|
539
|
+
<span style={columnHeaderLabelStyle(question)}
|
|
540
|
+
dangerouslySetInnerHTML={{ __html: col.label }} />
|
|
576
541
|
</div>
|
|
577
542
|
);
|
|
578
543
|
})}
|
|
@@ -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
|
);
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Wizard-ready Header — copy to src/components/Header.tsx
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Brand row: [logo image] [company name] — company sits after logo when both exist.
|
|
5
|
+
* When no logo file: text fallback via data-cfm-logo-text (company name or empty).
|
|
6
|
+
*
|
|
7
|
+
* After wizard save, logo src MUST come from survey-ui-config.json global.logo.fileName
|
|
8
|
+
* via uiConfig.ts — never hardcode telekom-logo.svg or change fileName manually.
|
|
6
9
|
*/
|
|
7
10
|
'use client';
|
|
8
11
|
|
|
@@ -16,131 +19,76 @@ const seedCompany = getCompanyName();
|
|
|
16
19
|
const seedLogoSrc = getLogoSrc({ staticExport: true });
|
|
17
20
|
const seedShowCompany = shouldShowCompanyName();
|
|
18
21
|
|
|
19
|
-
function
|
|
20
|
-
const hasLogoFile = Boolean(seedLogoSrc);
|
|
21
|
-
return (
|
|
22
|
-
<div
|
|
23
|
-
className="cfm-header-logo-slot shrink-0"
|
|
24
|
-
style={{
|
|
25
|
-
width: 'var(--cfm-header-logo-width, 80px)',
|
|
26
|
-
height: 'var(--cfm-header-logo-height, 80px)',
|
|
27
|
-
padding: 'var(--cfm-header-logo-padding, 8px)',
|
|
28
|
-
boxSizing: 'border-box',
|
|
29
|
-
display: 'flex',
|
|
30
|
-
alignItems: 'center',
|
|
31
|
-
justifyContent: 'center',
|
|
32
|
-
}}
|
|
33
|
-
>
|
|
34
|
-
{hasLogoFile ? (
|
|
35
|
-
<img
|
|
36
|
-
data-cfm-logo
|
|
37
|
-
src={seedLogoSrc!}
|
|
38
|
-
alt={seedCompany || 'Logo'}
|
|
39
|
-
className="h-full w-full object-contain"
|
|
40
|
-
style={{ background: 'transparent' }}
|
|
41
|
-
/>
|
|
42
|
-
) : (
|
|
43
|
-
<img data-cfm-logo src="" alt="Logo" className="h-full w-full object-contain" style={{ display: 'none' }} />
|
|
44
|
-
)}
|
|
45
|
-
</div>
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function CompanyName({ inBrandRow = false }: { inBrandRow?: boolean }) {
|
|
22
|
+
export default function Header({ embedded = false }: HeaderProps) {
|
|
50
23
|
const hasLogoFile = Boolean(seedLogoSrc);
|
|
51
|
-
const show =
|
|
52
|
-
seedShowCompany && (hasLogoFile || seedCompany) && (hasLogoFile || !inBrandRow);
|
|
53
24
|
|
|
54
|
-
return (
|
|
55
|
-
<>
|
|
56
|
-
<span
|
|
57
|
-
data-cfm-logo-text
|
|
58
|
-
className="font-semibold"
|
|
59
|
-
style={{
|
|
60
|
-
display: hasLogoFile ? 'none' : seedCompany ? '' : 'none',
|
|
61
|
-
color: 'var(--cfm-header-company-color, var(--cfm-text))',
|
|
62
|
-
fontSize: 'var(--cfm-header-company-size, 18px)',
|
|
63
|
-
fontWeight: 'var(--cfm-header-company-weight, 600)',
|
|
64
|
-
}}
|
|
65
|
-
>
|
|
66
|
-
{seedCompany}
|
|
67
|
-
</span>
|
|
68
|
-
<span
|
|
69
|
-
data-cfm-company
|
|
70
|
-
className="font-semibold"
|
|
71
|
-
style={{
|
|
72
|
-
display: show ? (hasLogoFile ? '' : 'none') : 'none',
|
|
73
|
-
marginTop: 'var(--cfm-header-company-margin-top, 0)',
|
|
74
|
-
marginLeft: inBrandRow ? 0 : 'var(--cfm-header-company-margin-left, 0)',
|
|
75
|
-
marginRight: inBrandRow ? 0 : 'var(--cfm-header-company-margin-right, 0)',
|
|
76
|
-
color: 'var(--cfm-header-company-color, var(--cfm-text))',
|
|
77
|
-
fontSize: 'var(--cfm-header-company-size, 18px)',
|
|
78
|
-
fontWeight: 'var(--cfm-header-company-weight, 600)',
|
|
79
|
-
lineHeight: 1.2,
|
|
80
|
-
}}
|
|
81
|
-
>
|
|
82
|
-
{seedCompany}
|
|
83
|
-
</span>
|
|
84
|
-
</>
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export default function Header({ embedded = false }: HeaderProps) {
|
|
89
25
|
return (
|
|
90
26
|
<header
|
|
91
27
|
className={`cfm-header relative w-full ${embedded ? '' : 'z-10 shadow-[0_2px_8px_rgba(0,0,0,0.06)]'}`}
|
|
92
28
|
style={{
|
|
93
|
-
height: 'var(--cfm-header-height, 140px)',
|
|
94
29
|
background: 'var(--cfm-header-bg, #fff)',
|
|
95
30
|
borderBottom: '1px solid var(--cfm-header-border, #e5e7eb)',
|
|
96
31
|
boxShadow: embedded ? undefined : 'var(--cfm-header-shadow, 0 2px 8px rgba(0,0,0,0.06))',
|
|
97
32
|
}}
|
|
98
33
|
>
|
|
99
34
|
<div
|
|
100
|
-
className="cfm-header-inner mx-auto
|
|
101
|
-
style={{
|
|
102
|
-
maxWidth: 'var(--cfm-max-width, 48rem)',
|
|
103
|
-
gridTemplateColumns: '1fr 1fr 1fr',
|
|
104
|
-
padding:
|
|
105
|
-
'var(--cfm-header-padding-y, 12px) var(--cfm-header-padding-x, 40px)',
|
|
106
|
-
boxSizing: 'border-box',
|
|
107
|
-
}}
|
|
35
|
+
className="cfm-header-inner mx-auto w-full"
|
|
36
|
+
style={{ maxWidth: 'var(--cfm-max-width, 48rem)' }}
|
|
108
37
|
>
|
|
109
|
-
{/* Brand row: logo + company adjacent (left+left or right+right) */}
|
|
110
38
|
<div
|
|
111
|
-
className="cfm-header-brand
|
|
112
|
-
style={{
|
|
113
|
-
gridColumn: 'var(--cfm-header-logo-col, 1)',
|
|
114
|
-
justifySelf: 'var(--cfm-header-logo-justify, start)',
|
|
115
|
-
gap: 'var(--cfm-header-brand-gap, 16px)',
|
|
116
|
-
display: 'var(--cfm-header-brand-row-display, none)',
|
|
117
|
-
}}
|
|
39
|
+
className="cfm-header-brand flex items-center"
|
|
40
|
+
style={{ gap: 'var(--cfm-header-brand-gap, 16px)' }}
|
|
118
41
|
>
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
42
|
+
{hasLogoFile ? (
|
|
43
|
+
<img
|
|
44
|
+
data-cfm-logo
|
|
45
|
+
src={seedLogoSrc!}
|
|
46
|
+
alt={seedCompany || 'Logo'}
|
|
47
|
+
className="shrink-0 object-contain"
|
|
48
|
+
style={{
|
|
49
|
+
width: 'var(--cfm-header-logo-width, 120px)',
|
|
50
|
+
height: 'var(--cfm-header-logo-height, 120px)',
|
|
51
|
+
padding: 'var(--cfm-header-logo-padding, 10px)',
|
|
52
|
+
background: 'transparent',
|
|
53
|
+
boxSizing: 'border-box',
|
|
54
|
+
}}
|
|
55
|
+
/>
|
|
56
|
+
) : (
|
|
57
|
+
<img
|
|
58
|
+
data-cfm-logo
|
|
59
|
+
src=""
|
|
60
|
+
alt="Logo"
|
|
61
|
+
className="shrink-0 object-contain"
|
|
62
|
+
style={{ display: 'none' }}
|
|
63
|
+
/>
|
|
64
|
+
)}
|
|
122
65
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
66
|
+
<span
|
|
67
|
+
data-cfm-logo-text
|
|
68
|
+
className="font-semibold"
|
|
69
|
+
style={{
|
|
70
|
+
display: hasLogoFile ? 'none' : seedCompany ? '' : 'none',
|
|
71
|
+
color: 'var(--cfm-header-company-color, var(--cfm-text))',
|
|
72
|
+
fontSize: 'var(--cfm-header-company-size, 18px)',
|
|
73
|
+
fontWeight: 'var(--cfm-header-company-weight, 600)',
|
|
74
|
+
}}
|
|
75
|
+
>
|
|
76
|
+
{seedCompany}
|
|
77
|
+
</span>
|
|
134
78
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
79
|
+
{seedShowCompany && hasLogoFile && seedCompany ? (
|
|
80
|
+
<span
|
|
81
|
+
data-cfm-company
|
|
82
|
+
className="font-semibold"
|
|
83
|
+
style={{
|
|
84
|
+
color: 'var(--cfm-header-company-color, var(--cfm-text))',
|
|
85
|
+
fontSize: 'var(--cfm-header-company-size, 18px)',
|
|
86
|
+
fontWeight: 'var(--cfm-header-company-weight, 600)',
|
|
87
|
+
}}
|
|
88
|
+
>
|
|
89
|
+
{seedCompany}
|
|
90
|
+
</span>
|
|
91
|
+
) : null}
|
|
144
92
|
</div>
|
|
145
93
|
</div>
|
|
146
94
|
</header>
|