@bytebrand/fe-ui-core-autobahn 1.0.80 → 1.0.82
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/cards/CarCard.tsx +14 -13
- package/cards/cardParts.tsx +7 -5
- package/compat/CookieModal/modals/ManageCookieModal/ManageCookieModal.tsx +10 -8
- package/compat/MdAccordionGroup/MdAccordionGroup.tsx +1 -1
- package/compat/MdField/MdField.tsx +29 -5
- package/constants.ts +31 -31
- package/locales/de/autobahn.json +9 -2
- package/locales/en/autobahn.json +9 -2
- package/package.json +1 -1
- package/theme/md3.css +167 -4
- package/utils/financeFormat.ts +62 -62
- package/utils/priceRating.ts +58 -58
- package/vdp/FinanceCalculator.tsx +146 -39
- package/vdp/VdpModules.tsx +184 -20
package/vdp/VdpModules.tsx
CHANGED
|
@@ -27,7 +27,7 @@ import { TranslationFunction } from '../types';
|
|
|
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
29
|
import { defaultVdpSpecs, EquipmentGroups, PriceRatingBands, VehicleDetail, VdpSpec, VdpReview } from './types';
|
|
30
|
-
import { CalcRow, DocCheckIcon, EvalBar, expanderBtnStyle, FinanceMode, Slider, TermChips, ToggleRow } from './FinanceCalculator';
|
|
30
|
+
import { AddServicesTooltipBody, CalcRow, DocCheckIcon, EvalBar, expanderBtnStyle, FinanceMode, Slider, TermChips, ToggleRow } from './FinanceCalculator';
|
|
31
31
|
import { formatEur as eur, formatPct as pct } from '../utils/financeFormat';
|
|
32
32
|
|
|
33
33
|
/** Figma "check with open arc" glyph (modern lucide circle-check-big; the bundled lucide
|
|
@@ -1460,6 +1460,12 @@ export interface VdpBottomBarProps {
|
|
|
1460
1460
|
ratingLabel?: string;
|
|
1461
1461
|
/** primary CTA click. */
|
|
1462
1462
|
onSecure?: () => void;
|
|
1463
|
+
/** true once the customer already has an offer request/checkout in progress for this car —
|
|
1464
|
+
* turns the primary CTA green (see .fin-cta-secured) instead of the default filled style. */
|
|
1465
|
+
secured?: boolean;
|
|
1466
|
+
/** overrides the primary CTA label while `secured` is true (e.g. "Zur Bestellung"). Ignored
|
|
1467
|
+
* when `secured` is false — the CTA keeps its own default label in that case. */
|
|
1468
|
+
secureLabel?: string;
|
|
1463
1469
|
/** phone CTA click. */
|
|
1464
1470
|
onCall?: () => void;
|
|
1465
1471
|
/** opens the "Rate anpassen" sheet; when omitted the button is not rendered. */
|
|
@@ -1488,6 +1494,8 @@ export const VdpBottomBar: React.FunctionComponent<VdpBottomBarProps> = ({
|
|
|
1488
1494
|
oldMonthly,
|
|
1489
1495
|
ratingLabel,
|
|
1490
1496
|
onSecure,
|
|
1497
|
+
secured,
|
|
1498
|
+
secureLabel,
|
|
1491
1499
|
onCall,
|
|
1492
1500
|
onOpenAdjust,
|
|
1493
1501
|
down,
|
|
@@ -1501,10 +1509,10 @@ export const VdpBottomBar: React.FunctionComponent<VdpBottomBarProps> = ({
|
|
|
1501
1509
|
const rate = monthly != null ? monthly : vehicle.monthly;
|
|
1502
1510
|
const tierIdx = TIER_LABELS.indexOf(ratingLabel as (typeof TIER_LABELS)[number]);
|
|
1503
1511
|
const evalTier = tierIdx >= 0 ? tierIdx : 1;
|
|
1504
|
-
// "good" collapses into the same "Top-Preis" label as the actual top tier (superscript marks
|
|
1505
|
-
// the collapse) — identical convention to CarCard's PriceEvalCol / FinanceCalculator's pill.
|
|
1506
1512
|
const isGoodTier = TIER_KEYS[evalTier] === 'good';
|
|
1507
|
-
|
|
1513
|
+
// each tier shows its own real label now (Top/Guter/Fairer/Erhöht/Hoher Preis) — no more
|
|
1514
|
+
// collapsing 'good' onto the "Top-Preis" claim. Pill styling (colour/bg/class/footnote) unchanged.
|
|
1515
|
+
const evalTierLabel = t('autobahn:tier.' + TIER_KEYS[evalTier]);
|
|
1508
1516
|
const evalPillColor = isGoodTier ? TIER_COLORS[0] : TIER_COLORS[evalTier];
|
|
1509
1517
|
const evalPillBackground = isGoodTier ? TIER_BACKGROUNDS[0] : TIER_BACKGROUNDS[evalTier];
|
|
1510
1518
|
// condition summary — same wording/order as FinanceCalculator's own .fin-cond (financing
|
|
@@ -1713,6 +1721,7 @@ export const VdpBottomBar: React.FunctionComponent<VdpBottomBarProps> = ({
|
|
|
1713
1721
|
<div className="bb-cta-row">
|
|
1714
1722
|
<Button
|
|
1715
1723
|
variant="filled"
|
|
1724
|
+
className={secured ? 'fin-cta-secured' : undefined}
|
|
1716
1725
|
style={{
|
|
1717
1726
|
width: isCash ? undefined : 180,
|
|
1718
1727
|
flex: isCash ? 1 : undefined,
|
|
@@ -1730,8 +1739,8 @@ export const VdpBottomBar: React.FunctionComponent<VdpBottomBarProps> = ({
|
|
|
1730
1739
|
}}
|
|
1731
1740
|
onClick={onSecure}
|
|
1732
1741
|
>
|
|
1733
|
-
<DocCheckIcon size={23} />
|
|
1734
|
-
{t('autobahn:bottom.secureOffer')}
|
|
1742
|
+
{secured ? <Icon name="shield-check" size={18} /> : <DocCheckIcon size={23} />}
|
|
1743
|
+
{secured && secureLabel ? secureLabel : t('autobahn:bottom.secureOffer')}
|
|
1735
1744
|
</Button>
|
|
1736
1745
|
{/* padding:0 — icon-only button (same fix as the sheet's phone button) */}
|
|
1737
1746
|
{onCall && (
|
|
@@ -2192,18 +2201,172 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
|
|
|
2192
2201
|
{/* ============================== DESKTOP (≥601px) — always-expanded breakdown, no tabs
|
|
2193
2202
|
(the desktop .fin-calc-card owns its own tabs outside this modal) ============================== */}
|
|
2194
2203
|
<div className="hide-mobile">
|
|
2195
|
-
<div
|
|
2196
|
-
<
|
|
2197
|
-
{
|
|
2198
|
-
|
|
2199
|
-
|
|
2204
|
+
<div className={'fin-price-grid' + (isCash ? ' fin-price-grid--cash' : '')} style={{ marginBottom: 14 }}>
|
|
2205
|
+
<div className="fin-rate-main">
|
|
2206
|
+
{isCash && listPrice != null && listPrice > price && (
|
|
2207
|
+
<div
|
|
2208
|
+
style={{ display: 'flex', justifyContent: 'flex-end', lineHeight: 1, marginTop: 0 }}
|
|
2209
|
+
role="button"
|
|
2210
|
+
tabIndex={0}
|
|
2211
|
+
onClick={() => setUvpTooltipOpen((v) => !v)}
|
|
2212
|
+
>
|
|
2213
|
+
<s className="num fin-rate-old fin-rate-old-list">
|
|
2214
|
+
{eur(listPrice)} €
|
|
2215
|
+
<span
|
|
2216
|
+
className="pp-eyebrow-badge"
|
|
2217
|
+
role="button"
|
|
2218
|
+
tabIndex={0}
|
|
2219
|
+
onClick={(e) => {
|
|
2220
|
+
e.stopPropagation();
|
|
2221
|
+
setUvpTooltipOpen((v) => !v);
|
|
2222
|
+
}}
|
|
2223
|
+
>
|
|
2224
|
+
2
|
|
2225
|
+
</span>
|
|
2226
|
+
{uvpTooltipOpen && (
|
|
2227
|
+
<div className="pp-uvp-tooltip" onClick={(e) => e.stopPropagation()}>
|
|
2228
|
+
<button
|
|
2229
|
+
type="button"
|
|
2230
|
+
className="pp-uvp-tooltip-close"
|
|
2231
|
+
aria-label={t('autobahn:modal.close')}
|
|
2232
|
+
onClick={(e) => {
|
|
2233
|
+
e.stopPropagation();
|
|
2234
|
+
setUvpTooltipOpen(false);
|
|
2235
|
+
}}
|
|
2236
|
+
>
|
|
2237
|
+
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2238
|
+
<path d="M5 5L15 15M15 5L5 15" stroke="white" strokeWidth={2.4} strokeLinecap="round" />
|
|
2239
|
+
</svg>
|
|
2240
|
+
</button>
|
|
2241
|
+
<div className="pp-uvp-tooltip-body">{t('autobahn:card.price.uvpListTooltipBody')}</div>
|
|
2242
|
+
</div>
|
|
2243
|
+
)}
|
|
2244
|
+
</s>
|
|
2245
|
+
</div>
|
|
2246
|
+
)}
|
|
2247
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
|
|
2248
|
+
{isCash ? (
|
|
2249
|
+
<span
|
|
2250
|
+
className={`num fin-rate-cash-mobile${
|
|
2251
|
+
(() => {
|
|
2252
|
+
const digits = String(Math.round(price)).length;
|
|
2253
|
+
if (digits >= 8) return ' fin-rate-cash-mobile--len8';
|
|
2254
|
+
if (digits === 7) return ' fin-rate-cash-mobile--len7';
|
|
2255
|
+
if (digits === 6) return ' fin-rate-cash-mobile--len6';
|
|
2256
|
+
return '';
|
|
2257
|
+
})()
|
|
2258
|
+
}`}
|
|
2259
|
+
style={{ fontSize: 45, fontWeight: 800, color: 'var(--md-primary)', letterSpacing: '-2px' }}
|
|
2260
|
+
>
|
|
2261
|
+
{eur(price)} €<sup>1</sup>
|
|
2262
|
+
</span>
|
|
2263
|
+
) : (
|
|
2264
|
+
<>
|
|
2265
|
+
<span
|
|
2266
|
+
className={`num fin-rate-hero${
|
|
2267
|
+
(() => {
|
|
2268
|
+
const digits = String(Math.round(monthly)).length;
|
|
2269
|
+
if (digits >= 6) return ' fin-rate-hero--len6';
|
|
2270
|
+
if (digits === 5) return ' fin-rate-hero--len5';
|
|
2271
|
+
if (digits === 4) return ' fin-rate-hero--len4';
|
|
2272
|
+
return '';
|
|
2273
|
+
})()
|
|
2274
|
+
}`}
|
|
2275
|
+
style={{ fontSize: 65, fontWeight: 800, color: 'var(--md-primary)', letterSpacing: '-2px' }}
|
|
2276
|
+
>
|
|
2277
|
+
{eur(Math.round(monthly))}<span style={{ marginLeft: 6 }}>€</span>
|
|
2278
|
+
</span>
|
|
2279
|
+
<span className="fin-rate-unit">
|
|
2280
|
+
{oldMonthly != null && (
|
|
2281
|
+
<span
|
|
2282
|
+
style={{ display: 'flex', flexDirection: 'column', cursor: 'pointer' }}
|
|
2283
|
+
role="button"
|
|
2284
|
+
tabIndex={0}
|
|
2285
|
+
onClick={() => setUvpTooltipOpen((v) => !v)}
|
|
2286
|
+
>
|
|
2287
|
+
<span className="pp-eyebrow">
|
|
2288
|
+
{t('autobahn:card.price.uvpRateLabel')}
|
|
2289
|
+
<span
|
|
2290
|
+
className="pp-eyebrow-badge"
|
|
2291
|
+
role="button"
|
|
2292
|
+
tabIndex={0}
|
|
2293
|
+
onClick={(e) => {
|
|
2294
|
+
e.stopPropagation();
|
|
2295
|
+
setUvpTooltipOpen((v) => !v);
|
|
2296
|
+
}}
|
|
2297
|
+
>
|
|
2298
|
+
3
|
|
2299
|
+
</span>
|
|
2300
|
+
{uvpTooltipOpen && (
|
|
2301
|
+
<div
|
|
2302
|
+
className={'pp-uvp-tooltip' + (mode === 'leasing' ? ' pp-uvp-tooltip--leasing' : '')}
|
|
2303
|
+
onClick={(e) => e.stopPropagation()}
|
|
2304
|
+
>
|
|
2305
|
+
<button
|
|
2306
|
+
type="button"
|
|
2307
|
+
className="pp-uvp-tooltip-close"
|
|
2308
|
+
aria-label={t('autobahn:modal.close')}
|
|
2309
|
+
onClick={(e) => {
|
|
2310
|
+
e.stopPropagation();
|
|
2311
|
+
setUvpTooltipOpen(false);
|
|
2312
|
+
}}
|
|
2313
|
+
>
|
|
2314
|
+
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2315
|
+
<path d="M5 5L15 15M15 5L5 15" stroke="white" strokeWidth={2.4} strokeLinecap="round" />
|
|
2316
|
+
</svg>
|
|
2317
|
+
</button>
|
|
2318
|
+
<div className="pp-uvp-tooltip-body">
|
|
2319
|
+
{t(
|
|
2320
|
+
mode === 'leasing'
|
|
2321
|
+
? 'autobahn:card.price.uvpRateTooltipBodyLeasing'
|
|
2322
|
+
: 'autobahn:card.price.uvpRateTooltipBody',
|
|
2323
|
+
)}
|
|
2324
|
+
</div>
|
|
2325
|
+
</div>
|
|
2326
|
+
)}
|
|
2327
|
+
</span>
|
|
2328
|
+
<s
|
|
2329
|
+
className={`num fin-rate-old fin-rate-example-price${
|
|
2330
|
+
String(Math.round(oldMonthly)).length >= 5
|
|
2331
|
+
? ' fin-rate-example-price--len5'
|
|
2332
|
+
: String(Math.round(oldMonthly)).length === 4
|
|
2333
|
+
? ' fin-rate-example-price--len4'
|
|
2334
|
+
: ''
|
|
2335
|
+
}`}
|
|
2336
|
+
>
|
|
2337
|
+
{eur(Math.round(oldMonthly))} €
|
|
2338
|
+
</s>
|
|
2339
|
+
</span>
|
|
2340
|
+
)}
|
|
2341
|
+
<span className="fin-rate-example-suffix">
|
|
2342
|
+
{t('autobahn:finance.perMonth')}
|
|
2343
|
+
</span>
|
|
2344
|
+
</span>
|
|
2345
|
+
</>
|
|
2346
|
+
)}
|
|
2347
|
+
</div>
|
|
2348
|
+
</div>
|
|
2349
|
+
<div className="fin-eval-col">
|
|
2350
|
+
<div className="fin-eval-pill-cell">
|
|
2351
|
+
{ratingLabel && (
|
|
2352
|
+
<span
|
|
2353
|
+
className={`fin-eval-pill${TIER_KEYS[evalTier] === 'good' ? ' fin-eval-pill--good' : ''}`}
|
|
2354
|
+
style={{ color: TIER_COLORS[evalTier], background: `color-mix(in srgb, ${TIER_COLORS[evalTier]} 16%, transparent)` }}
|
|
2355
|
+
>
|
|
2356
|
+
{evalTierLabel}
|
|
2357
|
+
{TIER_KEYS[evalTier] === 'good' && <sup>4</sup>}
|
|
2358
|
+
</span>
|
|
2359
|
+
)}
|
|
2360
|
+
</div>
|
|
2361
|
+
<div className="fin-eval-bar-cell">{ratingLabel && <EvalBar tier={evalTier} />}</div>
|
|
2362
|
+
</div>
|
|
2200
2363
|
</div>
|
|
2201
2364
|
{/* adjustment sliders — change a value and every figure below recomputes live (via the page) */}
|
|
2202
2365
|
{adjustable && (
|
|
2203
2366
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 16, marginBottom: 18 }}>
|
|
2204
2367
|
<div>
|
|
2205
2368
|
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 6 }}>
|
|
2206
|
-
<span className="md-title-sm">{t('autobahn:finance.
|
|
2369
|
+
<span className="md-title-sm">{t('autobahn:finance.downPaymentSliderLabelSheet')}</span>
|
|
2207
2370
|
<span className="num" style={{ fontWeight: 700 }}>
|
|
2208
2371
|
{eur(down)} €
|
|
2209
2372
|
</span>
|
|
@@ -2266,6 +2429,8 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
|
|
|
2266
2429
|
checked={!!withAddServices}
|
|
2267
2430
|
disabled={addServicesDisabled}
|
|
2268
2431
|
onChange={onToggleAddServices}
|
|
2432
|
+
tooltip={<AddServicesTooltipBody t={t} isLease={isLease} />}
|
|
2433
|
+
tooltipCloseLabel={t('autobahn:modal.close')}
|
|
2269
2434
|
/>
|
|
2270
2435
|
)}
|
|
2271
2436
|
<div ref={desktopBreakdownRef} style={!isCash ? { marginTop: 18 } : undefined}>
|
|
@@ -2564,7 +2729,7 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
|
|
|
2564
2729
|
</div>
|
|
2565
2730
|
)}
|
|
2566
2731
|
|
|
2567
|
-
<div className={'fin-price-grid' + (isCash ? ' fin-price-grid--cash' : '')}>
|
|
2732
|
+
<div className={'fin-price-grid' + (isCash ? ' fin-price-grid--cash' : '')} style={{ paddingBottom: 10 }}>
|
|
2568
2733
|
<div className="fin-rate-main">
|
|
2569
2734
|
{/* Figma Kaufen sheet: struck list price ABOVE the price, aligned to its right
|
|
2570
2735
|
edge (inline-beside wraps — the 44px phone hero fills the whole column) */}
|
|
@@ -2728,12 +2893,9 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
|
|
|
2728
2893
|
{isCash ? (
|
|
2729
2894
|
<>
|
|
2730
2895
|
<div>
|
|
2731
|
-
<CalcRow className="calcrow--payment" label={t('autobahn:finance.paymentLabel')}>
|
|
2896
|
+
<CalcRow className="calcrow--payment" label={t('autobahn:finance.paymentLabel')} last={!available}>
|
|
2732
2897
|
{t('autobahn:finance.paymentValue')}
|
|
2733
2898
|
</CalcRow>
|
|
2734
|
-
<CalcRow className="calcrow--payment" label={t('autobahn:finance.warrantyLabel')} last={!available}>
|
|
2735
|
-
{t('autobahn:finance.warrantyValue')}
|
|
2736
|
-
</CalcRow>
|
|
2737
2899
|
{/* Lieferung row disabled per request, replaced by real availability
|
|
2738
2900
|
(carStore.isAvailable) — same "Sofort verfügbar" wording as VdpTrustRow.
|
|
2739
2901
|
<CalcRow className="calcrow--payment" label={t('autobahn:finance.deliveryLabel')} last>
|
|
@@ -2755,6 +2917,8 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
|
|
|
2755
2917
|
disabled={addServicesDisabled}
|
|
2756
2918
|
onChange={(checked) => onToggleAddServices && onToggleAddServices(checked)}
|
|
2757
2919
|
staticNote={t('autobahn:finance.addServicesIncluded')}
|
|
2920
|
+
tooltip={<AddServicesTooltipBody t={t} isCash />}
|
|
2921
|
+
tooltipCloseLabel={t('autobahn:modal.close')}
|
|
2758
2922
|
/>
|
|
2759
2923
|
)}
|
|
2760
2924
|
{/* pushed to the sheet's bottom edge — Figma keeps a big breathing gap above */}
|
|
@@ -2810,8 +2974,7 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
|
|
|
2810
2974
|
<div>
|
|
2811
2975
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
2812
2976
|
<span className="md-title-sm" style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
|
|
2813
|
-
{t('autobahn:finance.
|
|
2814
|
-
<Icon name="info" size={13} color="var(--md-on-surface-variant)" />
|
|
2977
|
+
{t('autobahn:finance.downPaymentSliderLabelSheet')}
|
|
2815
2978
|
</span>
|
|
2816
2979
|
<span className="num fin-down-value" style={{ fontWeight: 700, color: 'var(--md-primary)' }}>
|
|
2817
2980
|
{eur(down)} €
|
|
@@ -2842,7 +3005,6 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
|
|
|
2842
3005
|
>
|
|
2843
3006
|
<span className="md-title-sm" style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
|
|
2844
3007
|
{t('autobahn:finance.annualMileageLabel')}
|
|
2845
|
-
<Icon name="info" size={13} color="var(--md-on-surface-variant)" />
|
|
2846
3008
|
</span>
|
|
2847
3009
|
<span className="num" style={{ fontWeight: 700, color: 'var(--md-primary)' }}>
|
|
2848
3010
|
{eur(annualMileage)} km
|
|
@@ -2891,6 +3053,8 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
|
|
|
2891
3053
|
checked={!!withAddServices}
|
|
2892
3054
|
disabled={addServicesDisabled}
|
|
2893
3055
|
onChange={onToggleAddServices}
|
|
3056
|
+
tooltip={<AddServicesTooltipBody t={t} isLease={isLease} />}
|
|
3057
|
+
tooltipCloseLabel={t('autobahn:modal.close')}
|
|
2894
3058
|
/>
|
|
2895
3059
|
)}
|
|
2896
3060
|
|