@bytebrand/fe-ui-core-autobahn 1.0.15 → 1.0.16

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytebrand/fe-ui-core-autobahn",
3
- "version": "1.0.15",
3
+ "version": "1.0.16",
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,6 +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) — same compact non-interactive
159
+ sizing as the listcard equipment tags, standalone (not nested under .listcard-chips). */
160
+ .md3 .vdp-trust-chip { display: inline-flex; align-items: center; gap: 6px; height: 24px; padding: 0 10px;
161
+ border-radius: 8px; font-size: 11.5px; font-weight: 500; line-height: 1; cursor: default; white-space: nowrap;
162
+ background: var(--md-surface); color: var(--md-on-surface-variant); border: 1px solid var(--md-outline-variant); }
163
+ .md3 .vdp-trust-chip .lucide { width: 14px; height: 14px; flex: none; }
158
164
 
159
165
  /* status / rating chip (no CTA) */
160
166
  .md3 .statchip { display: inline-flex; align-items: center; gap: 5px; height: 24px; padding: 0 10px;
@@ -1283,7 +1289,14 @@
1283
1289
  .md3 .srp-rail { display: none; } /* rail is desktop-only; ≤980 uses the filter sheet */
1284
1290
  .md3 .srp-grid { grid-template-columns: 1fr; }
1285
1291
  .md3 .vdp-grid { display: flex; flex-direction: column; }
1286
- .md3 .vdp-gallery { order: 1; } .md3 .vdp-aside { order: 2; } .md3 .vdp-details { order: 3; }
1292
+ .md3 .vdp-gallery { order: 1; }
1293
+ /* mobile-only "first screen": identity + trust row BEFORE the finance card, highlights/
1294
+ condition tiles moved AFTER it (both split out of the desktop-only VdpHeaderCard,
1295
+ which stays bundled inside .vdp-gallery, hidden on mobile via hide-mobile). */
1296
+ .md3 .vdp-header-summary { order: 2; padding: 0 4px; margin-bottom: 4px; }
1297
+ .md3 .vdp-aside { order: 3; }
1298
+ .md3 .vdp-header-extras { order: 4; margin-top: 16px; }
1299
+ .md3 .vdp-details { order: 5; }
1287
1300
  .md3 .vdp-aside { position: static; }
1288
1301
  .md3 .price-card { position: static; }
1289
1302
  .md3 .fin-call { display: block; } /* tap-to-call restored on touch */
@@ -51,14 +51,10 @@ export interface VdpHeaderCardProps {
51
51
  t: TranslationFunction;
52
52
  }
53
53
 
54
- /**
55
- * One container holding the entire vehicle identity: headline + trim/power, the
56
- * dot-separated fact line, the hand-picked highlight chips, and the condition
57
- * (zustand) tiles footer. The gallery itself is composed by the page (W3b) — this
58
- * is just the textual header card that sits beneath it.
59
- */
60
- export const VdpHeaderCard: React.FunctionComponent<VdpHeaderCardProps> = ({ vehicle, t }) => {
61
- const { condition } = vehicle;
54
+ /** headline + trim/power + rating pill, then the dot-separated fact line — the vehicle's
55
+ * identity block. Split out so the mobile-only layout can show this BEFORE the finance
56
+ * card (see VdpTrustRow) while the highlights/condition tiles move after it. */
57
+ export const VdpHeaderSummary: React.FunctionComponent<VdpHeaderCardProps> = ({ vehicle, t }) => {
62
58
  const factLine: ReadonlyArray<readonly [string, string]> = [
63
59
  ['gauge', fmtKm(vehicle.km)],
64
60
  ['calendar', vehicle.reg],
@@ -66,15 +62,8 @@ export const VdpHeaderCard: React.FunctionComponent<VdpHeaderCardProps> = ({ veh
66
62
  ['fuel', stripParen(vehicle.fuel)],
67
63
  ['user', t('autobahn:header.owners', { owners: vehicle.owners, count: vehicle.owners })],
68
64
  ];
69
- // condition.hu === 'Neu' compares the RAW backend data value, not a display string — left as-is.
70
- const tiles: ReadonlyArray<readonly [string, string, string]> = [
71
- ['wrench', t('autobahn:header.huLabel'), condition.hu === 'Neu' ? t('autobahn:header.huNew') : t('autobahn:header.huUntil', { date: condition.hu })],
72
- ['shield-check', t('autobahn:header.accidentFree'), condition.unfallfrei ? t('autobahn:header.yes') : t('autobahn:header.no')],
73
- ['clipboard-check', t('autobahn:header.serviceHistory'), condition.scheckheft ? t('autobahn:header.yes') : t('autobahn:header.no')],
74
- ['award', t('autobahn:header.warranty'), condition.garantie],
75
- ];
76
65
  return (
77
- <div className="mcard" style={{ marginTop: 16, padding: '22px 24px 18px' }}>
66
+ <>
78
67
  <div className="vdp-headrow">
79
68
  <div style={{ minWidth: 0 }}>
80
69
  <h1 className="md-headline" style={{ margin: '0 0 4px' }}>
@@ -106,43 +95,112 @@ export const VdpHeaderCard: React.FunctionComponent<VdpHeaderCardProps> = ({ veh
106
95
  </span>
107
96
  ))}
108
97
  </div>
98
+ </>
99
+ );
100
+ };
109
101
 
110
- {/* hand-picked highlights — the single accent row of the header */}
111
- <div
112
- className="vdp-highlights"
113
- style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 8, marginTop: 14 }}
114
- >
115
- {vehicle.highlights.slice(0, 6).map((f) => (
116
- <span key={f} className="chip chip--filter chip--selected" style={{ cursor: 'default' }}>
117
- <Icon name="check" size={14} stroke={2.4} />
118
- {f}
119
- </span>
120
- ))}
121
- </div>
102
+ /** hand-picked highlights — the single accent row of the header. */
103
+ export const VdpHeaderHighlights: React.FunctionComponent<VdpHeaderCardProps> = ({ vehicle }) => (
104
+ <div className="vdp-highlights" style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 8 }}>
105
+ {vehicle.highlights.slice(0, 6).map((f) => (
106
+ <span key={f} className="chip chip--filter chip--selected" style={{ cursor: 'default' }}>
107
+ <Icon name="check" size={14} stroke={2.4} />
108
+ {f}
109
+ </span>
110
+ ))}
111
+ </div>
112
+ );
122
113
 
