@bytebrand/fe-ui-core-autobahn 1.0.115 → 1.0.117
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 +1 -1
- package/cards/cardParts.tsx +2 -2
- package/data/toAutobahnCar.ts +28 -2
- package/package.json +30 -30
- package/theme/md3.css +42 -5
- package/ui/Dropdown.tsx +10 -1
package/cards/CarRow.tsx
CHANGED
|
@@ -272,7 +272,7 @@ const CarRow: React.FunctionComponent<CarRowProps> = ({
|
|
|
272
272
|
{facts.map(([ic, v]) => (
|
|
273
273
|
<span key={ic} style={{ display: 'inline-flex', alignItems: 'center', gap: 7, whiteSpace: 'nowrap' }}>
|
|
274
274
|
<Icon name={ic} size={15} color="var(--md-primary)" stroke={1.8} style={{ flex: 'none' }} />
|
|
275
|
-
<span
|
|
275
|
+
<span className="specstrip-val">{v}</span>
|
|
276
276
|
</span>
|
|
277
277
|
))}
|
|
278
278
|
</div>
|
package/cards/cardParts.tsx
CHANGED
|
@@ -334,7 +334,7 @@ export const EcoLine: React.FunctionComponent<EcoLineProps> = ({ car, style, t }
|
|
|
334
334
|
if (car.phev) {
|
|
335
335
|
const p = car.phev;
|
|
336
336
|
return (
|
|
337
|
-
<div style={wrap}>
|
|
337
|
+
<div className="eco-line" style={wrap}>
|
|
338
338
|
{p.consEl}{' '}
|
|
339
339
|
{t('autobahn:card.eco.phevConsumptionLine', {
|
|
340
340
|
consFuel: p.consFuel,
|
|
@@ -347,7 +347,7 @@ export const EcoLine: React.FunctionComponent<EcoLineProps> = ({ car, style, t }
|
|
|
347
347
|
);
|
|
348
348
|
}
|
|
349
349
|
return (
|
|
350
|
-
<div style={wrap}>
|
|
350
|
+
<div className="eco-line" style={wrap}>
|
|
351
351
|
{t('autobahn:card.eco.consumptionLine', { cons: car.cons, co2: car.co2, co2class: car.co2class })}
|
|
352
352
|
</div>
|
|
353
353
|
);
|
package/data/toAutobahnCar.ts
CHANGED
|
@@ -96,6 +96,33 @@ function num(v: any): number | undefined {
|
|
|
96
96
|
return Number.isFinite(n) ? n : undefined;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
/** like num(), but 0/negative count as absent — a 0-filled consumption field is
|
|
100
|
+
* "no data" (CBD skips zeros on enrichment), never a real figure to print. */
|
|
101
|
+
function pos(v: any): number | undefined {
|
|
102
|
+
const n = num(v);
|
|
103
|
+
return n != null && n > 0 ? n : undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** gas-type fuels whose consumption is measured in kg/100 km (legacy cbdConsumptionUnit set). */
|
|
107
|
+
const KG_FUELS = ['selector_fuel_naturalGas', 'selector_fuel_carGas', 'selector_fuel_hydrogen', 'selector_fuel_ethanol'];
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* EnVKV combined-consumption label, fuel-aware. Electric cars consume kWh — their
|
|
111
|
+
* l/100km fields (wltpCombined/consumptionCombined-as-liters) arrive as 0, so the
|
|
112
|
+
* flat `l/100 km` template printed "0 l/100 km" on every EV; gas/hydrogen cars
|
|
113
|
+
* measure kg. WLTP figure preferred, else NEDC — same order as before.
|
|
114
|
+
*/
|
|
115
|
+
function consumptionLabel(consumption: any, language?: string): string {
|
|
116
|
+
const fuel = String(consumption.fuel || '');
|
|
117
|
+
const electric = fuel === 'selector_fuel_electrical';
|
|
118
|
+
const val = electric
|
|
119
|
+
? pos(consumption.wltpPowerCombined) ?? pos(consumption.consumptionPowerCombined) ?? pos(consumption.consumptionCombined)
|
|
120
|
+
: pos(consumption.wltpCombined) ?? pos(consumption.consumptionCombined);
|
|
121
|
+
if (val == null) return '';
|
|
122
|
+
const unit = electric ? 'kWh/100 km' : KG_FUELS.indexOf(fuel) >= 0 ? 'kg/100 km' : 'l/100 km';
|
|
123
|
+
return `${val.toLocaleString(localeTag(language))} ${unit}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
99
126
|
// strike-price ("UVP-Rate"/list-price) eligibility — a separate product rule from the NEU
|
|
100
127
|
// badge's own showNewLabel (which also requires a recent modificationDate; see fe-main's
|
|
101
128
|
// DataTransformers.ts shouldShowNewLabel). Here: as-good-as-new only — mileage under 1,000 km
|
|
@@ -371,9 +398,8 @@ export function toAutobahnCar(storeCar: any, deps: ToAutobahnCarDeps = {}): Auto
|
|
|
371
398
|
|
|
372
399
|
// --- EnVKV consumption line (REQUIRED on every German car ad) ---
|
|
373
400
|
// Prefer WLTP figures when present (the legacy cards do too), else NEDC.
|
|
374
|
-
const consVal = num(consumption.wltpCombined) ?? num(consumption.consumptionCombined);
|
|
375
401
|
const co2Val = num(emissions.wltpCo2) ?? num(emissions.co2);
|
|
376
|
-
const cons =
|
|
402
|
+
const cons = consumptionLabel(consumption, language);
|
|
377
403
|
const co2 = co2Val != null ? `${co2Val.toLocaleString(localeTag(language))} g/km` : '';
|
|
378
404
|
const co2class = co2ClassLetter(emissions.wltpEnergyEfficiencyClass);
|
|
379
405
|
const phev = buildPhev(storeCar, language);
|
package/package.json
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@bytebrand/fe-ui-core-autobahn",
|
|
3
|
-
"version": "1.0.
|
|
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
|
-
"main": "index.ts",
|
|
6
|
-
"license": "UNLICENSED",
|
|
7
|
-
"peerDependencies": {
|
|
8
|
-
"react": "^18.0.0",
|
|
9
|
-
"react-dom": "^18.0.0"
|
|
10
|
-
},
|
|
11
|
-
"dependencies": {
|
|
12
|
-
"@bytebrand/car-schema-selectors": "^2.0.7",
|
|
13
|
-
"@bytebrand/fe-financing": "^5.14.19",
|
|
14
|
-
"classnames": "2.2.6",
|
|
15
|
-
"clsx": "^2.1.1",
|
|
16
|
-
"d3-format": "1.4.5",
|
|
17
|
-
"js-cookie": "^2.2.0",
|
|
18
|
-
"lodash": "^4.17.21",
|
|
19
|
-
"lucide-react": "^0.288.0",
|
|
20
|
-
"mobx-react": "^7.6.0",
|
|
21
|
-
"moment": "^2.29.1",
|
|
22
|
-
"qs": "^6.6.0",
|
|
23
|
-
"react-device-detect": "^2.1.2",
|
|
24
|
-
"react-grid-system": "^5.0.2",
|
|
25
|
-
"react-lazyload": "^3.2.0",
|
|
26
|
-
"react-scroll": "^1.9.3",
|
|
27
|
-
"react-slick": "^0.29.0",
|
|
28
|
-
"slick-carousel": "1.8.1"
|
|
29
|
-
}
|
|
30
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@bytebrand/fe-ui-core-autobahn",
|
|
3
|
+
"version": "1.0.117",
|
|
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
|
+
"main": "index.ts",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"peerDependencies": {
|
|
8
|
+
"react": "^18.0.0",
|
|
9
|
+
"react-dom": "^18.0.0"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@bytebrand/car-schema-selectors": "^2.0.7",
|
|
13
|
+
"@bytebrand/fe-financing": "^5.14.19",
|
|
14
|
+
"classnames": "2.2.6",
|
|
15
|
+
"clsx": "^2.1.1",
|
|
16
|
+
"d3-format": "1.4.5",
|
|
17
|
+
"js-cookie": "^2.2.0",
|
|
18
|
+
"lodash": "^4.17.21",
|
|
19
|
+
"lucide-react": "^0.288.0",
|
|
20
|
+
"mobx-react": "^7.6.0",
|
|
21
|
+
"moment": "^2.29.1",
|
|
22
|
+
"qs": "^6.6.0",
|
|
23
|
+
"react-device-detect": "^2.1.2",
|
|
24
|
+
"react-grid-system": "^5.0.2",
|
|
25
|
+
"react-lazyload": "^3.2.0",
|
|
26
|
+
"react-scroll": "^1.9.3",
|
|
27
|
+
"react-slick": "^0.29.0",
|
|
28
|
+
"slick-carousel": "1.8.1"
|
|
29
|
+
}
|
|
30
|
+
}
|
package/theme/md3.css
CHANGED
|
@@ -4222,6 +4222,24 @@ line-height: 31px;
|
|
|
4222
4222
|
background: var(--md-sc-low);
|
|
4223
4223
|
border-radius: var(--md-r-md);
|
|
4224
4224
|
}
|
|
4225
|
+
/* spec strip values — mobile keeps the bold 13.5px; desktop follows the design (11px/400) */
|
|
4226
|
+
.md3 .specstrip-val {
|
|
4227
|
+
font-size: 13.5px;
|
|
4228
|
+
font-weight: 600;
|
|
4229
|
+
color: var(--md-on-surface);
|
|
4230
|
+
}
|
|
4231
|
+
@media (min-width: 601px) {
|
|
4232
|
+
.md3 .listcard .specstrip-val {
|
|
4233
|
+
font-size: 11px;
|
|
4234
|
+
font-weight: 400;
|
|
4235
|
+
}
|
|
4236
|
+
/* SRP list card EnVKV line — design wants full-contrast text (#1a1b22 = --md-on-surface) on
|
|
4237
|
+
desktop; the component's inline base color (on-surface-variant) stays for every other
|
|
4238
|
+
surface (grid card, mobile), hence the !important against the inline style. */
|
|
4239
|
+
.md3 .listcard .eco-line {
|
|
4240
|
+
color: var(--md-on-surface) !important;
|
|
4241
|
+
}
|
|
4242
|
+
}
|
|
4225
4243
|
/* desktop only (mobile keeps the flex-wrap flow above, untouched): a clean 2-column grid so
|
|
4226
4244
|
pairs line up (km|EZ, Kraftstoff|Getriebe, Leistung|Halter) instead of reflowing by text
|
|
4227
4245
|
length (Figma "mcard"). Facts render in DOM order [km, EZ, Getriebe, Kraftstoff, Leistung,
|
|
@@ -5336,6 +5354,11 @@ line-height: 31px;
|
|
|
5336
5354
|
.md3 .card-cta-row .mbtn { font-size: 12.5px; padding: 0 10px; gap: 5px; min-width: 0; }
|
|
5337
5355
|
.md3 .card-cta-row .mbtn span, .md3 .card-cta-row .mbtn { overflow: hidden; text-overflow: ellipsis; }
|
|
5338
5356
|
.md3 .card-cta-row .mbtn .lucide { width: 15px; height: 15px; flex: none; }
|
|
5357
|
+
/* the phone/"Rate anpassen" outlined buttons set their own explicit `border` (1.6px solid) inline —
|
|
5358
|
+
.mbtn--outlined's inset box-shadow (the DEFAULT way that class draws its border everywhere else)
|
|
5359
|
+
stacks on top of it as a second, slightly offset ring ("double border"). Suppress just the
|
|
5360
|
+
shadow here; the real border keeps rendering from each button's own inline style. */
|
|
5361
|
+
.md3 .card-cta-row .mbtn--outlined { box-shadow: none; }
|
|
5339
5362
|
/* muted buyer activity (#5) */
|
|
5340
5363
|
.md3 .pp-activity {
|
|
5341
5364
|
display: flex;
|
|
@@ -6767,16 +6790,30 @@ line-height: 31px;
|
|
|
6767
6790
|
}
|
|
6768
6791
|
.md3 .srp-bar {
|
|
6769
6792
|
position: sticky;
|
|
6770
|
-
/*
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6793
|
+
/* default: docked directly under the always-visible header (its 64px height). Same 0.25s
|
|
6794
|
+
duration as .topbar's own transform transition (below) — the header slides in/out over
|
|
6795
|
+
0.25s, so this bar's top must animate in step with it, not jump instantly, or a gap (bar
|
|
6796
|
+
already at 64px, header still sliding in) or an overlap (bar already at 0, header still
|
|
6797
|
+
sliding out) flashes for that quarter-second. */
|
|
6798
|
+
top: 64px;
|
|
6799
|
+
transition: top 0.25s;
|
|
6774
6800
|
z-index: 20;
|
|
6775
6801
|
background: #fff;
|
|
6776
6802
|
padding: 8px 0;
|
|
6777
6803
|
margin: 0 0 8px;
|
|
6778
6804
|
box-shadow: 0 6px 8px -8px rgba(0, 0, 0, 0.3);
|
|
6779
6805
|
}
|
|
6806
|
+
/* once the header slides away on scroll-down (autoHideOnScroll, TopBar.tsx adds
|
|
6807
|
+
.topbar--hidden), this bar takes its place at the very top instead of leaving a 64px gap.
|
|
6808
|
+
The header's box stays in the layout flow even while hidden (transform doesn't affect
|
|
6809
|
+
`position: sticky`'s slot — see the .topbar--hidden rule above), so without this override
|
|
6810
|
+
the bar's own sticky top would also need to jump — and it must jump BACK to 64px the moment
|
|
6811
|
+
scroll-up brings the header back (topbar--hidden removed), or the reappearing header
|
|
6812
|
+
(z-index 50) sticks at the same top:0 slot and paints over/hides this bar (z-index 20)
|
|
6813
|
+
underneath it instead of sitting above it. */
|
|
6814
|
+
.md3:has(.topbar--hidden) .srp-bar {
|
|
6815
|
+
top: 0px;
|
|
6816
|
+
}
|
|
6780
6817
|
/* full-bleed white to the true viewport edges, WITHOUT moving .srp-bar's own text — it stays
|
|
6781
6818
|
inside .srp-main's own side padding, this pseudo-element just paints white past it on both
|
|
6782
6819
|
sides so no gap of page background shows while the bar is sticky. NOT a box-shadow spread
|
|
@@ -7327,7 +7364,7 @@ line-height: 31px;
|
|
|
7327
7364
|
no separate per-element rule to keep in sync with this one. */
|
|
7328
7365
|
@media (max-width: 600px) {
|
|
7329
7366
|
.md3 .vdp-main {
|
|
7330
|
-
padding: 0
|
|
7367
|
+
padding: 0 14px 48px !important;
|
|
7331
7368
|
}
|
|
7332
7369
|
.md3 .vdp-push-badges {
|
|
7333
7370
|
gap: 6px;
|
package/ui/Dropdown.tsx
CHANGED
|
@@ -330,6 +330,15 @@ const Dropdown: React.FunctionComponent<DropdownProps> = ({
|
|
|
330
330
|
|
|
331
331
|
const editing = searchable && open;
|
|
332
332
|
|
|
333
|
+
// Bug 53/37: "Rate bis" / "km bis" allow a hand-typed amount the preset rungs don't cover
|
|
334
|
+
// (allowCustomValue) — that typed number never matches any option label, so filteredOptions
|
|
335
|
+
// comes back empty and the popover would show "Keine Treffer", reading as "no cars match this"
|
|
336
|
+
// to the customer even though the typed value is perfectly valid input. Once there's an actual
|
|
337
|
+
// typed query with zero matches on an allowCustomValue field, suppress the popover entirely
|
|
338
|
+
// (not just the "Keine Treffer" row) — an empty floating box would be just as confusing. Plain
|
|
339
|
+
// (non-custom) searchable dropdowns, e.g. Make/Model, are unaffected and keep showing it.
|
|
340
|
+
const suppressEmptyMenu = allowCustomValue && searchable && query.trim() !== '' && filteredOptions.length === 0;
|
|
341
|
+
|
|
333
342
|
// shared between the in-place popover and the menuPortal one — identical option list either way.
|
|
334
343
|
const renderMenuList = () => (
|
|
335
344
|
<ul
|
|
@@ -407,7 +416,7 @@ const Dropdown: React.FunctionComponent<DropdownProps> = ({
|
|
|
407
416
|
<Icon name="chevron-down" cls="mselect-chev" />
|
|
408
417
|
</button>
|
|
409
418
|
)}
|
|
410
|
-
{open && (menuPortal && portalRoot
|
|
419
|
+
{open && !suppressEmptyMenu && (menuPortal && portalRoot
|
|
411
420
|
? ReactDOM.createPortal(
|
|
412
421
|
<div
|
|
413
422
|
ref={menuElRef}
|