@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/cards/CarCard.tsx
CHANGED
|
@@ -142,8 +142,8 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
|
|
|
142
142
|
}}
|
|
143
143
|
>
|
|
144
144
|
<div style={{ display: 'flex', flexWrap: 'nowrap', gap: 5, alignItems: 'center' }}>
|
|
145
|
-
{car.isNew && <span className="neu-badge">NEU</span>}
|
|
146
145
|
{car.drop != null ? <DropChip t={t} amount={car.drop} /> : showSave && <SaveChip t={t} amount={car.savings!} />}
|
|
146
|
+
<PercentChip pct={car.dropPct} />
|
|
147
147
|
<ZeroDownChip
|
|
148
148
|
t={t}
|
|
149
149
|
show={car.zeroDown}
|
|
@@ -162,7 +162,7 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
|
|
|
162
162
|
color: '#005CFF',
|
|
163
163
|
}}
|
|
164
164
|
/>
|
|
165
|
-
<
|
|
165
|
+
{car.isNew && <span className="neu-badge">NEU</span>}
|
|
166
166
|
</div>
|
|
167
167
|
<OfferBadge show={showOfferBadge} t={t} />
|
|
168
168
|
</div>
|
|
@@ -208,11 +208,23 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
|
|
|
208
208
|
t={t}
|
|
209
209
|
/>
|
|
210
210
|
</div>
|
|
211
|
+
{/* Mobile-only row (CardCtaRow itself carries the show-mobile class) — always
|
|
212
|
+
rendered, with real fallbacks the page can override once it wires its own
|
|
213
|
+
handlers (e.g. the "Rate anpassen" FinanceDetailsModal sheet). Never hidden. */}
|
|
214
|
+
<CardCtaRow
|
|
215
|
+
car={car}
|
|
216
|
+
mode={mode}
|
|
217
|
+
onRequestOffer={onRequestOffer || (() => onOpen(car))}
|
|
218
|
+
onCall={onCall || callDealerSalesPhone}
|
|
219
|
+
onOpenRateAdjust={onOpenRateAdjust || (() => onOpen(car))}
|
|
220
|
+
t={t}
|
|
221
|
+
/>
|
|
211
222
|
<EcoLine
|
|
212
223
|
t={t}
|
|
213
224
|
car={car}
|
|
214
225
|
style={{
|
|
215
226
|
marginTop: 'auto',
|
|
227
|
+
borderTop: 'none',
|
|
216
228
|
fontFamily: 'Arial, Helvetica, sans-serif',
|
|
217
229
|
fontStyle: 'normal',
|
|
218
230
|
fontWeight: 400,
|
|
@@ -222,17 +234,6 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
|
|
|
222
234
|
color: '#0e2238',
|
|
223
235
|
}}
|
|
224
236
|
/>
|
|
225
|
-
{/* Mobile-only row (CardCtaRow itself carries the show-mobile class) — always
|
|
226
|
-
rendered, with real fallbacks the page can override once it wires its own
|
|
227
|
-
handlers (e.g. the "Rate anpassen" FinanceDetailsModal sheet). Never hidden. */}
|
|
228
|
-
<CardCtaRow
|
|
229
|
-
car={car}
|
|
230
|
-
mode={mode}
|
|
231
|
-
onRequestOffer={onRequestOffer || (() => onOpen(car))}
|
|
232
|
-
onCall={onCall || callDealerSalesPhone}
|
|
233
|
-
onOpenRateAdjust={onOpenRateAdjust || (() => onOpen(car))}
|
|
234
|
-
t={t}
|
|
235
|
-
/>
|
|
236
237
|
{/* legacy VehicleSmallCard's "SeoText" footer line (full spec + equipment list) —
|
|
237
238
|
renders nothing when the caller hasn't populated car.seoText. */}
|
|
238
239
|
<SeoLine car={car} style={{ marginTop: 6 }} />
|
package/cards/cardParts.tsx
CHANGED
|
@@ -444,10 +444,9 @@ export const PriceEvalCol: React.FunctionComponent<{ car: AutobahnCar; t: Transl
|
|
|
444
444
|
? Math.max(4, Math.min(96, ((car.price - car.marketLow!) / (car.marketHigh! - car.marketLow!)) * 100))
|
|
445
445
|
: undefined;
|
|
446
446
|
const isGood = TIER_KEYS[tier] === 'good';
|
|
447
|
-
//
|
|
448
|
-
//
|
|
449
|
-
|
|
450
|
-
const tierLabel = isGood ? t('autobahn:tier.top') : t('autobahn:tier.' + car.rating);
|
|
447
|
+
// each tier shows its own real label now (Top/Guter/Fairer/Erhöht/Hoher Preis) — no more
|
|
448
|
+
// collapsing 'good' onto the "Top-Preis" claim. Pill styling (colour/class/footnote) unchanged.
|
|
449
|
+
const tierLabel = t('autobahn:tier.' + car.rating);
|
|
451
450
|
// 'good' collapses onto the 'top' tier's own color too (#168a45), matching the fixed
|
|
452
451
|
// "Top-Preis" text above rather than tier 1's lighter TIER_COLORS[1] green.
|
|
453
452
|
const pillColor = isGood ? TIER_COLORS[0] : TIER_COLORS[tier];
|
|
@@ -777,7 +776,10 @@ export interface CardCtaRowProps {
|
|
|
777
776
|
* keeps its text label — only the phone button is icon-only, matching the mockup.
|
|
778
777
|
*/
|
|
779
778
|
export const CardCtaRow: React.FunctionComponent<CardCtaRowProps> = ({ car, mode, onRequestOffer, onCall, onOpenRateAdjust, t }) => (
|
|
780
|
-
<div
|
|
779
|
+
<div
|
|
780
|
+
className="card-cta-row show-mobile"
|
|
781
|
+
style={{ display: 'flex', gap: 8, borderTop: '1px solid var(--md-outline-variant)', paddingTop: 8 }}
|
|
782
|
+
>
|
|
781
783
|
<Button
|
|
782
784
|
variant="filled"
|
|
783
785
|
style={{ flex: 2 }}
|
|
@@ -30,11 +30,11 @@ const ManageCookieModal = ({ toggleModal, handleSentryInit } : IManageCookieModa
|
|
|
30
30
|
() => {
|
|
31
31
|
const newAnalyticsSwitches = Object.values(COOKIE_SCHEMA.analytics)
|
|
32
32
|
.reduce((acc, val) => acc.concat(val), [])
|
|
33
|
-
.reduce((acc, item) => ({ ...acc, [item]:
|
|
33
|
+
.reduce((acc, item) => ({ ...acc, [item]: true }), {});
|
|
34
34
|
|
|
35
35
|
const newMarketingSwitches = Object.values(COOKIE_SCHEMA.marketing)
|
|
36
36
|
.reduce((acc, val) => acc.concat(val), [])
|
|
37
|
-
.reduce((acc, item) => ({ ...acc, [item]:
|
|
37
|
+
.reduce((acc, item) => ({ ...acc, [item]: true }), {});
|
|
38
38
|
setAnalyticsSwitches(newAnalyticsSwitches);
|
|
39
39
|
setMarketingSwitches(newMarketingSwitches);
|
|
40
40
|
},
|
|
@@ -42,9 +42,11 @@ const ManageCookieModal = ({ toggleModal, handleSentryInit } : IManageCookieModa
|
|
|
42
42
|
);
|
|
43
43
|
|
|
44
44
|
const onHandleAnalyticsSwitchValue = (value: boolean) => {
|
|
45
|
+
// per-item state is stored inverted (false = this item is on — see isChecked below),
|
|
46
|
+
// so turning the master ON (value=true) must store false on every item, not true.
|
|
45
47
|
const newAnalyticsSwitches = Object.values(COOKIE_SCHEMA.analytics)
|
|
46
48
|
.reduce((acc, val) => acc.concat(val), [])
|
|
47
|
-
.reduce((acc, item) => ({ ...acc, [item]: value }), {});
|
|
49
|
+
.reduce((acc, item) => ({ ...acc, [item]: !value }), {});
|
|
48
50
|
|
|
49
51
|
setAnalyticsSwitches(newAnalyticsSwitches);
|
|
50
52
|
setAnalyticsMasterSwitch(value);
|
|
@@ -53,7 +55,7 @@ const ManageCookieModal = ({ toggleModal, handleSentryInit } : IManageCookieModa
|
|
|
53
55
|
const onHandleMarketingSwitchValue = (value: boolean) => {
|
|
54
56
|
const newMarketingSwitches = Object.values(COOKIE_SCHEMA.marketing)
|
|
55
57
|
.reduce((acc, val) => acc.concat(val), [])
|
|
56
|
-
.reduce((acc, item) => ({ ...acc, [item]: value }), {});
|
|
58
|
+
.reduce((acc, item) => ({ ...acc, [item]: !value }), {});
|
|
57
59
|
setMarketingSwitches(newMarketingSwitches);
|
|
58
60
|
setMarketingMasterSwitch(value);
|
|
59
61
|
};
|
|
@@ -87,7 +89,7 @@ const ManageCookieModal = ({ toggleModal, handleSentryInit } : IManageCookieModa
|
|
|
87
89
|
const onHandleChangeInnerSwitchAnalytics = (cookiesFromCategory: any, isChecked: boolean) => {
|
|
88
90
|
const updatedSwitches = { ...analyticsSwitches };
|
|
89
91
|
for (const item of cookiesFromCategory) {
|
|
90
|
-
updatedSwitches[item] =
|
|
92
|
+
updatedSwitches[item] = isChecked
|
|
91
93
|
}
|
|
92
94
|
setAnalyticsSwitches(updatedSwitches);
|
|
93
95
|
};
|
|
@@ -95,7 +97,7 @@ const ManageCookieModal = ({ toggleModal, handleSentryInit } : IManageCookieModa
|
|
|
95
97
|
const onHandleChangeInnerSwitchMarketing = (cookiesFromCategory: any, isChecked: boolean) => {
|
|
96
98
|
const updatedSwitches = { ...marketingSwitches };
|
|
97
99
|
for (const item of cookiesFromCategory) {
|
|
98
|
-
updatedSwitches[item] =
|
|
100
|
+
updatedSwitches[item] = isChecked
|
|
99
101
|
}
|
|
100
102
|
setMarketingSwitches(updatedSwitches);
|
|
101
103
|
};
|
|
@@ -141,7 +143,7 @@ const ManageCookieModal = ({ toggleModal, handleSentryInit } : IManageCookieModa
|
|
|
141
143
|
<a className={styles.switchBlockLink} href={getLinkFromCategory} target='_blank'>Datenschutzbestimmungen</a>
|
|
142
144
|
<MaterialSwitch
|
|
143
145
|
sx={{ marginLeft: 'auto' }}
|
|
144
|
-
value={
|
|
146
|
+
value={isChecked}
|
|
145
147
|
onChange={() => onHandleChangeInnerSwitchAnalytics(getItemsFromCategory, isChecked)}
|
|
146
148
|
/>
|
|
147
149
|
</div>
|
|
@@ -165,7 +167,7 @@ const ManageCookieModal = ({ toggleModal, handleSentryInit } : IManageCookieModa
|
|
|
165
167
|
<a className={styles.switchBlockLink} href={getLinkFromCategory} target='_blank'>Datenschutzbestimmungen</a>
|
|
166
168
|
<MaterialSwitch
|
|
167
169
|
sx={{ marginLeft: 'auto' }}
|
|
168
|
-
value={
|
|
170
|
+
value={isChecked}
|
|
169
171
|
onChange={() => onHandleChangeInnerSwitchMarketing(getItemsFromCategory, isChecked)}
|
|
170
172
|
/>
|
|
171
173
|
</div>
|
|
@@ -45,7 +45,7 @@ const MdAccordionGroup: React.FC<IMdAccordionGroupProps> = ({ title, tooltipText
|
|
|
45
45
|
) : null}
|
|
46
46
|
<Icon name='chevron-down' cls='macc-chev' />
|
|
47
47
|
</div>
|
|
48
|
-
{open ? <div style={{ padding: '4px
|
|
48
|
+
{open ? <div style={{ padding: '4px 15px 12px' }}>{children}</div> : null}
|
|
49
49
|
</section>
|
|
50
50
|
);
|
|
51
51
|
};
|
|
@@ -28,18 +28,31 @@ export interface MdFieldProps {
|
|
|
28
28
|
label?: string;
|
|
29
29
|
placeholder?: string;
|
|
30
30
|
maxLength?: number;
|
|
31
|
+
/** opt-in Material floating label: label rests inside the field like a placeholder
|
|
32
|
+
* when empty and unfocused, then floats up to the normal small-label spot on focus
|
|
33
|
+
* or once a value is typed. Defaults to false — the static above-field label. */
|
|
34
|
+
floatingLabel?: boolean;
|
|
31
35
|
onChange: (value: string, name?: string, event?: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
const MdField: React.FunctionComponent<MdFieldProps> = ({
|
|
35
39
|
className, error, sx, required, disabled, multiline, autoComplete,
|
|
36
|
-
value, type, name, rows, label, placeholder, maxLength, onChange
|
|
40
|
+
value, type, name, rows, label, placeholder, floatingLabel, maxLength, onChange
|
|
37
41
|
}) => {
|
|
38
42
|
const [reveal, setReveal] = React.useState(false);
|
|
43
|
+
const [focused, setFocused] = React.useState(false);
|
|
39
44
|
const isPassword = type === 'password';
|
|
40
45
|
const inputType = isPassword && reveal ? 'text' : (type || 'text');
|
|
41
46
|
const hasError = !!error;
|
|
42
47
|
const generatedId = React.useId();
|
|
48
|
+
// floating label (Material pattern): while empty and unfocused, the label text is
|
|
49
|
+
// shown as the input's own native placeholder (styled via .mfield--floating
|
|
50
|
+
// input::placeholder) and the <label> itself is not rendered at all; once focused or
|
|
51
|
+
// a value is typed, the real <label> appears in its normal static spot above the
|
|
52
|
+
// field and the native placeholder goes quiet — the two never show at the same time.
|
|
53
|
+
const hasValue = value != null && value !== '';
|
|
54
|
+
const resting = !!floatingLabel && !focused && !hasValue;
|
|
55
|
+
const effectivePlaceholder = floatingLabel ? (resting ? (label || placeholder || '') : '') : (placeholder || '');
|
|
43
56
|
|
|
44
57
|
const handleChange = (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
|
45
58
|
onChange(event.currentTarget.value, event.currentTarget.name || name, event);
|
|
@@ -47,22 +60,31 @@ const MdField: React.FunctionComponent<MdFieldProps> = ({
|
|
|
47
60
|
|
|
48
61
|
return (
|
|
49
62
|
<div className={classnames('mfield-group', className)} style={sx as React.CSSProperties}>
|
|
50
|
-
{label ? (
|
|
63
|
+
{label && !resting ? (
|
|
51
64
|
<label className='md-label-sm mfield-label' htmlFor={generatedId}>
|
|
52
65
|
{label}{required ? ' *' : ''}
|
|
53
66
|
</label>
|
|
54
67
|
) : null}
|
|
55
|
-
<div
|
|
68
|
+
<div
|
|
69
|
+
className={
|
|
70
|
+
'mfield'
|
|
71
|
+
+ (hasError ? ' mfield--error' : '')
|
|
72
|
+
+ (disabled ? ' mfield--disabled' : '')
|
|
73
|
+
+ (floatingLabel ? ' mfield--floating' : '')
|
|
74
|
+
}
|
|
75
|
+
>
|
|
56
76
|
{multiline ? (
|
|
57
77
|
<textarea
|
|
58
78
|
id={generatedId}
|
|
59
79
|
name={name}
|
|
60
80
|
value={value == null ? '' : value}
|
|
61
81
|
onChange={handleChange}
|
|
82
|
+
onFocus={() => setFocused(true)}
|
|
83
|
+
onBlur={() => setFocused(false)}
|
|
62
84
|
disabled={disabled}
|
|
63
85
|
rows={rows || 3}
|
|
64
86
|
maxLength={maxLength}
|
|
65
|
-
placeholder={
|
|
87
|
+
placeholder={effectivePlaceholder}
|
|
66
88
|
autoComplete={autoComplete}
|
|
67
89
|
style={{ width: '100%', resize: 'vertical' }}
|
|
68
90
|
/>
|
|
@@ -73,9 +95,11 @@ const MdField: React.FunctionComponent<MdFieldProps> = ({
|
|
|
73
95
|
type={inputType}
|
|
74
96
|
value={value == null ? '' : value}
|
|
75
97
|
onChange={handleChange}
|
|
98
|
+
onFocus={() => setFocused(true)}
|
|
99
|
+
onBlur={() => setFocused(false)}
|
|
76
100
|
disabled={disabled}
|
|
77
101
|
maxLength={maxLength}
|
|
78
|
-
placeholder={
|
|
102
|
+
placeholder={effectivePlaceholder}
|
|
79
103
|
autoComplete={autoComplete}
|
|
80
104
|
style={{ width: '100%' }}
|
|
81
105
|
/>
|
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/locales/de/autobahn.json
CHANGED
|
@@ -93,6 +93,7 @@
|
|
|
93
93
|
"availabilityLabel": "Verfügbarkeit",
|
|
94
94
|
"availabilityValue": "Sofort",
|
|
95
95
|
"downPaymentSliderLabel": "Anzahlung",
|
|
96
|
+
"downPaymentSliderLabelSheet": "Sonderzahlung",
|
|
96
97
|
"annualMileageLabel": "Laufleistung / Jahr",
|
|
97
98
|
"purchasePrice": "Kaufpreis",
|
|
98
99
|
"perMonth": "/mtl.",
|
|
@@ -105,7 +106,13 @@
|
|
|
105
106
|
"addServicesToggle": "Rundum-Sorglos-Paket",
|
|
106
107
|
"addServicesPerMonth": "+{{value}} € / Monat",
|
|
107
108
|
"addServicesOnce": "+{{value}} €",
|
|
108
|
-
"addServicesIncluded": "inklusive"
|
|
109
|
+
"addServicesIncluded": "inklusive",
|
|
110
|
+
"addServicesTooltipIntroCash": "Das Rundum-Sorglos-Paket ist Bestandteil des Angebots und im Kaufpreis enthalten.",
|
|
111
|
+
"addServicesTooltipIntroFinanced": "Das Rundum-Sorglos-Paket ist verpflichtender Bestandteil des Angebots. Die Kosten fallen unabhängig von der gewählten Zahlungsweise an.",
|
|
112
|
+
"addServicesTooltipHeading": "Im Rundum-Sorglos-Paket enthalten:",
|
|
113
|
+
"addServicesTooltipItems": "kompletter Zulassungsservice inklusive Wunschkennzeichen, soweit verfügbar\nsorgfältige Prüfung und Vorbereitung aller Zulassungsunterlagen\nbundesweite Fahrzeugauslieferung bis vor Ihre Haustür\npersönliche Fahrzeugübergabe\nhochwertige Fahrzeugaufbereitung innen und außen\nausführlicher technischer Übergabecheck\nHU und Inspektion, falls erforderlich\nTank- beziehungsweise Ladegrundfüllung bei Übergabe\npersönlicher Ansprechpartner von Beginn an",
|
|
114
|
+
"addServicesTooltipOutroFinancing": "Sie können die Paketkosten entweder einmalig in Höhe von 1.499,00 € bezahlen oder in die Fahrzeugfinanzierung einbeziehen lassen. Bei der Mitfinanzierung wird keine zusätzliche Einmalzahlung für das Paket erhoben. Die gewählte Zahlungsweise sowie die daraus resultierende monatliche Finanzierungsrate und der Gesamtbetrag werden in den Finanzierungsdetails ausgewiesen. Die Paketkosten sind in jedem Fall im ausgewiesenen Gesamtbetrag berücksichtigt.",
|
|
115
|
+
"addServicesTooltipOutroLeasing": "Sie können die Paketkosten entweder einmalig in Höhe von 1.499,00 € bezahlen oder in die monatliche Leasingrate einrechnen lassen. Bei der Rateneinrechnung wird keine zusätzliche Einmalzahlung für das Paket erhoben. Die gewählte Zahlungsweise sowie die daraus resultierende Monatsrate und der Gesamtbetrag werden in den Leasingdetails ausgewiesen. Die Paketkosten sind in jedem Fall im ausgewiesenen Gesamtbetrag berücksichtigt."
|
|
109
116
|
},
|
|
110
117
|
"specTable": {
|
|
111
118
|
"title": "Fahrzeugdaten",
|
|
@@ -691,7 +698,7 @@
|
|
|
691
698
|
"vehiclePrice": "Fahrzeugpreis ggf. inkl. MwSt.",
|
|
692
699
|
"leasingAmount": "Leasingbetrag",
|
|
693
700
|
"netLoanAmount": "Nettodarlehensbetrag",
|
|
694
|
-
"downPayment": "
|
|
701
|
+
"downPayment": "Sonderzahlung",
|
|
695
702
|
"annualMileage": "Laufleistung / Jahr",
|
|
696
703
|
"term": "Vertragslaufzeit",
|
|
697
704
|
"months": "{{months}} Monat",
|
package/locales/en/autobahn.json
CHANGED
|
@@ -93,6 +93,7 @@
|
|
|
93
93
|
"availabilityLabel": "Availability",
|
|
94
94
|
"availabilityValue": "Immediately",
|
|
95
95
|
"downPaymentSliderLabel": "Down payment",
|
|
96
|
+
"downPaymentSliderLabelSheet": "Special payment",
|
|
96
97
|
"annualMileageLabel": "Mileage / year",
|
|
97
98
|
"purchasePrice": "Purchase price",
|
|
98
99
|
"perMonth": "/mo.",
|
|
@@ -105,7 +106,13 @@
|
|
|
105
106
|
"addServicesToggle": "All-round care package",
|
|
106
107
|
"addServicesPerMonth": "+€{{value}} / month",
|
|
107
108
|
"addServicesOnce": "+€{{value}}",
|
|
108
|
-
"addServicesIncluded": "included"
|
|
109
|
+
"addServicesIncluded": "included",
|
|
110
|
+
"addServicesTooltipIntroCash": "The all-round care package is part of the offer and included in the purchase price.",
|
|
111
|
+
"addServicesTooltipIntroFinanced": "The all-round care package is a mandatory part of the offer. Its cost applies regardless of the payment method chosen.",
|
|
112
|
+
"addServicesTooltipHeading": "Included in the all-round care package:",
|
|
113
|
+
"addServicesTooltipItems": "complete registration service including a custom license plate, where available\ncareful review and preparation of all registration documents\nnationwide vehicle delivery to your doorstep\npersonal vehicle handover\nhigh-quality interior and exterior vehicle detailing\ndetailed technical handover check\nroadworthiness test (HU) and inspection, if required\nfuel or charge top-up at handover\npersonal point of contact from day one",
|
|
114
|
+
"addServicesTooltipOutroFinancing": "You can either pay the package cost as a one-off amount of €1,499.00, or have it included in the vehicle financing. If included in financing, no separate one-off payment for the package is charged. The payment method you choose, along with the resulting monthly financing rate and total amount, is shown in the financing details. The package cost is included in the total amount shown in either case.",
|
|
115
|
+
"addServicesTooltipOutroLeasing": "You can either pay the package cost as a one-off amount of €1,499.00, or have it included in the monthly leasing rate. If included in the rate, no separate one-off payment for the package is charged. The payment method you choose, along with the resulting monthly rate and total amount, is shown in the leasing details. The package cost is included in the total amount shown in either case."
|
|
109
116
|
},
|
|
110
117
|
"specTable": {
|
|
111
118
|
"title": "Vehicle data",
|
|
@@ -691,7 +698,7 @@
|
|
|
691
698
|
"vehiclePrice": "Vehicle price (incl. VAT, if applicable)",
|
|
692
699
|
"leasingAmount": "Leasing amount",
|
|
693
700
|
"netLoanAmount": "Net loan amount",
|
|
694
|
-
"downPayment": "
|
|
701
|
+
"downPayment": "Special payment",
|
|
695
702
|
"annualMileage": "Mileage / year",
|
|
696
703
|
"term": "Contract term",
|
|
697
704
|
"months": "{{months}} month",
|
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.82",
|
|
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
|
@@ -233,6 +233,16 @@ line-height: 31px;
|
|
|
233
233
|
box-shadow: var(--md-e2);
|
|
234
234
|
background: var(--autode-blau-strong);
|
|
235
235
|
}
|
|
236
|
+
/* "already requested/checked out" state (legacy compat offerButtonSuccess parity) — the
|
|
237
|
+
primary VDP CTA turns green with a "continue" label instead of the default "Angebot
|
|
238
|
+
anfragen"/"Angebot sichern" once the customer already has a request/checkout in progress. */
|
|
239
|
+
.md3 .fin-cta-secured {
|
|
240
|
+
background: var(--md-good) !important;
|
|
241
|
+
}
|
|
242
|
+
.md3 .fin-cta-secured:hover {
|
|
243
|
+
background: var(--md-good) !important;
|
|
244
|
+
filter: brightness(0.92);
|
|
245
|
+
}
|
|
236
246
|
.md3 .mbtn--tonal {
|
|
237
247
|
background: var(--md-primary-container);
|
|
238
248
|
color: var(--md-on-primary-container);
|
|
@@ -637,6 +647,19 @@ line-height: 31px;
|
|
|
637
647
|
color: var(--md-on-surface-variant);
|
|
638
648
|
font-weight: 600;
|
|
639
649
|
}
|
|
650
|
+
/* MdField floatingLabel=true only: while the field is empty and unfocused, the <label>
|
|
651
|
+
isn't rendered at all — the label text shows as the input's own native placeholder
|
|
652
|
+
instead, styled to match the Figma "resting" state. Firefox dims ::placeholder by
|
|
653
|
+
default (opacity ~0.54), so opacity:1 is needed to get the real #5b6b7f. */
|
|
654
|
+
.md3 .mfield--floating input::placeholder,
|
|
655
|
+
.md3 .mfield--floating textarea::placeholder {
|
|
656
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
657
|
+
font-weight: 700;
|
|
658
|
+
font-size: 16px;
|
|
659
|
+
line-height: 14px;
|
|
660
|
+
color: #9aa5b4;
|
|
661
|
+
opacity: 1;
|
|
662
|
+
}
|
|
640
663
|
.md3 .mfield--error {
|
|
641
664
|
box-shadow: inset 0 0 0 2px var(--md-error);
|
|
642
665
|
}
|
|
@@ -4813,6 +4836,10 @@ line-height: 31px;
|
|
|
4813
4836
|
right: -100px;
|
|
4814
4837
|
}
|
|
4815
4838
|
}
|
|
4839
|
+
/* Leasing tab's "3" badge tooltip needs its own offset — Finanzierung keeps -135px/-95px above. */
|
|
4840
|
+
.md3 .fin-calc-card .pp-uvp-tooltip--leasing {
|
|
4841
|
+
right: -110px;
|
|
4842
|
+
}
|
|
4816
4843
|
/* floating circular close button — overlaps the tooltip's top-right corner from outside,
|
|
4817
4844
|
same "glass circle" convention as VdpViewer's close/back/next overlay buttons. */
|
|
4818
4845
|
.md3 .cc-price .pp-uvp-tooltip-close,
|
|
@@ -5199,6 +5226,22 @@ line-height: 31px;
|
|
|
5199
5226
|
width: 112px;
|
|
5200
5227
|
justify-self: end;
|
|
5201
5228
|
}
|
|
5229
|
+
/* .fin-modal doesn't render .fin-cond at all, so it never needed the base "rate pill"/"cond bar"
|
|
5230
|
+
2-row split — that's what was leaving the pill vertically centered in a tall empty row above
|
|
5231
|
+
the bar (the "big gap"). One row, same evalcol grouping as .fin-calc-card above. */
|
|
5232
|
+
.md3 .fin-modal .fin-price-grid {
|
|
5233
|
+
grid-template-columns: minmax(0, 1fr) 112px;
|
|
5234
|
+
grid-template-areas: "rate evalcol";
|
|
5235
|
+
column-gap: 15px;
|
|
5236
|
+
}
|
|
5237
|
+
.md3 .fin-modal .fin-eval-col {
|
|
5238
|
+
display: flex;
|
|
5239
|
+
flex-direction: column;
|
|
5240
|
+
gap: 16px;
|
|
5241
|
+
grid-area: evalcol;
|
|
5242
|
+
width: 112px;
|
|
5243
|
+
justify-self: end;
|
|
5244
|
+
}
|
|
5202
5245
|
/* pill now sits above the bar in a real flex column (see above) — stretch it to the bar's own
|
|
5203
5246
|
width instead of the base .fin-eval-pill's inline-flex/content-hugging size. */
|
|
5204
5247
|
.md3 .fin-calc-card .fin-eval-pill--good {
|
|
@@ -5342,6 +5385,29 @@ line-height: 31px;
|
|
|
5342
5385
|
transform: rotate(12.53deg);
|
|
5343
5386
|
transform-origin: center;
|
|
5344
5387
|
}
|
|
5388
|
+
/* .fin-modal's own UVP-Rate old price — same diagonal-strike treatment as .fin-calc-card
|
|
5389
|
+
above, just scoped to the modal (was still using the shared 15px/700/error/line-through
|
|
5390
|
+
.fin-rate-old base, never got the desktop-card override). */
|
|
5391
|
+
.md3 .fin-modal .fin-rate-old.fin-rate-example-price {
|
|
5392
|
+
position: relative;
|
|
5393
|
+
display: inline-block;
|
|
5394
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
5395
|
+
font-weight: 400;
|
|
5396
|
+
font-size: 17px;
|
|
5397
|
+
line-height: 20px;
|
|
5398
|
+
color: #0e2238;
|
|
5399
|
+
text-decoration: none;
|
|
5400
|
+
}
|
|
5401
|
+
.md3 .fin-modal .fin-rate-old.fin-rate-example-price::after {
|
|
5402
|
+
content: '';
|
|
5403
|
+
position: absolute;
|
|
5404
|
+
left: -2px;
|
|
5405
|
+
right: -2px;
|
|
5406
|
+
top: 50%;
|
|
5407
|
+
border-top: 2px solid #e60000;
|
|
5408
|
+
transform: rotate(12.53deg);
|
|
5409
|
+
transform-origin: center;
|
|
5410
|
+
}
|
|
5345
5411
|
/* sticky bottom bar struck old rate — diagonal red strike (Figma), replacing the shared
|
|
5346
5412
|
.fin-rate-old's native horizontal line-through for this instance only. */
|
|
5347
5413
|
.md3 .bb-price-row .fin-rate-old.fin-rate-old-bb {
|
|
@@ -6378,7 +6444,7 @@ line-height: 31px;
|
|
|
6378
6444
|
display: none;
|
|
6379
6445
|
}
|
|
6380
6446
|
.md3 .srp-main {
|
|
6381
|
-
padding:
|
|
6447
|
+
padding: 14px 10px 48px !important;
|
|
6382
6448
|
}
|
|
6383
6449
|
.md3 .srp-sort {
|
|
6384
6450
|
width: 146px;
|
|
@@ -6628,7 +6694,13 @@ line-height: 31px;
|
|
|
6628
6694
|
}
|
|
6629
6695
|
/* compact hero so the search card + button surface above the fold */
|
|
6630
6696
|
.md3 .hero-inner {
|
|
6631
|
-
padding: 16px
|
|
6697
|
+
padding: 16px 12px 18px !important;
|
|
6698
|
+
}
|
|
6699
|
+
/* every NewMainPage section reuses .container with its own inline top/bottom padding
|
|
6700
|
+
(24px sides, hardcoded per-section) — only the shared side gutter narrows on mobile. */
|
|
6701
|
+
.md3 .container {
|
|
6702
|
+
padding-left: 12px !important;
|
|
6703
|
+
padding-right: 12px !important;
|
|
6632
6704
|
}
|
|
6633
6705
|
.md3 .hero-badge {
|
|
6634
6706
|
margin-bottom: 10px !important;
|
|
@@ -6898,11 +6970,12 @@ line-height: 31px;
|
|
|
6898
6970
|
(14px) than the 14/24px desktop default set inline. */
|
|
6899
6971
|
@media (max-width: 600px) {
|
|
6900
6972
|
.md3 .vdp-main {
|
|
6901
|
-
padding: 0
|
|
6973
|
+
padding: 0 10px 48px !important;
|
|
6902
6974
|
}
|
|
6903
6975
|
.md3 .vdp-push-badges {
|
|
6904
6976
|
gap: 6px;
|
|
6905
6977
|
margin-bottom: 13px;
|
|
6978
|
+
padding-left: 12px;
|
|
6906
6979
|
}
|
|
6907
6980
|
}
|
|
6908
6981
|
|
|
@@ -7088,6 +7161,96 @@ color: #005CFF;
|
|
|
7088
7161
|
text-align: right;
|
|
7089
7162
|
color: #1D2A38 !important;
|
|
7090
7163
|
}
|
|
7164
|
+
/* Rundum-Sorglos-Paket info popover (ToggleRow's `tooltip` prop) — same pp-uvp-tooltip look as
|
|
7165
|
+
the UVP-Rate footnote (grey card, shadow, floating dark close circle), inherited via that
|
|
7166
|
+
shared class; these rules only override its POSITION, since that one is tuned for a fixed
|
|
7167
|
+
281px card anchored beside the price hero, while this one hangs off a full-width row that can
|
|
7168
|
+
sit anywhere in the sheet. left:0/right:0 pins its width to the row itself (always the full
|
|
7169
|
+
sheet/modal/card width already) so it can never overflow horizontally; opens UPWARD (bottom:
|
|
7170
|
+
100%) since the row tends to sit near the bottom of the sheet, where dropping down would get
|
|
7171
|
+
clipped by the viewport; max-height + its own scroll is the vertical safety net for the same
|
|
7172
|
+
reason (long bullet list vs. a short one-line footnote).
|
|
7173
|
+
Selectors repeat the base rule's .fin-calc-card/.fin-modal ancestor scoping ON PURPOSE — both
|
|
7174
|
+
target .pp-uvp-tooltip at 3 class-selectors deep, so a plain ".fin-toggle-tooltip" (2 deep)
|
|
7175
|
+
loses specificity and only some properties would win, leaving top/bottom (and others) mixed
|
|
7176
|
+
from both rules instead of cleanly overridden. */
|
|
7177
|
+
.md3 .fin-calc-card .fin-toggle-tooltip,
|
|
7178
|
+
.md3 .fin-modal .fin-toggle-tooltip {
|
|
7179
|
+
position: absolute;
|
|
7180
|
+
top: auto;
|
|
7181
|
+
bottom: 100%;
|
|
7182
|
+
left: 0;
|
|
7183
|
+
right: 0;
|
|
7184
|
+
width: auto;
|
|
7185
|
+
z-index: 20;
|
|
7186
|
+
margin-bottom: 10px;
|
|
7187
|
+
cursor: default;
|
|
7188
|
+
}
|
|
7189
|
+
.md3 .fin-modal .fin-toggle-addservices--cash .fin-toggle-tooltip {
|
|
7190
|
+
top: auto;
|
|
7191
|
+
bottom: auto;
|
|
7192
|
+
}
|
|
7193
|
+
.md3 .fin-modal .show-mobile .fin-toggle-tooltip {
|
|
7194
|
+
border-radius: 11px;
|
|
7195
|
+
}
|
|
7196
|
+
/* desktop (.hide-mobile) row sits close to the top of a short, non-scrolling card — opening
|
|
7197
|
+
upward has nowhere near enough room for the now-long financing/leasing copy (intro + 9
|
|
7198
|
+
bullets + outro). */
|
|
7199
|
+
.md3 .fin-modal .hide-mobile .fin-toggle-tooltip {
|
|
7200
|
+
bottom: auto;
|
|
7201
|
+
}
|
|
7202
|
+
.md3 .fin-calc-card .fin-toggle-tooltip-close,
|
|
7203
|
+
.md3 .fin-modal .fin-toggle-tooltip-close {
|
|
7204
|
+
position: absolute;
|
|
7205
|
+
top: -43px;
|
|
7206
|
+
right: -8px;
|
|
7207
|
+
display: flex;
|
|
7208
|
+
align-items: center;
|
|
7209
|
+
justify-content: center;
|
|
7210
|
+
width: 40px;
|
|
7211
|
+
height: 40px;
|
|
7212
|
+
border-radius: 50%;
|
|
7213
|
+
background: rgba(11, 19, 32, 0.45);
|
|
7214
|
+
border: 1.5px solid rgba(255, 255, 255, 0.25);
|
|
7215
|
+
padding: 0;
|
|
7216
|
+
cursor: pointer;
|
|
7217
|
+
}
|
|
7218
|
+
.md3 .fin-toggle-tooltip-body ul {
|
|
7219
|
+
display: flex;
|
|
7220
|
+
flex-direction: column;
|
|
7221
|
+
gap: 4px;
|
|
7222
|
+
margin: 0;
|
|
7223
|
+
padding-left: 14px;
|
|
7224
|
+
}
|
|
7225
|
+
/* .fin-calc-card's own cash toggle only (not the mobile sheet's — that one keeps the
|
|
7226
|
+
standalone look above). Drops the standalone-toggle chrome so it reads as one more
|
|
7227
|
+
.calcrow under Zahlung/Verfügbarkeit: same padding, no border, same label/value type. */
|
|
7228
|
+
.md3 .fin-calc-card .fin-toggle-addservices.fin-toggle-addservices--cash {
|
|
7229
|
+
padding: 8px 0 !important;
|
|
7230
|
+
border-top: 0;
|
|
7231
|
+
border-bottom: 0;
|
|
7232
|
+
align-items: baseline;
|
|
7233
|
+
gap: 12px;
|
|
7234
|
+
}
|
|
7235
|
+
.md3 .fin-calc-card .fin-toggle-addservices.fin-toggle-addservices--cash .md-title-sm {
|
|
7236
|
+
font-family: inherit;
|
|
7237
|
+
font-weight: 600;
|
|
7238
|
+
font-size: 11px;
|
|
7239
|
+
line-height: normal;
|
|
7240
|
+
letter-spacing: 0.4px;
|
|
7241
|
+
color: var(--md-on-surface-variant);
|
|
7242
|
+
}
|
|
7243
|
+
.md3 .fin-calc-card .fin-toggle-addservices.fin-toggle-addservices--cash .md-title-sm .lucide {
|
|
7244
|
+
display: none;
|
|
7245
|
+
}
|
|
7246
|
+
.md3 .fin-calc-card .fin-toggle-addservices.fin-toggle-addservices--cash .num,
|
|
7247
|
+
.md3 .fin-calc-card .fin-toggle-addservices.fin-toggle-addservices--cash > div > span:last-child {
|
|
7248
|
+
font-family: var(--md-font-display) !important;
|
|
7249
|
+
font-weight: 600 !important;
|
|
7250
|
+
font-size: 13.5px !important;
|
|
7251
|
+
line-height: normal !important;
|
|
7252
|
+
color: var(--md-on-surface) !important;
|
|
7253
|
+
}
|
|
7091
7254
|
.md3 .fin-sheet-cta-row {
|
|
7092
7255
|
border-top: 1px solid var(--fig-tile-border);
|
|
7093
7256
|
padding-top: 20px;
|
|
@@ -8155,7 +8318,7 @@ color: #005CFF;
|
|
|
8155
8318
|
/* modal-only: sup sits higher here than on the desktop card's mobile-cash price
|
|
8156
8319
|
(same shared .fin-rate-cash-mobile), so scope via the modal's --cash modifier */
|
|
8157
8320
|
.md3 .fin-price-grid--cash .fin-rate-cash-mobile sup {
|
|
8158
|
-
top:
|
|
8321
|
+
top: 4px;
|
|
8159
8322
|
}
|
|
8160
8323
|
/* digit-count sizing (round 15): Kaufen/Barzahlung hero price shrinks as its digit
|
|
8161
8324
|
count grows — repeats the class for specificity so it wins over the 52px default
|