123
- {/* condition — footer row of the header card, divided by a hairline */}
124
- <div className="zustand-grid">
125
- {tiles.map(([ic, l, v]) => (
126
- <div key={l} className="zustand-tile">
127
- <Icon name={ic} size={19} color="var(--md-primary)" stroke={1.9} style={{ flex: 'none' }} />
128
- <span style={{ minWidth: 0 }}>
129
- <span
130
- className="md-label-sm"
131
- style={{ display: 'block', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}
132
- >
133
- {l}
134
- </span>
135
- <span className="md-title-sm" style={{ display: 'block' }}>
136
- {v}
137
- </span>
114
+ /** condition (zustand) tiles — TÜV/Unfallfrei/Scheckheft/Garantie. */
115
+ export const VdpHeaderCondition: React.FunctionComponent<VdpHeaderCardProps> = ({ vehicle, t }) => {
116
+ const { condition } = vehicle;
117
+ // condition.hu === 'Neu' compares the RAW backend data value, not a display string — left as-is.
118
+ const tiles: ReadonlyArray<readonly [string, string, string]> = [
119
+ ['wrench', t('autobahn:header.huLabel'), condition.hu === 'Neu' ? t('autobahn:header.huNew') : t('autobahn:header.huUntil', { date: condition.hu })],
120
+ ['shield-check', t('autobahn:header.accidentFree'), condition.unfallfrei ? t('autobahn:header.yes') : t('autobahn:header.no')],
121
+ ['clipboard-check', t('autobahn:header.serviceHistory'), condition.scheckheft ? t('autobahn:header.yes') : t('autobahn:header.no')],
122
+ ['award', t('autobahn:header.warranty'), condition.garantie],
123
+ ];
124
+ return (
125
+ <div className="zustand-grid">
126
+ {tiles.map(([ic, l, v]) => (
127
+ <div key={l} className="zustand-tile">
128
+ <Icon name={ic} size={19} color="var(--md-primary)" stroke={1.9} style={{ flex: 'none' }} />
129
+ <span style={{ minWidth: 0 }}>
130
+ <span
131
+ className="md-label-sm"
132
+ style={{ display: 'block', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}
133
+ >
134
+ {l}
138
135
  </span>
139
- </div>
140
- ))}
141
- </div>
136
+ <span className="md-title-sm" style={{ display: 'block' }}>
137
+ {v}
138
+ </span>
139
+ </span>
140
+ </div>
141
+ ))}
142
142
  </div>
143
143
  );
144
144
  };
145
145
 
146
+ /**
147
+ * One container holding the entire vehicle identity: headline + trim/power, the
148
+ * dot-separated fact line, the hand-picked highlight chips, and the condition
149
+ * (zustand) tiles footer. The gallery itself is composed by the page (W3b) — this
150
+ * is just the textual header card that sits beneath it. Desktop-only usage (mobile
151
+ * splits these 3 pieces across the "before/after finance card" layout — see the page).
152
+ */
153
+ export const VdpHeaderCard: React.FunctionComponent<VdpHeaderCardProps> = ({ vehicle, t }) => (
154
+ <div className="mcard" style={{ marginTop: 16, padding: '22px 24px 18px' }}>
155
+ <VdpHeaderSummary vehicle={vehicle} t={t} />
156
+ <div style={{ marginTop: 14 }}>
157
+ <VdpHeaderHighlights vehicle={vehicle} t={t} />
158
+ </div>
159
+ <VdpHeaderCondition vehicle={vehicle} t={t} />
160
+ </div>
161
+ );
162
+
163
+ export interface VdpTrustRowProps {
164
+ /** maps to carStore.isAvailable / state.isOnline — "Sofort verfügbar". */
165
+ available: boolean;
166
+ /** real totalCarImpCount from /service/user-actions/api/v1/stats/viewers — omit while loading. */
167
+ viewCount?: number;
168
+ /** real totalFavCount from the same endpoint — omit while loading. */
169
+ favCount?: number;
170
+ /** i18n function (drilled from the page). */
171
+ t: TranslationFunction;
172
+ }
173
+
174
+ /** mobile-only trust-signal row shown between the identity block and the finance card:
175
+ * availability · view count · favorite count · "Geprüft" (every auto.de listing is
176
+ * 130-point inspected, so this one is always shown, not conditional real data). */
177
+ export const VdpTrustRow: React.FunctionComponent<VdpTrustRowProps> = ({ available, viewCount, favCount, t }) => (
178
+ <div style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 6, marginTop: 10 }}>
179
+ {available && (
180
+ <span className="vdp-trust-chip">
181
+ <Icon name="check" size={13} stroke={2.4} color="var(--md-good)" />
182
+ {t('autobahn:availability.immediately')}
183
+ </span>
184
+ )}
185
+ {viewCount != null && (
186
+ <span className="vdp-trust-chip">
187
+ <Icon name="eye" size={13} />
188
+ {t('autobahn:activity.viewsCount', { count: viewCount })}
189
+ </span>
190
+ )}
191
+ {favCount != null && (
192
+ <span className="vdp-trust-chip">
193
+ <Icon name="heart" size={13} />
194
+ {t('autobahn:activity.favoritesCount', { count: favCount })}
195
+ </span>
196
+ )}
197
+ <span className="vdp-trust-chip">
198
+ <Icon name="shield-check" size={13} />
199
+ {t('autobahn:trust.checked')}
200
+ </span>
201
+ </div>
202
+ );
203
+
146
204
  // ---------------------------------------------------------------------------
147
205
  // Fahrzeugdaten spec table (mobile clamp + "Mehr anzeigen")
148
206
  // ---------------------------------------------------------------------------
package/vdp/index.ts CHANGED
@@ -23,6 +23,10 @@ export type {
23
23
  // Remaining presentational modules.
24
24
  export {
25
25
  VdpHeaderCard,
26
+ VdpHeaderSummary,
27
+ VdpHeaderHighlights,
28
+ VdpHeaderCondition,
29
+ VdpTrustRow,
26
30
  VdpSpecTable,
27
31
  EquipmentList,
28
32
  Co2Label,
@@ -41,6 +45,7 @@ export {
41
45
  } from './VdpModules';
42
46
  export type {
43
47
  VdpHeaderCardProps,
48
+ VdpTrustRowProps,
44
49
  VdpSpecTableProps,
45
50
  EquipmentListProps,
46
51
  Co2LabelProps,