@explorer02/cfm-survey-sdk 0.3.3 → 0.3.4
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 +54 -54
- package/dist/cli/index.mjs +60 -60
- package/package.json +1 -1
- package/templates/docs/00-integration/component-checklist.md +1 -0
- package/templates/docs/templates/CsatMatrixScale.tsx +84 -32
- package/templates/docs/templates/CustomSliderTrack.tsx +6 -4
- package/templates/docs/templates/Footer.tsx +69 -26
- package/templates/docs/templates/Header.tsx +102 -65
- package/templates/docs/templates/LikertMatrixScale.tsx +4 -4
- package/templates/docs/templates/SliderMatrixScale.tsx +2 -4
- package/templates/docs/templates/labelStyles.ts +59 -16
- package/templates/docs/templates/surveyUiIcons.tsx +1 -1
- package/templates/preview-harness/vite-app/src/fixtures/questions.ts +18 -9
- package/templates/survey-theme.css +14 -2
- package/templates/wizard-dist/assets/{PreviewMock-_9SsNLwC.js → PreviewMock-CC1UlWe-.js} +1 -1
- package/templates/wizard-dist/assets/TypePanel-DISCXm0z.js +1 -0
- package/templates/wizard-dist/assets/index-Blpq4QjK.js +34 -0
- package/templates/wizard-dist/index.html +1 -1
- package/templates/wizard-dist/assets/TypePanel-DyEcZlWP.js +0 -1
- package/templates/wizard-dist/assets/index-pKQOivEa.js +0 -34
package/package.json
CHANGED
|
@@ -117,6 +117,7 @@ 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
|
|
120
121
|
- [ ] `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
|
|
121
122
|
- [ ] **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)
|
|
122
123
|
- [ ] Per-type scale vars per `wizard-question-type-styling.md` for **all 11 types** (wizard exports all; survey inventory may be smaller)
|
|
@@ -16,13 +16,19 @@ import {
|
|
|
16
16
|
mcqSelectedAccentVar,
|
|
17
17
|
mcqSelectedBgVar,
|
|
18
18
|
} from '@/lib/surveyUi/selectionStyles';
|
|
19
|
-
import { csatColumnLabelStyle } from '@/lib/surveyUi/labelStyles';
|
|
19
|
+
import { columnLabelPillStyle, csatColumnLabelStyle } 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 GraphicsColumnLabelsRow({
|
|
25
|
+
function GraphicsColumnLabelsRow({
|
|
26
|
+
scaleColumns,
|
|
27
|
+
pillScope = 'csat' as 'csat' | 'rating',
|
|
28
|
+
}: {
|
|
29
|
+
scaleColumns: ScaleColumn[];
|
|
30
|
+
pillScope?: 'csat' | 'rating';
|
|
31
|
+
}) {
|
|
26
32
|
if (scaleColumns.length === 0) return null;
|
|
27
33
|
|
|
28
34
|
return (
|
|
@@ -39,15 +45,13 @@ function GraphicsColumnLabelsRow({ scaleColumns }: { scaleColumns: ScaleColumn[]
|
|
|
39
45
|
transform: 'translateX(-50%)',
|
|
40
46
|
bottom: 0,
|
|
41
47
|
textAlign: 'center',
|
|
42
|
-
fontSize: '12px',
|
|
43
|
-
fontWeight: 500,
|
|
44
|
-
color: '#4b5563',
|
|
45
|
-
lineHeight: 1.2,
|
|
46
|
-
wordBreak: 'break-word',
|
|
47
48
|
width: '64px',
|
|
48
49
|
}}
|
|
49
50
|
>
|
|
50
|
-
<span
|
|
51
|
+
<span
|
|
52
|
+
style={{ fontSize: '12px', fontWeight: 500, ...columnLabelPillStyle(pillScope) }}
|
|
53
|
+
dangerouslySetInnerHTML={{ __html: col.label }}
|
|
54
|
+
/>
|
|
51
55
|
</div>
|
|
52
56
|
);
|
|
53
57
|
})}
|
|
@@ -189,20 +193,21 @@ function CsatMatrixGrid({
|
|
|
189
193
|
isGraphics
|
|
190
194
|
}: GridProps) {
|
|
191
195
|
const { statementRows, scaleAnchorLabels: labels, hasNotApplicableOption: hasNotApplicable, reverseScaleOrder } = question;
|
|
196
|
+
const isRating = question.type === 'RATING_MATRIX';
|
|
192
197
|
const [hoveredCell, setHoveredCell] = useState<string | null>(null);
|
|
193
198
|
|
|
194
|
-
const
|
|
199
|
+
const showAnchorLabels = isRating && labels && labels.length > 0;
|
|
195
200
|
const m = scaleColumns.length;
|
|
196
201
|
const n = labels?.length || 0;
|
|
197
202
|
|
|
198
203
|
return (
|
|
199
204
|
<div style={{ width: '100%' }}>
|
|
200
|
-
{/*
|
|
201
|
-
{
|
|
205
|
+
{/* RATING anchor labels (max ~5 along scale) */}
|
|
206
|
+
{showAnchorLabels && m > 0 && (
|
|
202
207
|
<div style={{ display: 'flex', alignItems: 'flex-end', marginBottom: '12px' }}>
|
|
203
208
|
<div style={{ width: MATRIX_ROW_LABEL_WIDTH, flexShrink: 0 }} />
|
|
204
209
|
<div style={{ flex: 1, display: 'flex' }}>
|
|
205
|
-
<div style={{ flex: 1, position: 'relative', height: '
|
|
210
|
+
<div style={{ flex: 1, position: 'relative', height: '28px', ...(isGraphics ? { margin: '0 40px' } : {}) }}>
|
|
206
211
|
{labels!.map((label, i) => {
|
|
207
212
|
const startPercent = isGraphics ? 0 : 0.5 / m;
|
|
208
213
|
const rangePercent = isGraphics ? 1 : (m - 1) / m;
|
|
@@ -213,10 +218,10 @@ function CsatMatrixGrid({
|
|
|
213
218
|
position: 'absolute',
|
|
214
219
|
left: `${percent}%`,
|
|
215
220
|
transform: 'translateX(-50%)',
|
|
216
|
-
textAlign: 'center', fontSize: '13px', fontWeight: 600,
|
|
217
|
-
color: '#4b5563', lineHeight: 1.3, whiteSpace: 'nowrap'
|
|
218
221
|
}}>
|
|
219
|
-
{
|
|
222
|
+
<span style={{ fontSize: '13px', fontWeight: 600, ...columnLabelPillStyle('rating') }}>
|
|
223
|
+
{label}
|
|
224
|
+
</span>
|
|
220
225
|
</div>
|
|
221
226
|
);
|
|
222
227
|
})}
|
|
@@ -226,12 +231,12 @@ function CsatMatrixGrid({
|
|
|
226
231
|
</div>
|
|
227
232
|
)}
|
|
228
233
|
|
|
229
|
-
{/* Column tick labels for graphics sliders
|
|
234
|
+
{/* Column tick labels for graphics sliders */}
|
|
230
235
|
{isGraphics && m > 0 && (
|
|
231
|
-
<div style={{ display: 'flex', alignItems: 'flex-end', marginBottom:
|
|
236
|
+
<div style={{ display: 'flex', alignItems: 'flex-end', marginBottom: showAnchorLabels ? '8px' : '16px', minHeight: '24px' }}>
|
|
232
237
|
<div style={{ width: MATRIX_ROW_LABEL_WIDTH, flexShrink: 0 }} />
|
|
233
238
|
<div style={{ flex: 1, display: 'flex' }}>
|
|
234
|
-
<GraphicsColumnLabelsRow scaleColumns={scaleColumns} />
|
|
239
|
+
<GraphicsColumnLabelsRow scaleColumns={scaleColumns} pillScope={isRating ? 'rating' : 'csat'} />
|
|
235
240
|
{hasNotApplicable && (
|
|
236
241
|
<div style={{ width: '56px', flexShrink: 0 }} />
|
|
237
242
|
)}
|
|
@@ -239,8 +244,8 @@ function CsatMatrixGrid({
|
|
|
239
244
|
</div>
|
|
240
245
|
)}
|
|
241
246
|
|
|
242
|
-
{/* Column
|
|
243
|
-
{!isGraphics &&
|
|
247
|
+
{/* Column headers — CSAT always; RATING numeric headers when not graphics */}
|
|
248
|
+
{!isGraphics && m > 0 && (
|
|
244
249
|
<div style={{ display: 'flex', alignItems: 'flex-end', marginBottom: '16px', minHeight: '24px' }}>
|
|
245
250
|
<div style={{ width: MATRIX_ROW_LABEL_WIDTH, flexShrink: 0 }} />
|
|
246
251
|
<div style={{ flex: 1, display: 'flex' }}>
|
|
@@ -248,7 +253,13 @@ function CsatMatrixGrid({
|
|
|
248
253
|
{scaleColumns.map(col => (
|
|
249
254
|
<div key={col.id} style={{ display: 'flex', justifyContent: 'center', padding: '0 4px' }}>
|
|
250
255
|
<span
|
|
251
|
-
style={{
|
|
256
|
+
style={{
|
|
257
|
+
fontSize: '13px',
|
|
258
|
+
fontWeight: 500,
|
|
259
|
+
...(isRating
|
|
260
|
+
? { color: 'var(--cfm-text, #374151)', textAlign: 'center' as const }
|
|
261
|
+
: columnLabelPillStyle('csat')),
|
|
262
|
+
}}
|
|
252
263
|
dangerouslySetInnerHTML={{ __html: col.label }}
|
|
253
264
|
/>
|
|
254
265
|
</div>
|
|
@@ -292,6 +303,16 @@ function CsatMatrixGrid({
|
|
|
292
303
|
ticks={scaleColumns.length}
|
|
293
304
|
tickLabels={scaleColumns.map(col => col.label)}
|
|
294
305
|
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
|
+
}
|
|
295
316
|
onChange={v => onCellSelect(row.id, columnSubmitValue(scaleColumns[v]))}
|
|
296
317
|
/>
|
|
297
318
|
</div>
|
|
@@ -419,20 +440,27 @@ function CsatMatrixCarousel({
|
|
|
419
440
|
isGraphics
|
|
420
441
|
}: CarouselProps) {
|
|
421
442
|
const { statementRows, scaleAnchorLabels: labels, hasNotApplicableOption: hasNotApplicable, reverseScaleOrder } = question;
|
|
443
|
+
const isRating = question.type === 'RATING_MATRIX';
|
|
422
444
|
const [activeCarouselIndex, setActiveCarouselIndex] = useState(0);
|
|
423
445
|
const [hoveredCell, setHoveredCell] = useState<string | null>(null);
|
|
424
446
|
|
|
425
447
|
const row = statementRows[activeCarouselIndex];
|
|
426
448
|
if (!row) return null;
|
|
427
|
-
|
|
428
|
-
const
|
|
449
|
+
|
|
450
|
+
const showAnchorLabels = isRating && labels && labels.length > 0;
|
|
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)))';
|
|
429
457
|
|
|
430
458
|
return (
|
|
431
459
|
<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' }}>
|
|
432
460
|
<h3 style={{ fontSize: '18px', fontWeight: 500, color: '#111827', marginBottom: '32px', textAlign: 'center' }} dangerouslySetInnerHTML={{ __html: row.statementText }} />
|
|
433
461
|
|
|
434
|
-
{
|
|
435
|
-
<div style={{ width: '100%', maxWidth: '800px', display: 'flex', position: 'relative', height: '
|
|
462
|
+
{showAnchorLabels && scaleColumns.length > 0 && (
|
|
463
|
+
<div style={{ width: '100%', maxWidth: '800px', display: 'flex', position: 'relative', height: '28px', marginBottom: '16px' }}>
|
|
436
464
|
{labels!.map((label, i) => {
|
|
437
465
|
const m = scaleColumns.length;
|
|
438
466
|
const n = labels!.length;
|
|
@@ -443,19 +471,33 @@ function CsatMatrixCarousel({
|
|
|
443
471
|
return (
|
|
444
472
|
<div key={i} style={{
|
|
445
473
|
position: 'absolute', left: `${percent}%`, transform: 'translateX(-50%)',
|
|
446
|
-
textAlign: 'center',
|
|
447
|
-
color: '#4b5563', lineHeight: 1.3, whiteSpace: 'nowrap'
|
|
474
|
+
textAlign: 'center',
|
|
448
475
|
}}>
|
|
449
|
-
{
|
|
476
|
+
<span style={{ fontSize: '13px', fontWeight: 600, ...columnLabelPillStyle('rating') }}>
|
|
477
|
+
{label}
|
|
478
|
+
</span>
|
|
450
479
|
</div>
|
|
451
480
|
);
|
|
452
481
|
})}
|
|
453
482
|
</div>
|
|
454
483
|
)}
|
|
455
484
|
|
|
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
|
+
|
|
456
498
|
{isGraphics && scaleColumns.length > 0 && (
|
|
457
|
-
<div style={{ width: '100%', maxWidth: '800px', marginBottom:
|
|
458
|
-
<GraphicsColumnLabelsRow scaleColumns={scaleColumns} />
|
|
499
|
+
<div style={{ width: '100%', maxWidth: '800px', marginBottom: showAnchorLabels ? '8px' : '16px' }}>
|
|
500
|
+
<GraphicsColumnLabelsRow scaleColumns={scaleColumns} pillScope={isRating ? 'rating' : 'csat'} />
|
|
459
501
|
</div>
|
|
460
502
|
)}
|
|
461
503
|
|
|
@@ -473,6 +515,8 @@ function CsatMatrixCarousel({
|
|
|
473
515
|
sliderType="graphics" ticks={scaleColumns.length}
|
|
474
516
|
tickLabels={scaleColumns.map(col => col.label)}
|
|
475
517
|
reverseScaleOrder={reverseScaleOrder}
|
|
518
|
+
trackVar={trackVar}
|
|
519
|
+
thumbVar={thumbVar}
|
|
476
520
|
onChange={v => onCellSelect(row.id, columnSubmitValue(scaleColumns[v]))}
|
|
477
521
|
/>
|
|
478
522
|
</div>
|
|
@@ -519,8 +563,16 @@ function CsatMatrixCarousel({
|
|
|
519
563
|
>
|
|
520
564
|
{(EmojiNode ?? StarNode ?? (isNumbered ? (displayIdx + 1) : RadioNode)) as React.ReactNode}
|
|
521
565
|
</button>
|
|
522
|
-
<span
|
|
523
|
-
|
|
566
|
+
<span
|
|
567
|
+
style={{
|
|
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
|
+
/>
|
|
524
576
|
</div>
|
|
525
577
|
);
|
|
526
578
|
})}
|
|
@@ -22,6 +22,8 @@ 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;
|
|
25
27
|
onChange: (v: number) => void;
|
|
26
28
|
};
|
|
27
29
|
|
|
@@ -35,6 +37,8 @@ export function CustomSliderTrack({
|
|
|
35
37
|
ticks,
|
|
36
38
|
tickLabels,
|
|
37
39
|
reverseScaleOrder,
|
|
40
|
+
trackVar = 'var(--cfm-slider-track, #e5e7eb)',
|
|
41
|
+
thumbVar = 'var(--cfm-slider-thumb, var(--cfm-input-focus-ring, var(--cfm-primary)))',
|
|
38
42
|
onChange,
|
|
39
43
|
}: CustomSliderTrackProps) {
|
|
40
44
|
const [isHovered, setIsHovered] = React.useState(false);
|
|
@@ -50,9 +54,7 @@ export function CustomSliderTrack({
|
|
|
50
54
|
? tooltipLabel.replace(/<[^>]*>/g, '')
|
|
51
55
|
: String(Math.round(value));
|
|
52
56
|
|
|
53
|
-
const themeColor = disabled
|
|
54
|
-
? '#9ca3af'
|
|
55
|
-
: 'var(--cfm-slider-thumb, var(--cfm-input-focus-ring, var(--cfm-primary)))';
|
|
57
|
+
const themeColor = disabled ? '#9ca3af' : thumbVar;
|
|
56
58
|
|
|
57
59
|
return (
|
|
58
60
|
<div
|
|
@@ -62,7 +64,7 @@ export function CustomSliderTrack({
|
|
|
62
64
|
>
|
|
63
65
|
<div style={{
|
|
64
66
|
position: 'absolute', top: '50%', left: 0, right: 0, transform: 'translateY(-50%)',
|
|
65
|
-
height: '4px', borderRadius: '2px', backgroundColor:
|
|
67
|
+
height: '4px', borderRadius: '2px', backgroundColor: trackVar,
|
|
66
68
|
}} />
|
|
67
69
|
|
|
68
70
|
<div style={{
|
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Wizard-ready Footer — copy to src/components/Footer.tsx
|
|
3
|
-
*
|
|
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.
|
|
4
6
|
*/
|
|
5
7
|
'use client';
|
|
6
8
|
|
|
9
|
+
import {
|
|
10
|
+
getFooterCopyright,
|
|
11
|
+
getFooterLinks,
|
|
12
|
+
getLayoutFlags,
|
|
13
|
+
getLogoSrc,
|
|
14
|
+
} from '@/lib/uiConfig';
|
|
15
|
+
|
|
16
|
+
const seedLogoSrc = getLogoSrc({ staticExport: true });
|
|
17
|
+
const seedCopyright = getFooterCopyright();
|
|
18
|
+
const seedLinks = getFooterLinks();
|
|
19
|
+
const { showFooterLogo } = getLayoutFlags();
|
|
20
|
+
|
|
7
21
|
export default function Footer() {
|
|
22
|
+
const showLogo = showFooterLogo && Boolean(seedLogoSrc);
|
|
23
|
+
|
|
8
24
|
return (
|
|
9
25
|
<footer
|
|
10
26
|
className="cfm-footer w-full"
|
|
@@ -15,56 +31,83 @@ export default function Footer() {
|
|
|
15
31
|
}}
|
|
16
32
|
>
|
|
17
33
|
<div
|
|
18
|
-
className="cfm-footer-inner mx-auto
|
|
34
|
+
className="cfm-footer-inner mx-auto w-full items-end"
|
|
19
35
|
style={{
|
|
36
|
+
maxWidth: 'var(--cfm-max-width, 48rem)',
|
|
20
37
|
display: 'var(--cfm-footer-inner-display, grid)',
|
|
21
38
|
flexDirection: 'var(--cfm-footer-inner-direction, row)' as React.CSSProperties['flexDirection'],
|
|
22
|
-
gridTemplateColumns: '1fr
|
|
39
|
+
gridTemplateColumns: '1fr auto 1fr',
|
|
40
|
+
gap: 'var(--cfm-footer-inner-gap, 24px)',
|
|
23
41
|
}}
|
|
24
42
|
>
|
|
25
43
|
<div
|
|
26
|
-
className="
|
|
44
|
+
className="cfm-footer-logo-column"
|
|
27
45
|
style={{
|
|
28
46
|
gridColumn: 'var(--cfm-footer-logo-col, 1)',
|
|
29
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)',
|
|
30
52
|
}}
|
|
31
53
|
>
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
+
) : (
|
|
74
|
+
<img
|
|
75
|
+
data-cfm-footer-logo
|
|
76
|
+
data-cfm-logo
|
|
77
|
+
src=""
|
|
78
|
+
alt=""
|
|
79
|
+
style={{ display: 'none' }}
|
|
80
|
+
/>
|
|
81
|
+
)}
|
|
44
82
|
<p
|
|
45
83
|
data-cfm-copyright
|
|
46
|
-
className="text-xs"
|
|
84
|
+
className="cfm-footer-copyright m-0 text-xs"
|
|
47
85
|
style={{ color: 'var(--cfm-footer-text, #9ca3af)' }}
|
|
48
86
|
>
|
|
49
|
-
|
|
87
|
+
{seedCopyright}
|
|
50
88
|
</p>
|
|
51
89
|
</div>
|
|
52
90
|
|
|
53
91
|
<nav
|
|
54
92
|
data-cfm-footer-links
|
|
55
|
-
className="flex flex-wrap
|
|
93
|
+
className="cfm-footer-links flex flex-wrap text-xs"
|
|
56
94
|
style={{
|
|
57
95
|
gridColumn: 'var(--cfm-footer-links-col, 3)',
|
|
58
96
|
justifySelf: 'var(--cfm-footer-links-justify, end)',
|
|
97
|
+
gap: 'var(--cfm-footer-inner-gap, 24px)',
|
|
98
|
+
alignSelf: 'end',
|
|
59
99
|
}}
|
|
60
100
|
>
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
101
|
+
{seedLinks.map((link, i) => (
|
|
102
|
+
<a
|
|
103
|
+
key={`${link.label}-${i}`}
|
|
104
|
+
href={link.url || '#'}
|
|
105
|
+
className="cfm-footer-link transition-colors hover:opacity-80"
|
|
106
|
+
style={{ color: 'var(--cfm-footer-link, #9ca3af)' }}
|
|
107
|
+
>
|
|
108
|
+
{link.label}
|
|
109
|
+
</a>
|
|
110
|
+
))}
|
|
68
111
|
</nav>
|
|
69
112
|
</div>
|
|
70
113
|
</footer>
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Wizard-ready Header — copy to src/components/Header.tsx
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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.
|
|
4
|
+
* Logo + company placement driven entirely by --cfm-header-* CSS vars from survey-ui-config.
|
|
5
|
+
* Logo src from getLogoSrc() — never hardcode asset paths.
|
|
9
6
|
*/
|
|
10
7
|
'use client';
|
|
11
8
|
|
|
@@ -19,91 +16,131 @@ const seedCompany = getCompanyName();
|
|
|
19
16
|
const seedLogoSrc = getLogoSrc({ staticExport: true });
|
|
20
17
|
const seedShowCompany = shouldShowCompanyName();
|
|
21
18
|
|
|
22
|
-
|
|
19
|
+
function LogoSlot() {
|
|
23
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 }) {
|
|
50
|
+
const hasLogoFile = Boolean(seedLogoSrc);
|
|
51
|
+
const show =
|
|
52
|
+
seedShowCompany && (hasLogoFile || seedCompany) && (hasLogoFile || !inBrandRow);
|
|
24
53
|
|
|
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) {
|
|
25
89
|
return (
|
|
26
90
|
<header
|
|
27
91
|
className={`cfm-header relative w-full ${embedded ? '' : 'z-10 shadow-[0_2px_8px_rgba(0,0,0,0.06)]'}`}
|
|
28
92
|
style={{
|
|
29
|
-
height: 'var(--cfm-header-height,
|
|
93
|
+
height: 'var(--cfm-header-height, 140px)',
|
|
30
94
|
background: 'var(--cfm-header-bg, #fff)',
|
|
31
95
|
borderBottom: '1px solid var(--cfm-header-border, #e5e7eb)',
|
|
32
96
|
boxShadow: embedded ? undefined : 'var(--cfm-header-shadow, 0 2px 8px rgba(0,0,0,0.06))',
|
|
33
97
|
}}
|
|
34
98
|
>
|
|
35
99
|
<div
|
|
36
|
-
className="cfm-header-inner grid h-full w-full items-center"
|
|
100
|
+
className="cfm-header-inner mx-auto grid h-full w-full items-center"
|
|
37
101
|
style={{
|
|
102
|
+
maxWidth: 'var(--cfm-max-width, 48rem)',
|
|
38
103
|
gridTemplateColumns: '1fr 1fr 1fr',
|
|
39
|
-
padding:
|
|
104
|
+
padding:
|
|
105
|
+
'var(--cfm-header-padding-y, 12px) var(--cfm-header-padding-x, 40px)',
|
|
106
|
+
boxSizing: 'border-box',
|
|
40
107
|
}}
|
|
41
108
|
>
|
|
109
|
+
{/* Brand row: logo + company adjacent (left+left or right+right) */}
|
|
42
110
|
<div
|
|
43
|
-
className="cfm-header-brand flex items-center"
|
|
111
|
+
className="cfm-header-brand-row flex items-center"
|
|
44
112
|
style={{
|
|
45
113
|
gridColumn: 'var(--cfm-header-logo-col, 1)',
|
|
46
114
|
justifySelf: 'var(--cfm-header-logo-justify, start)',
|
|
47
115
|
gap: 'var(--cfm-header-brand-gap, 16px)',
|
|
116
|
+
display: 'var(--cfm-header-brand-row-display, none)',
|
|
48
117
|
}}
|
|
49
118
|
>
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
maxHeight: 'calc(var(--cfm-header-height, 120px) - 8px)',
|
|
60
|
-
padding: 'var(--cfm-header-logo-padding, 10px)',
|
|
61
|
-
background: 'transparent',
|
|
62
|
-
}}
|
|
63
|
-
/>
|
|
64
|
-
) : (
|
|
65
|
-
<img
|
|
66
|
-
data-cfm-logo
|
|
67
|
-
src=""
|
|
68
|
-
alt="Logo"
|
|
69
|
-
className="shrink-0 object-contain"
|
|
70
|
-
style={{ display: 'none' }}
|
|
71
|
-
/>
|
|
72
|
-
)}
|
|
119
|
+
<LogoSlot />
|
|
120
|
+
<CompanyName inBrandRow />
|
|
121
|
+
</div>
|
|
73
122
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
</span>
|
|
123
|
+
{/* Separate slots when company is not adjacent to logo */}
|
|
124
|
+
<div
|
|
125
|
+
className="cfm-header-logo-slot-only"
|
|
126
|
+
style={{
|
|
127
|
+
gridColumn: 'var(--cfm-header-logo-col, 1)',
|
|
128
|
+
justifySelf: 'var(--cfm-header-logo-justify, start)',
|
|
129
|
+
display: 'var(--cfm-header-logo-slot-display, flex)',
|
|
130
|
+
}}
|
|
131
|
+
>
|
|
132
|
+
<LogoSlot />
|
|
133
|
+
</div>
|
|
86
134
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
: 'none',
|
|
97
|
-
marginTop: 'var(--cfm-header-company-margin-top, 0)',
|
|
98
|
-
marginLeft: 'var(--cfm-header-company-margin-left, 0)',
|
|
99
|
-
marginRight: 'var(--cfm-header-company-margin-right, 0)',
|
|
100
|
-
color: 'var(--cfm-header-company-color, var(--cfm-text))',
|
|
101
|
-
fontSize: 'var(--cfm-header-company-size, 18px)',
|
|
102
|
-
fontWeight: 'var(--cfm-header-company-weight, 600)',
|
|
103
|
-
}}
|
|
104
|
-
>
|
|
105
|
-
{seedCompany}
|
|
106
|
-
</span>
|
|
135
|
+
<div
|
|
136
|
+
className="cfm-header-company-slot flex items-center"
|
|
137
|
+
style={{
|
|
138
|
+
gridColumn: 'var(--cfm-header-company-col, 2)',
|
|
139
|
+
justifySelf: 'var(--cfm-header-company-justify, center)',
|
|
140
|
+
display: 'var(--cfm-header-company-slot-display, flex)',
|
|
141
|
+
}}
|
|
142
|
+
>
|
|
143
|
+
<CompanyName />
|
|
107
144
|
</div>
|
|
108
145
|
</div>
|
|
109
146
|
</header>
|