@bytebrand/fe-ui-core-autobahn 1.0.20 → 1.0.22
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/locales/de/autobahn.json +4 -1
- package/locales/en/autobahn.json +4 -1
- package/package.json +1 -1
- package/theme/md3.css +43 -12
- package/ui/CardCarousel.tsx +1 -0
- package/vdp/FinanceCalculator.tsx +53 -31
- package/vdp/VdpModules.tsx +11 -3
package/locales/de/autobahn.json
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"leaseTerms": "{{mileage}} km/Jahr · {{months}} Monate",
|
|
15
15
|
"purchasePrice": "Kaufpreis",
|
|
16
16
|
"priceDropAmount": "−{{amount}} €",
|
|
17
|
+
"priceDropSave": "{{amount}} € sparen",
|
|
17
18
|
"savedAmount": "{{amount}} € gespart",
|
|
18
19
|
"zeroDownCompact": "0 € Anz.",
|
|
19
20
|
"zeroDown": "0 € Anzahlung",
|
|
@@ -414,7 +415,9 @@
|
|
|
414
415
|
"back": "zurück",
|
|
415
416
|
"share": "teilen",
|
|
416
417
|
"photos": "Alle {{count}} Fotos",
|
|
417
|
-
"photos_plural": "Alle {{count}} Fotos"
|
|
418
|
+
"photos_plural": "Alle {{count}} Fotos",
|
|
419
|
+
"photosCount": "{{count}} Fotos",
|
|
420
|
+
"photosCount_plural": "{{count}} Fotos"
|
|
418
421
|
},
|
|
419
422
|
"favorite": {
|
|
420
423
|
"label": "merken"
|
package/locales/en/autobahn.json
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"leaseTerms": "{{mileage}} km/year · {{months}} months",
|
|
15
15
|
"purchasePrice": "Purchase price",
|
|
16
16
|
"priceDropAmount": "−{{amount}} €",
|
|
17
|
+
"priceDropSave": "Save {{amount}} €",
|
|
17
18
|
"savedAmount": "€{{amount}} saved",
|
|
18
19
|
"zeroDownCompact": "€0 down",
|
|
19
20
|
"zeroDown": "€0 down payment",
|
|
@@ -414,7 +415,9 @@
|
|
|
414
415
|
"back": "Back",
|
|
415
416
|
"share": "Share",
|
|
416
417
|
"photos": "All {{count}} photos",
|
|
417
|
-
"photos_plural": "All {{count}} photos"
|
|
418
|
+
"photos_plural": "All {{count}} photos",
|
|
419
|
+
"photosCount": "{{count}} photos",
|
|
420
|
+
"photosCount_plural": "{{count}} photos"
|
|
418
421
|
},
|
|
419
422
|
"favorite": {
|
|
420
423
|
"label": "Save"
|
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.22",
|
|
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
|
@@ -155,12 +155,12 @@
|
|
|
155
155
|
NOT the 38px tappable filter-chip the base .chip is. */
|
|
156
156
|
.md3 .listcard-chips .chip { height: 24px; padding: 0 10px; font-size: 11.5px; border-radius: 8px; cursor: default; }
|
|
157
157
|
.md3 .listcard-chips .chip:hover { background: var(--md-surface); border-color: var(--md-outline-variant); }
|
|
158
|
-
/* VDP trust row (Sofort verfügbar/views/favorites/Geprüft) —
|
|
159
|
-
|
|
160
|
-
.md3 .vdp-trust-chip { display: inline-flex; align-items: center; gap: 6px; height:
|
|
161
|
-
border-radius:
|
|
162
|
-
background:
|
|
163
|
-
.md3 .vdp-trust-chip .lucide { width:
|
|
158
|
+
/* VDP trust row (Sofort verfügbar/views/favorites/Geprüft) — mobile-only, so the figma
|
|
159
|
+
push-chip palette is used literally (light blue-gray fill, dark bold label). */
|
|
160
|
+
.md3 .vdp-trust-chip { display: inline-flex; align-items: center; gap: 6px; height: 30px; padding: 0 11px;
|
|
161
|
+
border-radius: 11px; font-size: 11px; font-weight: 700; line-height: 1; cursor: default; white-space: nowrap;
|
|
162
|
+
background: #F5F7FA; color: #1D2A38; border: 1px solid #E5EAF0; }
|
|
163
|
+
.md3 .vdp-trust-chip .lucide { width: 16px; height: 16px; flex: none; }
|
|
164
164
|
|
|
165
165
|
/* status / rating chip (no CTA) */
|
|
166
166
|
.md3 .statchip { display: inline-flex; align-items: center; gap: 5px; height: 24px; padding: 0 10px;
|
|
@@ -174,6 +174,18 @@
|
|
|
174
174
|
border-radius: var(--md-r-full); background: rgba(0,0,0,.55); color: #fff; font-size: 12px; font-weight: 500; backdrop-filter: blur(2px); }
|
|
175
175
|
.md3 .imglabel .lucide { width: 14px; height: 14px; }
|
|
176
176
|
|
|
177
|
+
/* mobile-only row under the gallery image (mockup): bordered "X Fotos" pill · slider dots ·
|
|
178
|
+
share/favorite — all in ONE row in normal flow, nothing overlaid on the image. */
|
|
179
|
+
.md3 .gallery-controls-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 10px 2px 0; }
|
|
180
|
+
.md3 .gallery-photos-btn { display: inline-flex; align-items: center; gap: 7px; cursor: pointer; font: inherit;
|
|
181
|
+
font-size: 12px; font-weight: 700; color: var(--md-on-surface); height: 34px; padding: 0 13px; flex: none;
|
|
182
|
+
background: #fff; border: 1px solid #D4DDE7; border-radius: 16px; }
|
|
183
|
+
.md3 .gallery-photos-btn .lucide { width: 17px; height: 17px; }
|
|
184
|
+
/* page-driven slider dots (the carousel's own overlay dots are hidden on the phone VDP) */
|
|
185
|
+
.md3 .gallery-dots { display: flex; align-items: center; gap: 6px; min-width: 0; overflow: hidden; }
|
|
186
|
+
.md3 .gallery-dots .gdot { width: 6px; height: 6px; border-radius: 3px; background: #D4DDE7; flex: none; }
|
|
187
|
+
.md3 .gallery-dots .gdot--on { width: 30px; height: 5px; background: var(--md-on-surface); }
|
|
188
|
+
|
|
177
189
|
/* ---------- cards ---------- */
|
|
178
190
|
.md3 .mcard { background: var(--md-sc-lowest); border-radius: var(--md-r-md); border: 1px solid var(--md-outline-variant);
|
|
179
191
|
overflow: hidden; transition: box-shadow var(--dur-2) var(--ease-standard), border-color var(--dur-2) var(--ease-standard), transform var(--dur-2) var(--ease-standard); }
|
|
@@ -1150,7 +1162,7 @@
|
|
|
1150
1162
|
.md3 .fin-tabs { display: flex; border-bottom: 1px solid var(--md-outline-variant); margin-bottom: 18px; }
|
|
1151
1163
|
.md3 .fin-tab { flex: 1; background: none; border: 0; padding: 11px 4px; cursor: pointer; font: inherit;
|
|
1152
1164
|
font-size: 13.5px; font-weight: 600; color: var(--md-on-surface-variant); border-bottom: 2px solid transparent;
|
|
1153
|
-
margin-bottom: -1px; transition: color .15s, border-color .15s; }
|
|
1165
|
+
margin-bottom: -1px; transition: color .15s, border-color .15s; text-align: center; }
|
|
1154
1166
|
.md3 .fin-tab.on { color: var(--md-primary); border-bottom-color: var(--md-primary); }
|
|
1155
1167
|
.md3 .fin-tab:hover:not(.on) { color: var(--md-on-surface); }
|
|
1156
1168
|
/* 2-col grid: left grows, right fixed 148px — pill (row 1) and bar (row 2) share a column edge, never wrap */
|
|
@@ -1208,10 +1220,13 @@
|
|
|
1208
1220
|
.md3 .enquiry-phone { margin-top: 16px; }
|
|
1209
1221
|
}
|
|
1210
1222
|
|
|
1211
|
-
/* sticky mobile CTA bar (VDP)
|
|
1223
|
+
/* sticky mobile CTA bar (VDP) — hidden (slid off-screen) until the page scrolls past the
|
|
1224
|
+
main offer card, matching the desktop VdpStickyBar's show/scroll-gated behavior. */
|
|
1212
1225
|
.md3 .vdp-bottom-bar { position: fixed; left: 0; right: 0; bottom: 0; z-index: 60; background: var(--md-sc-lowest);
|
|
1213
1226
|
border-top: 1px solid var(--md-outline-variant); box-shadow: 0 -2px 14px rgba(0,0,0,.10);
|
|
1214
|
-
padding: 10px 16px; align-items: center; gap: 14px;
|
|
1227
|
+
padding: 10px 16px; align-items: center; gap: 14px;
|
|
1228
|
+
transform: translateY(100%); transition: transform var(--dur-2, .25s); }
|
|
1229
|
+
.md3 .vdp-bottom-bar--on { transform: translateY(0); }
|
|
1215
1230
|
|
|
1216
1231
|
/* bottom-sheet (SRP filters) — sticky header + scrollable body + sticky action bar, slide-up entrance */
|
|
1217
1232
|
.md3 .sheet-scrim { position: fixed; inset: 0; background: rgba(16,22,42,.5); z-index: 70;
|
|
@@ -1453,13 +1468,29 @@
|
|
|
1453
1468
|
display: flex; align-items: center; justify-content: center; rotate: 45deg; box-shadow: var(--md-e2); }
|
|
1454
1469
|
.md3 .dealer-pin .lucide { rotate: -45deg; }
|
|
1455
1470
|
|
|
1456
|
-
/* VDP: floating back button over the gallery —
|
|
1471
|
+
/* VDP: floating back button over the gallery — hidden everywhere now (the phone mockup
|
|
1472
|
+
shows a clean image with only the nav arrows; desktop has the breadcrumb). */
|
|
1457
1473
|
.md3 .vdp-back { display: none; position: absolute; top: 12px; left: 14px; z-index: 3; }
|
|
1458
1474
|
|
|
1475
|
+
/* mobile push-badges row ABOVE the gallery (mockup): flat tinted pills in normal flow —
|
|
1476
|
+
replaces the white overlay chips (DropChip & co) that sat on the image. Figma palette
|
|
1477
|
+
used literally; one non-wrapping swipeable line. */
|
|
1478
|
+
.md3 .vdp-push-badges { display: flex; align-items: center; gap: 8px; flex-wrap: nowrap; overflow-x: auto;
|
|
1479
|
+
scrollbar-width: none; margin-bottom: 10px; }
|
|
1480
|
+
.md3 .vdp-push-badges::-webkit-scrollbar { display: none; }
|
|
1481
|
+
.md3 .push-badge { display: inline-flex; align-items: center; justify-content: center; height: 28px; padding: 0 13px;
|
|
1482
|
+
border-radius: 11px; font-size: 13.5px; font-weight: 700; line-height: 1; white-space: nowrap; flex: none; }
|
|
1483
|
+
.md3 .push-badge--save { background: #E6F7EE; color: #168A45; border: 1px solid rgba(22,138,69,.07); }
|
|
1484
|
+
.md3 .push-badge--discount { background: #E60000; color: #fff; }
|
|
1485
|
+
.md3 .push-badge--down { background: #EAF2FF; color: #005CFF; border: 1px solid rgba(0,92,255,.05); }
|
|
1486
|
+
|
|
1459
1487
|
@media (max-width: 600px) {
|
|
1460
1488
|
/* full-bleed gallery: edge-to-edge, app-like (production pattern) */
|
|
1461
|
-
.md3 .vdp-
|
|
1462
|
-
|
|
1489
|
+
.md3 .vdp-gallerybox { margin: 0 -24px; border-radius: 0; border: 0; box-shadow: none; }
|
|
1490
|
+
/* the carousel's own overlay dots move into the below-image controls row (page-rendered) */
|
|
1491
|
+
.md3 .vdp-gallerybox .cardcar-dots { display: none; }
|
|
1492
|
+
/* mockup hero rate is a notch bigger on phone (inline style says 48 — hence !important) */
|
|
1493
|
+
.md3 .fin-rate-main .num { font-size: 52px !important; letter-spacing: -1.5px !important; }
|
|
1463
1494
|
}
|
|
1464
1495
|
|
|
1465
1496
|
/* ---------- save-search bar ---------- */
|
package/ui/CardCarousel.tsx
CHANGED
|
@@ -312,7 +312,17 @@ const FinanceCalculator: React.FunctionComponent<FinanceCalculatorProps> = ({
|
|
|
312
312
|
className={'fin-tab' + (mode === k ? ' on' : '')}
|
|
313
313
|
onClick={() => onModeChange(k)}
|
|
314
314
|
>
|
|
315
|
-
{
|
|
315
|
+
{k === 'cash' ? (
|
|
316
|
+
<>
|
|
317
|
+
{/* mockup names the cash tab "Kaufen" on phone; desktop keeps "Barzahlung" */}
|
|
318
|
+
<span className="hide-mobile">{t(labelKey)}</span>
|
|
319
|
+
<span className="show-mobile" style={{ justifyContent: 'center' }}>
|
|
320
|
+
{t('autobahn:card.price.buy')}
|
|
321
|
+
</span>
|
|
322
|
+
</>
|
|
323
|
+
) : (
|
|
324
|
+
t(labelKey)
|
|
325
|
+
)}
|
|
316
326
|
</button>
|
|
317
327
|
))}
|
|
318
328
|
</div>
|
|
@@ -469,34 +479,30 @@ const FinanceCalculator: React.FunctionComponent<FinanceCalculatorProps> = ({
|
|
|
469
479
|
|
|
470
480
|
{/* ============================== MOBILE (≤600px) — new layout ============================== */}
|
|
471
481
|
<div className="show-mobile" style={{ flexDirection: 'column' }}>
|
|
472
|
-
{/* side-by-side CTAs
|
|
473
|
-
|
|
474
|
-
|
|
482
|
+
{/* side-by-side CTAs — mockup ratio ~2:1, with the tiny auto.de-ID sitting
|
|
483
|
+
directly under the Anrufen button (not in its own full-width row). */}
|
|
484
|
+
<div style={{ display: 'flex', gap: 10, marginTop: 14, alignItems: 'flex-start' }}>
|
|
485
|
+
<Button variant="filled" size="lg" icon="shield-check" style={{ flex: 2 }} onClick={onSecure}>
|
|
475
486
|
{t('autobahn:finance.secureOffer')}
|
|
476
487
|
</Button>
|
|
477
|
-
<
|
|
478
|
-
{
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
{!isCash && onShowDetails && (
|
|
489
|
-
<DetailsLink
|
|
490
|
-
label={mode === 'leasing' ? t('autobahn:finance.leasingDetails') : t('autobahn:finance.financingDetails')}
|
|
491
|
-
onClick={onShowDetails}
|
|
492
|
-
/>
|
|
488
|
+
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 4, minWidth: 0 }}>
|
|
489
|
+
<Button variant="outlined" size="lg" icon="phone" block onClick={onCall}>
|
|
490
|
+
{t('autobahn:finance.callCta')}
|
|
491
|
+
</Button>
|
|
492
|
+
{autoDeId && (
|
|
493
|
+
<span
|
|
494
|
+
className="num"
|
|
495
|
+
style={{ fontSize: 10, fontWeight: 700, color: 'var(--md-primary)', textAlign: 'center', whiteSpace: 'nowrap' }}
|
|
496
|
+
>
|
|
497
|
+
auto.de ID: {autoDeId}
|
|
498
|
+
</span>
|
|
493
499
|
)}
|
|
494
500
|
</div>
|
|
495
|
-
|
|
501
|
+
</div>
|
|
496
502
|
|
|
497
|
-
{/* Persönliche Beratung (left) · Finanzierungs-/Leasingdetails or hint (right) */}
|
|
503
|
+
{/* Persönliche Beratung (left) · divider · Finanzierungs-/Leasingdetails or hint (right) */}
|
|
498
504
|
<div className="fin-info">
|
|
499
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 }}>
|
|
505
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 0, flex: 1 }}>
|
|
500
506
|
<Icon name="user" size={18} color="var(--md-on-surface-variant)" style={{ flex: 'none' }} />
|
|
501
507
|
<div style={{ minWidth: 0 }}>
|
|
502
508
|
<div className="md-label-sm" style={{ fontWeight: 700, color: 'var(--md-on-surface)' }}>
|
|
@@ -505,18 +511,34 @@ const FinanceCalculator: React.FunctionComponent<FinanceCalculatorProps> = ({
|
|
|
505
511
|
<div className="md-label-sm">{t('autobahn:finance.personalConsultationSub')}</div>
|
|
506
512
|
</div>
|
|
507
513
|
</div>
|
|
508
|
-
{
|
|
514
|
+
<span style={{ width: 1, alignSelf: 'stretch', background: 'var(--md-outline-variant)', flex: 'none' }} />
|
|
515
|
+
{!isCash && onShowDetails ? (
|
|
516
|
+
<button
|
|
517
|
+
type="button"
|
|
518
|
+
onClick={onShowDetails}
|
|
519
|
+
style={{
|
|
520
|
+
display: 'flex',
|
|
521
|
+
alignItems: 'center',
|
|
522
|
+
gap: 8,
|
|
523
|
+
background: 'none',
|
|
524
|
+
border: 0,
|
|
525
|
+
padding: 0,
|
|
526
|
+
cursor: 'pointer',
|
|
527
|
+
font: 'inherit',
|
|
528
|
+
color: 'var(--md-on-surface)',
|
|
529
|
+
textAlign: 'left',
|
|
530
|
+
}}
|
|
531
|
+
>
|
|
532
|
+
<Icon name="info" size={18} color="var(--md-on-surface-variant)" style={{ flex: 'none' }} />
|
|
533
|
+
<span className="md-label-sm" style={{ maxWidth: 82, lineHeight: 1.5 }}>
|
|
534
|
+
{mode === 'leasing' ? t('autobahn:finance.leasingDetails') : t('autobahn:finance.financingDetails')}
|
|
535
|
+
</span>
|
|
536
|
+
</button>
|
|
537
|
+
) : (
|
|
509
538
|
<div style={{ display: 'flex', alignItems: 'center', gap: 6, color: 'var(--md-on-surface-variant)' }}>
|
|
510
539
|
<Icon name="info" size={15} />
|
|
511
540
|
<span className="md-label-sm">{t('autobahn:finance.financingLeasingPossible')}</span>
|
|
512
541
|
</div>
|
|
513
|
-
) : (
|
|
514
|
-
onShowDetails && (
|
|
515
|
-
<DetailsLink
|
|
516
|
-
label={mode === 'leasing' ? t('autobahn:finance.leasingDetails') : t('autobahn:finance.financingDetails')}
|
|
517
|
-
onClick={onShowDetails}
|
|
518
|
-
/>
|
|
519
|
-
)
|
|
520
542
|
)}
|
|
521
543
|
</div>
|
|
522
544
|
|
package/vdp/VdpModules.tsx
CHANGED
|
@@ -922,6 +922,13 @@ export const VdpStickyBar: React.FunctionComponent<VdpStickyBarProps> = ({ vehic
|
|
|
922
922
|
|
|
923
923
|
export interface VdpBottomBarProps {
|
|
924
924
|
vehicle: VehicleDetail;
|
|
925
|
+
/**
|
|
926
|
+
* Whether the bar is docked/visible — mirrors VdpStickyBar's `show` (same
|
|
927
|
+
* page-owned scroll-position toggle, STICKY_SHOW_AT). Was unconditionally
|
|
928
|
+
* visible before, unlike the desktop VdpStickyBar; the mockup only shows it
|
|
929
|
+
* after scrolling past the main offer card.
|
|
930
|
+
*/
|
|
931
|
+
show: boolean;
|
|
925
932
|
/** primary CTA click. */
|
|
926
933
|
onSecure?: () => void;
|
|
927
934
|
/** i18n function (drilled from the page). */
|
|
@@ -929,8 +936,8 @@ export interface VdpBottomBarProps {
|
|
|
929
936
|
}
|
|
930
937
|
|
|
931
938
|
/** Mobile sticky bottom CTA bar — rate + Kaufpreis + "Angebot sichern". */
|
|
932
|
-
export const VdpBottomBar: React.FunctionComponent<VdpBottomBarProps> = ({ vehicle, onSecure, t }) => (
|
|
933
|
-
<div className=
|
|
939
|
+
export const VdpBottomBar: React.FunctionComponent<VdpBottomBarProps> = ({ vehicle, show, onSecure, t }) => (
|
|
940
|
+
<div className={'vdp-bottom-bar show-mobile' + (show ? ' vdp-bottom-bar--on' : '')}>
|
|
934
941
|
<div style={{ flex: 1, minWidth: 0 }}>
|
|
935
942
|
<div style={{ display: 'flex', alignItems: 'baseline', gap: 5, whiteSpace: 'nowrap' }}>
|
|
936
943
|
<span className="md-label-sm">{t('autobahn:bottom.from')}</span>
|
|
@@ -1271,7 +1278,8 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
|
|
|
1271
1278
|
[
|
|
1272
1279
|
['fin', 'autobahn:finance.mode.financing'],
|
|
1273
1280
|
['leasing', 'autobahn:finance.mode.leasing'],
|
|
1274
|
-
|
|
1281
|
+
// this sheet is mobile-only, so the cash tab is "Kaufen" here (desktop card says "Barzahlung")
|
|
1282
|
+
['cash', 'autobahn:card.price.buy'],
|
|
1275
1283
|
] as Array<[FinanceMode, string]>
|
|
1276
1284
|
).map(([k, labelKey]) => (
|
|
1277
1285
|
<button
|