@wasto/cms-components 1.5.16 → 1.5.19

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": "@wasto/cms-components",
3
- "version": "1.5.16",
3
+ "version": "1.5.19",
4
4
  "description": "Wast'O CMS React Native components",
5
5
  "main": "./src/index.ts",
6
6
  "module": "./src/index.ts",
@@ -92,7 +92,7 @@
92
92
  "react-native-reanimated": "~4.1.1",
93
93
  "tsup": "^8.5.1",
94
94
  "typescript": "^6.0.2",
95
- "@wasto/sdk": "1.0.5"
95
+ "@wasto/sdk": "1.0.6"
96
96
  },
97
97
  "keywords": [],
98
98
  "author": "",
@@ -1,5 +1,5 @@
1
1
  import type { ReactNode } from "react";
2
- import { Modal, Pressable, ScrollView, StyleSheet, Text, View } from "react-native";
2
+ import { Image, Modal, Pressable, ScrollView, StyleSheet, Text, View } from "react-native";
3
3
  import { X } from "lucide-react-native";
4
4
  import { useAnnouncements } from "../../contexts/AnnouncementsContext";
5
5
  import { useTheme } from "../../contexts/ThemeContext";
@@ -9,6 +9,7 @@ import { getContrastColor } from "../../helpers/getContrastColor";
9
9
  import { Button } from "../button/Button";
10
10
  import { hapticLight } from "../../helpers/haptics";
11
11
  import { SPACE } from "../../helpers/layout";
12
+ import { useOptionalAppNavigation } from "../../navigation/NavigationContext";
12
13
 
13
14
  /** Oversized circle (relative to sheet width) → gentle convex arc. */
14
15
  const CIRCLE_WIDTH_PCT = "500%";
