@bytebrand/fe-ui-core-autobahn 1.0.2 → 1.0.4
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 +38 -38
- package/cards/cardParts.tsx +28 -26
- package/cards/types.ts +4 -2
- package/package.json +1 -1
- package/theme/md3.css +9 -14
package/cards/CarRow.tsx
CHANGED
|
@@ -65,45 +65,45 @@ const CarRow: React.FunctionComponent<CarRowProps> = ({ car, isFavorite, onToggl
|
|
|
65
65
|
onClick={() => onOpen(car)}
|
|
66
66
|
style={{ background: 'var(--md-sc-lowest)' }}
|
|
67
67
|
>
|
|
68
|
-
<div className="listcard-media" onMouseEnter={onMediaEnter} onTouchStart={onMediaEnter}>
|
|
69
|
-
<
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
>
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
<
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
<IconButton name="heart" filled active={isFavorite} onClick={onToggleFavorite} label={t('autobahn:card.action.save')} />
|
|
104
|
-
</div>
|
|
68
|
+
<div className="listcard-media" style={{ position: 'relative' }} onMouseEnter={onMediaEnter} onTouchStart={onMediaEnter}>
|
|
69
|
+
<CardCarousel images={car.gallery} height="100%" fit="contain" />
|
|
70
|
+
{/* deal badges — ONE compact line: NEU · −€ · 0 € Anz. · −%. The price RATING
|
|
71
|
+
('Guter Preis') is NOT here — it lives in the price column (PricePaths meter). */}
|
|
72
|
+
<div
|
|
73
|
+
style={{
|
|
74
|
+
position: 'absolute',
|
|
75
|
+
top: 10,
|
|
76
|
+
left: 10,
|
|
77
|
+
right: 10,
|
|
78
|
+
zIndex: 3,
|
|
79
|
+
display: 'flex',
|
|
80
|
+
flexDirection: 'row',
|
|
81
|
+
flexWrap: 'nowrap',
|
|
82
|
+
alignItems: 'center',
|
|
83
|
+
gap: 5,
|
|
84
|
+
}}
|
|
85
|
+
>
|
|
86
|
+
{car.isNew && <span className="neu-badge">NEU</span>}
|
|
87
|
+
{car.drop != null ? <DropChip t={t} amount={car.drop} /> : showSave && <SaveChip t={t} amount={car.savings!} />}
|
|
88
|
+
<ZeroDownChip t={t} show={car.zeroDown} compact />
|
|
89
|
+
<PercentChip pct={car.dropPct} />
|
|
90
|
+
</div>
|
|
91
|
+
<div style={{ position: 'absolute', bottom: 10, right: 10, zIndex: 3 }}>
|
|
92
|
+
<span className="imglabel">
|
|
93
|
+
<Icon name="image" size={13} />
|
|
94
|
+
{car.photos}
|
|
95
|
+
</span>
|
|
96
|
+
</div>
|
|
97
|
+
<div
|
|
98
|
+
onClick={(e) => e.stopPropagation()}
|
|
99
|
+
className="show-mobile"
|
|
100
|
+
style={{ position: 'absolute', top: 8, right: 8, zIndex: 3 }}
|
|
101
|
+
>
|
|
102
|
+
<IconButton name="heart" filled active={isFavorite} onClick={onToggleFavorite} label={t('autobahn:card.action.save')} />
|
|
105
103
|
</div>
|
|
106
|
-
{/* activity/trust
|
|
104
|
+
{/* activity/trust badges — overlaid on the studio-white floor at the bottom of the photo
|
|
105
|
+
itself (PM ref), not a separate divided strip below it. Opposite corner from the photo
|
|
106
|
+
count so the two never collide. */}
|
|
107
107
|
<GalleryBadges car={car} t={t} />
|
|
108
108
|
</div>
|
|
109
109
|
|
package/cards/cardParts.tsx
CHANGED
|
@@ -494,36 +494,38 @@ export interface GalleryBadgesProps {
|
|
|
494
494
|
}
|
|
495
495
|
|
|
496
496
|
/**
|
|
497
|
-
* Trust/activity
|
|
498
|
-
*
|
|
499
|
-
*
|
|
500
|
-
*
|
|
501
|
-
*
|
|
502
|
-
*
|
|
497
|
+
* Trust/activity badges overlaid on the photo itself, bottom-left (PM reference, 2026-07-10) —
|
|
498
|
+
* flat icon+text, no pill border/background/divider, sitting on the studio-white floor every
|
|
499
|
+
* inventory photo already has (see .cc-media/.listcard-media background in md3.css). Mirrors the
|
|
500
|
+
* mobile VDP gallery header (availability · views · favorites · Geprüft).
|
|
501
|
+
*
|
|
502
|
+
* PM decision (Viktoriia — "this logic already exists on prod"): the row is all-or-nothing. A
|
|
503
|
+
* missing or zero view/favorite count is NOT shown as "0×" — the whole row (including the
|
|
504
|
+
* always-on availability/Geprüft items) is hidden rather than reading as an empty/broken card. It
|
|
505
|
+
* only appears once BOTH counts are real and positive.
|
|
503
506
|
*/
|
|
504
|
-
export const GalleryBadges: React.FunctionComponent<GalleryBadgesProps> = ({ car, t }) =>
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
507
|
+
export const GalleryBadges: React.FunctionComponent<GalleryBadgesProps> = ({ car, t }) => {
|
|
508
|
+
if (!car.viewsCount || !car.favoritesCount) return null;
|
|
509
|
+
return (
|
|
510
|
+
<div className="gallery-badges" style={{ position: 'absolute', left: 10, bottom: 10, zIndex: 3 }}>
|
|
511
|
+
{car.availability && (
|
|
512
|
+
<span className="gallery-pill">
|
|
513
|
+
<Icon name="circle-check" size={15} stroke={2} color="var(--md-good)" />
|
|
514
|
+
{car.availability}
|
|
515
|
+
</span>
|
|
516
|
+
)}
|
|
513
517
|
<span className="gallery-pill">
|
|
514
|
-
<Icon name="eye" size={
|
|
518
|
+
<Icon name="eye" size={15} stroke={1.9} />
|
|
515
519
|
{t('autobahn:card.activity.viewsCount', { count: car.viewsCount })}
|
|
516
520
|
</span>
|
|
517
|
-
)}
|
|
518
|
-
{car.favoritesCount != null && (
|
|
519
521
|
<span className="gallery-pill">
|
|
520
|
-
<Icon name="heart" size={
|
|
522
|
+
<Icon name="heart" size={15} stroke={1.9} />
|
|
521
523
|
{t('autobahn:card.activity.favoritesCount', { count: car.favoritesCount })}
|
|
522
524
|
</span>
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
</
|
|
528
|
-
|
|
529
|
-
|
|
525
|
+
<span className="gallery-pill">
|
|
526
|
+
<Icon name="shield-check" size={15} stroke={1.9} />
|
|
527
|
+
{t('autobahn:card.trust.checked')}
|
|
528
|
+
</span>
|
|
529
|
+
</div>
|
|
530
|
+
);
|
|
531
|
+
};
|
package/cards/types.ts
CHANGED
|
@@ -113,8 +113,10 @@ export interface AutobahnCar {
|
|
|
113
113
|
views?: number;
|
|
114
114
|
|
|
115
115
|
// --- gallery activity strip (real data: UserActionsAPI 'stats/viewers', loaded async and
|
|
116
|
-
// keyed by carId in SearchResultsStore.statsData — see fieldMapping.ts). Absent until
|
|
117
|
-
//
|
|
116
|
+
// keyed by carId in SearchResultsStore.statsData — see fieldMapping.ts). Absent until that
|
|
117
|
+
// fetch resolves. PM decision: the WHOLE badge row (availability/views/favorites/Geprüft)
|
|
118
|
+
// is all-or-nothing — it renders only when BOTH counts are present and > 0; a missing or
|
|
119
|
+
// zero count hides the entire row rather than showing a "0×" pill (GalleryBadges). ---
|
|
118
120
|
/** total detail-page views (stats totalCarImpCount) — shown as the eye pill under the image. */
|
|
119
121
|
viewsCount?: number;
|
|
120
122
|
/** total users who favorited this car (stats totalFavCount) — shown as the heart pill under the image. */
|
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.4",
|
|
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
|
@@ -1002,17 +1002,13 @@
|
|
|
1002
1002
|
|
|
1003
1003
|
/* horizontal listing card (SRP): image | info | price */
|
|
1004
1004
|
.md3 .listcard { display: grid; grid-template-columns: 300px 1fr 240px; align-items: stretch; }
|
|
1005
|
-
.md3 .listcard-media { position: relative; min-height: 100%;
|
|
1006
|
-
/*
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
.md3 .gallery-
|
|
1011
|
-
|
|
1012
|
-
.md3 .gallery-pill { display: inline-flex; align-items: center; gap: 5px; height: 26px; padding: 0 10px;
|
|
1013
|
-
border-radius: var(--md-r-full); border: 1px solid var(--md-outline-variant); background: var(--md-sc-lowest);
|
|
1014
|
-
color: var(--md-on-surface); font-size: 11.5px; font-weight: 600; white-space: nowrap; }
|
|
1015
|
-
.md3 .gallery-pill--good { color: var(--md-good); border-color: var(--md-good); }
|
|
1005
|
+
.md3 .listcard-media { position: relative; min-height: 100%; }
|
|
1006
|
+
/* activity/trust badges overlaid on the photo's studio-white floor, bottom-left (opposite corner
|
|
1007
|
+
from the bottom-right photo-count .imglabel so they never collide) — PM ref, no divider/box,
|
|
1008
|
+
flat icon+text pairs. */
|
|
1009
|
+
.md3 .gallery-badges { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }
|
|
1010
|
+
.md3 .gallery-pill { display: inline-flex; align-items: center; gap: 5px; color: var(--md-on-surface-variant);
|
|
1011
|
+
font-size: 13px; font-weight: 600; white-space: nowrap; }
|
|
1016
1012
|
.md3 .listcard-info { padding: 16px 18px; display: flex; flex-direction: column; gap: 11px; min-width: 0; }
|
|
1017
1013
|
.md3 .listcard-price { position: relative; padding: 16px 18px; border-left: 1px solid var(--md-outline-variant);
|
|
1018
1014
|
display: flex; flex-direction: column; justify-content: flex-start; gap: 16px; background: var(--md-sc-low); }
|
|
@@ -1298,10 +1294,9 @@
|
|
|
1298
1294
|
|
|
1299
1295
|
@media (max-width: 600px) {
|
|
1300
1296
|
.md3 .cards-3, .md3 .cards-2 { grid-template-columns: 1fr; }
|
|
1301
|
-
/* listing card stacks: image (4:3) →
|
|
1297
|
+
/* listing card stacks: image (4:3) → info → price */
|
|
1302
1298
|
.md3 .listcard { grid-template-columns: 1fr; }
|
|
1303
|
-
.md3 .listcard-media { min-height: 0; }
|
|
1304
|
-
.md3 .listcard-media-img { aspect-ratio: 4 / 3; flex: none; }
|
|
1299
|
+
.md3 .listcard-media { aspect-ratio: 4 / 3; min-height: 0; }
|
|
1305
1300
|
.md3 .listcard-price { border-left: 0; border-top: 1px solid var(--md-outline-variant); padding: 14px 16px;
|
|
1306
1301
|
flex-direction: column; justify-content: flex-start; align-items: stretch; gap: 14px; }
|
|
1307
1302
|
.md3 .listcard-price .pricepaths { flex: none; min-width: 0; }
|