@bytebrand/fe-ui-core-autobahn 1.0.44 → 1.0.45

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.
@@ -27,10 +27,29 @@ import { TranslationFunction } from '../types';
27
27
  import { Button, Icon, Spec, StatChip, Stars, Switch } from '../ui';
28
28
  import { CO2_CLASSES, CO2_COLORS, TIER_LABELS, TIER_KEYS, TIER_COLORS } from '../constants';
29
29
  import { defaultVdpSpecs, EquipmentGroups, PriceRatingBands, VehicleDetail, VdpSpec, VdpReview } from './types';
30
- import { CalcRow, EvalBar, expanderBtnStyle, FinanceMode, Slider, TermChips } from './FinanceCalculator';
30
+ import { CalcRow, DocCheckIcon, EvalBar, expanderBtnStyle, FinanceMode, Slider, TermChips } from './FinanceCalculator';
31
31
 
32
32
  const eur = (n: number): string => n.toLocaleString('de-DE');
33
33
 
34
+ /** Figma "check with open arc" glyph (modern lucide circle-check-big; the bundled lucide
35
+ * 0.288 only ships the closed-circle variant, so the two paths are inlined). */
36
+ const ArcCheckIcon: React.FunctionComponent<{ size?: number; color?: string }> = ({ size = 20, color = 'currentColor' }) => (
37
+ <svg
38
+ width={size}
39
+ height={size}
40
+ viewBox="0 0 24 24"
41
+ fill="none"
42
+ stroke={color}
43
+ strokeWidth={2.4}
44
+ strokeLinecap="round"
45
+ strokeLinejoin="round"
46
+ style={{ flex: 'none' }}
47
+ >
48
+ <path d="M21.801 10A10 10 0 1 1 17 3.335" />
49
+ <path d="m9 11 3 3L22 4" />
50
+ </svg>
51
+ );
52
+
34
53
  /** mileage → 'X.XXX km' (de-DE grouping). */
35
54
  function fmtKm(n: number): string {
36
55
  return `${n.toLocaleString('de-DE')} km`;
@@ -658,7 +677,7 @@ export const PriceHistory: React.FunctionComponent<PriceHistoryProps> = ({ vehic
658
677
  background: 'var(--md-good-container)',
659
678
  }}
660
679
  >
661
- <Icon name="check-circle" size={18} color="var(--md-good)" />
680
+ <ArcCheckIcon size={20} color="var(--md-good)" />
662
681
  <span style={{ fontSize: 14, fontWeight: 700, color: 'var(--md-good)' }}>
663
682
  {t('autobahn:marketValue.belowAverage', { rating: t('autobahn:tier.' + vehicle.rating), amount: eur(delta) })}
664
683
  </span>
@@ -1250,7 +1269,8 @@ export const VdpBottomBar: React.FunctionComponent<VdpBottomBarProps> = ({
1250
1269
  )}
1251
1270
  </div>
1252
1271
  <div className="bb-cta-row">
1253
- <Button variant="filled" icon="file-check" style={{ flex: 1, minWidth: 0 }} onClick={onSecure}>
1272
+ <Button variant="filled" style={{ flex: 1, minWidth: 0 }} onClick={onSecure}>
1273
+ <DocCheckIcon />
1254
1274
  {t('autobahn:bottom.secureOffer')}
1255
1275
  </Button>
1256
1276
  {/* padding:0 — icon-only button (same fix as the sheet's phone button) */}
@@ -1679,14 +1699,16 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
1679
1699
 
1680
1700
  <div className="fin-price-grid">
1681
1701
  <div className="fin-rate-main">
1682
- {/* Figma Kaufen sheet: struck list price on its own line ABOVE the big figure */}
1683
- {isCash && listPrice != null && listPrice > price && (
1684
- <s className="num fin-rate-old" style={{ display: 'block' }}>{eur(listPrice)} €</s>
1685
- )}
1686
1702
  <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
1687
1703
  <span className="num" style={{ fontSize: 34, fontWeight: 800, color: 'var(--md-primary)', letterSpacing: '-1px' }}>
1688
1704
  {isCash ? eur(price) : eur(Math.round(monthly))} €
1689
1705
  </span>
1706
+ {/* Figma Kaufen sheet: struck list price top-right beside the big figure */}
1707
+ {isCash && listPrice != null && listPrice > price && (
1708
+ <span className="fin-rate-unit" style={{ alignSelf: 'flex-start', paddingTop: 2 }}>
1709
+ <s className="num fin-rate-old">{eur(listPrice)} €</s>
1710
+ </span>
1711
+ )}
1690
1712
  {!isCash && (
1691
1713
  <>
1692
1714
  <span className="md-label-sm" style={{ fontWeight: 600, alignSelf: 'flex-end', paddingBottom: 5 }}>
@@ -1733,7 +1755,8 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
1733
1755
  />
1734
1756
  )}
1735
1757
  <div style={{ display: 'flex', gap: 10 }}>
1736
- <Button variant="filled" size="lg" icon="file-check" style={{ flex: 1 }} onClick={onSecure}>
1758
+ <Button variant="filled" size="lg" style={{ flex: 1 }} onClick={onSecure}>
1759
+ <DocCheckIcon />
1737
1760
  {t('autobahn:finance.secureOffer')}
1738
1761
  </Button>
1739
1762
  {/* padding:0 — mbtn--lg's 30px side padding left no room for the icon at width 52 */}
@@ -1814,7 +1837,8 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
1814
1837
  )}
