@wasto/cms-components 1.5.18 → 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/dist/index.d.mts +6 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +1748 -1270
- package/dist/index.mjs +1479 -1000
- package/package.json +2 -2
- package/src/components/announcement-modal/AnnouncementModal.tsx +46 -7
- package/src/components/collection-calendar/CollectionCalendar.tsx +32 -4
- package/src/components/collection-calendar/CollectionDayModal.tsx +286 -0
- package/src/components/header/Header.tsx +22 -3
- package/src/components/locations-map/LocationsMapShell.tsx +1 -1
- package/src/components/push/PushNotificationNavigator.tsx +133 -0
- package/src/components/push/PushTokenRegistrar.tsx +5 -3
- package/src/contexts/AnnouncementsContext.tsx +72 -8
- package/src/contexts/LocalContext.tsx +9 -5
- package/src/contexts/WastoContext.tsx +4 -2
- package/src/hooks/useCityAnnouncements.ts +4 -4
- package/src/hooks/useCollectionCalendar.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wasto/cms-components",
|
|
3
|
-
"version": "1.5.
|
|
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.
|
|
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
|
-
<
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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 {
|
|
3
|
-
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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
|
+
});
|
|
@@ -100,8 +100,18 @@ export type HeaderProps = {
|
|
|
100
100
|
* il faut l’inset). Passer `false` seulement si le parent inset déjà.
|
|
101
101
|
*/
|
|
102
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;
|
|
103
108
|
};
|
|
104
109
|
|
|
110
|
+
function isIdentityFreeRoute(name: string): boolean {
|
|
111
|
+
const leaf = name.split("/").pop()?.toLowerCase() ?? "";
|
|
112
|
+
return leaf === "notifications" || leaf === "profile";
|
|
113
|
+
}
|
|
114
|
+
|
|
105
115
|
function resolveLogoUri(
|
|
106
116
|
apiUrl: string,
|
|
107
117
|
el: AppHeaderLogoElement,
|
|
@@ -194,6 +204,7 @@ export function Header({
|
|
|
194
204
|
overlay = false,
|
|
195
205
|
headerConfig,
|
|
196
206
|
safeTop,
|
|
207
|
+
showCityIdentity,
|
|
197
208
|
}: HeaderProps): ReactNode {
|
|
198
209
|
const { theme } = useTheme();
|
|
199
210
|
const t = resolveTheme(theme);
|
|
@@ -209,6 +220,8 @@ export function Header({
|
|
|
209
220
|
() => options.title ?? route.name,
|
|
210
221
|
[options.title, route.name],
|
|
211
222
|
);
|
|
223
|
+
const showIdentity =
|
|
224
|
+
showCityIdentity ?? !isIdentityFreeRoute(route.name);
|
|
212
225
|
|
|
213
226
|
/** Config explicite → navbar (logo/actions), jamais le fallback titre d’écran. */
|
|
214
227
|
const hasExplicitConfig = headerConfig != null;
|
|
@@ -350,7 +363,9 @@ export function Header({
|
|
|
350
363
|
</HeaderActionButton>
|
|
351
364
|
) : null}
|
|
352
365
|
{bySlot.left.map(renderElement)}
|
|
353
|
-
{!hasResolvableLogo ?
|
|
366
|
+
{showIdentity && !hasResolvableLogo ? (
|
|
367
|
+
<CityLogoFallback />
|
|
368
|
+
) : null}
|
|
354
369
|
</View>
|
|
355
370
|
<View
|
|
356
371
|
style={styles.slotCenter}
|
|
@@ -379,8 +394,12 @@ export function Header({
|
|
|
379
394
|
/>
|
|
380
395
|
</HeaderActionButton>
|
|
381
396
|
) : null}
|
|
382
|
-
|
|
383
|
-
|
|
397
|
+
{showIdentity ? (
|
|
398
|
+
<>
|
|
399
|
+
<CityLogoFallback />
|
|
400
|
+
<Title text={screenTitle} level="1" />
|
|
401
|
+
</>
|
|
402
|
+
) : null}
|
|
384
403
|
</View>
|
|
385
404
|
<View style={styles.actions}>{defaultActions}</View>
|
|
386
405
|
</>
|