@bytebrand/fe-ui-core-autobahn 1.0.82 → 1.0.83
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 +9 -1
- package/cards/CarRow.tsx +31 -3
- package/cards/cardParts.tsx +23 -3
- package/package.json +1 -1
- package/theme/md3.css +12 -1
- package/vdp/VdpModules.tsx +6 -4
package/cards/CarCard.tsx
CHANGED
|
@@ -102,6 +102,14 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
|
|
|
102
102
|
const [viewerOpen, setViewerOpen] = useState(false);
|
|
103
103
|
const showSave = car.savings != null && car.savings > 600;
|
|
104
104
|
const showOfferBadge = !!(car.isNew || car.drop != null || showSave || car.dropPct != null || car.zeroDown);
|
|
105
|
+
// PM call: the badge row only fits 3 pills — when Drop/Save + Percent + ZeroDown are all
|
|
106
|
+
// active that's already 3, so NEU is dropped rather than causing a 4th to overflow.
|
|
107
|
+
const activeDealChips = [
|
|
108
|
+
car.drop != null || showSave,
|
|
109
|
+
car.dropPct != null && car.dropPct > 0,
|
|
110
|
+
!!car.zeroDown,
|
|
111
|
+
].filter(Boolean).length;
|
|
112
|
+
const showNeu = car.isNew && activeDealChips < 3;
|
|
105
113
|
return (
|
|
106
114
|
<article
|
|
107
115
|
className="mcard mcard--interactive cc-card"
|
|
@@ -162,7 +170,7 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
|
|
|
162
170
|
color: '#005CFF',
|
|
163
171
|
}}
|
|
164
172
|
/>
|
|
165
|
-
{
|
|
173
|
+
{showNeu && <span className="neu-badge">NEU</span>}
|
|
166
174
|
</div>
|
|
167
175
|
<OfferBadge show={showOfferBadge} t={t} />
|
|
168
176
|
</div>
|
package/cards/CarRow.tsx
CHANGED
|
@@ -60,13 +60,36 @@ export interface CarRowProps {
|
|
|
60
60
|
* Rendered as a quiet button under the CTA; the page decides visibility (JWT claim).
|
|
61
61
|
*/
|
|
62
62
|
adminAction?: { label: string; onClick: () => void };
|
|
63
|
+
/**
|
|
64
|
+
* takes over the desktop price column's "Details" links (financing/leasing terms rows) — e.g.
|
|
65
|
+
* navigate to this car's own VDP and open its finance modal already expanded, on the tab the
|
|
66
|
+
* link was under. Optional/page-owned like CarCard's identically-named prop; when omitted the
|
|
67
|
+
* links render as plain text (see PricePaths' own fallback).
|
|
68
|
+
*/
|
|
69
|
+
onShowFinancingDetails?: (car: AutobahnCar, mode: PaymentMode) => void;
|
|
63
70
|
}
|
|
64
71
|
|
|
65
|
-
const CarRow: React.FunctionComponent<CarRowProps> = ({
|
|
72
|
+
const CarRow: React.FunctionComponent<CarRowProps> = ({
|
|
73
|
+
car,
|
|
74
|
+
isFavorite,
|
|
75
|
+
onToggleFavorite,
|
|
76
|
+
onOpen,
|
|
77
|
+
onMediaEnter,
|
|
78
|
+
t,
|
|
79
|
+
adminAction,
|
|
80
|
+
onShowFinancingDetails,
|
|
81
|
+
}) => {
|
|
66
82
|
const [mode, setMode] = useState<PaymentMode>('financing');
|
|
67
83
|
const [photoIndex, setPhotoIndex] = useState(0);
|
|
68
84
|
const showSave = car.savings != null && car.savings > 600;
|
|
69
85
|
const showOfferBadge = !!(car.isNew || car.drop != null || showSave || car.dropPct != null || car.zeroDown);
|
|
86
|
+
// same overflow rule as CarCard: the row only fits 3 pills, so NEU drops when the other 3 are all active.
|
|
87
|
+
const activeDealChips = [
|
|
88
|
+
car.drop != null || showSave,
|
|
89
|
+
car.dropPct != null && car.dropPct > 0,
|
|
90
|
+
!!car.zeroDown,
|
|
91
|
+
].filter(Boolean).length;
|
|
92
|
+
const showNeu = car.isNew && activeDealChips < 3;
|
|
70
93
|
const facts: Array<[icon: string, value: string]> = [
|
|
71
94
|
['gauge', fmtKm(car.km)],
|
|
72
95
|
['calendar', t('autobahn:card.specs.firstRegistration', { reg: car.reg })],
|
|
@@ -106,7 +129,7 @@ const CarRow: React.FunctionComponent<CarRowProps> = ({ car, isFavorite, onToggl
|
|
|
106
129
|
}}
|
|
107
130
|
>
|
|
108
131
|
<div style={{ display: 'flex', flexWrap: 'nowrap', alignItems: 'center', gap: 5 }}>
|
|
109
|
-
{
|
|
132
|
+
{showNeu && <span className="neu-badge">NEU</span>}
|
|
110
133
|
{car.drop != null ? <DropChip t={t} amount={car.drop} /> : showSave && <SaveChip t={t} amount={car.savings!} />}
|
|
111
134
|
<ZeroDownChip t={t} show={car.zeroDown} compact className="show-mobile" />
|
|
112
135
|
<ZeroDownChip
|
|
@@ -259,7 +282,12 @@ const CarRow: React.FunctionComponent<CarRowProps> = ({ car, isFavorite, onToggl
|
|
|
259
282
|
<PpActivity t={t} car={car} />
|
|
260
283
|
</div>
|
|
261
284
|
|
|
262
|
-
<PricePaths
|
|
285
|
+
<PricePaths
|
|
286
|
+
car={car}
|
|
287
|
+
t={t}
|
|
288
|
+
className="hide-mobile"
|
|
289
|
+
onShowDetails={onShowFinancingDetails ? (mode) => onShowFinancingDetails(car, mode) : undefined}
|
|
290
|
+
/>
|
|
263
291
|
<div className="pp-purchase hide-mobile">
|
|
264
292
|
<PriceKauf car={car} t={t} />
|
|
265
293
|
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
package/cards/cardParts.tsx
CHANGED
|
@@ -386,6 +386,11 @@ export interface PricePathsProps {
|
|
|
386
386
|
t: TranslationFunction;
|
|
387
387
|
/** extra class appended to the root (e.g. the SRP card's desktop-only `hide-mobile` marker). */
|
|
388
388
|
className?: string;
|
|
389
|
+
/** takes over the financing/leasing terms rows' own "Details" links — e.g. navigate to this
|
|
390
|
+
* car's VDP and open its finance modal already expanded, on the tab the link was under
|
|
391
|
+
* (financing/leasing). Optional/page-owned like CarCard's onShowFinancingDetails; when
|
|
392
|
+
* omitted the links render as plain (non-interactive) text, same as before this prop existed. */
|
|
393
|
+
onShowDetails?: (mode: PaymentMode) => void;
|
|
389
394
|
}
|
|
390
395
|
|
|
391
396
|
/** Map the AutobahnCar rating tone → meter segment index (0 best … 4 worst). */
|
|
@@ -926,7 +931,7 @@ const PpRow: React.FunctionComponent<PpRowProps> = ({ label, value, unit, old, f
|
|
|
926
931
|
* is omitted entirely when leasing isn't active. The Kaufen row is rendered separately
|
|
927
932
|
* (PriceKauf) below a divider so only the financing rate stands out — no wall of blue prices.
|
|
928
933
|
*/
|
|
929
|
-
export const PricePaths: React.FunctionComponent<PricePathsProps> = ({ car, t, className }) => {
|
|
934
|
+
export const PricePaths: React.FunctionComponent<PricePathsProps> = ({ car, t, className, onShowDetails }) => {
|
|
930
935
|
const hasLease = car.lease != null;
|
|
931
936
|
// Terms of the config the displayed rate was actually computed with (per-car:
|
|
932
937
|
// dealer rate + the user's saved localStorage adjustments) — default config
|
|
@@ -938,6 +943,21 @@ export const PricePaths: React.FunctionComponent<PricePathsProps> = ({ car, t, c
|
|
|
938
943
|
const leasMonths = car.leaseMonths ?? _leasPeriod;
|
|
939
944
|
const hasLeasTerms = Boolean(leasMileage && leasMonths);
|
|
940
945
|
const leasMileageStr = leasMileage ? Number(leasMileage).toLocaleString('de-DE') : '';
|
|
946
|
+
const detailsLink = (mode: PaymentMode): React.ReactNode =>
|
|
947
|
+
onShowDetails ? (
|
|
948
|
+
<button
|
|
949
|
+
type="button"
|
|
950
|
+
className="pp-details-link"
|
|
951
|
+
onClick={(e) => {
|
|
952
|
+
e.stopPropagation();
|
|
953
|
+
onShowDetails(mode);
|
|
954
|
+
}}
|
|
955
|
+
>
|
|
956
|
+
{t('autobahn:card.price.details')}
|
|
957
|
+
</button>
|
|
958
|
+
) : (
|
|
959
|
+
<span className="pp-details-link">{t('autobahn:card.price.details')}</span>
|
|
960
|
+
);
|
|
941
961
|
return (
|
|
942
962
|
<div className={'pricepaths' + (className ? ' ' + className : '')}>
|
|
943
963
|
<div className="pp-eyebrow">{t('autobahn:card.price.financingFrom')}</div>
|
|
@@ -952,7 +972,7 @@ export const PricePaths: React.FunctionComponent<PricePathsProps> = ({ car, t, c
|
|
|
952
972
|
months: finMonths,
|
|
953
973
|
count: finMonths,
|
|
954
974
|
})}{' '}
|
|
955
|
-
·
|
|
975
|
+
· {detailsLink('financing')}
|
|
956
976
|
</div>
|
|
957
977
|
{hasLease && (
|
|
958
978
|
<div className="pp-lease-row">
|
|
@@ -967,7 +987,7 @@ export const PricePaths: React.FunctionComponent<PricePathsProps> = ({ car, t, c
|
|
|
967
987
|
months: leasMonths,
|
|
968
988
|
mileage: leasMileageStr,
|
|
969
989
|
})}{' '}
|
|
970
|
-
·
|
|
990
|
+
· {detailsLink('leasing')}
|
|
971
991
|
</div>
|
|
972
992
|
)}
|
|
973
993
|
</div>
|
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.83",
|
|
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
|
@@ -4447,7 +4447,18 @@ line-height: 31px;
|
|
|
4447
4447
|
.md3 .listcard-price .pricepaths .pp-hero-terms { font-size: 10.5px; }
|
|
4448
4448
|
/* "Details" link at the end of the financing/leasing terms lines (Figma "mcard") — quiet, same
|
|
4449
4449
|
color as the rest of the terms line but underlined so it still reads as tappable. */
|
|
4450
|
-
.md3 .pp-details-link {
|
|
4450
|
+
.md3 .pp-details-link {
|
|
4451
|
+
border: 0;
|
|
4452
|
+
background: none;
|
|
4453
|
+
padding: 0;
|
|
4454
|
+
margin: 0;
|
|
4455
|
+
font: inherit;
|
|
4456
|
+
color: var(--md-primary);
|
|
4457
|
+
text-decoration: underline;
|
|
4458
|
+
font-weight: 600;
|
|
4459
|
+
white-space: nowrap;
|
|
4460
|
+
cursor: pointer;
|
|
4461
|
+
}
|
|
4451
4462
|
/* z.B./old-rate/unit column beside the CarCard hero figure — mirrors .fin-rate-unit
|
|
4452
4463
|
(FinanceCalculator.tsx/md3.css) exactly; only used inside .cc-price's PriceHero. */
|
|
4453
4464
|
.md3 .pp-hero-unit-group {
|
package/vdp/VdpModules.tsx
CHANGED
|
@@ -1967,8 +1967,10 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
|
|
|
1967
1967
|
const [uvpTooltipOpen, setUvpTooltipOpen] = React.useState(false);
|
|
1968
1968
|
// opened directly via the "Finanzierungsdetails" link (initialShowBreakdown) — on mobile the
|
|
1969
1969
|
// breakdown is still gated behind its own toggle, so scroll the sheet down to reveal it instead
|
|
1970
|
-
// of leaving the user at the top. Desktop always shows the breakdown inline
|
|
1971
|
-
//
|
|
1970
|
+
// of leaving the user at the top. Desktop always shows the breakdown inline (no toggle), but it
|
|
1971
|
+
// can still sit below the fold inside .fin-modal-body's own scroll area, so it gets the same
|
|
1972
|
+
// treatment — whichever of the two refs is actually visible (show-mobile/hide-mobile via CSS)
|
|
1973
|
+
// is the one scrolled.
|
|
1972
1974
|
const desktopBreakdownRef = React.useRef<HTMLDivElement | null>(null);
|
|
1973
1975
|
const mobileBreakdownRef = React.useRef<HTMLDivElement | null>(null);
|
|
1974
1976
|
React.useEffect(() => {
|
|
@@ -1976,8 +1978,8 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
|
|
|
1976
1978
|
setShowBreakdown(!!initialShowBreakdown);
|
|
1977
1979
|
if (!initialShowBreakdown) return;
|
|
1978
1980
|
const timer = window.setTimeout(() => {
|
|
1979
|
-
const target = mobileBreakdownRef.current;
|
|
1980
|
-
if (target
|
|
1981
|
+
const target = [mobileBreakdownRef.current, desktopBreakdownRef.current].find((el) => el && el.offsetParent !== null);
|
|
1982
|
+
if (target) target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
1981
1983
|
}, 50);
|
|
1982
1984
|
return () => window.clearTimeout(timer);
|
|
1983
1985
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|