1815
1838
 
1816
1839
  <div style={{ display: 'flex', gap: 10 }}>
1817
- <Button variant="filled" size="lg" icon="file-check" style={{ flex: 1 }} onClick={onSecure}>
1840
+ <Button variant="filled" size="lg" style={{ flex: 1 }} onClick={onSecure}>
1841
+ <DocCheckIcon />
1818
1842
  {t('autobahn:finance.requestRate')}
1819
1843
  </Button>
1820
1844
  {/* padding:0 — mbtn--lg's 30px side padding left no room for the icon at width 52 */}
@@ -2091,13 +2115,14 @@ export const VdpEnquiry: React.FunctionComponent<VdpEnquiryProps> = ({
2091
2115
  const emailOk = /.+@.+\..+/.test(email);
2092
2116
  const phoneDigits = (areaCode + phoneNum).replace(/\D/g, '');
2093
2117
  const phoneOk = phoneDigits.length >= 6;
2094
- // Figma round 12: the required-* follows the chosen contact channel — E-Mail* when email is
2095
- // preferred, Telefonnummer* when phone is; names + consent are always required.
2118
+ // Round 13 (explicit spec): PHONE is always required (the * sits on the country-code field),
2119
+ // e-mail is always optional (validated only when filled); names + consent always required.
2096
2120
  const valid =
2097
2121
  firstName.trim().length > 0 &&
2098
2122
  lastName.trim().length > 0 &&
2099
2123
  consent &&
2100
- (pref === 'phone' ? phoneOk : emailOk);
2124
+ phoneOk &&
2125
+ (email.trim().length === 0 || emailOk);
2101
2126
 
2102
2127
  const submit = async (): Promise<void> => {
2103
2128
  if (!valid || sending) return;
@@ -2213,8 +2238,8 @@ export const VdpEnquiry: React.FunctionComponent<VdpEnquiryProps> = ({
2213
2238
  <input
2214
2239
  style={enquiryInput}
2215
2240
  type="email"
2216
- // the required-* follows the preferred channel (Figma round 12)
2217
- placeholder={pref === 'email' ? t('autobahn:enquiry.form.emailPlaceholder') : t('autobahn:enquiry.form.emailPlaceholder').replace(/\*$/, '')}
2241
+ // e-mail is always optional (round 13) — no required-*
2242
+ placeholder={t('autobahn:enquiry.form.emailPlaceholder').replace(/\*$/, '')}
2218
2243
  value={email}
2219
2244
  onChange={(e) => setEmail(e.target.value)}
2220
2245
  aria-label={t('autobahn:enquiry.contactMethod.email')}
@@ -2222,7 +2247,8 @@ export const VdpEnquiry: React.FunctionComponent<VdpEnquiryProps> = ({
2222
2247
  </div>
2223
2248
  {/* Land (+49 fixed) · Vorwahl · Telefonnummer — outlined float-label row (Figma) */}
2224
2249
  <div className="enq-phone-row" style={{ marginTop: 16 }}>
2225
- <FloatField label={t('autobahn:enquiry.form.countryLabel')}>
2250
+ {/* phone is always required — the * sits on the country-code field (round 13) */}
2251
+ <FloatField label={t('autobahn:enquiry.form.countryLabel') + '*'}>
2226
2252
  <span className="enq-country num">
2227
2253
  +49
2228
2254
  <Icon name="chevron-down" size={14} color="var(--md-on-surface-variant)" />
@@ -2236,7 +2262,7 @@ export const VdpEnquiry: React.FunctionComponent<VdpEnquiryProps> = ({
2236
2262
  aria-label={t('autobahn:enquiry.form.areaCodeLabel')}
2237
2263
  />
2238
2264
  </FloatField>
2239
- <FloatField label={t('autobahn:enquiry.form.phoneNumberLabel') + (pref === 'phone' ? '*' : '')}>
2265
+ <FloatField label={t('autobahn:enquiry.form.phoneNumberLabel')}>
2240
2266
  <input
2241
2267
  type="tel"
2242
2268
  value={phoneNum}