@bytebrand/fe-ui-core-autobahn 1.0.126 → 1.0.127
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/compat/Checkbox/Checkbox.tsx +191 -191
- package/package.json +1 -1
- package/theme/md3.css +29 -0
- package/vdp/FinanceCalculator.tsx +44 -36
|
@@ -1,191 +1,191 @@
|
|
|
1
|
-
import React, { ReactNode } from 'react';
|
|
2
|
-
import styles from './Checkbox.styl';
|
|
3
|
-
import classnames from 'classnames';
|
|
4
|
-
import IconSVG from '../../iconsvg/IconSVG';
|
|
5
|
-
import Tooltip from '../Tooltip/Tooltip';
|
|
6
|
-
import FormattedNumber from '../FormattedNumber/FormattedNumber';
|
|
7
|
-
|
|
8
|
-
export interface ICheckboxProps {
|
|
9
|
-
// Native checkbox props
|
|
10
|
-
name?: string;
|
|
11
|
-
value?: any;
|
|
12
|
-
checked?: boolean;
|
|
13
|
-
disabled?: boolean;
|
|
14
|
-
reversed?: boolean;
|
|
15
|
-
isAboAccordionExpanded0?: boolean;
|
|
16
|
-
onChange?: (event: React.ChangeEvent<HTMLInputElement>, checked?: boolean, value?: any) => void;
|
|
17
|
-
onBlur?: (event: React.FocusEvent<HTMLDivElement>) => void;
|
|
18
|
-
onFocus?: (event: React.FocusEvent<HTMLDivElement>) => void;
|
|
19
|
-
|
|
20
|
-
// Custom checkbox props
|
|
21
|
-
containerClassName?: string;
|
|
22
|
-
checkboxClassName?: string;
|
|
23
|
-
labelClassName?: string;
|
|
24
|
-
labelContentClassName?: string;
|
|
25
|
-
tooltipDescription?: string;
|
|
26
|
-
checkboxPriceValue: any;
|
|
27
|
-
tooltipPosX?: string;
|
|
28
|
-
error?: boolean;
|
|
29
|
-
label?: JSX.Element | string | null | ReactNode;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Unique-per-instance id for the text content span, so the (visually hidden) native
|
|
33
|
-
// <input type="checkbox"> can point aria-labelledby at it — see render() below for why the
|
|
34
|
-
// text moved out of the <label> and needs this to still work as the checkbox's accessible
|
|
35
|
-
// name for screen readers.
|
|
36
|
-
let contentIdCounter = 0;
|
|
37
|
-
|
|
38
|
-
class Checkbox extends React.Component<ICheckboxProps, {}> {
|
|
39
|
-
static defaultProps = {
|
|
40
|
-
name: '',
|
|
41
|
-
value: '',
|
|
42
|
-
checked: false,
|
|
43
|
-
disabled: false,
|
|
44
|
-
reversed: false,
|
|
45
|
-
|
|
46
|
-
onChange: () => { },
|
|
47
|
-
onBlur: () => { },
|
|
48
|
-
onFocus: () => { },
|
|
49
|
-
|
|
50
|
-
containerClassName: '',
|
|
51
|
-
checkboxClassName: '',
|
|
52
|
-
labelClassName: '',
|
|
53
|
-
labelContentClassName: '',
|
|
54
|
-
tooltipDescription: '',
|
|
55
|
-
checkboxPriceValue: '',
|
|
56
|
-
tooltipPosX: '',
|
|
57
|
-
|
|
58
|
-
error: false,
|
|
59
|
-
label: false
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
input: HTMLInputElement = null;
|
|
63
|
-
contentId: string;
|
|
64
|
-
|
|
65
|
-
constructor(props: ICheckboxProps) {
|
|
66
|
-
super(props);
|
|
67
|
-
contentIdCounter += 1;
|
|
68
|
-
this.contentId = `checkbox-content-${contentIdCounter}`;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
setInputRef = (ref: HTMLInputElement) => {
|
|
72
|
-
this.input = ref;
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
76
|
-
const { disabled, value } = this.props;
|
|
77
|
-
const { checked } = event.target;
|
|
78
|
-
|
|
79
|
-
if (disabled) {
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
if (!!event) {
|
|
83
|
-
event.stopPropagation();
|
|
84
|
-
event.nativeEvent.stopImmediatePropagation();
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
this.props.onChange(event, checked, value);
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
onKeyPress = (event: any) => {
|
|
91
|
-
const { disabled } = this.props;
|
|
92
|
-
const checked = !this.input.checked;
|
|
93
|
-
const value = this.input.value;
|
|
94
|
-
|
|
95
|
-
if (disabled) {
|
|
96
|
-
return false;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
this.props.onChange(event, checked, value);
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
render() {
|
|
103
|
-
const {
|
|
104
|
-
containerClassName, checkboxClassName, labelClassName, labelContentClassName, reversed,
|
|
105
|
-
checked, disabled, value, error, label, tooltipDescription, checkboxPriceValue, tooltipPosX, isAboAccordionExpanded0 = true
|
|
106
|
-
} = this.props;
|
|
107
|
-
|
|
108
|
-
const containerClasses = classnames(
|
|
109
|
-
styles.container,
|
|
110
|
-
containerClassName,
|
|
111
|
-
{ [styles.checked]: checked },
|
|
112
|
-
{ [styles.error]: error },
|
|
113
|
-
{ [styles.disabled]: disabled }
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
const labelClasses = classnames(
|
|
117
|
-
styles.label,
|
|
118
|
-
labelClassName
|
|
119
|
-
);
|
|
120
|
-
|
|
121
|
-
const labelContentClasses = classnames(
|
|
122
|
-
styles.labelContent,
|
|
123
|
-
labelContentClassName
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
const checkboxClasses = classnames(
|
|
127
|
-
styles.checkbox,
|
|
128
|
-
checkboxClassName,
|
|
129
|
-
{ [styles.withLabel]: !!label }
|
|
130
|
-
);
|
|
131
|
-
|
|
132
|
-
const wrapInputClasses = classnames(
|
|
133
|
-
styles.wrapInput,
|
|
134
|
-
{ [styles.reversed]: reversed }
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
return (
|
|
138
|
-
// tabIndex: was 1 (a positive value) — a well-known anti-pattern (WCAG 2.4.3):
|
|
139
|
-
// positive tabindex elements are visited in their own ascending-order pass BEFORE
|
|
140
|
-
// every tabindex="0"/naturally-focusable element on the page, regardless of where
|
|
141
|
-
// they sit in the DOM. In BaseModal's new focus trap (which walks the panel's
|
|
142
|
-
// focusable elements in DOM order to decide where Tab should wrap) that mismatch
|
|
143
|
-
// made this checkbox unreachable by Tab entirely inside a trapped modal — verified
|
|
144
|
-
// live: 0 hits in 20 Tab presses cycling the RegistrationForm consent checkbox.
|
|
145
|
-
// 0 makes it a normal stop in DOM-order tab flow, matching the trap's own model and
|
|
146
|
-
// putting it where a sighted user would visually expect it (after the fields above,
|
|
147
|
-
// before the CTA below) instead of unpredictably early.
|
|
148
|
-
<div className={containerClasses} tabIndex={0} onFocus={this.props.onFocus} onBlur={this.props.onBlur} onKeyPress={this.onKeyPress}>
|
|
149
|
-
<div className={labelClasses}>
|
|
150
|
-
{/* Real <label>, scoped to JUST the checkbox (+ its optional price badge) — was
|
|
151
|
-
wrapping the text content too, so clicking anywhere in a long paragraph like the
|
|
152
|
-
checkout modal's terms-agreement text toggled the checkbox as a side effect.
|
|
153
|
-
Native label-click-forwarding only fires for clicks landing inside THIS element
|
|
154
|
-
now, and it still works with zero extra JS since the hidden <input> is nested
|
|
155
|
-
inside it. */}
|
|
156
|
-
<label className={wrapInputClasses}>
|
|
157
|
-
{/* Visual representation of the checkbox */}
|
|
158
|
-
{/* HidePlus class for OrderOverview component */}
|
|
159
|
-
{checkboxPriceValue && <span className={styles.checkboxPrice}><span className={styles.hidePlus}>+</span><FormattedNumber numbersAfterDot={0} value={checkboxPriceValue} /> €</span>}
|
|
160
|
-
{/* Hidden input for SEO and HTML semantics */}
|
|
161
|
-
<input
|
|
162
|
-
className={styles.hiddenInput}
|
|
163
|
-
type='checkbox'
|
|
164
|
-
checked={checked}
|
|
165
|
-
value={value}
|
|
166
|
-
disabled={disabled}
|
|
167
|
-
tabIndex={-1}
|
|
168
|
-
onChange={this.onChange}
|
|
169
|
-
ref={this.setInputRef}
|
|
170
|
-
// The text is no longer a DOM ancestor/descendant of this input (moved out to a
|
|
171
|
-
// plain sibling <span> below so it stops being a click target) — aria-labelledby
|
|
172
|
-
// keeps it as the input's accessible name for assistive tech regardless.
|
|
173
|
-
aria-labelledby={label ? this.contentId : undefined}
|
|
174
|
-
/>
|
|
175
|
-
|
|
176
|
-
{isAboAccordionExpanded0 && <span className={checkboxClasses}>{checked ? <IconSVG name='common_CheckBlue' className={styles.checkBlueIcon} /> : false}</span>}
|
|
177
|
-
|
|
178
|
-
</label>
|
|
179
|
-
{!!label ? (
|
|
180
|
-
<span id={this.contentId} className={labelContentClasses}>
|
|
181
|
-
{label}
|
|
182
|
-
{tooltipDescription && <Tooltip description={tooltipDescription} positionY={'top'} positionX={tooltipPosX} tooltipClassName={styles.tooltipCheckbox} iconName='InfoTransparent' />}
|
|
183
|
-
</span>
|
|
184
|
-
) : false}
|
|
185
|
-
</div>
|
|
186
|
-
</div>
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
export default Checkbox;
|
|
1
|
+
import React, { ReactNode } from 'react';
|
|
2
|
+
import styles from './Checkbox.styl';
|
|
3
|
+
import classnames from 'classnames';
|
|
4
|
+
import IconSVG from '../../iconsvg/IconSVG';
|
|
5
|
+
import Tooltip from '../Tooltip/Tooltip';
|
|
6
|
+
import FormattedNumber from '../FormattedNumber/FormattedNumber';
|
|
7
|
+
|
|
8
|
+
export interface ICheckboxProps {
|
|
9
|
+
// Native checkbox props
|
|
10
|
+
name?: string;
|
|
11
|
+
value?: any;
|
|
12
|
+
checked?: boolean;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
reversed?: boolean;
|
|
15
|
+
isAboAccordionExpanded0?: boolean;
|
|
16
|
+
onChange?: (event: React.ChangeEvent<HTMLInputElement>, checked?: boolean, value?: any) => void;
|
|
17
|
+
onBlur?: (event: React.FocusEvent<HTMLDivElement>) => void;
|
|
18
|
+
onFocus?: (event: React.FocusEvent<HTMLDivElement>) => void;
|
|
19
|
+
|
|
20
|
+
// Custom checkbox props
|
|
21
|
+
containerClassName?: string;
|
|
22
|
+
checkboxClassName?: string;
|
|
23
|
+
labelClassName?: string;
|
|
24
|
+
labelContentClassName?: string;
|
|
25
|
+
tooltipDescription?: string;
|
|
26
|
+
checkboxPriceValue: any;
|
|
27
|
+
tooltipPosX?: string;
|
|
28
|
+
error?: boolean;
|
|
29
|
+
label?: JSX.Element | string | null | ReactNode;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Unique-per-instance id for the text content span, so the (visually hidden) native
|
|
33
|
+
// <input type="checkbox"> can point aria-labelledby at it — see render() below for why the
|
|
34
|
+
// text moved out of the <label> and needs this to still work as the checkbox's accessible
|
|
35
|
+
// name for screen readers.
|
|
36
|
+
let contentIdCounter = 0;
|
|
37
|
+
|
|
38
|
+
class Checkbox extends React.Component<ICheckboxProps, {}> {
|
|
39
|
+
static defaultProps = {
|
|
40
|
+
name: '',
|
|
41
|
+
value: '',
|
|
42
|
+
checked: false,
|
|
43
|
+
disabled: false,
|
|
44
|
+
reversed: false,
|
|
45
|
+
|
|
46
|
+
onChange: () => { },
|
|
47
|
+
onBlur: () => { },
|
|
48
|
+
onFocus: () => { },
|
|
49
|
+
|
|
50
|
+
containerClassName: '',
|
|
51
|
+
checkboxClassName: '',
|
|
52
|
+
labelClassName: '',
|
|
53
|
+
labelContentClassName: '',
|
|
54
|
+
tooltipDescription: '',
|
|
55
|
+
checkboxPriceValue: '',
|
|
56
|
+
tooltipPosX: '',
|
|
57
|
+
|
|
58
|
+
error: false,
|
|
59
|
+
label: false
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
input: HTMLInputElement = null;
|
|
63
|
+
contentId: string;
|
|
64
|
+
|
|
65
|
+
constructor(props: ICheckboxProps) {
|
|
66
|
+
super(props);
|
|
67
|
+
contentIdCounter += 1;
|
|
68
|
+
this.contentId = `checkbox-content-${contentIdCounter}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
setInputRef = (ref: HTMLInputElement) => {
|
|
72
|
+
this.input = ref;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
76
|
+
const { disabled, value } = this.props;
|
|
77
|
+
const { checked } = event.target;
|
|
78
|
+
|
|
79
|
+
if (disabled) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
if (!!event) {
|
|
83
|
+
event.stopPropagation();
|
|
84
|
+
event.nativeEvent.stopImmediatePropagation();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.props.onChange(event, checked, value);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
onKeyPress = (event: any) => {
|
|
91
|
+
const { disabled } = this.props;
|
|
92
|
+
const checked = !this.input.checked;
|
|
93
|
+
const value = this.input.value;
|
|
94
|
+
|
|
95
|
+
if (disabled) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this.props.onChange(event, checked, value);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
render() {
|
|
103
|
+
const {
|
|
104
|
+
containerClassName, checkboxClassName, labelClassName, labelContentClassName, reversed,
|
|
105
|
+
checked, disabled, value, error, label, tooltipDescription, checkboxPriceValue, tooltipPosX, isAboAccordionExpanded0 = true
|
|
106
|
+
} = this.props;
|
|
107
|
+
|
|
108
|
+
const containerClasses = classnames(
|
|
109
|
+
styles.container,
|
|
110
|
+
containerClassName,
|
|
111
|
+
{ [styles.checked]: checked },
|
|
112
|
+
{ [styles.error]: error },
|
|
113
|
+
{ [styles.disabled]: disabled }
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const labelClasses = classnames(
|
|
117
|
+
styles.label,
|
|
118
|
+
labelClassName
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const labelContentClasses = classnames(
|
|
122
|
+
styles.labelContent,
|
|
123
|
+
labelContentClassName
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const checkboxClasses = classnames(
|
|
127
|
+
styles.checkbox,
|
|
128
|
+
checkboxClassName,
|
|
129
|
+
{ [styles.withLabel]: !!label }
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const wrapInputClasses = classnames(
|
|
133
|
+
styles.wrapInput,
|
|
134
|
+
{ [styles.reversed]: reversed }
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
// tabIndex: was 1 (a positive value) — a well-known anti-pattern (WCAG 2.4.3):
|
|
139
|
+
// positive tabindex elements are visited in their own ascending-order pass BEFORE
|
|
140
|
+
// every tabindex="0"/naturally-focusable element on the page, regardless of where
|
|
141
|
+
// they sit in the DOM. In BaseModal's new focus trap (which walks the panel's
|
|
142
|
+
// focusable elements in DOM order to decide where Tab should wrap) that mismatch
|
|
143
|
+
// made this checkbox unreachable by Tab entirely inside a trapped modal — verified
|
|
144
|
+
// live: 0 hits in 20 Tab presses cycling the RegistrationForm consent checkbox.
|
|
145
|
+
// 0 makes it a normal stop in DOM-order tab flow, matching the trap's own model and
|
|
146
|
+
// putting it where a sighted user would visually expect it (after the fields above,
|
|
147
|
+
// before the CTA below) instead of unpredictably early.
|
|
148
|
+
<div className={containerClasses} tabIndex={0} onFocus={this.props.onFocus} onBlur={this.props.onBlur} onKeyPress={this.onKeyPress}>
|
|
149
|
+
<div className={labelClasses}>
|
|
150
|
+
{/* Real <label>, scoped to JUST the checkbox (+ its optional price badge) — was
|
|
151
|
+
wrapping the text content too, so clicking anywhere in a long paragraph like the
|
|
152
|
+
checkout modal's terms-agreement text toggled the checkbox as a side effect.
|
|
153
|
+
Native label-click-forwarding only fires for clicks landing inside THIS element
|
|
154
|
+
now, and it still works with zero extra JS since the hidden <input> is nested
|
|
155
|
+
inside it. */}
|
|
156
|
+
<label className={wrapInputClasses}>
|
|
157
|
+
{/* Visual representation of the checkbox */}
|
|
158
|
+
{/* HidePlus class for OrderOverview component */}
|
|
159
|
+
{checkboxPriceValue && <span className={styles.checkboxPrice}><span className={styles.hidePlus}>+</span><FormattedNumber numbersAfterDot={0} value={checkboxPriceValue} /> €</span>}
|
|
160
|
+
{/* Hidden input for SEO and HTML semantics */}
|
|
161
|
+
<input
|
|
162
|
+
className={styles.hiddenInput}
|
|
163
|
+
type='checkbox'
|
|
164
|
+
checked={checked}
|
|
165
|
+
value={value}
|
|
166
|
+
disabled={disabled}
|
|
167
|
+
tabIndex={-1}
|
|
168
|
+
onChange={this.onChange}
|
|
169
|
+
ref={this.setInputRef}
|
|
170
|
+
// The text is no longer a DOM ancestor/descendant of this input (moved out to a
|
|
171
|
+
// plain sibling <span> below so it stops being a click target) — aria-labelledby
|
|
172
|
+
// keeps it as the input's accessible name for assistive tech regardless.
|
|
173
|
+
aria-labelledby={label ? this.contentId : undefined}
|
|
174
|
+
/>
|
|
175
|
+
|
|
176
|
+
{isAboAccordionExpanded0 && <span className={checkboxClasses}>{checked ? <IconSVG name='common_CheckBlue' className={styles.checkBlueIcon} /> : false}</span>}
|
|
177
|
+
|
|
178
|
+
</label>
|
|
179
|
+
{!!label ? (
|
|
180
|
+
<span id={this.contentId} className={labelContentClasses}>
|
|
181
|
+
{label}
|
|
182
|
+
{tooltipDescription && <Tooltip description={tooltipDescription} positionY={'top'} positionX={tooltipPosX} tooltipClassName={styles.tooltipCheckbox} iconName='InfoTransparent' />}
|
|
183
|
+
</span>
|
|
184
|
+
) : false}
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export default Checkbox;
|
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.127",
|
|
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
|
@@ -5695,11 +5695,29 @@ line-height: 31px;
|
|
|
5695
5695
|
pill, CTA buttons) shifts when .fin-cond collapses to empty on Buy. */
|
|
5696
5696
|
.md3 .fin-calc-card .fin-price-grid.fin-price-grid--cash {
|
|
5697
5697
|
gap: 12px 15px;
|
|
5698
|
+
/* Cross-tab height parity (desktop): with no floor at all, Financing/Leasing's .fin-calc-card
|
|
5699
|
+
naturally measures 372×366.19px (their own real content — .fin-rate-main + the "0 €
|
|
5700
|
+
Anzahlung · 60 Monate · ..." .fin-cond line). Kaufen has no .fin-cond, but its .fin-rate-main
|
|
5701
|
+
is itself taller (the "Festpreis inkl. MwSt." caption, +the struck old list price when the
|
|
5702
|
+
car has a discount) — the two roughly cancel out, landing Kaufen within ~1px of the same
|
|
5703
|
+
366.19px on its own, with nothing reserved here at all. This 1px closes that last gap so all
|
|
5704
|
+
three tabs measure identically. (Earlier passes at 21/27px reserved space Kaufen's .fin-cond
|
|
5705
|
+
row never needed and weren't the actual cause — see .fin-rate-main above.) */
|
|
5706
|
+
grid-template-rows: auto 1px;
|
|
5698
5707
|
}
|
|
5699
5708
|
.md3 .fin-rate-main {
|
|
5700
5709
|
grid-area: rate;
|
|
5701
5710
|
min-width: 0;
|
|
5702
5711
|
}
|
|
5712
|
+
/* Kaufen desktop: reserves the struck-old-price row's height (FinanceCalculator.tsx now always
|
|
5713
|
+
renders this wrapper on Kaufen, content or not — see its own comment) so .fin-rate-main is the
|
|
5714
|
+
SAME height whether this car has a discount or not. 20px — measured value (devtools, the <s>
|
|
5715
|
+
price line's own rendered height), confirmed. Without this, a no-discount Kaufen car's whole
|
|
5716
|
+
.fin-calc-card lands ~20px shorter than Financing/Leasing, breaking the cross-tab height parity
|
|
5717
|
+
the .fin-price-grid--cash rule above (1px) assumes. */
|
|
5718
|
+
.md3 .fin-rate-strike-slot {
|
|
5719
|
+
min-height: 20px;
|
|
5720
|
+
}
|
|
5703
5721
|
/* Kaufen desktop hero price footnote — same small/quiet treatment as the mobile
|
|
5704
5722
|
.fin-rate-cash-mobile sup, instead of inheriting the 48px blue hero size. */
|
|
5705
5723
|
.md3 .fin-rate-main .num.hide-mobile sup {
|
|
@@ -5736,6 +5754,17 @@ line-height: 31px;
|
|
|
5736
5754
|
grid-template-columns: minmax(0, 1fr) 112px;
|
|
5737
5755
|
grid-template-areas: "rate evalcol" "cond cond";
|
|
5738
5756
|
column-gap: 15px;
|
|
5757
|
+
/* Same cross-tab jump as mobile (see the ≤600px rule further down this file, "Request/Call row
|
|
5758
|
+
jump"), mirrored for desktop: on Kaufen, .fin-cond isn't rendered at all (isCash — see
|
|
5759
|
+
FinanceCalculator.tsx), collapsing the "cond" row to 0 instead of a text line, which pulls
|
|
5760
|
+
.fin-adjust/the CTA buttons/.fin-info up compared to Financing/Leasing. A min-height on the
|
|
5761
|
+
whole grid was tried first and reverted — align-content defaults to stretch for grid
|
|
5762
|
+
containers, so the reserved space split across BOTH rows instead of just the missing one,
|
|
5763
|
+
opening a big gap under the price on Kaufen. Reserving the row itself (not the container)
|
|
5764
|
+
targets only the row that actually disappears.
|
|
5765
|
+
21px — measured value (devtools: Leasing 166.19px total − Kaufen 145.19px total), confirmed.
|
|
5766
|
+
(A later bump to 27px, chasing a 6px .fin-calc-card-level residual, was wrong and reverted —
|
|
5767
|
+
that 6px comes from outside .fin-price-grid, see the .fin-adjust rule below.) */
|
|
5739
5768
|
}
|
|
5740
5769
|
.md3 .fin-calc-card .fin-eval-col {
|
|
5741
5770
|
display: flex;
|
|
@@ -656,47 +656,55 @@ const FinanceCalculator: React.FunctionComponent<FinanceCalculatorProps> = ({
|
|
|
656
656
|
</div>
|
|
657
657
|
)}
|
|
658
658
|
{/* desktop: same struck list price ABOVE the rate, same fin-rate-old-list styling +
|
|
659
|
-
clickable UVP badge/tooltip as the mobile sheet's own Kaufen block.
|
|
660
|
-
|
|
659
|
+
clickable UVP badge/tooltip as the mobile sheet's own Kaufen block.
|
|
660
|
+
Wrapper now renders on EVERY Kaufen car (isCash alone, not isCash && showStrike) —
|
|
661
|
+
cross-tab height parity (md3.css) reserves this row's height once, assuming it's
|
|
662
|
+
always there; a car with no discount used to skip it entirely, landing .fin-rate-main
|
|
663
|
+
(and the whole card) ~20px shorter than a discounted one and breaking that parity.
|
|
664
|
+
fin-rate-strike-slot (md3.css) gives it a fixed height so the slot itself doesn't
|
|
665
|
+
move even though the <s> price inside is still conditional on showStrike. */}
|
|
666
|
+
{isCash && (
|
|
661
667
|
<div
|
|
662
|
-
className="hide-mobile"
|
|
663
|
-
style={{ display: 'flex', justifyContent: 'flex-end', lineHeight: 1, marginTop: 0, cursor: 'pointer' }}
|
|
668
|
+
className="hide-mobile fin-rate-strike-slot"
|
|
669
|
+
style={{ display: 'flex', justifyContent: 'flex-end', lineHeight: 1, marginTop: 0, cursor: showStrike ? 'pointer' : 'default' }}
|
|
664
670
|
role="button"
|
|
665
671
|
tabIndex={0}
|
|
666
672
|
onClick={() => setUvpTooltipOpen((v) => !v)}
|
|
667
673
|
>
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
e
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
<
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
e
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
<
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
674
|
+
{showStrike && (
|
|
675
|
+
<s className="num fin-rate-old fin-rate-old-list">
|
|
676
|
+
{eur(listPrice as number)} €
|
|
677
|
+
<span
|
|
678
|
+
className="pp-eyebrow-badge"
|
|
679
|
+
role="button"
|
|
680
|
+
tabIndex={0}
|
|
681
|
+
onClick={(e) => {
|
|
682
|
+
e.stopPropagation();
|
|
683
|
+
setUvpTooltipOpen((v) => !v);
|
|
684
|
+
}}
|
|
685
|
+
>
|
|
686
|
+
2
|
|
687
|
+
</span>
|
|
688
|
+
{uvpTooltipOpen && (
|
|
689
|
+
<div className="pp-uvp-tooltip" onClick={(e) => e.stopPropagation()}>
|
|
690
|
+
<button
|
|
691
|
+
type="button"
|
|
692
|
+
className="pp-uvp-tooltip-close"
|
|
693
|
+
aria-label={t('autobahn:modal.close')}
|
|
694
|
+
onClick={(e) => {
|
|
695
|
+
e.stopPropagation();
|
|
696
|
+
setUvpTooltipOpen(false);
|
|
697
|
+
}}
|
|
698
|
+
>
|
|
699
|
+
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
700
|
+
<path d="M5 5L15 15M15 5L5 15" stroke="white" strokeWidth={2.4} strokeLinecap="round" />
|
|
701
|
+
</svg>
|
|
702
|
+
</button>
|
|
703
|
+
<div className="pp-uvp-tooltip-body">{t('autobahn:card.price.uvpListTooltipBody')}</div>
|
|
704
|
+
</div>
|
|
705
|
+
)}
|
|
706
|
+
</s>
|
|
707
|
+
)}
|
|
700
708
|
</div>
|
|
701
709
|
)}
|
|
702
710
|
<div style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
|