@bytebrand/fe-ui-core-autobahn 1.0.132 → 1.0.133

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,30 +1,30 @@
1
- {
2
- "name": "@bytebrand/fe-ui-core-autobahn",
3
- "version": "1.0.132",
4
- "description": "auto.de md3 (Autobahn) design system — UI primitives, cards, chrome, VDP modules, theme CSS and locales. Ships raw TypeScript source (like fe-ui-core before it); each consumer's bundler compiles it.",
5
- "main": "index.ts",
6
- "license": "UNLICENSED",
7
- "peerDependencies": {
8
- "react": "^18.0.0",
9
- "react-dom": "^18.0.0"
10
- },
11
- "dependencies": {
12
- "@bytebrand/car-schema-selectors": "^2.0.7",
13
- "@bytebrand/fe-financing": "^5.14.20",
14
- "classnames": "2.2.6",
15
- "clsx": "^2.1.1",
16
- "d3-format": "1.4.5",
17
- "js-cookie": "^2.2.0",
18
- "lodash": "^4.17.21",
19
- "lucide-react": "^0.288.0",
20
- "mobx-react": "^7.6.0",
21
- "moment": "^2.29.1",
22
- "qs": "^6.6.0",
23
- "react-device-detect": "^2.1.2",
24
- "react-grid-system": "^5.0.2",
25
- "react-lazyload": "^3.2.0",
26
- "react-scroll": "^1.9.3",
27
- "react-slick": "^0.29.0",
28
- "slick-carousel": "1.8.1"
29
- }
30
- }
1
+ {
2
+ "name": "@bytebrand/fe-ui-core-autobahn",
3
+ "version": "1.0.133",
4
+ "description": "auto.de md3 (Autobahn) design system — UI primitives, cards, chrome, VDP modules, theme CSS and locales. Ships raw TypeScript source (like fe-ui-core before it); each consumer's bundler compiles it.",
5
+ "main": "index.ts",
6
+ "license": "UNLICENSED",
7
+ "peerDependencies": {
8
+ "react": "^18.0.0",
9
+ "react-dom": "^18.0.0"
10
+ },
11
+ "dependencies": {
12
+ "@bytebrand/car-schema-selectors": "^2.0.7",
13
+ "@bytebrand/fe-financing": "^5.14.20",
14
+ "classnames": "2.2.6",
15
+ "clsx": "^2.1.1",
16
+ "d3-format": "1.4.5",
17
+ "js-cookie": "^2.2.0",
18
+ "lodash": "^4.17.21",
19
+ "lucide-react": "^0.288.0",
20
+ "mobx-react": "^7.6.0",
21
+ "moment": "^2.29.1",
22
+ "qs": "^6.6.0",
23
+ "react-device-detect": "^2.1.2",
24
+ "react-grid-system": "^5.0.2",
25
+ "react-lazyload": "^3.2.0",
26
+ "react-scroll": "^1.9.3",
27
+ "react-slick": "^0.29.0",
28
+ "slick-carousel": "1.8.1"
29
+ }
30
+ }
package/theme/md3.css CHANGED
@@ -11276,3 +11276,49 @@ color: #005CFF;
11276
11276
  border-bottom-right-radius: 2px;
11277
11277
  }
11278
11278
  }
11279
+
11280
+ /* VdpEnergyTaxSummary ("Energiekosten und Kraftfahrzeugsteuer pro Jahr" card, VdpModules.tsx)
11281
+ — desktop layout (row, padding: 15px, border-right divider between the 2 columns) is
11282
+ inline JS styles on the component, untouched. padding + the divider are the only two
11283
+ properties that change on mobile, so THOSE (only) live here instead of inline, letting
11284
+ this media query override them without !important. */
11285
+ .md3 .vdp-ets-col {
11286
+ padding: 15px;
11287
+ }
11288
+ .md3 .vdp-ets-col--first {
11289
+ border-right: 1px solid var(--md-outline-variant);
11290
+ }
11291
+ @media (max-width: 600px) {
11292
+ .md3 .vdp-ets-row {
11293
+ flex-direction: column;
11294
+ }
11295
+ .md3 .vdp-ets-col {
11296
+ padding: 14px 16px;
11297
+ }
11298
+ .md3 .vdp-ets-col--first {
11299
+ border-right: none;
11300
+ border-bottom: 1px solid var(--md-outline-variant);
11301
+ }
11302
+ }
11303
+
11304
+ /* VdpCo2CostsSummary ("Mögliche CO₂-Kosten..." card, VdpModules.tsx) — same treatment as
11305
+ .vdp-ets-* above, just 3 columns (low/middle/high) instead of 2; the first two carry the
11306
+ divider, the last one never does (nothing to its right/below). */
11307
+ .md3 .vdp-ccs-col {
11308
+ padding: 15px;
11309
+ }
11310
+ .md3 .vdp-ccs-col--divider {
11311
+ border-right: 1px solid var(--md-outline-variant);
11312
+ }
11313
+ @media (max-width: 600px) {
11314
+ .md3 .vdp-ccs-row {
11315
+ flex-direction: column;
11316
+ }
11317
+ .md3 .vdp-ccs-col {
11318
+ padding: 14px 16px;
11319
+ }
11320
+ .md3 .vdp-ccs-col--divider {
11321
+ border-right: none;
11322
+ border-bottom: 1px solid var(--md-outline-variant);
11323
+ }
11324
+ }
@@ -26,7 +26,7 @@ import { TranslationFunction } from '../types';
26
26
 
