@bytebrand/fe-ui-core-autobahn 1.0.78 → 1.0.80

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.
@@ -392,27 +392,38 @@ export interface PricePathsProps {
392
392
  const TONE_TO_TIER: Record<string, number> = { top: 0, good: 1, fair: 2, raised: 3, high: 4 };
393
393
 
394
394
  /**
395
- * Continuous Preisbewertung gradient bar + knob (Figma: green→yellow→orange→red rail with a
396
- * small circular marker at the car's position — no text label, since the tier is already named
397
- * by the StatChip pill next to the price). Renders nothing when there is no rating/band
398
- * (needs-data: we never draw a fake market position).
395
+ * Discrete Preisbewertung tier bar + thumb (Figma: 5 solid segments — green/light-green/yellow/
396
+ * orange/red — with a white circular marker at the car's position, same `.fin-eval-bar`/
397
+ * `.fin-eval-seg` segmented treatment the grid CarCard's PriceEvalCol/EvalBar already uses; no
398
+ * text label here since the tier is already named by the StatChip pill next to the price).
399
+ * Renders nothing when there is no rating/band (needs-data: we never draw a fake market position).
399
400
  */
400
401
  export const PriceMeter: React.FunctionComponent<{ car: AutobahnCar; t: TranslationFunction }> = ({ car }) => {
401
402
  const hasMarket =
402
403
  car.marketLow != null && car.marketAvg != null && car.marketHigh != null && car.marketHigh > car.marketLow;
403
- const knob = (tier: number) => (
404
- <div className="pp-bar">
405
- <span className="pp-bar-dot" style={{ left: `${Math.max(4, Math.min(96, (tier / 4) * 100))}%` }} />
406
- </div>
407
- );
404
+ const bar = (tier: number) => {
405
+ const leftPct = Math.max(4, Math.min(96, (tier / 4) * 100));
406
+ const tierIndex = Math.max(0, Math.min(4, Math.round(tier)));
407
+ return (
408
+ <div className="fin-eval-bar">
409
+ {TIER_COLORS.map((c, i) => (
410
+ <span key={i} className="fin-eval-seg" style={{ background: c }} />
411
+ ))}
412
+ <div
413
+ className={`fin-eval-thumb${TIER_KEYS[tierIndex] === 'good' ? ' fin-eval-thumb--good' : ''}`}
414
+ style={{ left: `${leftPct}%`, borderColor: TIER_COLORS[tierIndex] }}
415
+ />
416
+ </div>
417
+ );
418
+ };
408
419
  if (hasMarket) {
409
420
  const mpos = Math.max(0, Math.min(1, (car.price - car.marketLow!) / (car.marketHigh! - car.marketLow!)));
410
- return knob(mpos * 4);
421
+ return bar(mpos * 4);
411
422
  }
412
423
  if (car.rating && TONE_TO_TIER[car.rating] != null) {
413
424
  // No market BAND yet, but the real Preisbewertung tier (same signal as the rating chip)
414
- // drives the gradient knob position. Real data, not faked.
415
- return knob(TONE_TO_TIER[car.rating]);
425
+ // drives the thumb position. Real data, not faked.
426
+ return bar(TONE_TO_TIER[car.rating]);
416
427
  }
417
428
  return null;
418
429
  };
package/constants.ts CHANGED
@@ -1,31 +1,31 @@
1
- // Autobahn (auto.de redesign) — shared lookup constants barrel.
2
- // Pure data re-exported from ./constants/autobahn (kept import-safe for plain Node consumers,
3
- // see that file's header) plus the legacy fe-ui-core shim re-exports below.
4
- export * from './constants/autobahn';
5
-
6
- // ---------------------------------------------------------------------------
7
- // Legacy shared constants (moved here from fe-ui-core).
8
- //
9
- // This block mirrors the public symbol set of the old
10
- // `@bytebrand/fe-ui-core/constants` barrel so consumers can swap that
11
- // specifier for `@bytebrand/fe-ui-core-autobahn/constants` 1:1. The
12
- // implementations live under ./constants/ (core.ts, app.ts, price.ts,
13
- // Search.ts, …); fe-ui-core re-exports them via shims until it is deleted.
14
- // ---------------------------------------------------------------------------
15
-
16
- export {
17
- offers,
18
- checkoutOffers,
19
- checkoutWarranties,
20
- checkoutSteps,
21
- checkoutSubStep,
22
- DELIVERY_PERIODS_EXTRA,
23
- THREE,
24
- WLTP_ENERGY_EFFICIENCY_CLASS
25
- } from './constants/core';
26
-
27
- export { CUSTOM_ERROR_MESSAGES, supportPhone, supportPhoneLink } from './constants/app';
28
-
29
- export { priceRatings } from './constants/price';
30
-
31
- export { THROTTLE_DELAY } from './constants/Search';
1
+ // Autobahn (auto.de redesign) — shared lookup constants barrel.
2
+ // Pure data re-exported from ./constants/autobahn (kept import-safe for plain Node consumers,
3
+ // see that file's header) plus the legacy fe-ui-core shim re-exports below.
4
+ export * from './constants/autobahn';
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // Legacy shared constants (moved here from fe-ui-core).
8
+ //
9
+ // This block mirrors the public symbol set of the old
10
+ // `@bytebrand/fe-ui-core/constants` barrel so consumers can swap that
11
+ // specifier for `@bytebrand/fe-ui-core-autobahn/constants` 1:1. The
12
+ // implementations live under ./constants/ (core.ts, app.ts, price.ts,
13
+ // Search.ts, …); fe-ui-core re-exports them via shims until it is deleted.
14
+ // ---------------------------------------------------------------------------
15
+
16
+ export {
17
+ offers,
18
+ checkoutOffers,
19
+ checkoutWarranties,
20
+ checkoutSteps,
21
+ checkoutSubStep,
22
+ DELIVERY_PERIODS_EXTRA,
23
+ THREE,
24
+ WLTP_ENERGY_EFFICIENCY_CLASS
25
+ } from './constants/core';
26
+
27
+ export { CUSTOM_ERROR_MESSAGES, supportPhone, supportPhoneLink } from './constants/app';
28
+
29
+ export { priceRatings } from './constants/price';
30
+
31
+ export { THROTTLE_DELAY } from './constants/Search';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytebrand/fe-ui-core-autobahn",
3
- "version": "1.0.78",
3
+ "version": "1.0.80",
4
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
5
  "main": "index.ts",
6
6
  "license": "UNLICENSED",
package/theme/md3.css CHANGED
@@ -4623,7 +4623,11 @@ line-height: 31px;
4623
4623
  .md3 .cc-card .cc-badges {
4624
4624
  gap: 8px !important;
4625
4625
  }
4626
- .md3 .cc-card .deal-chip {
4626
+ /* Same badge/chip treatment on the SRP desktop list-card (.listcard, CarRow.tsx) as the grid
4627
+ CarCard — was still the smaller/more-saturated default dealPill look (see cardParts.tsx)
4628
+ since these overrides were originally scoped to .cc-card only. */
4629
+ .md3 .cc-card .deal-chip,
4630
+ .md3 .listcard .deal-chip {
4627
4631
  height: 30px !important;
4628
4632
  padding: 0 10px !important;
4629
4633
  border-radius: 15px !important;
@@ -4632,7 +4636,8 @@ line-height: 31px;
4632
4636
  box-shadow: none !important;
4633
4637
  border: 1px solid transparent;
4634
4638
  }
4635
- .md3 .cc-card .deal-chip--drop {
4639
+ .md3 .cc-card .deal-chip--drop,
4640
+ .md3 .listcard .deal-chip--drop {
4636
4641
  /* solid equivalent of rgba(17, 110, 57, 0.2) over white — the badge now sits over an
4637
4642
  edge-to-edge photo (CardCarousel padding removed), so a translucent fill let the image
4638
4643
  show through instead of the intended pastel tint. */
@@ -4640,18 +4645,21 @@ line-height: 31px;
4640
4645
  border-color: rgba(17, 110, 57, 0.05);
4641
4646
  color: #116e39 !important;
4642
4647
  }
4643
- .md3 .cc-card .deal-chip--percent {
4648
+ .md3 .cc-card .deal-chip--percent,
4649
+ .md3 .listcard .deal-chip--percent {
4644
4650
  background: rgb(247, 213, 210) !important;
4645
4651
  border-color: rgba(217, 45, 32, 0.05);
4646
4652
  color: #d92d20 !important;
4647
4653
  }
4648
- .md3 .cc-card .deal-chip--zerodown {
4654
+ .md3 .cc-card .deal-chip--zerodown,
4655
+ .md3 .listcard .deal-chip--zerodown {
4649
4656
  background: rgb(204, 222, 255) !important;
4650
4657
  border-color: rgba(0, 92, 255, 0.05);
4651
4658
  color: #005cff !important;
4652
4659
  }
4653
4660
  /* NEU badge — Figma: amber tint pill, not the mobile solid-primary-blue pill */
4654
- .md3 .cc-card .neu-badge {
4661
+ .md3 .cc-card .neu-badge,
4662
+ .md3 .listcard .neu-badge {
4655
4663
  height: 30px;
4656
4664
  padding: 0 10px;
4657
4665
  background: rgb(255, 241, 204);
@@ -4661,7 +4669,8 @@ line-height: 31px;
4661
4669
  }
4662
4670
  /* "⚡ Angebot" badge — Figma: black-tinted ROUNDED RECT (10px radius, not a full pill), black
4663
4671
  text — bigger than and visually distinct from the mobile dark-solid pill. */
4664
- .md3 .cc-card .offer-badge {
4672
+ .md3 .cc-card .offer-badge,
4673
+ .md3 .listcard .offer-badge {
4665
4674
  height: 30px;
4666
4675
  padding: 0 10px;
4667
4676
  border-radius: 10px;
@@ -4670,7 +4679,8 @@ line-height: 31px;
4670
4679
  color: #000;
4671
4680
  font-size: 14px;
4672
4681
  }
4673
- .md3 .cc-card .offer-badge .lucide {
4682
+ .md3 .cc-card .offer-badge .lucide,
4683
+ .md3 .listcard .offer-badge .lucide {
4674
4684
  width: 15px;
4675
4685
  height: 15px;
4676
4686
  color: #000;
@@ -1,62 +1,62 @@
1
- // Autobahn (auto.de redesign) — shared financing-number formatting.
2
- //
3
- // Extracted from vdp/VdpModules.tsx's FinanceDetailsModal so non-React consumers (e.g. the
4
- // Meta AIA marketing-image renderer) format the SAME real numbers the same way, instead of a
5
- // second copy drifting from the VDP's. Pure TS, no React/JSX — safe to import from a plain
6
- // Node service.
7
- //
8
- // The "only real figures, never a fabricated total" rule is load-bearing (legal: balloon
9
- // financing means monthly×term is NOT the total) and lives here once, not per-caller.
10
-
11
- /** Monetary figure → de-DE grouped string (e.g. 47400 → "47.400"). Feed rounded integers —
12
- * toLocaleString on a fractional value prints a bogus 3-decimal grouping. */
13
- export function formatEur(n: number): string {
14
- return n.toLocaleString('de-DE');
15
- }
16
-
17
- /** Rate → de-DE percent string (e.g. 6.49 → "6,49 %"). */
18
- export function formatPct(n: number): string {
19
- return `${n.toFixed(2).replace('.', ',')} %`;
20
- }
21
-
22
- export interface FinanceExampleFields {
23
- price: number;
24
- apr: number;
25
- down: number;
26
- /** Real config figure — falls back to price-down ONLY when genuinely absent. */
27
- netLoanAmount?: number;
28
- nominalRate?: number;
29
- finalInstallment?: number;
30
- grossAmount?: number;
31
- }
32
-
33
- export interface FinanceExampleLabels {
34
- vehiclePrice: string;
35
- priceSuffix: string;
36
- apr: string;
37
- nominal: string;
38
- net: string;
39
- down: string;
40
- final: string;
41
- gross: string;
42
- }
43
-
44
- /**
45
- * The "Repräsentatives Beispiel" disclaimer line (PAngV). Only fields with a genuine value are
46
- * included — nominalRate/finalInstallment/grossAmount are optional real config figures, never
47
- * derived (see fields doc). Matches production wording/format exactly (VdpModules.tsx's
48
- * `exampleLine`, byte-for-byte).
49
- */
50
- export function buildFinanceExampleLine(fields: FinanceExampleFields, labels: FinanceExampleLabels, footnoteMarker = '³'): string {
51
- const netto = fields.netLoanAmount != null ? fields.netLoanAmount : Math.max(0, fields.price - fields.down);
52
- const parts: string[] = [
53
- `${labels.vehiclePrice}: ${formatEur(fields.price)} € ${labels.priceSuffix}`,
54
- ...(fields.apr > 0 ? [`${labels.apr}: ${formatPct(fields.apr)}`] : []),
55
- ...(fields.nominalRate != null && fields.nominalRate > 0 ? [`${labels.nominal}: ${formatPct(fields.nominalRate)}`] : []),
56
- `${labels.net}: ${formatEur(netto)} €`,
57
- `${labels.down}: ${formatEur(fields.down)} €`,
58
- ...(fields.finalInstallment != null && fields.finalInstallment > 0 ? [`${labels.final}: ${formatEur(fields.finalInstallment)} €`] : []),
59
- ...(fields.grossAmount != null && fields.grossAmount > 0 ? [`${labels.gross}: ${formatEur(fields.grossAmount)} €`] : []),
60
- ];
61
- return `${footnoteMarker} ${parts.join('; ')};`;
62
- }
1
+ // Autobahn (auto.de redesign) — shared financing-number formatting.
2
+ //
3
+ // Extracted from vdp/VdpModules.tsx's FinanceDetailsModal so non-React consumers (e.g. the
4
+ // Meta AIA marketing-image renderer) format the SAME real numbers the same way, instead of a
5
+ // second copy drifting from the VDP's. Pure TS, no React/JSX — safe to import from a plain
6
+ // Node service.
7
+ //
8
+ // The "only real figures, never a fabricated total" rule is load-bearing (legal: balloon
9
+ // financing means monthly×term is NOT the total) and lives here once, not per-caller.
10
+
11
+ /** Monetary figure → de-DE grouped string (e.g. 47400 → "47.400"). Feed rounded integers —
12
+ * toLocaleString on a fractional value prints a bogus 3-decimal grouping. */
13
+ export function formatEur(n: number): string {
14
+ return n.toLocaleString('de-DE');
15
+ }
16
+
17
+ /** Rate → de-DE percent string (e.g. 6.49 → "6,49 %"). */
18
+ export function formatPct(n: number): string {
19
+ return `${n.toFixed(2).replace('.', ',')} %`;
20
+ }
21
+
22
+ export interface FinanceExampleFields {
23
+ price: number;
24
+ apr: number;
25
+ down: number;
26
+ /** Real config figure — falls back to price-down ONLY when genuinely absent. */
27
+ netLoanAmount?: number;
28
+ nominalRate?: number;
29
+ finalInstallment?: number;
30
+ grossAmount?: number;
31
+ }
32
+
33
+ export interface FinanceExampleLabels {
34
+ vehiclePrice: string;
35
+ priceSuffix: string;
36
+ apr: string;
37
+ nominal: string;
38
+ net: string;
39
+ down: string;
40
+ final: string;
41
+ gross: string;
42
+ }
43
+
44
+ /**
45
+ * The "Repräsentatives Beispiel" disclaimer line (PAngV). Only fields with a genuine value are
46
+ * included — nominalRate/finalInstallment/grossAmount are optional real config figures, never
47
+ * derived (see fields doc). Matches production wording/format exactly (VdpModules.tsx's
48
+ * `exampleLine`, byte-for-byte).
49
+ */
50
+ export function buildFinanceExampleLine(fields: FinanceExampleFields, labels: FinanceExampleLabels, footnoteMarker = '³'): string {
51
+ const netto = fields.netLoanAmount != null ? fields.netLoanAmount : Math.max(0, fields.price - fields.down);
52
+ const parts: string[] = [
53
+ `${labels.vehiclePrice}: ${formatEur(fields.price)} € ${labels.priceSuffix}`,
54
+ ...(fields.apr > 0 ? [`${labels.apr}: ${formatPct(fields.apr)}`] : []),
55
+ ...(fields.nominalRate != null && fields.nominalRate > 0 ? [`${labels.nominal}: ${formatPct(fields.nominalRate)}`] : []),
56
+ `${labels.net}: ${formatEur(netto)} €`,
57
+ `${labels.down}: ${formatEur(fields.down)} €`,
58
+ ...(fields.finalInstallment != null && fields.finalInstallment > 0 ? [`${labels.final}: ${formatEur(fields.finalInstallment)} €`] : []),
59
+ ...(fields.grossAmount != null && fields.grossAmount > 0 ? [`${labels.gross}: ${formatEur(fields.grossAmount)} €`] : []),
60
+ ];
61
+ return `${footnoteMarker} ${parts.join('; ')};`;
62
+ }
@@ -1,58 +1,58 @@
1
- // Autobahn (auto.de redesign) — Preisbewertung rating math, extracted from CommonUtils.ts.
2
- //
3
- // Pulled out on purpose: CommonUtils.ts transitively imports `../constants/Search`, which
4
- // `require()`s image assets (webpack-only — breaks any plain Node `require`/`tsc` consumer).
5
- // This file only imports `../constants/price` (zero further imports) — a pure, testable leaf
6
- // module. CommonUtils.ts re-exports these for existing consumers — one implementation, not a fork.
7
- // (image-generator's Meta AIA renderer vendors a copy of this math directly instead of depending
8
- // on this package — see services/image-generator/src/services/metaAia/vendoredAutobahnMath.ts —
9
- // so a plain-Node backend doesn't need to npm-install this whole design system's dependency tree.)
10
- import { priceRatings, priceRatingConfig } from '../constants/price';
11
- import { PREDICTABLE_PRICE_INDEX } from '../constants/core';
12
-
13
- export const getPriceRatingConfig = (currentPrice: number, pricePredicted: number) => { // tslint:disable-line
14
- const round = (value: number) => {
15
- return Number.isFinite(pricePredicted)
16
- ? Math.round((pricePredicted * (1 + (value / 100))) / 100) * 100 // tslint:disable-line
17
- : '???';
18
- };
19
-
20
- if (currentPrice === 0) {
21
- console.log('Empty current price');
22
- }
23
-
24
- return Object.keys(priceRatingConfig).reduce((acc: any, key: any) => {
25
- const { min, max } = priceRatingConfig[key];
26
- return { ...acc, [key]: { min: round(min), max: round(max) } };
27
- }, {}); // tslint:disable-line
28
- };
29
-
30
- export const getPredictablePrice = (currentSalesPrice: number, predictablePrice: number) => {
31
- if (Number.isFinite(Number.parseFloat(`${predictablePrice}`))) {
32
- return predictablePrice;
33
- }
34
-
35
- return currentSalesPrice * PREDICTABLE_PRICE_INDEX;
36
- }
37
-
38
- export const getPriceRating = (currentSalesPrice: number, predictablePrice: number) => {
39
- const isCorrectCurrentSalesPrice = Number.isFinite(Number.parseFloat(`${currentSalesPrice}`));
40
- // const isCorrectPredictablePrice = Number.isFinite(Number.parseFloat(`${predictablePrice}`));
41
-
42
- if (!isCorrectCurrentSalesPrice) return 0;
43
-
44
-
45
- const validatedPredictablePrice = getPredictablePrice(currentSalesPrice, predictablePrice);
46
-
47
- const ratingConfig = getPriceRatingConfig(currentSalesPrice, validatedPredictablePrice);
48
-
49
- for (const key of Object.keys(ratingConfig)) {
50
- const { min, max } = ratingConfig[key];
51
- const TOP_PRICE = 5;
52
- if (key === 'highPrice' && currentSalesPrice === max) return 1;
53
- if (key === 'topPrice' && currentSalesPrice <= min) return TOP_PRICE;
54
- if (currentSalesPrice >= min && currentSalesPrice < max) return priceRatings.indexOf(key);
55
- }
56
-
57
- return 0;
58
- };
1
+ // Autobahn (auto.de redesign) — Preisbewertung rating math, extracted from CommonUtils.ts.
2
+ //
3
+ // Pulled out on purpose: CommonUtils.ts transitively imports `../constants/Search`, which
4
+ // `require()`s image assets (webpack-only — breaks any plain Node `require`/`tsc` consumer).
5
+ // This file only imports `../constants/price` (zero further imports) — a pure, testable leaf
6
+ // module. CommonUtils.ts re-exports these for existing consumers — one implementation, not a fork.
7
+ // (image-generator's Meta AIA renderer vendors a copy of this math directly instead of depending
8
+ // on this package — see services/image-generator/src/services/metaAia/vendoredAutobahnMath.ts —
9
+ // so a plain-Node backend doesn't need to npm-install this whole design system's dependency tree.)
10
+ import { priceRatings, priceRatingConfig } from '../constants/price';
11
+ import { PREDICTABLE_PRICE_INDEX } from '../constants/core';
12
+
13
+ export const getPriceRatingConfig = (currentPrice: number, pricePredicted: number) => { // tslint:disable-line
14
+ const round = (value: number) => {
15
+ return Number.isFinite(pricePredicted)
16
+ ? Math.round((pricePredicted * (1 + (value / 100))) / 100) * 100 // tslint:disable-line
17
+ : '???';
18
+ };
19
+
20
+ if (currentPrice === 0) {
21
+ console.log('Empty current price');
22
+ }
23
+
24
+ return Object.keys(priceRatingConfig).reduce((acc: any, key: any) => {
25
+ const { min, max } = priceRatingConfig[key];
26
+ return { ...acc, [key]: { min: round(min), max: round(max) } };
27
+ }, {}); // tslint:disable-line
28
+ };
29
+
30
+ export const getPredictablePrice = (currentSalesPrice: number, predictablePrice: number) => {
31
+ if (Number.isFinite(Number.parseFloat(`${predictablePrice}`))) {
32
+ return predictablePrice;
33
+ }
34
+
35
+ return currentSalesPrice * PREDICTABLE_PRICE_INDEX;
36
+ }
37
+
38
+ export const getPriceRating = (currentSalesPrice: number, predictablePrice: number) => {
39
+ const isCorrectCurrentSalesPrice = Number.isFinite(Number.parseFloat(`${currentSalesPrice}`));
40
+ // const isCorrectPredictablePrice = Number.isFinite(Number.parseFloat(`${predictablePrice}`));
41
+
42
+ if (!isCorrectCurrentSalesPrice) return 0;
43
+
44
+
45
+ const validatedPredictablePrice = getPredictablePrice(currentSalesPrice, predictablePrice);
46
+
47
+ const ratingConfig = getPriceRatingConfig(currentSalesPrice, validatedPredictablePrice);
48
+
49
+ for (const key of Object.keys(ratingConfig)) {
50
+ const { min, max } = ratingConfig[key];
51
+ const TOP_PRICE = 5;
52
+ if (key === 'highPrice' && currentSalesPrice === max) return 1;
53
+ if (key === 'topPrice' && currentSalesPrice <= min) return TOP_PRICE;
54
+ if (currentSalesPrice >= min && currentSalesPrice < max) return priceRatings.indexOf(key);
55
+ }
56
+
57
+ return 0;
58
+ };