@bytebrand/fe-ui-core-autobahn 1.0.160 → 1.0.162
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 +39 -9
- package/cards/cardParts.tsx +6 -1
- package/constants/app.ts +11 -1
- package/constants/core.ts +12 -1
- package/locales/legacy/data.ts +2 -1
- package/package.json +1 -1
- package/theme/md3.css +6 -3
- package/vehiclesProps/decoratedLightProps.tsx +16 -6
- package/vehiclesProps/decoratedProps.tsx +8 -4
package/cards/CarCard.tsx
CHANGED
|
@@ -157,6 +157,8 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
|
|
|
157
157
|
// active that's already 3, so NEU is dropped rather than causing a 4th to overflow.
|
|
158
158
|
const activeDealChips = [showDrop || showSave, showPct, !!car.zeroDown].filter(Boolean).length;
|
|
159
159
|
const showNeu = asGoodAsNew && car.isNew && activeDealChips < 3;
|
|
160
|
+
// tabs the price block can switch to (PriceTabs shows Leasing only when the car has a lease rate)
|
|
161
|
+
const paymentModes: PaymentMode[] = car.lease != null ? ['financing', 'leasing', 'buy'] : ['financing', 'buy'];
|
|
160
162
|
return (
|
|
161
163
|
<article
|
|
162
164
|
className="mcard mcard--interactive cc-card"
|
|
@@ -277,18 +279,46 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
|
|
|
277
279
|
<div className="cc-price" style={{ marginTop: 0, borderTop: '1px solid var(--md-outline-variant)' }}>
|
|
278
280
|
<PriceTabs car={car} mode={mode} onModeChange={setMode} t={t} />
|
|
279
281
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, marginTop: 10 }}>
|
|
280
|
-
|
|
282
|
+
{/* every tab's hero stacked in one grid cell (.cc-hero-stack), only the active one
|
|
283
|
+
visible, bottom-aligned — Kaufen's struck list price sits on its own line above
|
|
284
|
+
the price, so without this the row (and the card) grew/shrank by that line on
|
|
285
|
+
every tab switch. Same trick as .cc-terms-stack below. */}
|
|
286
|
+
<div className="cc-hero-stack">
|
|
287
|
+
{paymentModes.map((m) => (
|
|
288
|
+
<div
|
|
289
|
+
key={m}
|
|
290
|
+
className={'cc-hero-slot' + (m === mode ? '' : ' cc-hero-slot--hidden')}
|
|
291
|
+
aria-hidden={m === mode ? undefined : true}
|
|
292
|
+
>
|
|
293
|
+
<PriceHero car={car} mode={m} t={t} alwaysShowUnit />
|
|
294
|
+
</div>
|
|
295
|
+
))}
|
|
296
|
+
</div>
|
|
281
297
|
<PriceEvalCol car={car} t={t} />
|
|
282
298
|
</div>
|
|
283
299
|
{/* same fallback CardCtaRow already uses below: pages that haven't wired a real
|
|
284
|
-
"Rate anpassen" sheet yet still get a working "Details" link (opens the car).
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
300
|
+
"Rate anpassen" sheet yet still get a working "Details" link (opens the car).
|
|
301
|
+
Every tab's terms line is rendered into the same grid cell (.cc-terms-stack) and
|
|
302
|
+
only the active one is visible — the cell is as tall as the tallest line, so the
|
|
303
|
+
card keeps one height across Finanzierung/Leasing/Kaufen (Leasing's longer line
|
|
304
|
+
wraps to 2 lines on narrow phones). No change to the visible line itself. */}
|
|
305
|
+
<div className="cc-terms-stack">
|
|
306
|
+
{paymentModes.map((m) => (
|
|
307
|
+
<div
|
|
308
|
+
key={m}
|
|
309
|
+
className={'cc-terms-slot' + (m === mode ? '' : ' cc-terms-slot--hidden')}
|
|
310
|
+
aria-hidden={m === mode ? undefined : true}
|
|
311
|
+
>
|
|
312
|
+
<PriceTerms
|
|
313
|
+
car={car}
|
|
314
|
+
mode={m}
|
|
315
|
+
onOpenRateAdjust={onOpenRateAdjust || (() => onOpen(car))}
|
|
316
|
+
onShowDetails={onShowFinancingDetails ? () => onShowFinancingDetails(car, m) : undefined}
|
|
317
|
+
t={t}
|
|
318
|
+
/>
|
|
319
|
+
</div>
|
|
320
|
+
))}
|
|
321
|
+
</div>
|
|
292
322
|
</div>
|
|
293
323
|
{/* Mobile-only row (CardCtaRow itself carries the show-mobile class) — always
|
|
294
324
|
rendered, with real fallbacks the page can override once it wires its own
|
package/cards/cardParts.tsx
CHANGED
|
@@ -710,7 +710,12 @@ export const PriceTerms: React.FunctionComponent<{
|
|
|
710
710
|
onShowDetails?: () => void;
|
|
711
711
|
t: TranslationFunction;
|
|
712
712
|
}> = ({ car, mode, onOpenRateAdjust, onShowDetails, t }) => {
|
|
713
|
-
|
|
713
|
+
// Kaufen (buy): a cash purchase has no financing terms/details link — used to `return null`
|
|
714
|
+
// here, which collapsed .cc-terms to 0 height on that tab while Financing/Leasing rendered a
|
|
715
|
+
// real line, jumping the price row (and everything below it: rating pill, CTA row) up/down on
|
|
716
|
+
// every tab switch. Render an empty, still-present .cc-terms div instead — same fix as the VDP
|
|
717
|
+
// FinanceCalculator's .fin-cond (md3.css), which reserves its line's height the same way.
|
|
718
|
+
if (mode === 'buy') return <div className="cc-terms" />;
|
|
714
719
|
const finFirstInstallment =
|
|
715
720
|
car.finFirstInstallment != null ? car.finFirstInstallment.toLocaleString('de-DE') : _finFirstInstallment;
|
|
716
721
|
const finMonths = car.finMonths ?? _finMonths;
|
package/constants/app.ts
CHANGED
|
@@ -40,7 +40,17 @@ export const LOCAL_STORAGE_KEY = {
|
|
|
40
40
|
i18n_KEY: 'i18nLanguage'
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
// ADV-284: evaluated at import time — with site storage blocked, reading localStorage throws a
|
|
44
|
+
// SecurityError that took down the whole bundle. Blocked storage behaves like "nothing saved".
|
|
45
|
+
const readStoredLang = (): string | null => {
|
|
46
|
+
try {
|
|
47
|
+
return localStorage.getItem(LOCAL_STORAGE_KEY.i18n_KEY);
|
|
48
|
+
} catch (e) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const DEFAULT_LANG: string = typeof window !== 'undefined' ? readStoredLang() : 'de';
|
|
44
54
|
|
|
45
55
|
// Default support contact phone
|
|
46
56
|
export const CONTACT_PHONE: string = '+498004030182';
|
package/constants/core.ts
CHANGED
|
@@ -35,7 +35,18 @@ export const LOCAL_STORAGE_KEY = {
|
|
|
35
35
|
i18n_KEY: 'i18nLanguage'
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
// ADV-284: evaluated at import time — with site storage blocked, merely reading
|
|
39
|
+
// window.localStorage throws a SecurityError, which took down the whole bundle (landing
|
|
40
|
+
// page never rendered). Treat blocked storage like "no language saved".
|
|
41
|
+
const readStoredLang = (): string | null => {
|
|
42
|
+
try {
|
|
43
|
+
return typeof window !== 'undefined' ? window.localStorage.getItem(LOCAL_STORAGE_KEY.i18n_KEY) : null;
|
|
44
|
+
} catch (e) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const DEFAULT_LANG: string = readStoredLang() ?? 'de';
|
|
39
50
|
|
|
40
51
|
export const ROW_STYLES = {
|
|
41
52
|
marginBottom: 10
|
package/locales/legacy/data.ts
CHANGED
|
@@ -103,7 +103,8 @@ export const vehicleProps = (car?: any, isOfferAvailable: boolean = true) => {
|
|
|
103
103
|
wltpDischargedCombinedLabel: `${getFormattedNumber(vehicleOption.wltpCombined != null ? vehicleOption.wltpCombined : 19.9)}l/100km (entladen, komb)`,
|
|
104
104
|
wltpCo2WtdCombinedShort: `${getFormattedNumber(vehicleOption.wltpCo2)}g CO2/km (gew., komb)*`,
|
|
105
105
|
co2ClassWeighted: `CO2-Klasse ${(cbd.cbd as any)[vehicleOption.wltpEnergyEfficiencyClass] || vehicleOption.wltpEnergyEfficiencyClass || ''} (gew., komb)`,
|
|
106
|
-
|
|
106
|
+
// ADV-187: no hard-coded fallback class — callers only render this when the class is defined.
|
|
107
|
+
co2ClassDischarged: `CO2-Klasse ${(cbd.cbd as any)[vehicleOption.wltpDischargedEnergyEfficiencyClass] || vehicleOption.wltpDischargedEnergyEfficiencyClass || ''} (entladen, komb)`
|
|
107
108
|
},
|
|
108
109
|
title: {
|
|
109
110
|
'addOfferToMainSlide': 'Zum Schieberegler hinzufügen',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bytebrand/fe-ui-core-autobahn",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.162",
|
|
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
|
@@ -10100,7 +10100,8 @@ line-height: 31px;
|
|
|
10100
10100
|
z-index: 71;
|
|
10101
10101
|
background: var(--md-surface);
|
|
10102
10102
|
border-radius: var(--md-r-xl) var(--md-r-xl) 0 0;
|
|
10103
|
-
|
|
10103
|
+
/* % of the fixed containing block, not vh/dvh: on iOS vh is the toolbars-collapsed height, and Chrome on iOS does not shrink dvh while its toolbars are shown either, so a 92vh/92dvh bottom sheet grew under the address bar and hid its own header (title, download, close). The fixed box itself always matches the visible area. */
|
|
10104
|
+
max-height: 92%;
|
|
10104
10105
|
display: flex;
|
|
10105
10106
|
flex-direction: column;
|
|
10106
10107
|
box-shadow: 0 -8px 30px rgba(16, 22, 42, 0.22);
|
|
@@ -14151,7 +14152,8 @@ color: #005CFF;
|
|
|
14151
14152
|
flex-direction: column;
|
|
14152
14153
|
width: 100%;
|
|
14153
14154
|
max-width: 560px;
|
|
14154
|
-
|
|
14155
|
+
/* % of the scrim (fixed, inset 0, 16px padding) instead of calc(100vh - 32px): see the iOS note on the mobile .mmodal below */
|
|
14156
|
+
max-height: 100%;
|
|
14155
14157
|
background: var(--md-sc-lowest, #fff);
|
|
14156
14158
|
color: var(--md-on-surface);
|
|
14157
14159
|
border-radius: var(--md-r-lg);
|
|
@@ -14297,7 +14299,8 @@ color: #005CFF;
|
|
|
14297
14299
|
}
|
|
14298
14300
|
.md3 .mmodal {
|
|
14299
14301
|
max-width: 100%;
|
|
14300
|
-
|
|
14302
|
+
/* % of the fixed containing block, not vh/dvh: on iOS vh is the toolbars-collapsed height, and Chrome on iOS does not shrink dvh while its toolbars are shown either, so a 92vh/92dvh bottom sheet grew under the address bar and hid its own header (title, download, close). The fixed box itself always matches the visible area. */
|
|
14303
|
+
max-height: 92%;
|
|
14301
14304
|
border-radius: var(--md-r-lg) var(--md-r-lg) 0 0;
|
|
14302
14305
|
border-bottom: 0;
|
|
14303
14306
|
animation: mmodal-sheet-up 0.25s ease-out;
|
|
@@ -165,7 +165,10 @@ const getDecoratedLightProps = (
|
|
|
165
165
|
if (hybridPlugin) {
|
|
166
166
|
if (wltpWeightedPowerCombinedContent && wltpWeightedCombinedContent) {
|
|
167
167
|
const dischargedCombined = Number.isFinite(wltpConsumptionCombined) ? wltpConsumptionCombined : 19.9;
|
|
168
|
-
|
|
168
|
+
// ADV-187: the discharged class is derived server-side from wltpCo2Discharged; when the
|
|
169
|
+
// backend could not derive it the field is selector_unknown — hide the segment, never
|
|
170
|
+
// fall back to a hard-coded class here.
|
|
171
|
+
const dischargedClass = car.environmentEmissions.wltpDischargedEnergyEfficiencyClass;
|
|
169
172
|
const weightedCombinedLabeled = t('vehicleProps:value.wltpWeightedCombinedLabel', { consumption: wltpWeightedCombined.toLocaleString(language), unit: translatedUnit });
|
|
170
173
|
const dischargedCombinedLabeled = t('vehicleProps:value.wltpDischargedCombinedLabel', { consumption: dischargedCombined.toLocaleString(language), unit: translatedUnit });
|
|
171
174
|
const co2WeightedContent = Number.isFinite(wltpCo2)
|
|
@@ -174,14 +177,16 @@ const getDecoratedLightProps = (
|
|
|
174
177
|
const co2ClassWeighted = isPropDefined(wltpEnergyEfficiencyClass)
|
|
175
178
|
? t('vehicleProps:value.co2ClassWeighted', { class: t(`cbd:${wltpEnergyEfficiencyClass}`) })
|
|
176
179
|
: null;
|
|
177
|
-
const co2ClassDischarged =
|
|
180
|
+
const co2ClassDischarged = isPropDefined(dischargedClass)
|
|
181
|
+
? t('vehicleProps:value.co2ClassDischarged', { class: t(`cbd:${dischargedClass}`) })
|
|
182
|
+
: null;
|
|
178
183
|
return <React.Fragment>
|
|
179
184
|
{`${wltpWeightedPowerCombinedContent} + `}
|
|
180
185
|
{`${weightedCombinedLabeled}, `}
|
|
181
186
|
{`${dischargedCombinedLabeled}`}
|
|
182
187
|
{co2WeightedContent && <>, {co2WeightedContent}</>}
|
|
183
188
|
{co2ClassWeighted && <>, {co2ClassWeighted}</>}
|
|
184
|
-
{<>, {co2ClassDischarged}</>}
|
|
189
|
+
{co2ClassDischarged && <>, {co2ClassDischarged}</>}
|
|
185
190
|
</React.Fragment>
|
|
186
191
|
}
|
|
187
192
|
|
|
@@ -316,7 +321,10 @@ const getDecoratedLightProps = (
|
|
|
316
321
|
if (hybridPlugin) {
|
|
317
322
|
if (wltpWeightedPowerCombinedContent && wltpWeightedCombinedContent) {
|
|
318
323
|
const dischargedCombined = Number.isFinite(wltpConsumptionCombined) ? wltpConsumptionCombined : 19.9;
|
|
319
|
-
|
|
324
|
+
// ADV-187: the discharged class is derived server-side from wltpCo2Discharged; when the
|
|
325
|
+
// backend could not derive it the field is selector_unknown — hide the segment, never
|
|
326
|
+
// fall back to a hard-coded class here.
|
|
327
|
+
const dischargedClass = car.environmentEmissions.wltpDischargedEnergyEfficiencyClass;
|
|
320
328
|
const weightedCombinedLabeled = t('vehicleProps:value.wltpWeightedCombinedLabel', { consumption: wltpWeightedCombined.toLocaleString(language), unit: translatedUnit });
|
|
321
329
|
const dischargedCombinedLabeled = t('vehicleProps:value.wltpDischargedCombinedLabel', { consumption: dischargedCombined.toLocaleString(language), unit: translatedUnit });
|
|
322
330
|
const co2WeightedContent = Number.isFinite(wltpCo2)
|
|
@@ -325,14 +333,16 @@ const getDecoratedLightProps = (
|
|
|
325
333
|
const co2ClassWeighted = isPropDefined(wltpEnergyEfficiencyClass)
|
|
326
334
|
? t('vehicleProps:value.co2ClassWeighted', { class: t(`cbd:${wltpEnergyEfficiencyClass}`) })
|
|
327
335
|
: null;
|
|
328
|
-
const co2ClassDischarged =
|
|
336
|
+
const co2ClassDischarged = isPropDefined(dischargedClass)
|
|
337
|
+
? t('vehicleProps:value.co2ClassDischarged', { class: t(`cbd:${dischargedClass}`) })
|
|
338
|
+
: null;
|
|
329
339
|
return <React.Fragment>
|
|
330
340
|
{`${wltpWeightedPowerCombinedContent} + `}
|
|
331
341
|
{`${weightedCombinedLabeled}, `}
|
|
332
342
|
{`${dischargedCombinedLabeled}`}
|
|
333
343
|
{co2WeightedContent && <>, {co2WeightedContent}</>}
|
|
334
344
|
{co2ClassWeighted && <>, {co2ClassWeighted}</>}
|
|
335
|
-
{<>, {co2ClassDischarged}</>}
|
|
345
|
+
{co2ClassDischarged && <>, {co2ClassDischarged}</>}
|
|
336
346
|
</React.Fragment>
|
|
337
347
|
}
|
|
338
348
|
|
|
@@ -880,10 +880,12 @@ const getDecoratedProps = (
|
|
|
880
880
|
// (entladen, komb) fuel consumption + a discharged CO2 class.
|
|
881
881
|
if (hybridPlugin && wltpConsumptionWeightedPowerCombinedContent && wltpConsumptionWeightedCombinedContent) {
|
|
882
882
|
const ADV88_DEFAULT_DISCHARGED_COMBINED = 19.9;
|
|
883
|
-
const ADV88_DEFAULT_DISCHARGED_CO2_CLASS = 'selector_energyEfficiencyClass_g';
|
|
884
883
|
|
|
885
884
|
const dischargedCombined = Number.isFinite(wltpCombined) ? wltpCombined : ADV88_DEFAULT_DISCHARGED_COMBINED;
|
|
886
|
-
|
|
885
|
+
// ADV-187: the discharged class is derived server-side from wltpCo2Discharged; when the
|
|
886
|
+
// backend could not derive it the field is selector_unknown — hide the segment, never
|
|
887
|
+
// fall back to a hard-coded class here.
|
|
888
|
+
const dischargedClass = car.environmentEmissions.wltpDischargedEnergyEfficiencyClass;
|
|
887
889
|
|
|
888
890
|
const weightedCombinedLabeled = t('vehicleProps:value.wltpWeightedCombinedLabel', { consumption: wltpWeightedCombined.toLocaleString(language), unit: translatedUnit });
|
|
889
891
|
const dischargedCombinedLabeled = t('vehicleProps:value.wltpDischargedCombinedLabel', { consumption: dischargedCombined.toLocaleString(language), unit: translatedUnit });
|
|
@@ -893,7 +895,9 @@ const getDecoratedProps = (
|
|
|
893
895
|
const co2ClassWeighted = isPropDefined(wltpEnergyEfficiencyClass)
|
|
894
896
|
? t('vehicleProps:value.co2ClassWeighted', { class: t(`cbd:${wltpEnergyEfficiencyClass}`) })
|
|
895
897
|
: null;
|
|
896
|
-
const co2ClassDischarged =
|
|
898
|
+
const co2ClassDischarged = isPropDefined(dischargedClass)
|
|
899
|
+
? t('vehicleProps:value.co2ClassDischarged', { class: t(`cbd:${dischargedClass}`) })
|
|
900
|
+
: null;
|
|
897
901
|
|
|
898
902
|
return <React.Fragment>
|
|
899
903
|
{wrapValue(wltpConsumptionWeightedPowerCombinedContent)}{' + '}
|
|
@@ -901,7 +905,7 @@ const getDecoratedProps = (
|
|
|
901
905
|
{wrapValue(dischargedCombinedLabeled)}
|
|
902
906
|
{co2WeightedContent && <>{', '}{wrapValue(co2WeightedContent)}</>}
|
|
903
907
|
{co2ClassWeighted && <>{', '}{co2ClassWeighted}</>}
|
|
904
|
-
{', '}{co2ClassDischarged}
|
|
908
|
+
{co2ClassDischarged && <>{', '}{co2ClassDischarged}</>}
|
|
905
909
|
</React.Fragment>
|
|
906
910
|
}
|
|
907
911
|
|