@bytebrand/fe-ui-core-autobahn 1.0.152 → 1.0.154
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/chrome/Footer.tsx +16 -3
- package/compat/MdField/MdField.tsx +9 -3
- package/package.json +1 -1
- package/theme/md3.css +602 -61
- package/ui/FormCheckbox.tsx +15 -2
- package/ui/Icon.tsx +4 -0
- package/ui/MfieldCheckIcon.tsx +13 -13
- package/vdp/VdpModules.tsx +15 -3
package/chrome/Footer.tsx
CHANGED
|
@@ -86,6 +86,14 @@ export interface FooterProps {
|
|
|
86
86
|
* (VehicleDetailedNextPage), never site-wide, since every other page shows only footnotes 1-4.
|
|
87
87
|
*/
|
|
88
88
|
showCostFootnotes?: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Hides the brand blurb + "stay in contact"/social + 3 link columns block (the
|
|
91
|
+
* `.footer-grid`), leaving just the legal disclaimer and copyright/"Nach Oben" bar. Opt-in,
|
|
92
|
+
* off by default — every other caller keeps the full footer. Only CheckoutThankYouPage.tsx
|
|
93
|
+
* (checkout step 6 "Danke") uses this today: that page's own Figma reference (64637:37033)
|
|
94
|
+
* has just the compact legal/copyright strip, not the full site footer.
|
|
95
|
+
*/
|
|
96
|
+
hideBrandColumns?: boolean;
|
|
89
97
|
}
|
|
90
98
|
|
|
91
99
|
/**
|
|
@@ -93,7 +101,7 @@ export interface FooterProps {
|
|
|
93
101
|
* disclaimer block, and a copyright + "Nach Oben" bar. Pure/presentational MD3 chrome — renders
|
|
94
102
|
* correctly only inside a `<div className="md3">` subtree (scoped theme at source/theme/autobahn).
|
|
95
103
|
*/
|
|
96
|
-
const Footer: React.FunctionComponent<FooterProps> = ({ t, showCostFootnotes }) => {
|
|
104
|
+
const Footer: React.FunctionComponent<FooterProps> = ({ t, showCostFootnotes, hideBrandColumns }) => {
|
|
97
105
|
const scrollTop = (): void => window.scroll({ top: 0, left: 0, behavior: 'smooth' });
|
|
98
106
|
const now = new Date();
|
|
99
107
|
const year = now.getFullYear();
|
|
@@ -104,6 +112,7 @@ const Footer: React.FunctionComponent<FooterProps> = ({ t, showCostFootnotes })
|
|
|
104
112
|
|
|
105
113
|
return (
|
|
106
114
|
<footer style={{ background: 'var(--md-sc-low)', borderTop: '1px solid var(--md-outline-variant)' }}>
|
|
115
|
+
{!hideBrandColumns ? (
|
|
107
116
|
<div className="container footer-grid" style={{ padding: '40px 24px 24px' }}>
|
|
108
117
|
<div>
|
|
109
118
|
<img src={logoUrl} height="30" alt="auto.de" />
|
|
@@ -169,10 +178,14 @@ const Footer: React.FunctionComponent<FooterProps> = ({ t, showCostFootnotes })
|
|
|
169
178
|
</div>
|
|
170
179
|
))}
|
|
171
180
|
</div>
|
|
181
|
+
) : null}
|
|
172
182
|
|
|
173
183
|
{/* Legally required disclaimers (PAngV §17a financing example w/ live APR/nominal rate +
|
|
174
|
-
term, DAT CO₂-Leitfaden, MwSt, Streichpreis, image notice). Same text as the legacy footer.
|
|
175
|
-
|
|
184
|
+
term, DAT CO₂-Leitfaden, MwSt, Streichpreis, image notice). Same text as the legacy footer.
|
|
185
|
+
The top border separates this from .footer-grid above — with hideBrandColumns that
|
|
186
|
+
block is gone, so the border would just double up against whatever's already directly
|
|
187
|
+
above the footer on the page (CheckoutThankYouPage.tsx's own header border). */}
|
|
188
|
+
<div style={hideBrandColumns ? undefined : { borderTop: '1px solid var(--md-outline-variant)' }}>
|
|
176
189
|
<div className="container" style={{ padding: '18px 24px' }}>
|
|
177
190
|
<p
|
|
178
191
|
className="md-label-sm"
|
|
@@ -37,11 +37,17 @@ export interface MdFieldProps {
|
|
|
37
37
|
* Same contract/markup as FormField's own showValid (.mfield--valid/.mfield-ok, md3.css). */
|
|
38
38
|
showValid?: boolean;
|
|
39
39
|
onChange: (value: string, name?: string, event?: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
|
|
40
|
+
/** fires after the field's own internal blur handling (floating-label state). Callers use
|
|
41
|
+
* this to trigger `field.validate()` (mobx-react-form) on blur — this component only owns
|
|
42
|
+
* the `error` prop's display, not when validation actually runs, so without a caller wiring
|
|
43
|
+
* this in, `field.error` never populates from blur and a required-but-empty field never
|
|
44
|
+
* shows its error text/red border until some other validate() call happens to run. */
|
|
45
|
+
onBlur?: () => void;
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
const MdField: React.FunctionComponent<MdFieldProps> = ({
|
|
43
49
|
className, error, sx, required, disabled, multiline, autoComplete,
|
|
44
|
-
value, type, name, rows, label, placeholder, floatingLabel, maxLength, showValid, onChange
|
|
50
|
+
value, type, name, rows, label, placeholder, floatingLabel, maxLength, showValid, onChange, onBlur
|
|
45
51
|
}) => {
|
|
46
52
|
const [reveal, setReveal] = React.useState(false);
|
|
47
53
|
const [focused, setFocused] = React.useState(false);
|
|
@@ -113,7 +119,7 @@ const MdField: React.FunctionComponent<MdFieldProps> = ({
|
|
|
113
119
|
value={value == null ? '' : value}
|
|
114
120
|
onChange={handleChange}
|
|
115
121
|
onFocus={() => setFocused(true)}
|
|
116
|
-
onBlur={() => setFocused(false)}
|
|
122
|
+
onBlur={() => { setFocused(false); onBlur?.(); }}
|
|
117
123
|
disabled={disabled}
|
|
118
124
|
rows={rows || 3}
|
|
119
125
|
maxLength={maxLength}
|
|
@@ -132,7 +138,7 @@ const MdField: React.FunctionComponent<MdFieldProps> = ({
|
|
|
132
138
|
value={value == null ? '' : value}
|
|
133
139
|
onChange={handleChange}
|
|
134
140
|
onFocus={() => setFocused(true)}
|
|
135
|
-
onBlur={() => setFocused(false)}
|
|
141
|
+
onBlur={() => { setFocused(false); onBlur?.(); }}
|
|
136
142
|
disabled={disabled}
|
|
137
143
|
maxLength={maxLength}
|
|
138
144
|
placeholder={effectivePlaceholder}
|
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.154",
|
|
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
|
@@ -1491,6 +1491,10 @@ line-height: 31px;
|
|
|
1491
1491
|
line-height: 15px;
|
|
1492
1492
|
color: #44464F;
|
|
1493
1493
|
margin-bottom: 2px;
|
|
1494
|
+
/* long submodels (e.g. "C4 1.2 Plus/Rückfahrkamera/Sitzheizung/Tempomat") use "/" instead of
|
|
1495
|
+
spaces between items, so the browser's default word-wrap sees one unbreakable "word" and
|
|
1496
|
+
overflows past the 296px aside instead of wrapping (ADV-238). */
|
|
1497
|
+
overflow-wrap: break-word;
|
|
1494
1498
|
}
|
|
1495
1499
|
.md3 .co-aside-finance {
|
|
1496
1500
|
font-family: 'Arial';
|
|
@@ -2290,6 +2294,14 @@ line-height: 31px;
|
|
|
2290
2294
|
font-size: 13px;
|
|
2291
2295
|
line-height: 15px;
|
|
2292
2296
|
color: #44464F;
|
|
2297
|
+
/* a long .co-error message (e.g. "Bitte wählen Sie eine der Optionen für Garantiepaket und
|
|
2298
|
+
Fahrzeugzulassung.") sharing this row (≥601px: same row via margin-right:auto/flex-end,
|
|
2299
|
+
see .co-next's own media queries) squeezed this plain text span for space same as any
|
|
2300
|
+
other flex child — with nothing stopping it from wrapping, "Schritt 4 von 5" split onto 2
|
|
2301
|
+
lines instead of the long error text (the one thing actually meant to wrap) absorbing the
|
|
2302
|
+
squeeze. nowrap + flex-shrink:0 keeps this at its natural one-line width unconditionally. */
|
|
2303
|
+
white-space: nowrap;
|
|
2304
|
+
flex-shrink: 0;
|
|
2293
2305
|
}
|
|
2294
2306
|
/* "Weiter" — its own fixed-size look, not the shared .mbtn--lg sizing (icon already dropped
|
|
2295
2307
|
in the JSX, not just hidden here). Text styling lands on the button itself: Button.tsx
|
|
@@ -2356,6 +2368,22 @@ line-height: 31px;
|
|
|
2356
2368
|
background: #F4F5FA;
|
|
2357
2369
|
border-top: 0;
|
|
2358
2370
|
}
|
|
2371
|
+
/* DOM order is .co-error (only rendered when a step's validation fails), .co-back-btn,
|
|
2372
|
+
.co-step-count, Weiter. `order` visually moves Zurück before the error text (its DOM
|
|
2373
|
+
position doesn't change, so the back button's click handler/focus order are unaffected).
|
|
2374
|
+
Zurück's base margin-right:auto (line ~2265) is what pushes Schritt/Weiter to the row's
|
|
2375
|
+
right edge in the common case (no error, .co-back-btn is the only thing before the gap) —
|
|
2376
|
+
kept as-is here. Only when .co-error IS present does that job need to hand off to it
|
|
2377
|
+
instead (so the error text sits right after Zurück rather than the auto-margin gap opening
|
|
2378
|
+
up BEFORE the error, between it and Zurück): the `.co-error + .co-back-btn` sibling
|
|
2379
|
+
selector matches only that case (error is Zurück's immediately preceding DOM sibling) and
|
|
2380
|
+
zeroes Zurück's margin, leaving .co-error's own margin-right:auto below as the sole push. */
|
|
2381
|
+
.md3 .co-next .co-back-btn {
|
|
2382
|
+
order: -1;
|
|
2383
|
+
}
|
|
2384
|
+
.md3 .co-next .co-error + .co-back-btn {
|
|
2385
|
+
margin-right: 0;
|
|
2386
|
+
}
|
|
2359
2387
|
.md3 .co-next .co-error {
|
|
2360
2388
|
margin-right: auto;
|
|
2361
2389
|
}
|
|
@@ -2394,11 +2422,26 @@ line-height: 31px;
|
|
|
2394
2422
|
.md3 .co-form.co-form--payment-details {
|
|
2395
2423
|
border-bottom: 0;
|
|
2396
2424
|
}
|
|
2425
|
+
/* order-overview step only — same reasoning, see OrderOverviewNext.tsx's "co-form oo-page". */
|
|
2426
|
+
.md3 .co-form.oo-page {
|
|
2427
|
+
border-bottom: 0;
|
|
2428
|
+
}
|
|
2397
2429
|
.md3 .co-step-title {
|
|
2398
2430
|
font: 700 22px var(--md-font-display, var(--md-font));
|
|
2399
2431
|
margin: 0;
|
|
2400
2432
|
color: var(--md-on-surface);
|
|
2401
2433
|
}
|
|
2434
|
+
/* order-overview only ("Bestellübersicht") — desktop already shows this same title in
|
|
2435
|
+
CheckoutTopBarNext's own .co-topbar-heading (CheckoutChromeNext.tsx, same steps[activeStep].
|
|
2436
|
+
title source), so this becomes a duplicate there; kept for mobile, where that top bar is a
|
|
2437
|
+
compact single row with no room for it (CheckoutMobilePageTitle covers OTHER steps' mobile
|
|
2438
|
+
copy, but order-overview renders its own here instead). Same 600px mobile/desktop cutoff as
|
|
2439
|
+
.co-back-btn etc. elsewhere on this page. */
|
|
2440
|
+
@media (min-width: 601px) {
|
|
2441
|
+
.md3 .oo-page > .co-step-title {
|
|
2442
|
+
display: none;
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2402
2445
|
.md3 .co-step-sub {
|
|
2403
2446
|
color: var(--md-on-surface-variant);
|
|
2404
2447
|
font-size: 14px;
|
|
@@ -2884,6 +2927,13 @@ line-height: 31px;
|
|
|
2884
2927
|
height: 48px;
|
|
2885
2928
|
padding: 0 40px 0 10px;
|
|
2886
2929
|
}
|
|
2930
|
+
/* ADV-238: mandatory-but-empty Geburtsdatum must show the same red border as the other
|
|
2931
|
+
required fields. Same specificity as the rule above (two classes) so it only wins because
|
|
2932
|
+
it comes later in source order — .co-personal-grid .mfield--error can't reach in here, its
|
|
2933
|
+
border gets overridden by .co-birthdate .mfield right above for the same reason. */
|
|
2934
|
+
.md3 .co-birthdate .mfield--error {
|
|
2935
|
+
border: 1px solid #BA1A1A;
|
|
2936
|
+
}
|
|
2887
2937
|
/* on-the-border notch, matching .mfield-label--notch exactly (same mechanic as Vorwahl/
|
|
2888
2938
|
Telefonnummer's own label) instead of the earlier stacked-inside-the-box layout. */
|
|
2889
2939
|
.md3 .co-birthdate .mfield-label {
|
|
@@ -3051,6 +3101,25 @@ line-height: 31px;
|
|
|
3051
3101
|
.md3 .co-borrower-relationship .mselect-btn {
|
|
3052
3102
|
position: relative;
|
|
3053
3103
|
}
|
|
3104
|
+
/* ADV-240 QA (comment 35891): mandatory-but-empty red border — Dropdown has no built-in visual
|
|
3105
|
+
error state, so this keys off the `is-error` wrapper class PaymentDetailsNext.tsx adds. Three
|
|
3106
|
+
classes deep so it outranks both the base .mselect-btn idle box-shadow and this file's own
|
|
3107
|
+
.co-borrower-relationship .mselect-btn above without needing !important. */
|
|
3108
|
+
.md3 .co-borrower-relationship.is-error .mselect-btn {
|
|
3109
|
+
box-shadow: inset 0 0 0 1px #BA1A1A;
|
|
3110
|
+
}
|
|
3111
|
+
/* same chevron glyph as the ::after above, just stroke='%23BA1A1A' (red) instead of '%23005CFF'. */
|
|
3112
|
+
.md3 .co-borrower-relationship.is-error .mselect-btn::after {
|
|
3113
|
+
background-image: url("data:image/svg+xml,%3Csvg%20width='16'%20height='16'%20viewBox='0%200%2016%2016'%20fill='none'%20xmlns='http://www.w3.org/2000/svg'%3E%3Cpath%20d='M4%206L8%2010L12%206'%20stroke='%23BA1A1A'%20stroke-width='2'%20stroke-linecap='round'%20stroke-linejoin='round'/%3E%3C/svg%3E");
|
|
3114
|
+
}
|
|
3115
|
+
/* .co-borrower-relationship .mselect-btn:hover below is the SAME specificity (3 classes) as the
|
|
3116
|
+
.is-error border rule above and comes later in the file, so hovering an errored field was
|
|
3117
|
+
winning the box-shadow back to idle gray. One more class (:hover added to .is-error's own
|
|
3118
|
+
selector) outranks it regardless of source order — same fix shape as the co-birthdate
|
|
3119
|
+
:focus-within override above. */
|
|
3120
|
+
.md3 .co-borrower-relationship.is-error .mselect-btn:hover {
|
|
3121
|
+
box-shadow: inset 0 0 0 1px #BA1A1A;
|
|
3122
|
+
}
|
|
3054
3123
|
.md3 .co-borrower-relationship .mselect-btn:hover {
|
|
3055
3124
|
box-shadow: inset 0 0 0 1px var(--md-outline-variant);
|
|
3056
3125
|
}
|
|
@@ -3249,8 +3318,12 @@ line-height: 31px;
|
|
|
3249
3318
|
.md3 .co-pay-bene b {
|
|
3250
3319
|
display: block;
|
|
3251
3320
|
margin-bottom: 2px;
|
|
3252
|
-
font-
|
|
3253
|
-
|
|
3321
|
+
font-family: 'Arimo';
|
|
3322
|
+
font-style: normal;
|
|
3323
|
+
font-weight: 700;
|
|
3324
|
+
font-size: 15px;
|
|
3325
|
+
line-height: 140%;
|
|
3326
|
+
color: #1A1B22;
|
|
3254
3327
|
}
|
|
3255
3328
|
.md3 .co-pay-bene-desc {
|
|
3256
3329
|
font-family: 'Arimo';
|
|
@@ -3515,6 +3588,35 @@ line-height: 31px;
|
|
|
3515
3588
|
color: #1A1B22;
|
|
3516
3589
|
background: #FFFFFF;
|
|
3517
3590
|
}
|
|
3591
|
+
/* ADV-249 QA: Lieferadresse's "Abweichende Lieferadresse" form had no validation at all — this
|
|
3592
|
+
is its red error state, same red as .co-error (--md-error). Each field is now wrapped in
|
|
3593
|
+
.co-addr-form-item (the actual grid cell) so a per-field hint can sit directly under its own
|
|
3594
|
+
input instead of one generic message for the whole form. */
|
|
3595
|
+
.md3 .co-addr-form-item {
|
|
3596
|
+
display: flex;
|
|
3597
|
+
flex-direction: column;
|
|
3598
|
+
gap: 4px;
|
|
3599
|
+
min-width: 0;
|
|
3600
|
+
}
|
|
3601
|
+
.md3 .co-addr-form-field.is-error {
|
|
3602
|
+
border-color: var(--md-error);
|
|
3603
|
+
}
|
|
3604
|
+
/* native focus outline stacked on top of the red border looked like two rings at once
|
|
3605
|
+
(browser default blue + our red) — same inset-ring swap as .mtextarea:focus, red instead of
|
|
3606
|
+
blue while the field is still invalid. */
|
|
3607
|
+
.md3 .co-addr-form-field:focus {
|
|
3608
|
+
outline: 0;
|
|
3609
|
+
box-shadow: inset 0 0 0 2px #005CFF;
|
|
3610
|
+
}
|
|
3611
|
+
.md3 .co-addr-form-field.is-error:focus {
|
|
3612
|
+
box-shadow: inset 0 0 0 2px var(--md-error);
|
|
3613
|
+
}
|
|
3614
|
+
.md3 .co-addr-field-hint {
|
|
3615
|
+
color: var(--md-error);
|
|
3616
|
+
font-family: 'Arimo';
|
|
3617
|
+
font-size: 12px;
|
|
3618
|
+
line-height: 14px;
|
|
3619
|
+
}
|
|
3518
3620
|
.md3 .co-addr-form-actions {
|
|
3519
3621
|
grid-column: 1 / -1;
|
|
3520
3622
|
display: flex;
|
|
@@ -4474,10 +4576,17 @@ line-height: 31px;
|
|
|
4474
4576
|
}
|
|
4475
4577
|
/* "4. Lieferung & Services" (Figma node 63570:28320) — up to 3 cells in a row, divided by a 1px
|
|
4476
4578
|
rule; the rule is a border on the cell itself (:not(:first-child)) rather than a separate DOM
|
|
4477
|
-
node, so it stays correct no matter which 1-3 of the 3 cells are conditionally rendered.
|
|
4579
|
+
node, so it stays correct no matter which 1-3 of the 3 cells are conditionally rendered.
|
|
4580
|
+
align-items: stretch (not flex-start) — a border-left only ever reaches as far down as its own
|
|
4581
|
+
box, so with flex-start each divider stopped at its OWN cell's content height instead of the
|
|
4582
|
+
row's tallest cell (the 3-line-wrapping "9. Gewährleistung & Herstellergarantie" title makes
|
|
4583
|
+
that cell the tallest whenever it's shown), leaving the shorter cells' dividers visibly cut off
|
|
4584
|
+
partway down. Cells stretching to the row's full height doesn't shift their own icon+text
|
|
4585
|
+
(each cell keeps its own align-items: flex-start below), it only extends the empty space (and
|
|
4586
|
+
therefore the border) to match. */
|
|
4478
4587
|
.md3 .oo-ls-row {
|
|
4479
4588
|
display: flex;
|
|
4480
|
-
align-items:
|
|
4589
|
+
align-items: stretch;
|
|
4481
4590
|
}
|
|
4482
4591
|
.md3 .oo-ls-cell {
|
|
4483
4592
|
display: flex;
|
|
@@ -4801,11 +4910,6 @@ line-height: 31px;
|
|
|
4801
4910
|
color: var(--md-on-surface-variant);
|
|
4802
4911
|
cursor: pointer;
|
|
4803
4912
|
}
|
|
4804
|
-
.md3 .oo-underline {
|
|
4805
|
-
color: var(--md-primary);
|
|
4806
|
-
text-decoration: underline;
|
|
4807
|
-
cursor: pointer;
|
|
4808
|
-
}
|
|
4809
4913
|
/* "1. Fahrzeug & Verkäufer" card (Figma node 63570:28262) — own full-width .co-card, sitting
|
|
4810
4914
|
above .oo-grid (not a grid column), so no bottom divider/margin like the legacy .oo-car row
|
|
4811
4915
|
(that one used to sit above other rows sharing the same card). */
|
|
@@ -4948,16 +5052,94 @@ line-height: 31px;
|
|
|
4948
5052
|
font-size: 13px;
|
|
4949
5053
|
cursor: pointer;
|
|
4950
5054
|
}
|
|
4951
|
-
|
|
4952
|
-
|
|
5055
|
+
/* Figma 63570:28475 ("CK / Content — Rechtliche Hinweise") — text/underline color is the SAME
|
|
5056
|
+
muted gray as the body copy (var(--md-on-surface-variant) === Figma's var(--text/muted,
|
|
5057
|
+
#44464f) fallback exactly), not the design system's usual blue link color (.oo-underline,
|
|
5058
|
+
used elsewhere), so links here are styled with underline only. No border per explicit
|
|
5059
|
+
request. Sits below .oo-next (order-overview's own bottom bar) — a spot this exact node
|
|
5060
|
+
isn't shown at in Figma (its own component description has it above the buy button instead),
|
|
5061
|
+
so this margin-top is this page's own established gap rhythm, not a value read off the design. */
|
|
5062
|
+
.md3 .oo-legal-notice {
|
|
5063
|
+
margin-top: 16px;
|
|
5064
|
+
font-family: Arial;
|
|
4953
5065
|
font-size: 13px;
|
|
5066
|
+
line-height: 1.5;
|
|
4954
5067
|
color: var(--md-on-surface-variant);
|
|
4955
|
-
line-height: 1.6;
|
|
4956
5068
|
}
|
|
4957
|
-
.md3 .oo-
|
|
5069
|
+
.md3 .oo-legal-link {
|
|
5070
|
+
text-decoration: underline;
|
|
5071
|
+
color: inherit;
|
|
5072
|
+
cursor: pointer;
|
|
5073
|
+
}
|
|
5074
|
+
/* Order overview's own "Zurück / Schritt X von Y / Jetzt kaufen" action bar (Figma 63502:5801) —
|
|
5075
|
+
distinct classes from .co-next/.co-back-btn/.co-step-count (CheckoutChromeNext.tsx) rather than
|
|
5076
|
+
reusing them: that bar's CTA is "advance to next step" and returns null on the last step, this
|
|
5077
|
+
one's CTA is "buy now" (OfferRequestButtonWrapper) with its own, larger Figma-specified sizing. */
|
|
5078
|
+
.md3 .oo-next {
|
|
4958
5079
|
display: flex;
|
|
4959
|
-
|
|
4960
|
-
|
|
5080
|
+
align-items: center;
|
|
5081
|
+
gap: 16px;
|
|
5082
|
+
margin-top: 24px;
|
|
5083
|
+
padding-top: 18px;
|
|
5084
|
+
border-top: 1px solid #C5C6D2;
|
|
5085
|
+
}
|
|
5086
|
+
.md3 .oo-next-back {
|
|
5087
|
+
display: flex;
|
|
5088
|
+
align-items: center;
|
|
5089
|
+
justify-content: center;
|
|
5090
|
+
height: 48px;
|
|
5091
|
+
padding: 0 16px;
|
|
5092
|
+
margin-right: auto;
|
|
5093
|
+
border: 0;
|
|
5094
|
+
background: none;
|
|
5095
|
+
border-radius: 16px;
|
|
5096
|
+
font-family: 'Arial';
|
|
5097
|
+
font-style: normal;
|
|
5098
|
+
font-weight: 700;
|
|
5099
|
+
font-size: 15px;
|
|
5100
|
+
line-height: normal;
|
|
5101
|
+
color: #005CFF;
|
|
5102
|
+
cursor: pointer;
|
|
5103
|
+
}
|
|
5104
|
+
.md3 .oo-next-count {
|
|
5105
|
+
font-family: 'Arial';
|
|
5106
|
+
font-style: normal;
|
|
5107
|
+
font-weight: 400;
|
|
5108
|
+
font-size: 13px;
|
|
5109
|
+
line-height: normal;
|
|
5110
|
+
color: #44464F;
|
|
5111
|
+
white-space: nowrap;
|
|
5112
|
+
}
|
|
5113
|
+
.md3 .oo-buy-btn {
|
|
5114
|
+
display: flex;
|
|
5115
|
+
flex-direction: row;
|
|
5116
|
+
justify-content: center;
|
|
5117
|
+
align-items: center;
|
|
5118
|
+
padding: 0 16px;
|
|
5119
|
+
gap: 8px;
|
|
5120
|
+
width: 340px;
|
|
5121
|
+
height: 60px;
|
|
5122
|
+
background: #005CFF;
|
|
5123
|
+
border-radius: 16px;
|
|
5124
|
+
font-family: 'Arial';
|
|
5125
|
+
font-style: normal;
|
|
5126
|
+
font-weight: 700;
|
|
5127
|
+
font-size: 17px;
|
|
5128
|
+
line-height: normal;
|
|
5129
|
+
color: #FFFFFF;
|
|
5130
|
+
}
|
|
5131
|
+
@media (max-width: 700px) {
|
|
5132
|
+
.md3 .oo-next-count {
|
|
5133
|
+
display: none;
|
|
5134
|
+
}
|
|
5135
|
+
}
|
|
5136
|
+
@media (max-width: 600px) {
|
|
5137
|
+
.md3 .oo-next-back {
|
|
5138
|
+
display: none;
|
|
5139
|
+
}
|
|
5140
|
+
.md3 .oo-buy-btn {
|
|
5141
|
+
width: 100%;
|
|
5142
|
+
}
|
|
4961
5143
|
}
|
|
4962
5144
|
@media (max-width: 800px) {
|
|
4963
5145
|
.md3 .oo-grid {
|
|
@@ -5124,12 +5306,33 @@ line-height: 31px;
|
|
|
5124
5306
|
line instead of sitting visibly lower (more obvious once the label wraps to 2 lines). */
|
|
5125
5307
|
margin-top: 7px;
|
|
5126
5308
|
}
|
|
5127
|
-
/*
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
.md3 .oo-terms-block .mcheck-error-
|
|
5132
|
-
|
|
5309
|
+
/* Error state (Figma 64670:106293, "Jetzt kaufen" clicked without confirming both agreements) —
|
|
5310
|
+
unconfirmed checkbox gets a thicker red box, plus an icon+message row underneath, indented
|
|
5311
|
+
32px (20px box + 12px gap) to sit under the label rather than the box. errorIcon (FormCheckbox
|
|
5312
|
+
prop, opt-in) renders that icon; every other FormCheckbox caller sitewide is untouched. */
|
|
5313
|
+
.md3 .oo-terms-block .mcheck-group--error .mcheck-input:not(:checked) + .mcheck-box {
|
|
5314
|
+
border-width: 2px;
|
|
5315
|
+
}
|
|
5316
|
+
.md3 .oo-terms-block .mcheck-error-text--icon {
|
|
5317
|
+
display: flex;
|
|
5318
|
+
align-items: flex-start;
|
|
5319
|
+
gap: 6px;
|
|
5320
|
+
padding-left: 32px;
|
|
5321
|
+
margin-top: -2px;
|
|
5322
|
+
}
|
|
5323
|
+
.md3 .oo-terms-block .mcheck-error-icon {
|
|
5324
|
+
flex: none;
|
|
5325
|
+
margin-top: 1px;
|
|
5326
|
+
}
|
|
5327
|
+
.md3 .oo-terms-block .mcheck-error-msg {
|
|
5328
|
+
flex: 1 0 0;
|
|
5329
|
+
font-size: 13px;
|
|
5330
|
+
line-height: 1.35;
|
|
5331
|
+
}
|
|
5332
|
+
/* Card itself gets a red border once either checkbox is showing its error (OrderOverviewNext's
|
|
5333
|
+
termsHasError — same hasError+error double-check as FormCheckbox uses internally). */
|
|
5334
|
+
.md3 .oo-terms-card--error {
|
|
5335
|
+
border: 2px solid var(--md-error);
|
|
5133
5336
|
}
|
|
5134
5337
|
/* "7. Ergänzende Regelungen" dialogs (Verkürzung der Verjährungsfrist / Ausschluss der
|
|
5135
5338
|
Aktualisierung, Figma 64596:36289 desktop / 64596:36264 mobile) — rendered via the shared
|
|
@@ -5186,6 +5389,60 @@ line-height: 31px;
|
|
|
5186
5389
|
line-height: 1.5;
|
|
5187
5390
|
color: #0E2238;
|
|
5188
5391
|
}
|
|
5392
|
+
/* Legal-notice row's 3 dialogs (AGB von auto.de / AGB des Verkäufers / Widerrufsbelehrung —
|
|
5393
|
+
Figma 64604:48257 / 63583:6830 / 63583:6850) reuse the SAME oo-terms-modal chrome above, but
|
|
5394
|
+
these bodies are long reference documents rather than a single clause, so Figma specs them
|
|
5395
|
+
smaller/muted (text/muted #44464F) with bold text/strong (#1A1B22) section headings instead of
|
|
5396
|
+
.oo-terms-modal-text's bigger, darker single-paragraph style. oo-terms-modal-legal-text also
|
|
5397
|
+
carries the .oo-terms-modal-legal-html "AGB von auto.de"/"AGB des Verkäufers" bodies' shared
|
|
5398
|
+
base (dangerouslySetInnerHTML'd CMS content / seller.termCond plain text — both re-used
|
|
5399
|
+
verbatim from elsewhere in the app, not authored here, see OrderOverviewNext.tsx). */
|
|
5400
|
+
.md3 .oo-terms-modal-legal-text,
|
|
5401
|
+
.md3 .oo-terms-modal-legal-html {
|
|
5402
|
+
font-family: Arial;
|
|
5403
|
+
font-weight: 400;
|
|
5404
|
+
font-size: 13px;
|
|
5405
|
+
line-height: 1.48;
|
|
5406
|
+
color: #44464F;
|
|
5407
|
+
}
|
|
5408
|
+
.md3 .oo-terms-modal-legal-text {
|
|
5409
|
+
margin: 0;
|
|
5410
|
+
/* seller.termCond is plain text (Imprint.tsx renders it the same way) — preserves its literal
|
|
5411
|
+
line breaks instead of collapsing them. No-op for the revocation dialog's own JSON strings,
|
|
5412
|
+
which don't contain any. */
|
|
5413
|
+
white-space: pre-line;
|
|
5414
|
+
}
|
|
5415
|
+
.md3 .oo-terms-modal-legal-heading {
|
|
5416
|
+
margin: 0;
|
|
5417
|
+
font-family: Arial;
|
|
5418
|
+
font-weight: 700;
|
|
5419
|
+
font-size: 15px;
|
|
5420
|
+
line-height: 1.48;
|
|
5421
|
+
color: #1A1B22;
|
|
5422
|
+
}
|
|
5423
|
+
.md3 .oo-terms-modal-legal-body {
|
|
5424
|
+
display: flex;
|
|
5425
|
+
flex-direction: column;
|
|
5426
|
+
gap: 12px;
|
|
5427
|
+
}
|
|
5428
|
+
/* ourTermsAndConditions is a WordPress `content.rendered` HTML blob (CheckoutStore.ts) — its own
|
|
5429
|
+
<p> tags need their own spacing/heading treatment since it isn't discrete React children like
|
|
5430
|
+
oo-terms-modal-legal-body's blocks above. */
|
|
5431
|
+
.md3 .oo-terms-modal-legal-html p {
|
|
5432
|
+
margin: 0 0 12px;
|
|
5433
|
+
}
|
|
5434
|
+
.md3 .oo-terms-modal-legal-html p:last-child {
|
|
5435
|
+
margin-bottom: 0;
|
|
5436
|
+
}
|
|
5437
|
+
.md3 .oo-terms-modal-legal-html strong,
|
|
5438
|
+
.md3 .oo-terms-modal-legal-html b,
|
|
5439
|
+
.md3 .oo-terms-modal-legal-html h1,
|
|
5440
|
+
.md3 .oo-terms-modal-legal-html h2,
|
|
5441
|
+
.md3 .oo-terms-modal-legal-html h3 {
|
|
5442
|
+
font-weight: 700;
|
|
5443
|
+
font-size: 15px;
|
|
5444
|
+
color: #1A1B22;
|
|
5445
|
+
}
|
|
5189
5446
|
/* mobile bottom sheet (.mmodal's own @media max-width:560px already makes it full-width/
|
|
5190
5447
|
bottom-pinned/top-rounded) — Figma's mobile header additionally wants a 24px title (vs 22px
|
|
5191
5448
|
on desktop) and a decorative grip bar (Figma 64596:36264's handleRow), which this Modal
|
|
@@ -5256,15 +5513,16 @@ line-height: 31px;
|
|
|
5256
5513
|
.md3 .oo-promo-body .mfield:focus-within {
|
|
5257
5514
|
box-shadow: inset 0 0 0 1px var(--md-outline-variant);
|
|
5258
5515
|
}
|
|
5259
|
-
/*
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5516
|
+
/* Cards 9 "Gewährleistung" / 10 "Herstellergarantie" (ADV-295; Figma 64669:106647 /
|
|
5517
|
+
64669:108591) — share one .oo-warranty-card/.oo-warranty-body treatment (same card chrome,
|
|
5518
|
+
flat single-level collapse, same link/image styling); which fields each renders is decided in
|
|
5519
|
+
the JSX, not here. Both graphic blocks (the EU statutory-warranty notice, the GARAN
|
|
5520
|
+
certificate) are static images, not hand-coded — see the long comment above WARRANTY_NOTICE_IMG
|
|
5521
|
+
in OrderOverviewNext.tsx for why (Figma ships them fully vector-flattened). */
|
|
5265
5522
|
/* same fix as .oo-promo-card above — .co-card-title's shared margin-bottom:16px leaves a
|
|
5266
|
-
stray gap under the title when the card is collapsed
|
|
5267
|
-
|
|
5523
|
+
stray gap under the title when the card is collapsed; .oo-warranty-body's own margin-top
|
|
5524
|
+
restores that 16px only once actually expanded (both cards default expanded, but the user can
|
|
5525
|
+
still collapse them). */
|
|
5268
5526
|
.md3 .oo-warranty-card .co-card-title {
|
|
5269
5527
|
margin-bottom: 0;
|
|
5270
5528
|
}
|
|
@@ -5272,6 +5530,7 @@ line-height: 31px;
|
|
|
5272
5530
|
margin-top: 16px;
|
|
5273
5531
|
display: flex;
|
|
5274
5532
|
flex-direction: column;
|
|
5533
|
+
gap: 12px;
|
|
5275
5534
|
}
|
|
5276
5535
|
.md3 .oo-warranty-notice-img {
|
|
5277
5536
|
display: block;
|
|
@@ -5279,36 +5538,6 @@ line-height: 31px;
|
|
|
5279
5538
|
max-width: 400px;
|
|
5280
5539
|
height: auto;
|
|
5281
5540
|
}
|
|
5282
|
-
.md3 .oo-warranty-divider {
|
|
5283
|
-
margin-top: 16px;
|
|
5284
|
-
height: 1px;
|
|
5285
|
-
background: var(--md-outline-variant);
|
|
5286
|
-
}
|
|
5287
|
-
.md3 .oo-warranty-subrow {
|
|
5288
|
-
margin-top: 14px;
|
|
5289
|
-
padding-bottom: 6px;
|
|
5290
|
-
display: flex;
|
|
5291
|
-
align-items: center;
|
|
5292
|
-
justify-content: space-between;
|
|
5293
|
-
gap: 10px;
|
|
5294
|
-
width: 100%;
|
|
5295
|
-
background: none;
|
|
5296
|
-
border: 0;
|
|
5297
|
-
cursor: pointer;
|
|
5298
|
-
text-align: left;
|
|
5299
|
-
}
|
|
5300
|
-
.md3 .oo-warranty-subrow-label {
|
|
5301
|
-
font-family: Arial, Helvetica, Arimo, sans-serif;
|
|
5302
|
-
font-weight: 700;
|
|
5303
|
-
font-size: 15px;
|
|
5304
|
-
color: #0E2238;
|
|
5305
|
-
}
|
|
5306
|
-
.md3 .oo-warranty-manufacturer-body {
|
|
5307
|
-
margin-top: 10px;
|
|
5308
|
-
display: flex;
|
|
5309
|
-
flex-direction: column;
|
|
5310
|
-
gap: 12px;
|
|
5311
|
-
}
|
|
5312
5541
|
.md3 .oo-warranty-garan-img {
|
|
5313
5542
|
display: block;
|
|
5314
5543
|
width: 100%;
|
|
@@ -5783,11 +6012,20 @@ a.oo-warranty-link--icon:hover {
|
|
|
5783
6012
|
@media (min-width: 601px) and (max-width: 993px) {
|
|
5784
6013
|
.md3 .co-next {
|
|
5785
6014
|
flex-direction: row;
|
|
5786
|
-
flex-wrap:
|
|
6015
|
+
flex-wrap: wrap;
|
|
5787
6016
|
align-items: center;
|
|
5788
6017
|
justify-content: flex-end;
|
|
5789
6018
|
gap: 16px;
|
|
5790
6019
|
}
|
|
6020
|
+
/* same reasoning as the ≤700px block above: .co-error (a step-validation message, can run
|
|
6021
|
+
long — e.g. "Bitte wählen Sie eine der Optionen für Garantiepaket und Fahrzeugzulassung.")
|
|
6022
|
+
must not crowd onto the Zurück/Schritt/Weiter row. Without this, flex-wrap: nowrap forced
|
|
6023
|
+
all 4 children onto one line with no room, so the error visually overlapped Zurück and
|
|
6024
|
+
squeezed "Schritt X von Y" into wrapping onto 2 lines. flex-basis:100% forces the error onto
|
|
6025
|
+
its own first line; flex-wrap: wrap (above) then lays the 3 buttons/label out on the next. */
|
|
6026
|
+
.md3 .co-next .co-error {
|
|
6027
|
+
flex: 1 0 100%;
|
|
6028
|
+
}
|
|
5791
6029
|
}
|
|
5792
6030
|
/* below the 992px desktop breakpoint the content no longer reserves the 424px left
|
|
5793
6031
|
gap, so the fixed rail would overlap the form — hide it (mobile uses the header stepper). */
|
|
@@ -6240,7 +6478,8 @@ a.oo-warranty-link--icon:hover {
|
|
|
6240
6478
|
.md3 .enq-float input:focus-visible,
|
|
6241
6479
|
.md3 .enq-float textarea:focus-visible,
|
|
6242
6480
|
.md3 .enquiry-name-row input:focus-visible,
|
|
6243
|
-
.md3 .enquiry-email-wrap input:focus-visible
|
|
6481
|
+
.md3 .enquiry-email-wrap input:focus-visible,
|
|
6482
|
+
.md3 .co-addr-form-field:focus-visible {
|
|
6244
6483
|
box-shadow: none !important;
|
|
6245
6484
|
}
|
|
6246
6485
|
.md3 .link-accent {
|
|
@@ -9299,6 +9538,64 @@ a.oo-warranty-link--icon:hover {
|
|
|
9299
9538
|
padding: 18px 20px 22px;
|
|
9300
9539
|
overflow-y: auto;
|
|
9301
9540
|
}
|
|
9541
|
+
/* Checkout's Finanzierungsdetails/Leasingdetails modal (CheckoutFinancePanelNext's cf-details
|
|
9542
|
+
button) reuses this SAME FinanceDetailsModal as the VDP page, but the rate hero + price-rating
|
|
9543
|
+
pill/bar (.fin-price-grid) would just repeat the SAME figures the cf-panel already shows above
|
|
9544
|
+
this button — unlike the Anzahlung/Laufzeit sliders and Mit-Schlussrate/Rundum-Sorglos-Paket
|
|
9545
|
+
toggles (disabled by simply not passing their change handlers), nothing gates this block by
|
|
9546
|
+
prop, so it's hidden here instead. Scoped to the checkout portal wrapper only — VDP/SearchPage
|
|
9547
|
+
keep it. */
|
|
9548
|
+
.md3.cf-fin-details-modal .fin-price-grid {
|
|
9549
|
+
display: none;
|
|
9550
|
+
}
|
|
9551
|
+
/* Leasing's breakdown table runs longer than financing's (Leasinggeber+Vertragsart split, the
|
|
9552
|
+
mandatory Rundum-Sorglos-Paket notice box, an extra disclaimer line) — 88vh left it a little
|
|
9553
|
+
short of fitting whole, needing a few pixels of real scroll even after the hero/sliders/
|
|
9554
|
+
toggles/CTA above are stripped. A taller cap gives it room to fit outright on realistic
|
|
9555
|
+
viewports; overflow-y:auto above still catches genuinely short windows as a fallback. */
|
|
9556
|
+
.md3.cf-fin-details-modal .fin-modal {
|
|
9557
|
+
max-height: 95vh;
|
|
9558
|
+
}
|
|
9559
|
+
/* overflow-y:visible here (tried first for the short financing case) turned out to break the
|
|
9560
|
+
LONGER leasing case: without a real scroll container, flexbox's automatic min-height:auto
|
|
9561
|
+
let .fin-modal-body grow to its full content height (839px+ — leasing adds the Leasinggeber/
|
|
9562
|
+
Vertragsart split + the mandatory Rundum-Sorglos-Paket notice box) past .fin-modal's own
|
|
9563
|
+
max-height:88vh, and since .fin-modal-scrim centers it, the overflow pushed the header off the
|
|
9564
|
+
TOP of the viewport instead of clipping the bottom. Keep the real overflow-y:auto (correct
|
|
9565
|
+
height-constraining behaviour for any content length) and just hide the bar. */
|
|
9566
|
+
.md3.cf-fin-details-modal .fin-modal-body {
|
|
9567
|
+
scrollbar-width: none;
|
|
9568
|
+
-ms-overflow-style: none;
|
|
9569
|
+
}
|
|
9570
|
+
.md3.cf-fin-details-modal .fin-modal-body::-webkit-scrollbar {
|
|
9571
|
+
display: none;
|
|
9572
|
+
}
|
|
9573
|
+
/* "Rate anfragen"/call CTA row — checkout has no separate request flow to trigger from inside an
|
|
9574
|
+
already-running checkout (reviewing the breakdown IS the action), so this instance drops the
|
|
9575
|
+
row entirely rather than wiring it to a stand-in action. Unlike the sliders/toggles, it isn't
|
|
9576
|
+
gated by a handler prop, so it's hidden here. Shared class, one rule covers both breakpoints.
|
|
9577
|
+
`!important` — the element carries an inline style="display:flex" (VdpModules.tsx), which
|
|
9578
|
+
otherwise wins over any external rule regardless of selector specificity. */
|
|
9579
|
+
.md3.cf-fin-details-modal .fin-sheet-cta-row {
|
|
9580
|
+
display: none !important;
|
|
9581
|
+
}
|
|
9582
|
+
/* the (kept) legal breakdown's wrapper carries an inline marginTop:18 meant to separate it from
|
|
9583
|
+
the now-hidden hero/sliders/toggles above — stacked on .fin-modal-body's own 18px top padding
|
|
9584
|
+
that reads as unexplained empty space once those sections are gone. `!important` because the
|
|
9585
|
+
marginTop is inline (VdpModules.tsx), and only the DESKTOP wrapper needs it: it's the immediate
|
|
9586
|
+
next sibling of the (also hidden) .fin-price-grid there; the mobile breakdown wrapper is
|
|
9587
|
+
separated from .fin-price-grid by the still-visible data-sheet-fold button, so this selector
|
|
9588
|
+
doesn't reach it and mobile's spacing (already just marginBottom, no marginTop) is untouched. */
|
|
9589
|
+
.md3.cf-fin-details-modal .fin-price-grid + div {
|
|
9590
|
+
margin-top: 0 !important;
|
|
9591
|
+
}
|
|
9592
|
+
/* mobile breakpoint below sets align-items:flex-start on .fin-modal-head, top-aligning the close
|
|
9593
|
+
button against the 2-line "Kredit-Rate anpassen" + vehicle-name title — checkout's stripped
|
|
9594
|
+
instance has nothing that needs that extra top room, so it stays vertically centered instead
|
|
9595
|
+
(3-class selector already outweighs the 2-class mobile rule, no !important needed). */
|
|
9596
|
+
.md3.cf-fin-details-modal .fin-modal-head {
|
|
9597
|
+
align-items: center;
|
|
9598
|
+
}
|
|
9302
9599
|
@media (max-width: 600px) {
|
|
9303
9600
|
.md3 .fin-modal-scrim {
|
|
9304
9601
|
align-items: flex-end;
|
|
@@ -14177,3 +14474,247 @@ color: #005CFF;
|
|
|
14177
14474
|
border-bottom: 1px solid var(--md-outline-variant);
|
|
14178
14475
|
}
|
|
14179
14476
|
}
|
|
14477
|
+
|
|
14478
|
+
/* Post-purchase confirmation modal (CONGRATULATIONS_CHECKOUT_MODAL, Congratulations.tsx) —
|
|
14479
|
+
Figma 64637:37064 (desktop) / 63536:6276 (mobile). BaseModal's own `.modalCheckout` chrome
|
|
14480
|
+
(BaseModal.styl: fixed 375px width, square corners, its own white bg + shadow) is neutralized
|
|
14481
|
+
here via the `cgz-modal` className OfferRequestButtonWrapper passes — Congratulations.tsx's
|
|
14482
|
+
own `.cgz-card` supplies the actual Figma card (border/radius/bg/padding) instead, so there's
|
|
14483
|
+
no double box. `.md3` ancestor: App.tsx's root `<div className="${styles.app} md3">` wraps
|
|
14484
|
+
the whole SPA unconditionally (confirmed — this is NOT the same as the per-page opt-in `.md3`
|
|
14485
|
+
noted elsewhere for legacy MainContent pages), and BaseModal renders inline (no portal), so a
|
|
14486
|
+
single `.md3`-prefixed selector is enough, same as every other CheckoutPage-tree rule already
|
|
14487
|
+
in this file. */
|
|
14488
|
+
.md3 .cgz-modal {
|
|
14489
|
+
width: 800px;
|
|
14490
|
+
max-width: calc(100vw - 48px);
|
|
14491
|
+
background: transparent;
|
|
14492
|
+
border: 0;
|
|
14493
|
+
border-radius: 0;
|
|
14494
|
+
box-shadow: none;
|
|
14495
|
+
padding: 0;
|
|
14496
|
+
}
|
|
14497
|
+
.md3 .cgz-card {
|
|
14498
|
+
display: flex;
|
|
14499
|
+
flex-direction: column;
|
|
14500
|
+
align-items: center;
|
|
14501
|
+
gap: 32px;
|
|
14502
|
+
/* Figma: fixed 800px, not fluid — was width:100%, which (now that this also renders as a
|
|
14503
|
+
real page, not just inside the fixed-800px .cgz-modal) stretched edge-to-edge on any
|
|
14504
|
+
viewport wider than 800px+padding instead of staying a centered 800px card. max-width is
|
|
14505
|
+
just the narrow-viewport safety clamp (matches .cgz-modal's own calc(100vw - 48px)) —
|
|
14506
|
+
the max-width:600px block below still overrides both to 100% for the mobile layout. */
|
|
14507
|
+
width: 800px;
|
|
14508
|
+
max-width: calc(100vw - 48px);
|
|
14509
|
+
background: #FFFFFF;
|
|
14510
|
+
border: 1px solid var(--md-outline-variant);
|
|
14511
|
+
border-radius: 16px;
|
|
14512
|
+
padding: 48px 56px;
|
|
14513
|
+
box-sizing: border-box;
|
|
14514
|
+
}
|
|
14515
|
+
.md3 .cgz-intro {
|
|
14516
|
+
display: flex;
|
|
14517
|
+
flex-direction: column;
|
|
14518
|
+
align-items: center;
|
|
14519
|
+
gap: 12px;
|
|
14520
|
+
width: 100%;
|
|
14521
|
+
}
|
|
14522
|
+
.md3 .cgz-logo {
|
|
14523
|
+
width: 138.6px;
|
|
14524
|
+
height: 44px;
|
|
14525
|
+
}
|
|
14526
|
+
.md3 .cgz-badge {
|
|
14527
|
+
display: flex;
|
|
14528
|
+
align-items: center;
|
|
14529
|
+
gap: 8px;
|
|
14530
|
+
padding: 6px 13px;
|
|
14531
|
+
background: #DFF3E5;
|
|
14532
|
+
border-radius: 999px;
|
|
14533
|
+
font-family: Arimo, Arial, sans-serif;
|
|
14534
|
+
font-weight: 700;
|
|
14535
|
+
font-size: 13.5px;
|
|
14536
|
+
line-height: 1.35;
|
|
14537
|
+
color: #0F6B36;
|
|
14538
|
+
white-space: nowrap;
|
|
14539
|
+
}
|
|
14540
|
+
/* Figma specifies "42dot Sans" for this headline — not a typeface loaded anywhere in this
|
|
14541
|
+
project (confirmed: no other component/font-face references it). Mapped to the design
|
|
14542
|
+
system's own existing "big display heading" token instead (same one .co-step-title/hero
|
|
14543
|
+
numbers use) rather than adding a whole new webfont for one screen. 500 is the lightest
|
|
14544
|
+
weight actually loaded for it (Schibsted+Grotesk:wght@500;600;700;800, see :root) — closest
|
|
14545
|
+
available match to Figma's "Regular". */
|
|
14546
|
+
.md3 .cgz-title {
|
|
14547
|
+
margin: 0;
|
|
14548
|
+
font-family: var(--md-font-display, var(--md-font));
|
|
14549
|
+
font-weight: 500;
|
|
14550
|
+
font-size: 32px;
|
|
14551
|
+
line-height: 1.35;
|
|
14552
|
+
color: var(--md-on-surface);
|
|
14553
|
+
text-align: center;
|
|
14554
|
+
}
|
|
14555
|
+
.md3 .cgz-desc {
|
|
14556
|
+
margin: 0;
|
|
14557
|
+
max-width: 560px;
|
|
14558
|
+
font-family: Arimo, Arial, sans-serif;
|
|
14559
|
+
font-weight: 400;
|
|
14560
|
+
font-size: 16px;
|
|
14561
|
+
line-height: 1.35;
|
|
14562
|
+
color: var(--md-on-surface-variant);
|
|
14563
|
+
text-align: center;
|
|
14564
|
+
}
|
|
14565
|
+
.md3 .cgz-car {
|
|
14566
|
+
width: 420px;
|
|
14567
|
+
height: 275px;
|
|
14568
|
+
object-fit: contain;
|
|
14569
|
+
}
|
|
14570
|
+
.md3 .cgz-steps {
|
|
14571
|
+
display: flex;
|
|
14572
|
+
flex-direction: row;
|
|
14573
|
+
gap: 24px;
|
|
14574
|
+
width: 100%;
|
|
14575
|
+
padding: 20px 0;
|
|
14576
|
+
border-top: 1px solid var(--md-outline-variant);
|
|
14577
|
+
border-bottom: 1px solid var(--md-outline-variant);
|
|
14578
|
+
box-sizing: border-box;
|
|
14579
|
+
}
|
|
14580
|
+
.md3 .cgz-step {
|
|
14581
|
+
display: flex;
|
|
14582
|
+
flex: 1 0 0;
|
|
14583
|
+
min-width: 0;
|
|
14584
|
+
align-items: flex-start;
|
|
14585
|
+
gap: 10px;
|
|
14586
|
+
}
|
|
14587
|
+
.md3 .cgz-step-text {
|
|
14588
|
+
display: flex;
|
|
14589
|
+
flex-direction: column;
|
|
14590
|
+
gap: 1px;
|
|
14591
|
+
min-width: 0;
|
|
14592
|
+
}
|
|
14593
|
+
.md3 .cgz-step-title {
|
|
14594
|
+
margin: 0;
|
|
14595
|
+
font-family: Arimo, Arial, sans-serif;
|
|
14596
|
+
font-weight: 700;
|
|
14597
|
+
font-size: 14px;
|
|
14598
|
+
line-height: 1.35;
|
|
14599
|
+
color: var(--md-on-surface);
|
|
14600
|
+
}
|
|
14601
|
+
.md3 .cgz-step-sub {
|
|
14602
|
+
margin: 0;
|
|
14603
|
+
font-family: Arimo, Arial, sans-serif;
|
|
14604
|
+
font-weight: 400;
|
|
14605
|
+
font-size: 13px;
|
|
14606
|
+
line-height: 1.35;
|
|
14607
|
+
color: var(--md-on-surface-variant);
|
|
14608
|
+
}
|
|
14609
|
+
.md3 .cgz-btn {
|
|
14610
|
+
width: 320px;
|
|
14611
|
+
height: 48px;
|
|
14612
|
+
padding: 0 16px;
|
|
14613
|
+
gap: 8px;
|
|
14614
|
+
background: #005cff;
|
|
14615
|
+
border-radius: 16px;
|
|
14616
|
+
font-family: Arial;
|
|
14617
|
+
font-weight: 700;
|
|
14618
|
+
font-size: 15px;
|
|
14619
|
+
}
|
|
14620
|
+
.md3 .cgz-btn:hover {
|
|
14621
|
+
box-shadow: var(--md-e2);
|
|
14622
|
+
background: var(--autode-blau-strong);
|
|
14623
|
+
}
|
|
14624
|
+
@media (hover: none) {
|
|
14625
|
+
.md3 .cgz-btn:hover {
|
|
14626
|
+
box-shadow: none;
|
|
14627
|
+
background: #005cff;
|
|
14628
|
+
}
|
|
14629
|
+
}
|
|
14630
|
+
@media (max-width: 600px) {
|
|
14631
|
+
.md3 .cgz-modal {
|
|
14632
|
+
width: 100%;
|
|
14633
|
+
max-width: 100%;
|
|
14634
|
+
}
|
|
14635
|
+
.md3 .cgz-card {
|
|
14636
|
+
width: 100%;
|
|
14637
|
+
max-width: 100%;
|
|
14638
|
+
gap: 16px;
|
|
14639
|
+
padding: 24px 20px 26px;
|
|
14640
|
+
}
|
|
14641
|
+
.md3 .cgz-title {
|
|
14642
|
+
font-size: 26px;
|
|
14643
|
+
}
|
|
14644
|
+
.md3 .cgz-desc {
|
|
14645
|
+
max-width: none;
|
|
14646
|
+
font-family: Arial;
|
|
14647
|
+
font-size: 15px;
|
|
14648
|
+
}
|
|
14649
|
+
.md3 .cgz-car {
|
|
14650
|
+
width: 260px;
|
|
14651
|
+
height: 170px;
|
|
14652
|
+
}
|
|
14653
|
+
.md3 .cgz-steps {
|
|
14654
|
+
flex-direction: column;
|
|
14655
|
+
gap: 12px;
|
|
14656
|
+
padding: 16px 0;
|
|
14657
|
+
}
|
|
14658
|
+
.md3 .cgz-step-title,
|
|
14659
|
+
.md3 .cgz-step-sub {
|
|
14660
|
+
font-family: Arial;
|
|
14661
|
+
}
|
|
14662
|
+
.md3 .cgz-btn {
|
|
14663
|
+
width: 100%;
|
|
14664
|
+
}
|
|
14665
|
+
}
|
|
14666
|
+
|
|
14667
|
+
/* Escapes MainContent.styl's `container()` mixin (max-width: 1100px, centered) — App.tsx wraps
|
|
14668
|
+
every non-SPA-layout route (checkout included) in <MainContent>, which defaults to that
|
|
14669
|
+
1100px cap regardless of what the routed page itself renders. CheckoutPage.tsx already
|
|
14670
|
+
escapes it the CSS-module way (its own mainContentWrap, max-width:100%); this is the same
|
|
14671
|
+
fix as a plain literal class for CheckoutThankYouPage.tsx's <Page mainContentClass="..."> —
|
|
14672
|
+
consistent with cgz-modal above (a plain class, not a CSS-module one, so it's reliably
|
|
14673
|
+
targetable here in md3.css, a completely different bundle/file). */
|
|
14674
|
+
.md3 .cgz-main-content {
|
|
14675
|
+
max-width: 100%;
|
|
14676
|
+
min-height: 100%;
|
|
14677
|
+
height: auto;
|
|
14678
|
+
}
|
|
14679
|
+
|
|
14680
|
+
/* Checkout step 6 "Danke" page chrome (CheckoutThankYouPage.tsx) — Figma 64637:37033's own
|
|
14681
|
+
"Checkout Header/Footer Desktop", deliberately simpler than CheckoutTopBarNext/
|
|
14682
|
+
CheckoutHeaderNext (no car price/back/stepper — the order's already placed by the time this
|
|
14683
|
+
renders). `.cgz-card` etc. above are shared with the CONGRATULATIONS_CHECKOUT_MODAL path and
|
|
14684
|
+
need no changes for this context. */
|
|
14685
|
+
.md3 .cgz-page {
|
|
14686
|
+
display: flex;
|
|
14687
|
+
flex-direction: column;
|
|
14688
|
+
min-height: 100vh;
|
|
14689
|
+
}
|
|
14690
|
+
.md3 .cgz-page-header {
|
|
14691
|
+
display: flex;
|
|
14692
|
+
align-items: center;
|
|
14693
|
+
flex: none;
|
|
14694
|
+
height: 64px;
|
|
14695
|
+
padding: 0 120px;
|
|
14696
|
+
background: #FFFFFF;
|
|
14697
|
+
border-bottom: 1px solid var(--md-outline-variant);
|
|
14698
|
+
}
|
|
14699
|
+
.md3 .cgz-page-logo {
|
|
14700
|
+
display: block;
|
|
14701
|
+
width: 81.9px;
|
|
14702
|
+
height: 26px;
|
|
14703
|
+
}
|
|
14704
|
+
.md3 .cgz-page-main {
|
|
14705
|
+
display: flex;
|
|
14706
|
+
flex-direction: column;
|
|
14707
|
+
align-items: center;
|
|
14708
|
+
flex: 1 0 auto;
|
|
14709
|
+
padding: 56px 24px 80px;
|
|
14710
|
+
background: #F4F5FA;
|
|
14711
|
+
}
|
|
14712
|
+
@media (max-width: 600px) {
|
|
14713
|
+
.md3 .cgz-page-header {
|
|
14714
|
+
height: 56px;
|
|
14715
|
+
padding: 0 23px;
|
|
14716
|
+
}
|
|
14717
|
+
.md3 .cgz-page-main {
|
|
14718
|
+
padding: 24px 23px 40px;
|
|
14719
|
+
}
|
|
14720
|
+
}
|
package/ui/FormCheckbox.tsx
CHANGED
|
@@ -36,10 +36,14 @@ export interface FormCheckboxProps {
|
|
|
36
36
|
/** override the field's own disabled flag. */
|
|
37
37
|
disabled?: boolean;
|
|
38
38
|
id?: string;
|
|
39
|
+
/** show a small alert-circle glyph before the error text (Figma 64670:106293's "Fehlermeldung"
|
|
40
|
+
* row) — opt-in, defaults off so every other FormCheckbox caller keeps its plain text-only
|
|
41
|
+
* error message unchanged. */
|
|
42
|
+
errorIcon?: boolean;
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
const FormCheckbox: React.FunctionComponent<FormCheckboxProps> = observer(
|
|
42
|
-
({ field, t, label, children, disabled, id }) => {
|
|
46
|
+
({ field, t, label, children, disabled, id, errorIcon }) => {
|
|
43
47
|
const isChecked = !!field.checked;
|
|
44
48
|
const isDisabled = disabled != null ? disabled : !!field.disabled;
|
|
45
49
|
// field.hasError alone is a LIVE check (mobx-react-form's Field#hasError getter re-evaluates
|
|
@@ -77,7 +81,16 @@ const FormCheckbox: React.FunctionComponent<FormCheckboxProps> = observer(
|
|
|
77
81
|
</span>
|
|
78
82
|
{content != null ? <span className="mcheck-label">{content}</span> : null}
|
|
79
83
|
</label>
|
|
80
|
-
{hasError ?
|
|
84
|
+
{hasError ? (
|
|
85
|
+
errorIcon ? (
|
|
86
|
+
<span className="mcheck-error-text mcheck-error-text--icon">
|
|
87
|
+
<Icon name="alert-circle" size={16} stroke={2} cls="mcheck-error-icon" />
|
|
88
|
+
<span className="mcheck-error-msg">{field.error}</span>
|
|
89
|
+
</span>
|
|
90
|
+
) : (
|
|
91
|
+
<span className="mcheck-error-text">{field.error}</span>
|
|
92
|
+
)
|
|
93
|
+
) : null}
|
|
81
94
|
</div>
|
|
82
95
|
);
|
|
83
96
|
},
|
package/ui/Icon.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import {
|
|
3
3
|
Activity,
|
|
4
|
+
AlertCircle,
|
|
4
5
|
Apple,
|
|
5
6
|
Armchair,
|
|
6
7
|
ArrowLeft,
|
|
@@ -57,6 +58,7 @@ import {
|
|
|
57
58
|
Facebook,
|
|
58
59
|
File,
|
|
59
60
|
FileCheck,
|
|
61
|
+
FileSignature,
|
|
60
62
|
FileText,
|
|
61
63
|
Flame,
|
|
62
64
|
Fuel,
|
|
@@ -199,6 +201,7 @@ function toPascalCase(name: string): string {
|
|
|
199
201
|
*/
|
|
200
202
|
const REGISTRY: Record<string, React.ComponentType<LucideProps>> = {
|
|
201
203
|
Activity,
|
|
204
|
+
AlertCircle,
|
|
202
205
|
Apple,
|
|
203
206
|
Armchair,
|
|
204
207
|
ArrowLeft,
|
|
@@ -256,6 +259,7 @@ const REGISTRY: Record<string, React.ComponentType<LucideProps>> = {
|
|
|
256
259
|
Facebook,
|
|
257
260
|
File,
|
|
258
261
|
FileCheck,
|
|
262
|
+
FileSignature,
|
|
259
263
|
FileText,
|
|
260
264
|
Flame,
|
|
261
265
|
Fuel,
|
package/ui/MfieldCheckIcon.tsx
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
|
|
3
|
-
// Figma-exact valid-field checkmark (.mfield-ok, FormField/MdField's showValid) — a distinct
|
|
4
|
-
// path/stroke from the generic lucide Check that Icon name="check" renders elsewhere, so it's
|
|
5
|
-
// its own tiny component rather than a REGISTRY entry in Icon.tsx (which would change every
|
|
6
|
-
// other "check" usage in the app too).
|
|
7
|
-
const MfieldCheckIcon: React.FunctionComponent = () => (
|
|
8
|
-
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
9
|
-
<path d="M13.3334 4L6.00002 11.3333L2.66669 8" stroke="#168A45" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" />
|
|
10
|
-
</svg>
|
|
11
|
-
);
|
|
12
|
-
|
|
13
|
-
export default MfieldCheckIcon;
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
// Figma-exact valid-field checkmark (.mfield-ok, FormField/MdField's showValid) — a distinct
|
|
4
|
+
// path/stroke from the generic lucide Check that Icon name="check" renders elsewhere, so it's
|
|
5
|
+
// its own tiny component rather than a REGISTRY entry in Icon.tsx (which would change every
|
|
6
|
+
// other "check" usage in the app too).
|
|
7
|
+
const MfieldCheckIcon: React.FunctionComponent = () => (
|
|
8
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
9
|
+
<path d="M13.3334 4L6.00002 11.3333L2.66669 8" stroke="#168A45" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" />
|
|
10
|
+
</svg>
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
export default MfieldCheckIcon;
|
package/vdp/VdpModules.tsx
CHANGED
|
@@ -2126,6 +2126,12 @@ export interface FinanceDetailsModalProps {
|
|
|
2126
2126
|
/** opened directly via the page's Finanzierungs-/Leasingdetails link — the sheet opens
|
|
2127
2127
|
* scrolled to the breakdown instead of the "Rate anpassen" controls at its top. */
|
|
2128
2128
|
initialShowBreakdown?: boolean;
|
|
2129
|
+
/** skips the smooth scrollIntoView/scrollSheetToBreakdown animation initialShowBreakdown
|
|
2130
|
+
* otherwise triggers on open (still sets showBreakdown itself) — for a consumer whose sheet
|
|
2131
|
+
* has nothing ABOVE the breakdown to scroll past (e.g. checkout's stripped-down instance,
|
|
2132
|
+
* with the rate hero/sliders/toggles above it hidden), that animation has nowhere real to go
|
|
2133
|
+
* and only reads as a stray jump. */
|
|
2134
|
+
skipInitialScroll?: boolean;
|
|
2129
2135
|
/** mobile only — price-rating label, drives the eval pill/bar (same as the collapsed card). */
|
|
2130
2136
|
ratingLabel?: string;
|
|
2131
2137
|
/** Kaufen tab only — real carStore.isAvailable (same source as VdpTrustRow's "Sofort
|
|
@@ -2154,6 +2160,10 @@ export interface FinanceDetailsModalProps {
|
|
|
2154
2160
|
leasingAvailable?: boolean;
|
|
2155
2161
|
/** i18n function (drilled from the page). */
|
|
2156
2162
|
t: TranslationFunction;
|
|
2163
|
+
/** overrides the desktop dialog heading (default: the mode's own, shorter "Details" text) —
|
|
2164
|
+
* a consumer whose own trigger already carries a specific label (e.g. checkout's cf-details
|
|
2165
|
+
* button, "Finanzierungsdetails"/"Leasingdetails") can pass that same text through here. */
|
|
2166
|
+
titleOverride?: string;
|
|
2157
2167
|
}
|
|
2158
2168
|
|
|
2159
2169
|
/**
|
|
@@ -2197,6 +2207,7 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
|
|
|
2197
2207
|
onAnnualMileageChange,
|
|
2198
2208
|
onClose,
|
|
2199
2209
|
initialShowBreakdown,
|
|
2210
|
+
skipInitialScroll,
|
|
2200
2211
|
ratingLabel,
|
|
2201
2212
|
available,
|
|
2202
2213
|
onModeChange,
|
|
@@ -2211,6 +2222,7 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
|
|
|
2211
2222
|
onToggleAddServices,
|
|
2212
2223
|
leasingAvailable = true,
|
|
2213
2224
|
t,
|
|
2225
|
+
titleOverride,
|
|
2214
2226
|
}) => {
|
|
2215
2227
|
// same "UVP-Rate"/list-price footnote badge + popover as FinanceCalculator's own hero — this
|
|
2216
2228
|
// modal's mobile "Rate anpassen" sheet renders its own separate copy of that hero row, so it
|
|
@@ -2314,14 +2326,14 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
|
|
|
2314
2326
|
React.useEffect(() => {
|
|
2315
2327
|
if (!open) return;
|
|
2316
2328
|
setShowBreakdown(!!initialShowBreakdown);
|
|
2317
|
-
if (!initialShowBreakdown) return;
|
|
2329
|
+
if (!initialShowBreakdown || skipInitialScroll) return;
|
|
2318
2330
|
const timer = window.setTimeout(() => {
|
|
2319
2331
|
if (mobileBreakdownRef.current?.offsetParent) scrollSheetToBreakdown();
|
|
2320
2332
|
else desktopBreakdownRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
2321
2333
|
}, 50);
|
|
2322
2334
|
return () => window.clearTimeout(timer);
|
|
2323
2335
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2324
|
-
}, [open, initialShowBreakdown]);
|
|
2336
|
+
}, [open, initialShowBreakdown, skipInitialScroll]);
|
|
2325
2337
|
// in-sheet details link: expanding scrolls down to the breakdown, collapsing back to the top.
|
|
2326
2338
|
const revealBreakdownRef = React.useRef(false);
|
|
2327
2339
|
React.useEffect(() => {
|
|
@@ -2389,7 +2401,7 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
|
|
|
2389
2401
|
if (!open) return null;
|
|
2390
2402
|
const isLease = mode === 'leasing';
|
|
2391
2403
|
const isCash = mode === 'cash';
|
|
2392
|
-
const title = isLease ? t('autobahn:modal.leasingDetails') : t('autobahn:modal.financingDetails');
|
|
2404
|
+
const title = titleOverride || (isLease ? t('autobahn:modal.leasingDetails') : t('autobahn:modal.financingDetails'));
|
|
2393
2405
|
const mobileTitle = isCash
|
|
2394
2406
|
? t('autobahn:modal.sheetTitleCash')
|
|
2395
2407
|
: isLease
|