@bytebrand/fe-ui-core-autobahn 1.0.59 → 1.0.60
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/CarRow.tsx +66 -16
- package/cards/cardParts.tsx +37 -14
- package/locales/de/autobahn.json +2 -1
- package/locales/en/autobahn.json +2 -1
- package/package.json +1 -1
- package/theme/md3.css +20 -0
package/cards/CarRow.tsx
CHANGED
|
@@ -31,6 +31,8 @@ import {
|
|
|
31
31
|
OfferBadge,
|
|
32
32
|
PriceTabs,
|
|
33
33
|
PriceHero,
|
|
34
|
+
PricePaths,
|
|
35
|
+
PriceKauf,
|
|
34
36
|
DropChip,
|
|
35
37
|
PercentChip,
|
|
36
38
|
ZeroDownChip,
|
|
@@ -104,7 +106,8 @@ const CarRow: React.FunctionComponent<CarRowProps> = ({ car, isFavorite, onToggl
|
|
|
104
106
|
<div style={{ display: 'flex', flexWrap: 'nowrap', alignItems: 'center', gap: 5 }}>
|
|
105
107
|
{car.isNew && <span className="neu-badge">NEU</span>}
|
|
106
108
|
{car.drop != null ? <DropChip t={t} amount={car.drop} /> : showSave && <SaveChip t={t} amount={car.savings!} />}
|
|
107
|
-
<ZeroDownChip t={t} show={car.zeroDown} compact />
|
|
109
|
+
<ZeroDownChip t={t} show={car.zeroDown} compact className="show-mobile" />
|
|
110
|
+
<ZeroDownChip t={t} show={car.zeroDown} className="hide-mobile" />
|
|
108
111
|
<PercentChip pct={car.dropPct} />
|
|
109
112
|
</div>
|
|
110
113
|
<OfferBadge show={showOfferBadge} t={t} />
|
|
@@ -121,6 +124,14 @@ const CarRow: React.FunctionComponent<CarRowProps> = ({ car, isFavorite, onToggl
|
|
|
121
124
|
>
|
|
122
125
|
<IconButton name="heart" filled active={isFavorite} onClick={onToggleFavorite} label={t('autobahn:card.action.save')} />
|
|
123
126
|
</div>
|
|
127
|
+
{/* desktop: heart sits on the photo itself (Figma "mcard"), not next to the title. */}
|
|
128
|
+
<div
|
|
129
|
+
onClick={(e) => e.stopPropagation()}
|
|
130
|
+
className="hide-mobile"
|
|
131
|
+
style={{ position: 'absolute', top: 10, right: 10, zIndex: 3 }}
|
|
132
|
+
>
|
|
133
|
+
<IconButton name="heart" active={isFavorite} onClick={onToggleFavorite} label={t('autobahn:card.action.save')} />
|
|
134
|
+
</div>
|
|
124
135
|
{/* activity/trust badges — overlaid on the studio-white floor at the bottom of the photo
|
|
125
136
|
itself (PM ref), not a separate divided strip below it. Bottom-right, raised above the
|
|
126
137
|
carousel dots and the photo-count badge (positioning lives in .gallery-badges, md3.css). */}
|
|
@@ -128,17 +139,12 @@ const CarRow: React.FunctionComponent<CarRowProps> = ({ car, isFavorite, onToggl
|
|
|
128
139
|
</div>
|
|
129
140
|
|
|
130
141
|
<div className="listcard-info">
|
|
131
|
-
<div style={{
|
|
132
|
-
<div style={{
|
|
133
|
-
|
|
134
|
-
{car.make} {car.model}
|
|
135
|
-
</div>
|
|
136
|
-
<div className="md-body-sm" style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
|
137
|
-
{car.trim}
|
|
138
|
-
</div>
|
|
142
|
+
<div style={{ minWidth: 0 }}>
|
|
143
|
+
<div className="md-title" style={{ fontSize: 18, lineHeight: 1.2 }}>
|
|
144
|
+
{car.make} {car.model}
|
|
139
145
|
</div>
|
|
140
|
-
<div
|
|
141
|
-
|
|
146
|
+
<div className="md-body-sm" style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
|
147
|
+
{car.trim}
|
|
142
148
|
</div>
|
|
143
149
|
</div>
|
|
144
150
|
<div className="specstrip">
|
|
@@ -160,12 +166,13 @@ const CarRow: React.FunctionComponent<CarRowProps> = ({ car, isFavorite, onToggl
|
|
|
160
166
|
<EcoLine t={t} car={car} />
|
|
161
167
|
</div>
|
|
162
168
|
|
|
163
|
-
{/* price column
|
|
164
|
-
|
|
165
|
-
|
|
169
|
+
{/* price column — mobile keeps the Finanzierung/Leasing/Kaufen tab switcher (ONE hero price
|
|
170
|
+
at a time); desktop shows all three payment paths stacked at once (Figma "mcard"), via
|
|
171
|
+
the previously-unused PricePaths/PriceKauf pair. Both variants share the same
|
|
172
|
+
Preisbewertung meter + CTA, anchored to the bottom (margin-top:auto on .pp-purchase). */}
|
|
166
173
|
<div className="listcard-price">
|
|
167
|
-
<PriceTabs car={car} mode={mode} onModeChange={setMode} t={t} />
|
|
168
|
-
<div className="pp-purchase">
|
|
174
|
+
<PriceTabs car={car} mode={mode} onModeChange={setMode} t={t} className="show-mobile" />
|
|
175
|
+
<div className="pp-purchase show-mobile">
|
|
169
176
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
|
|
170
177
|
<PriceHero car={car} mode={mode} t={t} />
|
|
171
178
|
{car.rating && (
|
|
@@ -204,6 +211,49 @@ const CarRow: React.FunctionComponent<CarRowProps> = ({ car, isFavorite, onToggl
|
|
|
204
211
|
)}
|
|
205
212
|
<PpActivity t={t} car={car} />
|
|
206
213
|
</div>
|
|
214
|
+
|
|
215
|
+
<PricePaths car={car} t={t} className="hide-mobile" />
|
|
216
|
+
<div className="pp-purchase hide-mobile">
|
|
217
|
+
<PriceKauf car={car} t={t} />
|
|
218
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
|
219
|
+
{car.rating && (
|
|
220
|
+
<StatChip tone={ratingTone(car.rating)} icon={ratingTone(car.rating) === 'good' ? 'check' : 'minus'}>
|
|
221
|
+
{t('autobahn:tier.' + car.rating)}
|
|
222
|
+
</StatChip>
|
|
223
|
+
)}
|
|
224
|
+
<div style={{ flex: 1 }}>
|
|
225
|
+
<PriceMeter t={t} car={car} />
|
|
226
|
+
</div>
|
|
227
|
+
</div>
|
|
228
|
+
<div className="pp-cta">
|
|
229
|
+
<Button
|
|
230
|
+
variant="filled"
|
|
231
|
+
block
|
|
232
|
+
iconRight="arrow-right"
|
|
233
|
+
onClick={(e) => {
|
|
234
|
+
e.stopPropagation();
|
|
235
|
+
onOpen(car);
|
|
236
|
+
}}
|
|
237
|
+
>
|
|
238
|
+
{t('autobahn:card.cta.viewOffer')}
|
|
239
|
+
</Button>
|
|
240
|
+
</div>
|
|
241
|
+
{adminAction && (
|
|
242
|
+
<div className="pp-cta">
|
|
243
|
+
<Button
|
|
244
|
+
variant="outlined"
|
|
245
|
+
block
|
|
246
|
+
onClick={(e) => {
|
|
247
|
+
e.stopPropagation();
|
|
248
|
+
adminAction.onClick();
|
|
249
|
+
}}
|
|
250
|
+
>
|
|
251
|
+
{adminAction.label}
|
|
252
|
+
</Button>
|
|
253
|
+
</div>
|
|
254
|
+
)}
|
|
255
|
+
<PpActivity t={t} car={car} />
|
|
256
|
+
</div>
|
|
207
257
|
</div>
|
|
208
258
|
</article>
|
|
209
259
|
);
|
package/cards/cardParts.tsx
CHANGED
|
@@ -234,14 +234,18 @@ export const PercentChip: React.FunctionComponent<{ pct?: number }> = ({ pct })
|
|
|
234
234
|
* there is room (grid cards / wide image) and uses the compact "0 € Anz." on the narrow list-card
|
|
235
235
|
* image, so the badge row always stays on ONE line on both desktop and mobile.
|
|
236
236
|
*/
|
|
237
|
-
export const ZeroDownChip: React.FunctionComponent<{
|
|
238
|
-
show
|
|
239
|
-
compact
|
|
240
|
-
t
|
|
241
|
-
|
|
237
|
+
export const ZeroDownChip: React.FunctionComponent<{
|
|
238
|
+
show?: boolean;
|
|
239
|
+
compact?: boolean;
|
|
240
|
+
t: TranslationFunction;
|
|
241
|
+
className?: string;
|
|
242
|
+
}> = ({ show, compact, t, className }) => {
|
|
242
243
|
if (!show) return null;
|
|
243
244
|
return (
|
|
244
|
-
<span
|
|
245
|
+
<span
|
|
246
|
+
className={className}
|
|
247
|
+
style={{ ...dealPill, background: 'var(--md-accent-container)', color: 'var(--md-on-accent-container)' }}
|
|
248
|
+
>
|
|
245
249
|
{compact ? t('autobahn:card.price.zeroDownCompact') : t('autobahn:card.price.zeroDown')}
|
|
246
250
|
</span>
|
|
247
251
|
);
|
|
@@ -341,6 +345,8 @@ export const EcoLine: React.FunctionComponent<EcoLineProps> = ({ car, style, t }
|
|
|
341
345
|
export interface PricePathsProps {
|
|
342
346
|
car: AutobahnCar;
|
|
343
347
|
t: TranslationFunction;
|
|
348
|
+
/** extra class appended to the root (e.g. the SRP card's desktop-only `hide-mobile` marker). */
|
|
349
|
+
className?: string;
|
|
344
350
|
}
|
|
345
351
|
|
|
346
352
|
/** Map the AutobahnCar rating tone → meter segment index (0 best … 4 worst). */
|
|
@@ -379,6 +385,8 @@ export interface PriceTabsProps {
|
|
|
379
385
|
mode: PaymentMode;
|
|
380
386
|
onModeChange: (mode: PaymentMode) => void;
|
|
381
387
|
t: TranslationFunction;
|
|
388
|
+
/** extra class appended to the root (e.g. the SRP card's mobile-only `show-mobile` marker). */
|
|
389
|
+
className?: string;
|
|
382
390
|
}
|
|
383
391
|
|
|
384
392
|
/**
|
|
@@ -387,14 +395,14 @@ export interface PriceTabsProps {
|
|
|
387
395
|
* identical to the VDP's, not a second tab style. Leasing tab omitted when the car isn't
|
|
388
396
|
* leasing-eligible (car.lease undefined) — same rule PricePaths already used.
|
|
389
397
|
*/
|
|
390
|
-
export const PriceTabs: React.FunctionComponent<PriceTabsProps> = ({ car, mode, onModeChange, t }) => {
|
|
398
|
+
export const PriceTabs: React.FunctionComponent<PriceTabsProps> = ({ car, mode, onModeChange, t, className }) => {
|
|
391
399
|
const tabs: Array<[PaymentMode, string]> = [
|
|
392
400
|
['financing', t('autobahn:finance.mode.financing')],
|
|
393
401
|
...(car.lease != null ? ([['leasing', t('autobahn:finance.mode.leasing')]] as Array<[PaymentMode, string]>) : []),
|
|
394
402
|
['buy', t('autobahn:card.price.buy')],
|
|
395
403
|
];
|
|
396
404
|
return (
|
|
397
|
-
<div className=
|
|
405
|
+
<div className={'fin-tabs' + (className ? ' ' + className : '')} role="tablist" onClick={(e) => e.stopPropagation()}>
|
|
398
406
|
{tabs.map(([m, label]) => (
|
|
399
407
|
<button
|
|
400
408
|
key={m}
|
|
@@ -589,7 +597,7 @@ const PpRow: React.FunctionComponent<PpRowProps> = ({ label, value, unit, old, f
|
|
|
589
597
|
* is omitted entirely when leasing isn't active. The Kaufen row is rendered separately
|
|
590
598
|
* (PriceKauf) below a divider so only the financing rate stands out — no wall of blue prices.
|
|
591
599
|
*/
|
|
592
|
-
export const PricePaths: React.FunctionComponent<PricePathsProps> = ({ car, t }) => {
|
|
600
|
+
export const PricePaths: React.FunctionComponent<PricePathsProps> = ({ car, t, className }) => {
|
|
593
601
|
const hasLease = car.lease != null;
|
|
594
602
|
// Terms of the config the displayed rate was actually computed with (per-car:
|
|
595
603
|
// dealer rate + the user's saved localStorage adjustments) — default config
|
|
@@ -601,20 +609,34 @@ export const PricePaths: React.FunctionComponent<PricePathsProps> = ({ car, t })
|
|
|
601
609
|
const leasMonths = car.leaseMonths ?? _leasPeriod;
|
|
602
610
|
const hasLeasTerms = Boolean(leasMileage && leasMonths);
|
|
603
611
|
const leasMileageStr = leasMileage ? Number(leasMileage).toLocaleString('de-DE') : '';
|
|
612
|
+
// same real struck-through reference rate PriceHero shows for the financing tab (oldMonthly) —
|
|
613
|
+
// shown here too now that both rows render at once instead of behind a tab.
|
|
614
|
+
const showOldMonthly = car.oldMonthly != null && car.oldMonthly > car.monthly;
|
|
604
615
|
return (
|
|
605
|
-
<div className=
|
|
616
|
+
<div className={'pricepaths' + (className ? ' ' + className : '')}>
|
|
606
617
|
<div className="pp-hero">
|
|
607
618
|
<div className="pp-eyebrow">{t('autobahn:card.price.financingFrom')}</div>
|
|
608
619
|
<div className="pp-hero-rate">
|
|
609
|
-
|
|
610
|
-
|
|
620
|
+
{showOldMonthly && (
|
|
621
|
+
<span className="pp-hero-old">
|
|
622
|
+
{(car.oldMonthly as number).toLocaleString('de-DE')} €<sup>2</sup>
|
|
623
|
+
</span>
|
|
624
|
+
)}
|
|
625
|
+
<b className="num pp-hero-val">
|
|
626
|
+
{fmtRate(car.monthly)} €<sup>1</sup>
|
|
627
|
+
</b>
|
|
628
|
+
<span className="pp-hero-unit">
|
|
629
|
+
{t('autobahn:card.price.perMonth')}
|
|
630
|
+
<sup>3</sup>
|
|
631
|
+
</span>
|
|
611
632
|
</div>
|
|
612
633
|
<div className="pp-hero-terms">
|
|
613
634
|
{t('autobahn:card.price.financeTerms', {
|
|
614
635
|
firstInstallment: finFirstInstallment,
|
|
615
636
|
months: finMonths,
|
|
616
637
|
count: finMonths,
|
|
617
|
-
})}
|
|
638
|
+
})}{' '}
|
|
639
|
+
· <span className="pp-details-link">{t('autobahn:card.price.details')}</span>
|
|
618
640
|
</div>
|
|
619
641
|
</div>
|
|
620
642
|
{hasLease && (
|
|
@@ -622,7 +644,8 @@ export const PricePaths: React.FunctionComponent<PricePathsProps> = ({ car, t })
|
|
|
622
644
|
<PpRow label={t('autobahn:card.price.leasing')} value={car.lease as number} unit="mtl." />
|
|
623
645
|
{hasLeasTerms && (
|
|
624
646
|
<div className="pp-sub-terms">
|
|
625
|
-
{t('autobahn:card.price.leaseTerms', { mileage: leasMileageStr, months: leasMonths })}
|
|
647
|
+
{t('autobahn:card.price.leaseTerms', { mileage: leasMileageStr, months: leasMonths })}{' '}
|
|
648
|
+
· <span className="pp-details-link">{t('autobahn:card.price.details')}</span>
|
|
626
649
|
</div>
|
|
627
650
|
)}
|
|
628
651
|
</div>
|
package/locales/de/autobahn.json
CHANGED
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"perMonth": "/mtl.",
|
|
23
23
|
"leasing": "Leasing",
|
|
24
24
|
"leasingFrom": "Leasing ab",
|
|
25
|
-
"buy": "Kaufen"
|
|
25
|
+
"buy": "Kaufen",
|
|
26
|
+
"details": "Details"
|
|
26
27
|
},
|
|
27
28
|
"eco": {
|
|
28
29
|
"phevConsumptionLine": "+ {{consFuel}} (gew., komb.) · {{consDis}} (entl.) · CO₂ {{co2w}} · Klasse {{co2classW}} (gew.) / {{co2classD}} (entl.)*",
|
package/locales/en/autobahn.json
CHANGED
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"perMonth": "/mo.",
|
|
23
23
|
"leasing": "Leasing",
|
|
24
24
|
"leasingFrom": "Leasing from",
|
|
25
|
-
"buy": "Buy"
|
|
25
|
+
"buy": "Buy",
|
|
26
|
+
"details": "Details"
|
|
26
27
|
},
|
|
27
28
|
"eco": {
|
|
28
29
|
"phevConsumptionLine": "+ {{consFuel}} (weighted, combined) · {{consDis}} (discharged) · CO₂ {{co2w}} · Class {{co2classW}} (weighted) / {{co2classD}} (discharged)*",
|
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.60",
|
|
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
|
@@ -3962,6 +3962,23 @@ line-height: 31px;
|
|
|
3962
3962
|
background: var(--md-sc-low);
|
|
3963
3963
|
border-radius: var(--md-r-md);
|
|
3964
3964
|
}
|
|
3965
|
+
/* desktop only (mobile keeps the flex-wrap flow above, untouched): a clean 2-column grid so
|
|
3966
|
+
pairs line up (km|EZ, Kraftstoff|Getriebe, Leistung|Halter) instead of reflowing by text
|
|
3967
|
+
length (Figma "mcard"). Facts render in DOM order [km, EZ, Getriebe, Kraftstoff, Leistung,
|
|
3968
|
+
Halter, Verfügbarkeit]; only Getriebe/Kraftstoff swap columns via `order` to land as
|
|
3969
|
+
Kraftstoff|Getriebe per the mockup — the array itself stays untouched (mobile relies on it). */
|
|
3970
|
+
@media (min-width: 601px) {
|
|
3971
|
+
.md3 .specstrip {
|
|
3972
|
+
display: grid;
|
|
3973
|
+
grid-template-columns: 1fr 1fr;
|
|
3974
|
+
}
|
|
3975
|
+
/* km/EZ/Kraftstoff pulled negative (in that order) so they sort ahead of the untouched,
|
|
3976
|
+
default-order Getriebe/Leistung/Halter/Verfügbarkeit — net effect: km, EZ, Kraftstoff,
|
|
3977
|
+
Getriebe, Leistung, Halter, Verfügbarkeit, i.e. only Kraftstoff hops in front of Getriebe. */
|
|
3978
|
+
.md3 .specstrip > span:nth-child(1) { order: -3; }
|
|
3979
|
+
.md3 .specstrip > span:nth-child(2) { order: -2; }
|
|
3980
|
+
.md3 .specstrip > span:nth-child(4) { order: -1; }
|
|
3981
|
+
}
|
|
3965
3982
|
/* filter rail: collapsible sections */
|
|
3966
3983
|
.md3 .frail-group {
|
|
3967
3984
|
border-bottom: 1px solid var(--md-outline-variant);
|
|
@@ -4199,6 +4216,9 @@ line-height: 31px;
|
|
|
4199
4216
|
.md3 .pp-hero-old { font-size: 15px; color: var(--md-error); text-decoration: line-through; white-space: nowrap; }
|
|
4200
4217
|
.md3 .pp-hero-val sup, .md3 .pp-hero-old sup, .md3 .pp-hero-unit sup { font-size: .6em; }
|
|
4201
4218
|
.md3 .pp-hero-terms { margin-top: 9px; font-size: 11.5px; color: var(--md-on-surface-variant); }
|
|
4219
|
+
/* "Details" link at the end of the financing/leasing terms lines (Figma "mcard") — quiet, same
|
|
4220
|
+
color as the rest of the terms line but underlined so it still reads as tappable. */
|
|
4221
|
+
.md3 .pp-details-link { color: var(--md-primary); text-decoration: underline; font-weight: 600; white-space: nowrap; }
|
|
4202
4222
|
/* separator line between the financing block and the leasing row (legacy-design parity) */
|
|
4203
4223
|
.md3 .pp-lease-row {
|
|
4204
4224
|
margin-top: 16px;
|