@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@explorer02/cfm-survey-sdk",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.mjs",
6
6
  "types": "dist/index.d.ts",
@@ -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 { columnLabelPillStyle, csatColumnLabelStyle } from '@/lib/surveyUi/labelStyles';
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
- pillScope = 'csat' as 'csat' | 'rating',
38
+ question,
28
39
  }: {
29
40
  scaleColumns: ScaleColumn[];
30
- pillScope?: 'csat' | 'rating';
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 showAnchorLabels = isRating && labels && labels.length > 0;
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
- {/* RATING anchor labels (max ~5 along scale) */}
206
- {showAnchorLabels && m > 0 && (
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: '28px', ...(isGraphics ? { margin: '0 40px' } : {}) }}>
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
- <span style={{ fontSize: '13px', fontWeight: 600, ...columnLabelPillStyle('rating') }}>
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: showAnchorLabels ? '8px' : '16px', minHeight: '24px' }}>
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} pillScope={isRating ? 'rating' : 'csat'} />
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 headersCSAT always; RATING numeric headers when not graphics */}
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 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)))';
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
- {showAnchorLabels && scaleColumns.length > 0 && (
463
- <div style={{ width: '100%', maxWidth: '800px', display: 'flex', position: 'relative', height: '28px', marginBottom: '16px' }}>
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
- <span style={{ fontSize: '13px', fontWeight: 600, ...columnLabelPillStyle('rating') }}>
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: showAnchorLabels ? '8px' : '16px' }}>
500
- <GraphicsColumnLabelsRow scaleColumns={scaleColumns} pillScope={isRating ? 'rating' : 'csat'} />
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
- 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
- />
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 ? '#9ca3af' : thumbVar;
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: trackVar,
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 items-end"
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
- <div
44
- className="cfm-footer-logo-column"
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
- style={{ display: 'none' }}
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
- <p
83
- data-cfm-copyright
84
- className="cfm-footer-copyright m-0 text-xs"
85
- style={{ color: 'var(--cfm-footer-text, #9ca3af)' }}
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
- </div>
63
+ ) : (
64
+ <p data-cfm-copyright className="cfm-footer-copyright text-xs" style={{ display: 'none' }} />
65
+ )}
90
66
 
91
- <nav
92
- data-cfm-footer-links
93
- className="cfm-footer-links flex flex-wrap text-xs"
94
- style={{
95
- gridColumn: 'var(--cfm-footer-links-col, 3)',
96
- justifySelf: 'var(--cfm-footer-links-justify, end)',
97
- gap: 'var(--cfm-footer-inner-gap, 24px)',
98
- alignSelf: 'end',
99
- }}
100
- >
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
- ))}
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
- * Logo + company placement driven entirely by --cfm-header-* CSS vars from survey-ui-config.
5
- * Logo src from getLogoSrc() never hardcode asset paths.
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 LogoSlot() {
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 grid h-full w-full items-center"
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-row flex items-center"
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
- <LogoSlot />
120
- <CompanyName inBrandRow />
121
- </div>
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
- {/* 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>
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
- <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 />
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>