@@ -22,16 +23,25 @@ export function AnnouncementModal(): ReactNode {
22
23
  const t = resolveTheme(theme);
23
24
  const topInset = useSafeAreaPadding(["top"]);
24
25
  const bottomInset = useSafeAreaPadding(["bottom"]);
26
+ const navigation = useOptionalAppNavigation();
25
27
 
26
28
  const visible = forceVisible && !!announcement;
27
29
  const contrast = getContrastColor(theme.colors.primary);
28
30
  const sheetColor = theme.colors.surface;
31
+ const redirectHref = announcement?.redirectHref?.trim() || null;
29
32
 
30
33
  const handleDismiss = async () => {
31
34
  await hapticLight();
32
35
  await dismiss();
33
36
  };
34
37
 
38
+ const handleOpen = async () => {
39
+ if (!redirectHref) return;
40
+ await hapticLight();
41
+ navigation?.push(redirectHref);
42
+ await dismiss();
43
+ };
44
+
35
45
  return (
36
46
  <Modal visible={visible} animationType="slide" onRequestClose={handleDismiss}>
37
47
  <View style={[styles.root, { backgroundColor: theme.colors.primary }]}>
@@ -93,16 +103,35 @@ export function AnnouncementModal(): ReactNode {
93
103
  bounces={false}
94
104
  showsVerticalScrollIndicator={false}
95
105
  >
106
+ {announcement?.imageUrl ? (
107
+ <Image
108
+ source={{ uri: announcement.imageUrl }}
109
+ style={styles.image}
110
+ resizeMode="cover"
111
+ accessibilityLabel="Illustration de l'annonce"
112
+ />
113
+ ) : null}
96
114
  <Text style={[styles.bodyText, { color: t.colors.muted }]}>
97
115
  {announcement?.body}
98
116
  </Text>
99
117
  </ScrollView>
100
- <Button
101
- onPress={handleDismiss}
102
- style={[styles.cta, { borderRadius: t.radii.pill }]}
103
- >
104
- C'est compris !
105
- </Button>
118
+ <View style={styles.actions}>
119
+ {redirectHref ? (
120
+ <Button
121
+ onPress={handleOpen}
122
+ style={[styles.cta, { borderRadius: t.radii.pill }]}
123
+ >
124
+ Ouvrir
125
+ </Button>
126
+ ) : null}
127
+ <Button
128
+ onPress={handleDismiss}
129
+ variant={redirectHref ? "outline" : "primary"}
130
+ style={[styles.cta, { borderRadius: t.radii.pill }]}
131
+ >
132
+ C'est compris !
133
+ </Button>
134
+ </View>
106
135
  </View>
107
136
  </View>
108
137
  </View>
@@ -172,10 +201,20 @@ const styles = StyleSheet.create({
172
201
  bodyScrollContent: {
173
202
  paddingBottom: 8,
174
203
  },
204
+ image: {
205
+ width: "100%",
206
+ aspectRatio: 16 / 9,
207
+ borderRadius: 12,
208
+ marginBottom: 16,
209
+ },
175
210
  bodyText: {
176
211
  fontSize: 16,
177
212
  lineHeight: 24,
178
213
  },
214
+ actions: {
215
+ gap: 12,
216
+ alignSelf: "stretch",
217
+ },
179
218
  cta: {
180
219
  height: 52,
181
220
  alignSelf: "stretch",
@@ -1,6 +1,10 @@
1
1
  import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react-native";
2
- import { useCollectionCalendar, type WasteTypeInfoForDay } from "../../hooks/useCollectionCalendar";
3
- import { StyleSheet, Text, View } from "react-native";
2
+ import {
3
+ useCollectionCalendar,
4
+ type CalendarDayCell,
5
+ type WasteTypeInfoForDay,
6
+ } from "../../hooks/useCollectionCalendar";
7
+ import { Pressable, StyleSheet, Text, View } from "react-native";
4
8
  import Animated, { FadeIn, FadeOut } from "react-native-reanimated";
5
9
  import { NavButton } from "../nav-button/NavButton";
6
10
  import { ContentInset } from "../content-inset/ContentInset";
@@ -11,6 +15,7 @@ import { NextCollection } from "../next-collection/NextCollection";
11
15
  import { hapticSelection } from "../../helpers/haptics";
12
16
  import { SPACE } from "../../helpers/layout";
13
17
  import { useState, type ReactNode } from "react";
18
+ import { CollectionDayModal } from "./CollectionDayModal";
14
19
 
15
20
  export type CollectionCalendarProps = {};
16
21
 
@@ -47,6 +52,7 @@ export function CollectionCalendar({}: CollectionCalendarProps): ReactNode {
47
52
  weekDays,
48
53
  weeks,
49
54
  monthLabel,
55
+ wasteTypes,
50
56
  goToNextMonth,
51
57
  goToPrevMonth,
52
58
  } = useCollectionCalendar();
@@ -54,6 +60,7 @@ export function CollectionCalendar({}: CollectionCalendarProps): ReactNode {
54
60
  const t = resolveTheme(theme);
55
61
  const themedStyles = createThemedStyles(theme);
56
62
  const [monthKey, setMonthKey] = useState(0);
63
+ const [selectedDay, setSelectedDay] = useState<CalendarDayCell | null>(null);
57
64
  const today = new Date();
58
65
  const todayDay = today.getDate();
59
66
  const todayMonth = today.getMonth();
@@ -62,14 +69,20 @@ export function CollectionCalendar({}: CollectionCalendarProps): ReactNode {
62
69
 
63
70
  const prev = async () => {
64
71
  await hapticSelection();
72
+ setSelectedDay(null);
65
73
  goToPrevMonth();
66
74
  setMonthKey((k) => k - 1);
67
75
  };
68
76
  const next = async () => {
69
77
  await hapticSelection();
78
+ setSelectedDay(null);
70
79
  goToNextMonth();
71
80
  setMonthKey((k) => k + 1);
72
81
  };
82
+ const openDay = async (day: CalendarDayCell) => {
83
+ await hapticSelection();
84
+ setSelectedDay(day);
85
+ };
73
86
 
74
87
  return (
75
88
  <View style={styles.collectionCalendar}>
@@ -128,9 +141,19 @@ export function CollectionCalendar({}: CollectionCalendarProps): ReactNode {
128
141
  day.year === todayYear;
129
142
  const isException = day.exceptions.length > 0;
130
143
 
144
+ const collectionCount = day.wasteTypesForDay.length;
145
+ const dayLabel = `${day.day} ${monthLabel}${
146
+ collectionCount > 0
147
+ ? `, ${collectionCount} collecte${collectionCount > 1 ? "s" : ""}`
148
+ : ", aucune collecte"
149
+ }`;
150
+
131
151
  return (
132
- <View
152
+ <Pressable
133
153
  key={cellIndex}
154
+ onPress={() => void openDay(day)}
155
+ accessibilityRole="button"
156
+ accessibilityLabel={dayLabel}
134
157
  style={[
135
158
  styles.calendarBodyCell,
136
159
  !day.inCurrentMonth && styles.notCurrentMonth,
@@ -169,7 +192,7 @@ export function CollectionCalendar({}: CollectionCalendarProps): ReactNode {
169
192
  {day.day}
170
193
  </Text>
171
194
  <WasteTypePins types={day.wasteTypesForDay} />
172
- </View>
195
+ </Pressable>
173
196
  );
174
197
  })}
175
198
  </View>
@@ -178,6 +201,11 @@ export function CollectionCalendar({}: CollectionCalendarProps): ReactNode {
178
201
  </Animated.View>
179
202
 
180
203
  <NextCollection showCalendarCta={false} />
204
+ <CollectionDayModal
205
+ day={selectedDay}
206
+ wasteTypes={wasteTypes}
207
+ onClose={() => setSelectedDay(null)}
208
+ />
181
209
  </View>
182
210
  );
183
211
  }
@@ -0,0 +1,286 @@
1
+ import { useState, type ReactNode } from "react";
2
+ import { Modal, Pressable, ScrollView, StyleSheet, Text, View } from "react-native";
3
+ import { Image } from "expo-image";
4
+ import { X } from "lucide-react-native";
5
+ import type { CalendarDayCell } from "../../hooks/useCollectionCalendar";
6
+ import type { WasteType } from "../../helpers/nextCollection";
7
+ import { useTheme } from "../../contexts/ThemeContext";
8
+ import { useWasto } from "../../contexts/WastoContext";
9
+ import { cardStyle, resolveTheme } from "../../helpers/themeStyles";
10
+ import { resolveApiAssetUrl } from "../../helpers/resolveApiAssetUrl";
11
+ import { hapticLight, hapticSelection } from "../../helpers/haptics";
12
+ import { SPACE } from "../../helpers/layout";
13
+ import { useSafeAreaPadding } from "../../helpers/useSafeAreaPadding";
14
+ import { ConsignesModal } from "../consignes-modal/ConsignesModal";
15
+
16
+ export type CollectionDayModalProps = {
17
+ day: CalendarDayCell | null;
18
+ wasteTypes: WasteType[];
19
+ onClose: () => void;
20
+ };
21
+
22
+ function formatDayTitle(year: number, month: number, day: number): string {
23
+ const label = new Date(year, month, day).toLocaleDateString("fr-FR", {
24
+ weekday: "long",
25
+ day: "numeric",
26
+ month: "long",
27
+ });
28
+ return label.charAt(0).toUpperCase() + label.slice(1);
29
+ }
30
+
31
+ export function CollectionDayModal({
32
+ day,
33
+ wasteTypes,
34
+ onClose,
35
+ }: CollectionDayModalProps): ReactNode {
36
+ const { theme } = useTheme();
37
+ const t = resolveTheme(theme);
38
+ const { apiUrl } = useWasto();
39
+ const bottomInset = useSafeAreaPadding(["bottom"]);
40
+ const [consignesId, setConsignesId] = useState<string | null>(null);
41
+
42
+ const close = async () => {
43
+ await hapticLight();
44
+ setConsignesId(null);
45
+ onClose();
46
+ };
47
+
48
+ const rows = (day?.wasteTypesForDay ?? []).map((info) => {
49
+ const wasteType = wasteTypes.find((wt) => wt.id === info.wasteTypeId);
50
+ return {
51
+ id: info.wasteTypeId,
52
+ name: wasteType?.name?.trim() || "Collecte",
53
+ color: info.color,
54
+ imageUrl: resolveApiAssetUrl(apiUrl, wasteType?.imageUrl),
55
+ isException: Boolean(info.isException),
56
+ };
57
+ });
58
+ const hasException = (day?.exceptions.length ?? 0) > 0;
59
+
60
+ return (
61
+ <>
62
+ <Modal
63
+ visible={day != null && !consignesId}
64
+ transparent
65
+ animationType="fade"
66
+ onRequestClose={() => {
67
+ void close();
68
+ }}
69
+ >
70
+ <View style={styles.root}>
71
+ <Pressable
72
+ style={styles.backdrop}
73
+ onPress={() => {
74
+ void close();
75
+ }}
76
+ accessibilityLabel="Fermer le détail du jour"
77
+ />
78
+ <View
79
+ style={[
80
+ styles.sheet,
81
+ cardStyle(theme),
82
+ { borderRadius: t.radii.lg, backgroundColor: theme.colors.surface },
83
+ bottomInset,
84
+ ]}
85
+ >
86
+ <View style={styles.head}>
87
+ <View style={styles.headText}>
88
+ <Text style={[styles.eyebrow, { color: t.colors.muted }]}>
89
+ COLLECTE
90
+ </Text>
91
+ <Text style={[styles.title, { color: theme.colors.onSurface }]}>
92
+ {day
93
+ ? formatDayTitle(day.year, day.month, day.day)
94
+ : ""}
95
+ </Text>
96
+ </View>
97
+ <Pressable
98
+ onPress={() => {
99
+ void close();
100
+ }}
101
+ style={[
102
+ styles.closeBtn,
103
+ {
104
+ backgroundColor: theme.colors.background,
105
+ borderRadius: t.radii.pill,
106
+ },
107
+ ]}
108
+ accessibilityRole="button"
109
+ accessibilityLabel="Fermer"
110
+ >
111
+ <X size={18} color={theme.colors.onSurface} />
112
+ </Pressable>
113
+ </View>
114
+
115
+ {hasException ? (
116
+ <Text style={[styles.exception, { color: theme.colors.error }]}>
117
+ Une exception de collecte s’applique à cette date.
118
+ </Text>
119
+ ) : null}
120
+
121
+ {rows.length === 0 ? (
122
+ <Text style={[styles.empty, { color: t.colors.muted }]}>
123
+ Aucune collecte prévue ce jour.
124
+ </Text>
125
+ ) : (
126
+ <ScrollView
127
+ style={styles.list}
128
+ contentContainerStyle={styles.listContent}
129
+ bounces={false}
130
+ >
131
+ {rows.map((row) => (
132
+ <Pressable
133
+ key={row.id}
134
+ onPress={() => {
135
+ void (async () => {
136
+ await hapticSelection();
137
+ setConsignesId(row.id);
138
+ })();
139
+ }}
140
+ style={[
141
+ styles.row,
142
+ {
143
+ backgroundColor: theme.colors.background,
144
+ borderRadius: t.radii.md,
145
+ },
146
+ ]}
147
+ accessibilityRole="button"
148
+ accessibilityLabel={row.name}
149
+ >
150
+ {row.imageUrl ? (
151
+ <Image
152
+ source={row.imageUrl}
153
+ style={styles.thumb}
154
+ contentFit="contain"
155
+ />
156
+ ) : (
157
+ <View
158
+ style={[
159
+ styles.swatch,
160
+ { backgroundColor: row.color },
161
+ ]}
162
+ />
163
+ )}
164
+ <View style={styles.rowText}>
165
+ <Text
166
+ style={[
167
+ styles.rowTitle,
168
+ { color: theme.colors.onSurface },
169
+ ]}
170
+ >
171
+ {row.name}
172
+ </Text>
173
+ <Text style={[styles.rowHint, { color: t.colors.muted }]}>
174
+ {row.isException
175
+ ? "Reprise après exception"
176
+ : "Voir les consignes"}
177
+ </Text>
178
+ </View>
179
+ <View
180
+ style={[styles.pin, { backgroundColor: row.color }]}
181
+ />
182
+ </Pressable>
183
+ ))}
184
+ </ScrollView>
185
+ )}
186
+ </View>
187
+ </View>
188
+ </Modal>
189
+ <ConsignesModal
190
+ visible={!!consignesId}
191
+ wasteTypeId={consignesId ?? ""}
192
+ onClose={() => setConsignesId(null)}
193
+ />
194
+ </>
195
+ );
196
+ }
197
+
198
+ const styles = StyleSheet.create({
199
+ root: {
200
+ flex: 1,
201
+ justifyContent: "flex-end",
202
+ },
203
+ backdrop: {
204
+ ...StyleSheet.absoluteFillObject,
205
+ backgroundColor: "rgba(15, 23, 42, 0.45)",
206
+ },
207
+ sheet: {
208
+ marginHorizontal: SPACE.pageX,
209
+ marginBottom: SPACE.sectionY,
210
+ padding: SPACE.cardPad,
211
+ gap: 12,
212
+ maxHeight: "70%",
213
+ },
214
+ head: {
215
+ flexDirection: "row",
216
+ alignItems: "flex-start",
217
+ gap: 12,
218
+ },
219
+ headText: {
220
+ flex: 1,
221
+ gap: 2,
222
+ },
223
+ eyebrow: {
224
+ fontSize: 12,
225
+ fontWeight: "700",
226
+ letterSpacing: 0.6,
227
+ },
228
+ title: {
229
+ fontSize: 20,
230
+ fontWeight: "700",
231
+ },
232
+ closeBtn: {
233
+ width: SPACE.iconHit,
234
+ height: SPACE.iconHit,
235
+ alignItems: "center",
236
+ justifyContent: "center",
237
+ },
238
+ exception: {
239
+ fontSize: 14,
240
+ fontWeight: "600",
241
+ lineHeight: 20,
242
+ },
243
+ empty: {
244
+ fontSize: 15,
245
+ lineHeight: 22,
246
+ paddingBottom: 8,
247
+ },
248
+ list: {
249
+ flexGrow: 0,
250
+ },
251
+ listContent: {
252
+ gap: 8,
253
+ paddingBottom: 4,
254
+ },
255
+ row: {
256
+ flexDirection: "row",
257
+ alignItems: "center",
258
+ gap: 12,
259
+ padding: 12,
260
+ },
261
+ thumb: {
262
+ width: 40,
263
+ height: 40,
264
+ },
265
+ swatch: {
266
+ width: 40,
267
+ height: 40,
268
+ borderRadius: 12,
269
+ },
270
+ rowText: {
271
+ flex: 1,
272
+ gap: 2,
273
+ },
274
+ rowTitle: {
275
+ fontSize: 16,
276
+ fontWeight: "700",
277
+ },
278
+ rowHint: {
279
+ fontSize: 13,
280
+ },
281
+ pin: {
282
+ width: 10,
283
+ height: 10,
284
+ borderRadius: 5,
285
+ },
286
+ });
@@ -27,9 +27,11 @@ export type AppHeaderLogoElement = {
27
27
  id: string;
28
28
  type: "logo";
29
29
  slot: AppHeaderSlot;
30
- sourceType: "url" | "media";
30
+ sourceType: "url" | "media" | "bundled";
31
31
  url?: string;
32
32
  mediaId?: string;
33
+ /** `require(...)` d’un asset embarqué à l’export (ImageSource). */
34
+ module?: number;
33
35
  };
34
36
 
35
37
  export type AppHeaderTitleElement = {
@@ -94,16 +96,27 @@ export type HeaderProps = {
94
96
  */
95
97
  readonly headerConfig?: AppHeaderConfig | null;
96
98
  /**
97
- * Force le padding safe-area top. Par défaut : uniquement en `overlay`
98
- * (le header React Navigation est déjà placé sous la status bar).
99
+ * Padding safe-area top. Défaut `true` (header custom RN = edge-to-edge,
100
+ * il faut l’inset). Passer `false` seulement si le parent inset déjà.
99
101
  */
100
102
  readonly safeTop?: boolean;
103
+ /**
104
+ * Logo + titre de la collectivité. Défaut : masqué sur les écrans
105
+ * notifications et profil.
106
+ */
107
+ readonly showCityIdentity?: boolean;
101
108
  };
102
109
 
110
+ function isIdentityFreeRoute(name: string): boolean {
111
+ const leaf = name.split("/").pop()?.toLowerCase() ?? "";
112
+ return leaf === "notifications" || leaf === "profile";
113
+ }
114
+
103
115
  function resolveLogoUri(
104
116
  apiUrl: string,
105
117
  el: AppHeaderLogoElement,
106
118
  ): string | null {
119
+ if (el.sourceType === "bundled") return null;
107
120
  const url = (el.url || "").trim();
108
121
  if (url) return url;
109
122
  // Fallback média (export parfois laisse mediaId si rewrite manqué)
@@ -130,6 +143,16 @@ function HeaderLogo({
130
143
  readonly element: AppHeaderLogoElement;
131
144
  }): ReactNode {
132
145
  const { apiUrl } = useWasto();
146
+ if (element.sourceType === "bundled" && element.module != null) {
147
+ return (
148
+ <Image
149
+ source={element.module}
150
+ style={styles.logo as ImageStyle}
151
+ resizeMode="contain"
152
+ accessibilityLabel="Logo"
153
+ />
154
+ );
155
+ }
133
156
  const uri = resolveLogoUri(apiUrl, element);
134
157
  if (!uri) return null;
135
158
  return <HeaderLogoImage uri={uri} />;
@@ -143,8 +166,11 @@ function CityLogoFallback(): ReactNode {
143
166
  ? appData.city.logoMediaId
144
167
  : null;
145
168
  const uri = publicMediaUrl(apiUrl, logoMediaId);
146
- if (!uri) return null;
147
- return <HeaderLogoImage uri={uri} />;
169
+ if (uri) return <HeaderLogoImage uri={uri} />;
170
+ const cityName =
171
+ typeof appData?.city?.name === "string" ? appData.city.name.trim() : "";
172
+ if (cityName) return <Title text={cityName} level="1" />;
173
+ return null;
148
174
  }
149
175
 
150
176
  function HeaderActionButton({
@@ -178,21 +204,24 @@ export function Header({
178
204
  overlay = false,
179
205
  headerConfig,
180
206
  safeTop,
207
+ showCityIdentity,
181
208
  }: HeaderProps): ReactNode {
182
209
  const { theme } = useTheme();
183
210
  const t = resolveTheme(theme);
184
211
  const themedStyles = createThemedStyles(theme);
185
- // Nav header : RN place déjà sous la status bar → pas de double inset.
186
- // Overlay (carte) : on flotte sous le notch → inset requis.
187
- const applyTopInset = safeTop ?? overlay;
212
+ // Header custom = plein écran sous le notch → inset requis (Page ne le re-ajoute plus).
213
+ const applyTopInset = safeTop ?? true;
188
214
  const topInset = useSafeAreaPadding(applyTopInset ? ["top"] : []);
189
215
  const { hasUnread } = useAnnouncements();
190
216
  const { push } = useAppNavigation();
217
+ const { apiUrl } = useWasto();
191
218
 
192
219
  const screenTitle = useMemo(
193
220
  () => options.title ?? route.name,
194
221
  [options.title, route.name],
195
222
  );
223
+ const showIdentity =
224
+ showCityIdentity ?? !isIdentityFreeRoute(route.name);
196
225
 
197
226
  /** Config explicite → navbar (logo/actions), jamais le fallback titre d’écran. */
198
227
  const hasExplicitConfig = headerConfig != null;
@@ -202,7 +231,12 @@ export function Header({
202
231
  return raw.filter((el) => el.type !== "title");
203
232
  }, [headerConfig]);
204
233
  const hasCustomActions = elements.some((el) => el.type === "action");
205
- const hasLogo = elements.some((el) => el.type === "logo");
234
+ // Logo CMS sans URI / sans module bundlé → fallback ville.
235
+ const hasResolvableLogo = elements.some((el) => {
236
+ if (el.type !== "logo") return false;
237
+ if (el.sourceType === "bundled" && el.module != null) return true;
238
+ return resolveLogoUri(apiUrl, el) != null;
239
+ });
206
240
 
207
241
  const bySlot = useMemo(() => {
208
242
  const left: AppHeaderElement[] = [];
@@ -329,7 +363,9 @@ export function Header({
329
363
  </HeaderActionButton>
330
364
  ) : null}
331
365
  {bySlot.left.map(renderElement)}
332
- {!hasLogo ? <CityLogoFallback /> : null}
366
+ {showIdentity && !hasResolvableLogo ? (
367
+ <CityLogoFallback />
368
+ ) : null}
333
369
  </View>
334
370
  <View
335
371
  style={styles.slotCenter}
@@ -358,8 +394,12 @@ export function Header({
358
394
  />
359
395
  </HeaderActionButton>
360
396
  ) : null}
361
- <CityLogoFallback />
362
- <Title text={screenTitle} level="1" />
397
+ {showIdentity ? (
398
+ <>
399
+ <CityLogoFallback />
400
+ <Title text={screenTitle} level="1" />
401
+ </>
402
+ ) : null}
363
403
  </View>
364
404
  <View style={styles.actions}>{defaultActions}</View>
365
405
  </>
@@ -267,7 +267,7 @@ export function LocationsMapShell({
267
267
  </View>
268
268
 
269
269
  <View style={styles.topOverlay} pointerEvents="box-none">
270
- {overlayHeader ? (
270
+ {overlayHeader === true ? (
271
271
  <Header
272
272
  overlay
273
273
  options={{ headerBackVisible: false }}