27
27
  import { Button, Dropdown, DropdownOption, Icon, Spec, StatChip, Stars } from '../ui';
28
28
  import { CO2_CLASSES, CO2_COLORS, TIER_LABELS, TIER_KEYS, TIER_COLORS, TIER_BACKGROUNDS } from '../constants';
29
- import { defaultVdpSpecs, EquipmentGroups, PriceRatingBands, VehicleDetail, VdpSpec, VdpReview } from './types';
29
+ import { defaultVdpSpecs, EquipmentGroups, PriceRatingBands, VehicleDetail, VdpSpec, VdpReview, RunningCostRow, EnergyTaxSummary } from './types';
30
30
  import { AddServicesTooltipBody, CalcRow, DocCheckIcon, EvalBar, expanderBtnStyle, FinanceMode, Slider, TermChips, ToggleRow } from './FinanceCalculator';
31
31
  import { formatEur as eur, formatEurCents as eurCents, formatPct as pct } from '../utils/financeFormat';
32
32
 
@@ -364,6 +364,216 @@ export const VdpSpecTable: React.FunctionComponent<VdpSpecTableProps> = ({ vehic
364
364
  );
365
365
  };
366
366
 
367
+ // ---------------------------------------------------------------------------
368
+ // "Energiekosten und Kraftfahrzeugsteuer pro Jahr" summary card — new Figma design.
369
+ // Deliberately its OWN literal inline styles straight from the Figma spec (hex colors,
370
+ // 'Arimo', exact px) rather than the shared mcard/Spec/md-headline-sm grammar — this frame
371
+ // is a one-off design, not part of the .md3 token system. Covers petrol/diesel, BEV, and
372
+ // PHEV (fuel-type-branching data built by toVehicleDetail.ts) — this replaced the legacy-
373
+ // parity "Laufende Kosten pro Jahr" card (formerly VdpRunningCosts here), which showed the
374
+ // SAME figures via the old accordion.tableCostsPerYear layout/wording and has been removed
375
+ // now that this card covers every fuel type; keeping both would show the same numbers
376
+ // twice with inconsistent wording (e.g. old block's "0,00 EUR/Jahr" Kfz-Steuer vs. this
377
+ // card's real "befristet steuerbefreit" text for a BEV).
378
+ // ---------------------------------------------------------------------------
379
+
380
+ export interface VdpEnergyTaxSummaryProps {
381
+ vehicle: VehicleDetail;
382
+ t: TranslationFunction;
383
+ }
384
+
385
+ const energyTaxLabelStyle: React.CSSProperties = {
386
+ fontFamily: 'Arimo',
387
+ fontStyle: 'normal',
388
+ fontWeight: 400,
389
+ fontSize: 13,
390
+ lineHeight: '18px',
391
+ color: '#5B6B7F',
392
+ };
393
+
394
+ const energyTaxValueStyle: React.CSSProperties = {
395
+ fontFamily: 'Arimo',
396
+ fontStyle: 'normal',
397
+ fontWeight: 700,
398
+ fontSize: 18,
399
+ lineHeight: '24px',
400
+ color: '#0E2238',
401
+ };
402
+
403
+ const energyTaxNoteStyle: React.CSSProperties = {
404
+ fontFamily: 'Arimo',
405
+ fontStyle: 'normal',
406
+ fontWeight: 400,
407
+ fontSize: 11,
408
+ lineHeight: '15px',
409
+ color: '#5B6B7F',
410
+ };
411
+
412
+ // Shared by both new-Figma-design cards below (VdpEnergyTaxSummary + VdpCo2CostsSummary) —
413
+ // the "⁵"/"⁶" superscript footnote markers, and the h2 title.
414
+ const footnoteStyle: React.CSSProperties = {
415
+ fontFamily: 'Arimo',
416
+ fontStyle: 'normal',
417
+ fontWeight: 700,
418
+ fontSize: 18,
419
+ lineHeight: '24px',
420
+ color: '#005CFF',
421
+ };
422
+
423
+ const summaryCardTitleStyle: React.CSSProperties = {
424
+ // <h2> carries a UA-stylesheet default margin (top + bottom) unless reset — zero it
425
+ // explicitly so ONLY marginBottom: 10 (the spec'd value) applies, no stray top padding/gap.
426
+ margin: 0,
427
+ marginBottom: 10,
428
+ fontFamily: 'Arimo',
429
+ fontStyle: 'normal',
430
+ fontWeight: 700,
431
+ fontSize: 15,
432
+ lineHeight: '20px',
433
+ color: '#0E2238',
434
+ };
435
+
436
+ export const VdpEnergyTaxSummary: React.FunctionComponent<VdpEnergyTaxSummaryProps> = ({ vehicle, t }) => {
437
+ const s: EnergyTaxSummary | undefined = vehicle.energyTaxSummary;
438
+ if (!s) return null;
439
+ return (
440
+ <div id="vdp-energy-tax">
441
+ <h2 style={summaryCardTitleStyle}>{t('vehicleProps:title.energyTaxSummaryTitle')}</h2>
442
+ <div
443
+ className="vdp-ets-row"
444
+ style={{
445
+ display: 'flex',
446
+ width: '100%',
447
+ gap: 4,
448
+ background: 'var(--md-sc-lowest)',
449
+ borderRadius: 'var(--md-r-md)',
450
+ border: '1px solid var(--md-outline-variant)',
451
+ }}
452
+ >
453
+ <div className="vdp-ets-col vdp-ets-col--first" style={{ flex: 1 }}>
454
+ <div style={energyTaxLabelStyle}>{t('vehicleProps:title.energyCostsYearLabel')}</div>
455
+ <div style={energyTaxValueStyle}>{s.energyCost}</div>
456
+ {/* PHEV: both a Kraftstoffpreis AND a Strompreis line (getBaseFuelPrice returns
457
+ both when hybridPlugin===true) — ICE/BEV each only ever populate one. */}
458
+ {s.fuelPriceNote && s.fuelPriceNote.map((note, i) => (
459
+ <div key={i} style={energyTaxNoteStyle}>
460
+ {note}
461
+ </div>
462
+ ))}
463
+ </div>
464
+ <div className="vdp-ets-col" style={{ flex: 1 }}>
465
+ <div style={energyTaxLabelStyle}>{t('vehicleProps:title.vehicleTaxYearLabel')}</div>
466
+ <div style={{ height: 24, ...energyTaxValueStyle }}>
467
+ {s.tax}
468
+ <span style={footnoteStyle}>⁶</span>
469
+ </div>
470
+ </div>
471
+ </div>
472
+ </div>
473
+ );
474
+ };
475
+
476
+ // ---------------------------------------------------------------------------
477
+ // "Mögliche CO₂-Kosten über die nächsten 10 Jahre (15.000 km/Jahr)⁵" — new Figma design,
478
+ // same card grammar as VdpEnergyTaxSummary above (own literal inline styles), 3 equal
479
+ // columns (low/middle/high reference CO2 price) instead of 2.
480
+ // ---------------------------------------------------------------------------
481
+
482
+ export interface VdpCo2CostsSummaryProps {
483
+ vehicle: VehicleDetail;
484
+ t: TranslationFunction;
485
+ }
486
+
487
+ export const VdpCo2CostsSummary: React.FunctionComponent<VdpCo2CostsSummaryProps> = ({ vehicle, t }) => {
488
+ const s = vehicle.co2CostsSummary;
489
+ const rows: RunningCostRow[] = s ? ([s.low, s.middle, s.high].filter(Boolean) as RunningCostRow[]) : [];
490
+ if (!rows.length) return null;
491
+ return (
492
+ <div id="vdp-co2-costs">
493
+ <h2 style={summaryCardTitleStyle}>
494
+ {t('vehicleProps:title.co2CostsSummaryTitle')}
495
+ <span style={{ ...footnoteStyle, fontSize: 19 }}>⁵</span>
496
+ </h2>
497
+ <div
498
+ className="vdp-ccs-row"
499
+ style={{
500
+ display: 'flex',
501
+ width: '100%',
502
+ gap: 4,
503
+ background: 'var(--md-sc-lowest)',
504
+ borderRadius: 'var(--md-r-md)',
505
+ border: '1px solid var(--md-outline-variant)',
506
+ }}
507
+ >
508
+ {rows.map((row, i) => (
509
+ <div
510
+ key={i}
511
+ className={`vdp-ccs-col${i < rows.length - 1 ? ' vdp-ccs-col--divider' : ''}`}
512
+ style={{ flex: 1 }}
513
+ >
514
+ <div style={energyTaxLabelStyle}>{row.label}</div>
515
+ <div style={energyTaxValueStyle}>{row.value}</div>
516
+ </div>
517
+ ))}
518
+ </div>
519
+ </div>
520
+ );
521
+ };
522
+
523
+ // ---------------------------------------------------------------------------
524
+ // Legal footnote text for the "⁵"/"⁶" markers used in VdpCo2CostsSummary/VdpEnergyTaxSummary
525
+ // above — real Pkw-EnVKV/§3d KraftStG disclosure copy (verbatim, not paraphrased), plain
526
+ // text (no card/background). Needs-data: each paragraph only shows when its corresponding
527
+ // card above actually rendered (never an orphaned footnote for a marker nobody saw).
528
+ // ---------------------------------------------------------------------------
529
+
530
+ export interface VdpCostFootnotesProps {
531
+ vehicle: VehicleDetail;
532
+ t: TranslationFunction;
533
+ }
534
+
535
+ const costFootnoteStyle: React.CSSProperties = {
536
+ fontFamily: 'Arimo',
537
+ fontStyle: 'normal',
538
+ fontWeight: 700,
539
+ fontSize: 13.5,
540
+ lineHeight: '16px',
541
+ color: '#000000',
542
+ };
543
+
544
+ export const VdpCostFootnotes: React.FunctionComponent<VdpCostFootnotesProps> = ({ vehicle, t }) => {
545
+ const showCo2Footnote = !!vehicle.co2CostsSummary;
546
+ const showTaxFootnote = !!vehicle.energyTaxSummary;
547
+ if (!showCo2Footnote && !showTaxFootnote) return null;
548
+ return (
549
+ <div id="vdp-cost-footnotes" style={costFootnoteStyle}>
550
+ {showCo2Footnote && (
551
+ <>
552
+ {t('vehicleProps:value.co2CostsFootnoteTitle')}
553
+ <br />
554
+ <br />
555
+ {t('vehicleProps:value.co2CostsFootnote')}
556
+ </>
557
+ )}
558
+ {showCo2Footnote && showTaxFootnote && (
559
+ <>
560
+ <br />
561
+ <br />
562
+ <br />
563
+ </>
564
+ )}
565
+ {showTaxFootnote && (
566
+ <>
567
+ {t('vehicleProps:value.taxExemptFootnoteTitle')}
568
+ <br />
569
+ <br />
570
+ {t('vehicleProps:value.taxExemptFootnote')}
571
+ </>
572
+ )}
573
+ </div>
574
+ );
575
+ };
576
+
367
577
  // ---------------------------------------------------------------------------
