@bytebrand/fe-ui-core-autobahn 1.0.113 → 1.0.114
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/CarCard.tsx +50 -3
- package/cards/CarRow.tsx +53 -4
- package/cards/cardParts.tsx +23 -4
- package/chrome/TopBar.tsx +8 -3
- package/locales/de/autobahn.json +6 -1
- package/locales/en/autobahn.json +6 -1
- package/package.json +1 -1
- package/theme/md3.css +116 -40
- package/ui/CardCarousel.tsx +12 -1
- package/vdp/VdpModules.tsx +4069 -4050
- package/vdp/VdpViewer.tsx +97 -12
package/vdp/VdpViewer.tsx
CHANGED
|
@@ -64,6 +64,27 @@ const MOBILE_BREAKPOINT = 860;
|
|
|
64
64
|
/** Landscape thumbnails (Figma node 424:7566 etc.) are 68px, not portrait's 64. */
|
|
65
65
|
const LS_THUMB_W = 68;
|
|
66
66
|
|
|
67
|
+
/** Safe-area helpers — keep overlay controls (especially the close button) clear of the notch /
|
|
68
|
+
* Dynamic Island / status bar (top), home indicator (bottom) and, in landscape, whichever side the
|
|
69
|
+
* notch rotates onto. `env(safe-area-inset-*)` only resolves to anything but 0px when the page's
|
|
70
|
+
* `<meta name="viewport">` declares `viewport-fit=cover` (see services/fe-main/app.html) — the
|
|
71
|
+
* 0px fallback inside every `env()` call here means both of these are complete no-ops (identical
|
|
72
|
+
* to the previous hardcoded numbers) on any browser/device that has no safe area to begin with, so
|
|
73
|
+
* there's no behavioural change for the common case, only where a notch/indicator is actually
|
|
74
|
+
* present. Two shapes, used in different structural contexts below:
|
|
75
|
+
* - `withInset` ADDS the inset to a design offset — for absolute-positioned controls (landscape's
|
|
76
|
+
* floating close/logo/counter/prev-next/zoom/etc.) whose `top`/`right`/… is a gap from the edge
|
|
77
|
+
* of the *safe* content area, which needs to grow by however much the notch/indicator eats into
|
|
78
|
+
* the raw viewport edge.
|
|
79
|
+
* - `atLeastInset` guarantees a MINIMUM gap instead — for padding on a bar that already paints its
|
|
80
|
+
* own edge-to-edge background (portrait's header/bottom-CTA bar, the photosheet), where a small
|
|
81
|
+
* or absent safe area should still leave the existing 12px breathing room, not shrink it.
|
|
82
|
+
*/
|
|
83
|
+
const withInset = (px: number, side: 'top' | 'right' | 'bottom' | 'left'): string =>
|
|
84
|
+
`calc(${px}px + env(safe-area-inset-${side}, 0px))`;
|
|
85
|
+
const atLeastInset = (px: number, side: 'top' | 'right' | 'bottom' | 'left'): string =>
|
|
86
|
+
`max(${px}px, env(safe-area-inset-${side}, 0px))`;
|
|
87
|
+
|
|
67
88
|
type Layout = 'portrait' | 'landscape' | 'desktop';
|
|
68
89
|
const layoutForWidth = (w: number): Layout => (w <= PORTRAIT_MAX ? 'portrait' : w <= MOBILE_BREAKPOINT ? 'landscape' : 'desktop');
|
|
69
90
|
|
|
@@ -719,7 +740,10 @@ const VdpViewer: React.FunctionComponent<VdpViewerProps> = ({ images, start = 0,
|
|
|
719
740
|
display: 'flex',
|
|
720
741
|
alignItems: 'flex-start',
|
|
721
742
|
justifyContent: 'space-between',
|
|
722
|
-
|
|
743
|
+
// safe-area-aware: this bar sits flush against the top of the fullscreen overlay
|
|
744
|
+
// (the notch/Dynamic Island/status bar's exact territory), and its close button is
|
|
745
|
+
// the primary control the "always tappable" acceptance criterion is about.
|
|
746
|
+
padding: `${atLeastInset(12, 'top')} ${atLeastInset(12, 'right')} 10px ${atLeastInset(12, 'left')}`,
|
|
723
747
|
background: 'rgba(6,11,18,0.8)',
|
|
724
748
|
borderBottom: '1px solid rgba(255,255,255,0.12)',
|
|
725
749
|
}}
|
|
@@ -730,7 +754,10 @@ const VdpViewer: React.FunctionComponent<VdpViewerProps> = ({ images, start = 0,
|
|
|
730
754
|
ariaLabel={t('autobahn:viewer.closeLabel')}
|
|
731
755
|
onClick={onClose}
|
|
732
756
|
/>
|
|
733
|
-
|
|
757
|
+
{/* absolutely positioned (unlike its close/call siblings), so it does NOT
|
|
758
|
+
automatically shift down with the header's safe-area padding above — needs its
|
|
759
|
+
own top offset. */}
|
|
760
|
+
<div style={{ position: 'absolute', left: '50%', top: withInset(10, 'top'), transform: 'translateX(-50%)', width: 160, height: 71 }}>
|
|
734
761
|
<LogoMark />
|
|
735
762
|
</div>
|
|
736
763
|
{onCall ? (
|
|
@@ -914,7 +941,23 @@ const VdpViewer: React.FunctionComponent<VdpViewerProps> = ({ images, start = 0,
|
|
|
914
941
|
{/* full-width bottom bar — "Jetzt Anfragen" (primary) + "Anrufen" (dark), Figma nodes
|
|
915
942
|
424:8128-8134. */}
|
|
916
943
|
{(onEmail || onCall) && (
|
|
917
|
-
|
|
944
|
+
// safe-area-aware: last item before the true bottom edge — height is deliberately
|
|
945
|
+
// NOT fixed anymore (was 82px) so the extra bottom padding on a home-indicator device
|
|
946
|
+
// grows the bar instead of squeezing its 56px-tall buttons; `alignItems: 'center'`
|
|
947
|
+
// keeps them centered in that now-variable padding box.
|
|
948
|
+
<div
|
|
949
|
+
style={{
|
|
950
|
+
display: 'flex',
|
|
951
|
+
alignItems: 'center',
|
|
952
|
+
gap: THUMB_GAP,
|
|
953
|
+
paddingTop: 12,
|
|
954
|
+
paddingRight: atLeastInset(12, 'right'),
|
|
955
|
+
paddingBottom: atLeastInset(12, 'bottom'),
|
|
956
|
+
paddingLeft: atLeastInset(12, 'left'),
|
|
957
|
+
background: '#797979',
|
|
958
|
+
flex: 'none',
|
|
959
|
+
}}
|
|
960
|
+
>
|
|
918
961
|
{onEmail && (
|
|
919
962
|
<button
|
|
920
963
|
type="button"
|
|
@@ -1035,25 +1078,54 @@ const VdpViewer: React.FunctionComponent<VdpViewerProps> = ({ images, start = 0,
|
|
|
1035
1078
|
brand-blue/gradient mark (verified against its raw export), i.e. the SAME svg this
|
|
1036
1079
|
codebase already ships as `assets/autode-logo.svg` — reused directly, not portrait's
|
|
1037
1080
|
white LogoMark (landscape has no dark bar behind it to need a white variant). */}
|
|
1038
|
-
<div
|
|
1081
|
+
<div
|
|
1082
|
+
style={{
|
|
1083
|
+
position: 'absolute',
|
|
1084
|
+
left: withInset(4, 'left'),
|
|
1085
|
+
top: withInset(10, 'top'),
|
|
1086
|
+
width: 93,
|
|
1087
|
+
height: 42,
|
|
1088
|
+
display: 'flex',
|
|
1089
|
+
alignItems: 'center',
|
|
1090
|
+
justifyContent: 'center',
|
|
1091
|
+
}}
|
|
1092
|
+
>
|
|
1039
1093
|
<img src={logoUrl} alt="auto.de" style={{ width: 74, height: 21.46, display: 'block' }} />
|
|
1040
1094
|
</div>
|
|
1041
1095
|
|
|
1042
1096
|
{/* counter pill — Figma node 424:9277 "Counter / BG" */}
|
|
1043
|
-
<div style={lsCounterStyle} aria-label={t('autobahn:viewer.counterLabel', { current: i + 1, total: n })}>
|
|
1097
|
+
<div style={{ ...lsCounterStyle, top: withInset(12, 'top') }} aria-label={t('autobahn:viewer.counterLabel', { current: i + 1, total: n })}>
|
|
1044
1098
|
<span className="num">
|
|
1045
1099
|
{i + 1} | {n}
|
|
1046
1100
|
</span>
|
|
1047
1101
|
</div>
|
|
1048
1102
|
|
|
1049
|
-
{/* close — Figma nodes 424:7556/7557, no caption text in this layout
|
|
1050
|
-
|
|
1103
|
+
{/* close — Figma nodes 424:7556/7557, no caption text in this layout. Rotating the
|
|
1104
|
+
phone moves the notch/Dynamic Island to whichever side is now "up" (device-relative
|
|
1105
|
+
left/right, not a fixed screen side) — safe-area-aware on both axes since this sits
|
|
1106
|
+
at the true top-right corner, the corner most likely to overlap it. */}
|
|
1107
|
+
<LsCircleButton
|
|
1108
|
+
icon={<IconClose />}
|
|
1109
|
+
ariaLabel={t('autobahn:viewer.closeLabel')}
|
|
1110
|
+
onClick={onClose}
|
|
1111
|
+
style={{ right: withInset(12, 'right'), top: withInset(12, 'top') }}
|
|
1112
|
+
/>
|
|
1051
1113
|
|
|
1052
1114
|
{/* prev/next — Figma nodes 424:7560/7561 and 424:7563/7564 */}
|
|
1053
1115
|
{n > 1 && (
|
|
1054
1116
|
<>
|
|
1055
|
-
<LsCircleButton
|
|
1056
|
-
|
|
1117
|
+
<LsCircleButton
|
|
1118
|
+
icon={<IconChevronLeft />}
|
|
1119
|
+
ariaLabel={t('autobahn:viewer.previousLabel')}
|
|
1120
|
+
onClick={() => go(-1)}
|
|
1121
|
+
style={{ left: withInset(12, 'left'), top: 126 }}
|
|
1122
|
+
/>
|
|
1123
|
+
<LsCircleButton
|
|
1124
|
+
icon={<IconChevronRight />}
|
|
1125
|
+
ariaLabel={t('autobahn:viewer.nextLabel')}
|
|
1126
|
+
onClick={() => go(1)}
|
|
1127
|
+
style={{ right: withInset(12, 'right'), top: 126 }}
|
|
1128
|
+
/>
|
|
1057
1129
|
</>
|
|
1058
1130
|
)}
|
|
1059
1131
|
|
|
@@ -1062,8 +1134,8 @@ const VdpViewer: React.FunctionComponent<VdpViewerProps> = ({ images, start = 0,
|
|
|
1062
1134
|
<div
|
|
1063
1135
|
style={{
|
|
1064
1136
|
position: 'absolute',
|
|
1065
|
-
right: 12,
|
|
1066
|
-
bottom: 12,
|
|
1137
|
+
right: withInset(12, 'right'),
|
|
1138
|
+
bottom: withInset(12, 'bottom'),
|
|
1067
1139
|
width: 48,
|
|
1068
1140
|
height: 150,
|
|
1069
1141
|
borderRadius: 'var(--md-r-full)',
|
|
@@ -1126,7 +1198,20 @@ const VdpViewer: React.FunctionComponent<VdpViewerProps> = ({ images, start = 0,
|
|
|
1126
1198
|
align the way the design does, without hardcoding a container width that would break
|
|
1127
1199
|
across the 693-860 range. `right: 81` keeps this band clear of the zoom/360 column
|
|
1128
1200
|
(12 right-margin + 48 wide + the 21px gap Figma leaves before it = 81). */}
|
|
1129
|
-
<div
|
|
1201
|
+
<div
|
|
1202
|
+
style={{
|
|
1203
|
+
position: 'absolute',
|
|
1204
|
+
left: withInset(12, 'left'),
|
|
1205
|
+
// 81 = 12 right-margin + 48 zoom-column width + 21 Figma gap before it; add the
|
|
1206
|
+
// same right inset the zoom/360 column itself got above so this band still clears
|
|
1207
|
+
// it once that column has shifted in from a notched edge.
|
|
1208
|
+
right: withInset(81, 'right'),
|
|
1209
|
+
bottom: withInset(12, 'bottom'),
|
|
1210
|
+
height: 50,
|
|
1211
|
+
display: 'flex',
|
|
1212
|
+
alignItems: 'center',
|
|
1213
|
+
}}
|
|
1214
|
+
>
|
|
1130
1215
|
{(() => {
|
|
1131
1216
|
const overflow = images.length > lsFitCount;
|
|
1132
1217
|
const visibleCount = overflow ? lsFitCount - 1 : images.length;
|