@bytebrand/fe-ui-core-autobahn 1.0.58 → 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/chrome/TopBar.tsx +60 -56
- package/locales/de/autobahn.json +2 -1
- package/locales/en/autobahn.json +2 -1
- package/package.json +1 -1
- package/theme/md3.css +62 -9
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/chrome/TopBar.tsx
CHANGED
|
@@ -466,8 +466,8 @@ const TopBar: React.FunctionComponent<TopBarProps> = ({
|
|
|
466
466
|
}}
|
|
467
467
|
>
|
|
468
468
|
<div
|
|
469
|
-
className="container"
|
|
470
|
-
style={{ height: 64, display: 'flex', alignItems: 'center', gap: 16
|
|
469
|
+
className="container topbar-container"
|
|
470
|
+
style={{ height: 64, display: 'flex', alignItems: 'center', gap: 16 }}
|
|
471
471
|
>
|
|
472
472
|
{/* mobile hamburger */}
|
|
473
473
|
<button
|
|
@@ -481,7 +481,7 @@ const TopBar: React.FunctionComponent<TopBarProps> = ({
|
|
|
481
481
|
|
|
482
482
|
{/* full navigation — '/' belongs to WordPress behind traefik, not the SPA (see HOME_PATH). */}
|
|
483
483
|
<a href={HOME_PATH} style={{ flex: 'none', display: 'inline-flex' }}>
|
|
484
|
-
<img src={logoUrl} height="32" style={{ cursor: 'pointer', display: 'block' }} alt="auto.de" />
|
|
484
|
+
<img src={logoUrl} height="32" className="topbar-logo" style={{ cursor: 'pointer', display: 'block' }} alt="auto.de" />
|
|
485
485
|
</a>
|
|
486
486
|
|
|
487
487
|
{renderNav('hide-mobile', {
|
|
@@ -493,64 +493,68 @@ const TopBar: React.FunctionComponent<TopBarProps> = ({
|
|
|
493
493
|
|
|
494
494
|
<div style={{ flex: 1 }} />
|
|
495
495
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
<
|
|
500
|
-
<IconButton name="
|
|
501
|
-
{
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
</span>
|
|
505
|
-
{/* Suchaufträge → /my-searches (saved searches list) — desktop-only; no Figma mobile
|
|
506
|
-
header variant includes a bell icon. */}
|
|
507
|
-
<span className="hide-mobile" style={{ position: 'relative', display: 'inline-flex' }}>
|
|
508
|
-
<IconButton name="bell" label={t('autobahn:chrome.savedSearches')} onClick={() => goRoute(R_SAVED_SEARCHES)} />
|
|
509
|
-
{notificationCount > 0 && (
|
|
510
|
-
<span style={badgeStyle}>{notificationCount}</span>
|
|
511
|
-
)}
|
|
512
|
-
</span>
|
|
513
|
-
|
|
514
|
-
{/* Mobile-only reduced icon set — which ONE extra icon (besides search+account, both
|
|
515
|
-
always shown) appears depends on `variant`, per the 3 Figma mobile header specs. */}
|
|
516
|
-
{variant === 'browsing' && (
|
|
517
|
-
<span className="show-mobile" style={{ position: 'relative', display: 'inline-flex' }}>
|
|
496
|
+
{/* Action icons cluster — its own (smaller) gap, separate from the outer 16px gap between
|
|
497
|
+
hamburger/logo/nav: the Header-System spec calls for ~2-6px between actions but keeps
|
|
498
|
+
the wider spacing everywhere else. */}
|
|
499
|
+
<div className="topbar-actions" style={{ display: 'flex', alignItems: 'center' }}>
|
|
500
|
+
<IconButton name="search" label={t('autobahn:chrome.search')} onClick={goSearch} />
|
|
501
|
+
{/* Merkliste → /favourites with a real count badge — desktop-only now; the mobile row
|
|
502
|
+
below shows it ONLY for the 'browsing' variant instead. */}
|
|
503
|
+
<span className="hide-mobile" style={{ position: 'relative', display: 'inline-flex' }}>
|
|
518
504
|
<IconButton name="heart" label={t('autobahn:chrome.favorites')} onClick={() => goRoute(R_FAVOURITES)} />
|
|
519
|
-
{favoritesCount > 0 &&
|
|
505
|
+
{favoritesCount > 0 && (
|
|
506
|
+
<span style={badgeStyle}>{favoritesCount}</span>
|
|
507
|
+
)}
|
|
520
508
|
</span>
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
<span className="
|
|
524
|
-
<IconButton name="
|
|
509
|
+
{/* Suchaufträge → /my-searches (saved searches list) — desktop-only; no Figma mobile
|
|
510
|
+
header variant includes a bell icon. */}
|
|
511
|
+
<span className="hide-mobile" style={{ position: 'relative', display: 'inline-flex' }}>
|
|
512
|
+
<IconButton name="bell" label={t('autobahn:chrome.savedSearches')} onClick={() => goRoute(R_SAVED_SEARCHES)} />
|
|
513
|
+
{notificationCount > 0 && (
|
|
514
|
+
<span style={badgeStyle}>{notificationCount}</span>
|
|
515
|
+
)}
|
|
525
516
|
</span>
|
|
526
|
-
)}
|
|
527
517
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
<
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
518
|
+
{/* Mobile-only reduced icon set — which ONE extra icon (besides search+account, both
|
|
519
|
+
always shown) appears depends on `variant`, per the 3 Figma mobile header specs. */}
|
|
520
|
+
{variant === 'browsing' && (
|
|
521
|
+
<span className="show-mobile" style={{ position: 'relative', display: 'inline-flex' }}>
|
|
522
|
+
<IconButton name="heart" label={t('autobahn:chrome.favorites')} onClick={() => goRoute(R_FAVOURITES)} />
|
|
523
|
+
{favoritesCount > 0 && <span style={badgeStyle}>{favoritesCount}</span>}
|
|
524
|
+
</span>
|
|
525
|
+
)}
|
|
526
|
+
{variant === 'conversion' && (
|
|
527
|
+
<span className="show-mobile" style={{ position: 'relative', display: 'inline-flex' }}>
|
|
528
|
+
<IconButton name="phone" label={t('autobahn:chrome.callUs')} onClick={() => goNav({ href: phoneHref })} />
|
|
529
|
+
</span>
|
|
530
|
+
)}
|
|
535
531
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
<
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
532
|
+
{/* language segmented control — hidden ≤600px (the account menu carries language rows). */}
|
|
533
|
+
{onLangSelect && (
|
|
534
|
+
<div className="lang-seg hide-mobile" aria-label={t('autobahn:chrome.switchLanguage')}>
|
|
535
|
+
<button type="button" className={'lang-seg-btn' + (language !== 'en' ? ' on' : '')} onClick={() => onLangSelect('de')}>DE</button>
|
|
536
|
+
<button type="button" className={'lang-seg-btn' + (language === 'en' ? ' on' : '')} onClick={() => onLangSelect('en')}>EN</button>
|
|
537
|
+
</div>
|
|
538
|
+
)}
|
|
539
|
+
|
|
540
|
+
{/* Konto trigger + account layer — visible at EVERY width: this menu is the only place
|
|
541
|
+
sign-out / settings / dealer switching exist, so it must never be display:none'd (the
|
|
542
|
+
pre-cutover hide-tablet left 601–980px with no logout at all). Desktop keeps the
|
|
543
|
+
tonal "Konto" pill; ≤600px it collapses to a plain icon (topbar-account-btn CSS) and
|
|
544
|
+
opens the full-width mobile Account Layer instead of the small popover. */}
|
|
545
|
+
<span style={{ position: 'relative', display: 'inline-flex' }}>
|
|
546
|
+
<button
|
|
547
|
+
className="mbtn mbtn--tonal mbtn--sm topbar-account-btn"
|
|
548
|
+
onClick={() => setAccount((a) => !a)}
|
|
549
|
+
aria-expanded={account}
|
|
550
|
+
aria-label={t('autobahn:chrome.account')}
|
|
551
|
+
>
|
|
552
|
+
<Icon name="user" size={18} />
|
|
553
|
+
<span className="hide-mobile">{t('autobahn:chrome.account')}</span>
|
|
554
|
+
</button>
|
|
555
|
+
{account && renderAccountMenu()}
|
|
556
|
+
</span>
|
|
557
|
+
</div>
|
|
554
558
|
</div>
|
|
555
559
|
|
|
556
560
|
{account && renderMobileAccountLayer()}
|
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;
|
|
@@ -5103,6 +5123,16 @@ line-height: 31px;
|
|
|
5103
5123
|
padding: 8px 16px 16px;
|
|
5104
5124
|
}
|
|
5105
5125
|
|
|
5126
|
+
/* Base (desktop) values for the two TopBar layout knobs the mobile Header-System spec overrides
|
|
5127
|
+
below — kept as real CSS rules (not inline styles) since an inline style on the element would
|
|
5128
|
+
always beat the media-query override regardless of specificity. */
|
|
5129
|
+
.md3 .topbar-container {
|
|
5130
|
+
padding: 0 16px;
|
|
5131
|
+
}
|
|
5132
|
+
.md3 .topbar-actions {
|
|
5133
|
+
gap: 16px;
|
|
5134
|
+
}
|
|
5135
|
+
|
|
5106
5136
|
/* mobile-only "Account Layer" sheet (TopBar) — full-width panel under the header, per the Figma
|
|
5107
5137
|
"03 | Account Layer" mockups. .show-mobile forces display:flex (see the ≤600px override
|
|
5108
5138
|
below) — flex-direction:column comes from the inline style on the element itself. */
|
|
@@ -5131,30 +5161,53 @@ line-height: 31px;
|
|
|
5131
5161
|
/* desktop "Konto" pill collapses to a plain icon-only button on mobile — the account layer above
|
|
5132
5162
|
replaces the small popover the pill normally opens on wider screens. */
|
|
5133
5163
|
@media (max-width: 600px) {
|
|
5164
|
+
/* "Header-System" spec dimensions (390px reference) — all scoped to the TopBar's own <header>
|
|
5165
|
+
(or a dedicated class) so none of this touches .icon-btn/.mbtn/.container usage elsewhere in
|
|
5166
|
+
the app (VDP gallery controls, financing modal, other pages' own containers, etc.). */
|
|
5134
5167
|
.md3 .topbar-account-btn {
|
|
5135
5168
|
background: transparent;
|
|
5136
5169
|
border: 0;
|
|
5137
5170
|
padding: 0;
|
|
5138
|
-
|
|
5139
|
-
|
|
5171
|
+
/* Touch target 48×48 — WCAG 2.2 minimum is 24×24, but the spec recommends 48×48 consistently
|
|
5172
|
+
for auto.de given the broad age range and high mobile share. */
|
|
5173
|
+
width: 48px;
|
|
5174
|
+
height: 48px;
|
|
5140
5175
|
min-width: 0;
|
|
5141
5176
|
border-radius: 50%;
|
|
5142
5177
|
}
|
|
5143
5178
|
.md3 .topbar-account-btn:hover {
|
|
5144
5179
|
background: color-mix(in srgb, var(--md-on-surface) 8%, transparent);
|
|
5145
5180
|
}
|
|
5146
|
-
/*
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5181
|
+
/* Visible icon glyphs 24-28px (spec range) — the shared .icon-btn/.mbtn rules force 20px/18px
|
|
5182
|
+
everywhere else in the app, which is fine there but read visibly smaller than the Figma
|
|
5183
|
+
mobile header reference. */
|
|
5184
|
+
.md3 header .icon-btn {
|
|
5185
|
+
width: 48px;
|
|
5186
|
+
height: 48px;
|
|
5187
|
+
}
|
|
5151
5188
|
.md3 header .icon-btn .lucide {
|
|
5152
5189
|
width: 24px;
|
|
5153
5190
|
height: 24px;
|
|
5154
5191
|
}
|
|
5155
5192
|
.md3 header .topbar-account-btn .lucide {
|
|
5156
|
-
width:
|
|
5157
|
-
height:
|
|
5193
|
+
width: 24px;
|
|
5194
|
+
height: 24px;
|
|
5195
|
+
}
|
|
5196
|
+
/* Horizontal margin 12px (spec: "Horizontaler Rand"). */
|
|
5197
|
+
.md3 .topbar-container {
|
|
5198
|
+
padding: 0 12px;
|
|
5199
|
+
}
|
|
5200
|
+
/* Logo 118-126px wide (spec range) — the SVG's own aspect ratio (120:34.909) means the desktop
|
|
5201
|
+
height=32 attribute renders ~110px wide, just under range; this bumps it into range on mobile
|
|
5202
|
+
specifically via a CSS width override (wins over the HTML height attribute). */
|
|
5203
|
+
.md3 .topbar-logo {
|
|
5204
|
+
width: 122px;
|
|
5205
|
+
height: auto;
|
|
5206
|
+
}
|
|
5207
|
+
/* Gap between action icons 2-6px (spec: "Abstand Aktionen") — separate from the wider gap the
|
|
5208
|
+
hamburger/logo/nav still use (that's the outer .container's own gap, untouched here). */
|
|
5209
|
+
.md3 .topbar-actions {
|
|
5210
|
+
gap: 4px;
|
|
5158
5211
|
}
|
|
5159
5212
|
}
|
|
5160
5213
|
/* language segmented DE|EN — active tab filled, inactive ghost */
|