368
578
  // Equipment list (grouped + searchable)
369
579
  // ---------------------------------------------------------------------------
package/vdp/index.ts CHANGED
@@ -29,6 +29,9 @@ export {
29
29
  VdpHeaderCondition,
30
30
  VdpTrustRow,
31
31
  VdpSpecTable,
32
+ VdpEnergyTaxSummary,
33
+ VdpCo2CostsSummary,
34
+ VdpCostFootnotes,
32
35
  EquipmentList,
33
36
  Co2Label,
34
37
  VehicleDescription,
@@ -49,6 +52,9 @@ export type {
49
52
  VdpHeaderCardProps,
50
53
  VdpTrustRowProps,
51
54
  VdpSpecTableProps,
55
+ VdpEnergyTaxSummaryProps,
56
+ VdpCo2CostsSummaryProps,
57
+ VdpCostFootnotesProps,
52
58
  EquipmentListProps,
53
59
  Co2LabelProps,
54
60
  VehicleDescriptionProps,
@@ -76,6 +82,9 @@ export type {
76
82
  VehicleDetail,
77
83
  VehicleCondition,
78
84
  VdpSpec,
85
+ RunningCostRow,
86
+ EnergyTaxSummary,
87
+ Co2CostsSummary,
79
88
  EquipmentGroups,
80
89
  PriceHistoryPoint,
81
90
  PriceRatingBand,
package/vdp/types.ts CHANGED
@@ -60,6 +60,53 @@ export function isZeroSpecValue(formatted: string): boolean {
60
60
  */
61
61
  export type EquipmentGroups = Record<string, string[]>;
62
62
 
63
+ /**
64
+ * One "Laufende Kosten pro Jahr" row (ADV-123/ADV-242 — Pkw-EnVKV mandatory cost
65
+ * disclosure). `label` and `value` are already the fully-formatted, translated strings
66
+ * the legacy VDP showed (e.g. label "bei niedrigem Ø CO2-Preis von 50 EUR/t", value
67
+ * "312,50 EUR/Jahr"); `description`, when present, is the required explanatory/
68
+ * uncertainty note for the CO2-cost-scenario rows and must always render visibly —
69
+ * never hide it behind a tooltip-only interaction (ticket requirement).
70
+ */
71
+ export interface RunningCostRow {
72
+ label: string;
73
+ value: string;
74
+ description?: string;
75
+ }
76
+
77
+ /**
78
+ * "Energiekosten und Kraftfahrzeugsteuer pro Jahr" summary card — new Figma design.
79
+ * Covers petrol/diesel, BEV, and PHEV (fuel-type-branching data). Needs-data: undefined
80
+ * when energyCost/tax can't be computed — fuelPriceNote is separately optional (omitted
81
+ * for BEVs, which have no per-liter fuel price).
82
+ */
83
+ export interface EnergyTaxSummary {
84
+ /** e.g. "1.508,40 €" — for a BEV, the electricity-cost equivalent (getFuelPrice already
85
+ * branches on fuel type internally). */
86
+ energyCost: string;
87
+ /** e.g. "314,00 €" — OR, for a BEV, the real §3d KraftStG time-limited exemption text
88
+ * ("befristet steuerbefreit"), never a fabricated "0,00 €". */
89
+ tax: string;
90
+ /** 1 line for ICE ("Kraftstoffpreis: 1,95 EUR/l (Jahresdurchschnitt 2023)") or BEV
91
+ * ("Strompreis: 41,60 ct/kWh (Jahresdurchschnitt 2023)"); BOTH lines for a plug-in
92
+ * hybrid (getBaseFuelPrice returns fuelPrice AND powerPrice when hybridPlugin===true) —
93
+ * each rendered on its own line, same style. Omitted entirely (never a guessed value)
94
+ * when the car has no computable per-liter fuel price NOR ct/kWh electricity price. */
95
+ fuelPriceNote?: string[];
96
+ }
97
+
98
+ /**
99
+ * "Mögliche CO₂-Kosten über die nächsten 10 Jahre (15.000 km/Jahr)" summary card — new
100
+ * Figma design, 3-column sibling of EnergyTaxSummary. The label already carries the
101
+ * reference €/t price, so `RunningCostRow.description` is unused here. Needs-data: a
102
+ * scenario missing its value is simply omitted.
103
+ */
104
+ export interface Co2CostsSummary {
105
+ low?: RunningCostRow;
106
+ middle?: RunningCostRow;
107
+ high?: RunningCostRow;
108
+ }
109
+
63
110
  /** Vehicle condition block rendered as the header card's zustand tiles. */
64
111
  export interface VehicleCondition {
65
112
  /** TÜV/HU validity, e.g. '03/2027' — or 'Neu' for a new HU. */
@@ -163,6 +210,12 @@ export interface VehicleDetail {
163
210
  */
164
211
  specs?: VdpSpec[];
165
212
 
213
+ // --- Pkw-EnVKV mandatory cost disclosure (ADV-123/ADV-242) ---
214
+ /** Needs-data: undefined when energyCost/tax can't be computed. */
215
+ energyTaxSummary?: EnergyTaxSummary;
216
+ /** Needs-data: undefined when no CO2-cost scenario can be computed. */
217
+ co2CostsSummary?: Co2CostsSummary;
218
+
166
219
  // --- price (always real) ---
167
220
  /** financing monthly rate in EUR — the lead price shown in sticky/bottom bars. */
168
221
  monthly: number;