@wasto/cms-components 1.5.10

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.
Files changed (94) hide show
  1. package/README.md +47 -0
  2. package/dist/index.d.mts +465 -0
  3. package/dist/index.d.ts +465 -0
  4. package/dist/index.js +10445 -0
  5. package/dist/index.mjs +10553 -0
  6. package/package.json +90 -0
  7. package/src/components/announcement-modal/AnnouncementModal.tsx +183 -0
  8. package/src/components/article/Article.tsx +22 -0
  9. package/src/components/article/ArticleCard.tsx +81 -0
  10. package/src/components/article/ArticleExcerpts.tsx +80 -0
  11. package/src/components/article/ArticleList.tsx +114 -0
  12. package/src/components/article/ArticleMarkdown.tsx +170 -0
  13. package/src/components/article/ArticleReaderModal.tsx +93 -0
  14. package/src/components/article/ArticleSingle.tsx +57 -0
  15. package/src/components/bottom-tab/BottomTab.tsx +392 -0
  16. package/src/components/button/Button.tsx +154 -0
  17. package/src/components/challenge/Badges.tsx +84 -0
  18. package/src/components/challenge/BadgesModal.tsx +166 -0
  19. package/src/components/challenge/Challenge.tsx +380 -0
  20. package/src/components/challenge/ChallengeModal.tsx +231 -0
  21. package/src/components/challenge/ChallengeVignette.tsx +249 -0
  22. package/src/components/challenge/ChallengesModal.tsx +243 -0
  23. package/src/components/challenge/QuizModal.tsx +381 -0
  24. package/src/components/challenge/ResultModal.tsx +144 -0
  25. package/src/components/chat/Chat.tsx +928 -0
  26. package/src/components/collection-calendar/CollectionCalendar.tsx +274 -0
  27. package/src/components/columns/Columns.tsx +34 -0
  28. package/src/components/consignes-modal/ConsignesModal.tsx +267 -0
  29. package/src/components/content-inset/ContentInset.tsx +33 -0
  30. package/src/components/header/Header.tsx +391 -0
  31. package/src/components/image/Image.tsx +16 -0
  32. package/src/components/image/ImageFrame.tsx +102 -0
  33. package/src/components/image/ImageMedia.tsx +31 -0
  34. package/src/components/image/ImageUrl.tsx +30 -0
  35. package/src/components/locations-map/LocationSheet.tsx +417 -0
  36. package/src/components/locations-map/LocationsMap.native.tsx +260 -0
  37. package/src/components/locations-map/LocationsMap.tsx +13 -0
  38. package/src/components/locations-map/LocationsMap.web.tsx +10 -0
  39. package/src/components/locations-map/LocationsMapShell.tsx +527 -0
  40. package/src/components/locations-map/LocationsMapView.tsx +108 -0
  41. package/src/components/locations-map/LocationsMapView.web.tsx +331 -0
  42. package/src/components/nav-button/NavButton.tsx +59 -0
  43. package/src/components/next-collection/NextCollection.tsx +621 -0
  44. package/src/components/notifications/NotificationsScreen.tsx +247 -0
  45. package/src/components/page/Page.tsx +64 -0
  46. package/src/components/parapraph/Paragraph.tsx +67 -0
  47. package/src/components/profile/ProfileScreen.tsx +236 -0
  48. package/src/components/recycling-info/RecyclingInfo.tsx +189 -0
  49. package/src/components/safe-area/OptionalSafeAreaProvider.tsx +17 -0
  50. package/src/components/search/Search.tsx +320 -0
  51. package/src/components/select-street-modal/SelectStreetModal.tsx +475 -0
  52. package/src/components/title/Title.tsx +100 -0
  53. package/src/components/vision/Vision.tsx +447 -0
  54. package/src/components/vision/VisionActionCard.tsx +117 -0
  55. package/src/components/vision/VisionCapture.tsx +30 -0
  56. package/src/components/vision/VisionTapOverlay.tsx +122 -0
  57. package/src/contexts/AnnouncementsContext.tsx +171 -0
  58. package/src/contexts/ChallengesContext.tsx +182 -0
  59. package/src/contexts/LocalContext.tsx +227 -0
  60. package/src/contexts/ThemeContext.tsx +37 -0
  61. package/src/contexts/WastoContext.tsx +127 -0
  62. package/src/helpers/blogPost.ts +72 -0
  63. package/src/helpers/generateQuizQuestions.ts +247 -0
  64. package/src/helpers/getContrastColor.ts +19 -0
  65. package/src/helpers/haptics.ts +20 -0
  66. package/src/helpers/hoursSchedule.ts +230 -0
  67. package/src/helpers/htmlContentToMarkdown.ts +114 -0
  68. package/src/helpers/layout.ts +54 -0
  69. package/src/helpers/loadLinearGradient.ts +22 -0
  70. package/src/helpers/maquetteTheme.ts +76 -0
  71. package/src/helpers/markdownUnderlinePlugin.ts +32 -0
  72. package/src/helpers/normalizeWasteTypeRecyclingInfo.ts +80 -0
  73. package/src/helpers/publicMediaUrl.ts +8 -0
  74. package/src/helpers/resolveApiAssetUrl.ts +21 -0
  75. package/src/helpers/systemRoutes.ts +7 -0
  76. package/src/helpers/themeStyles.ts +114 -0
  77. package/src/helpers/useSafeAreaPadding.ts +14 -0
  78. package/src/hooks/useBadges.ts +100 -0
  79. package/src/hooks/useBlogPosts.ts +105 -0
  80. package/src/hooks/useChallenges.ts +110 -0
  81. package/src/hooks/useCities.ts +56 -0
  82. package/src/hooks/useCityAnnouncements.ts +118 -0
  83. package/src/hooks/useCollectionCalendar.ts +421 -0
  84. package/src/hooks/useColumns.ts +20 -0
  85. package/src/hooks/useConsignes.ts +62 -0
  86. package/src/hooks/useLocations.ts +248 -0
  87. package/src/hooks/useNextCollection.ts +238 -0
  88. package/src/hooks/useRecyclingInfo.ts +90 -0
  89. package/src/hooks/useWastoClient.ts +16 -0
  90. package/src/index.ts +142 -0
  91. package/src/navigation/NavigationContext.tsx +56 -0
  92. package/src/sentry/initSentry.ts +120 -0
  93. package/src/types/Theme.ts +72 -0
  94. package/src/types/css-modules.d.ts +1 -0
@@ -0,0 +1,166 @@
1
+ import type { ReactNode } from "react";
2
+ import { Dimensions, Modal, ScrollView, StyleSheet, View, Text } from "react-native";
3
+ import { Image } from "expo-image";
4
+ import { Lock, XIcon } from "lucide-react-native";
5
+ import { AcquiredBadge } from "../../hooks/useChallenges";
6
+ import { useTheme } from "../../contexts/ThemeContext";
7
+ import { Theme } from "../../types/Theme";
8
+ import { Title } from "../title/Title";
9
+ import { NavButton } from "../nav-button/NavButton";
10
+ import { Badge, useBadges } from "../../hooks/useBadges";
11
+ import { useColumns } from "../../hooks/useColumns";
12
+ import { useSafeAreaPadding } from "../../helpers/useSafeAreaPadding";
13
+ import { resolveTheme } from "../../helpers/themeStyles";
14
+ import { SPACE } from "../../helpers/layout";
15
+
16
+ export type BadgesModalProps = {
17
+ visible: boolean;
18
+ acquiredBadges: AcquiredBadge[];
19
+ onClose: () => void;
20
+ };
21
+
22
+ type BadgeWithRequired = Badge & { isAcquired: boolean };
23
+
24
+ export function BadgesModal({
25
+ visible,
26
+ acquiredBadges,
27
+ onClose,
28
+ }: BadgesModalProps): ReactNode {
29
+ const { badges } = useBadges();
30
+ const { theme } = useTheme();
31
+ const t = resolveTheme(theme);
32
+ const themedStyles = createThemedStyles(theme);
33
+ const topInset = useSafeAreaPadding(["top"]);
34
+ const bottomInset = useSafeAreaPadding(["bottom"]);
35
+
36
+ const rowsBadges = useColumns<BadgeWithRequired>(
37
+ badges.map((i) => ({
38
+ ...i,
39
+ isAcquired: acquiredBadges.findIndex((b) => b.badgeId === i.id) !== -1,
40
+ })),
41
+ );
42
+
43
+ return (
44
+ <Modal visible={visible} animationType="slide" onRequestClose={onClose}>
45
+ <View style={[styles.modal, themedStyles.modal]}>
46
+ <View style={topInset}>
47
+ <View style={styles.header}>
48
+ <Title text="Tous les badges" level="2" />
49
+ <NavButton icon={XIcon} onPress={onClose} />
50
+ </View>
51
+ </View>
52
+ <View style={[styles.listWrap, bottomInset]}>
53
+ <ScrollView bounces={false} contentContainerStyle={styles.body}>
54
+ {rowsBadges.map((row, index) => (
55
+ <View key={index.toString()} style={styles.badgesRow}>
56
+ {row.map((badge, bIndex) =>
57
+ badge ? (
58
+ <View key={badge.id} style={styles.badge}>
59
+ <View style={styles.badgeImageWrap}>
60
+ <Image
61
+ source={badge.imageUrl}
62
+ style={[
63
+ styles.badgeImage,
64
+ themedStyles.badgeImage,
65
+ !badge.isAcquired && styles.badgeImageLocked,
66
+ ]}
67
+ contentFit="cover"
68
+ />
69
+ {!badge.isAcquired ? (
70
+ <View style={styles.lockOverlay} pointerEvents="none">
71
+ <View
72
+ style={[
73
+ styles.lockBtn,
74
+ {
75
+ backgroundColor: theme.colors.primary,
76
+ borderRadius: t.radii.pill,
77
+ },
78
+ ]}
79
+ >
80
+ <Lock
81
+ size={18}
82
+ color={theme.colors.onPrimary}
83
+ />
84
+ </View>
85
+ </View>
86
+ ) : null}
87
+ </View>
88
+ <Text style={themedStyles.badgeTitle}>{badge.name}</Text>
89
+ </View>
90
+ ) : (
91
+ <View key={bIndex.toString()} style={styles.badge} />
92
+ ),
93
+ )}
94
+ </View>
95
+ ))}
96
+ </ScrollView>
97
+ </View>
98
+ </View>
99
+ </Modal>
100
+ );
101
+ }
102
+
103
+ const createThemedStyles = (theme: Theme) =>
104
+ StyleSheet.create({
105
+ modal: {
106
+ backgroundColor: theme.colors.background,
107
+ },
108
+ badgeImage: {
109
+ borderRadius: theme.radius,
110
+ },
111
+ badgeTitle: {
112
+ color: theme.colors.onBackground,
113
+ },
114
+ });
115
+
116
+ const styles = StyleSheet.create({
117
+ modal: {
118
+ height: Dimensions.get("screen").height,
119
+ },
120
+ header: {
121
+ padding: 20,
122
+ width: "100%",
123
+ flexDirection: "row",
124
+ justifyContent: "space-between",
125
+ alignItems: "center",
126
+ },
127
+ listWrap: {
128
+ flex: 1,
129
+ },
130
+ body: {
131
+ padding: 20,
132
+ flexGrow: 1,
133
+ gap: 10,
134
+ },
135
+ badgesRow: {
136
+ flexDirection: "row",
137
+ gap: 10,
138
+ },
139
+ badge: {
140
+ flex: 1,
141
+ gap: 5,
142
+ },
143
+ badgeImageWrap: {
144
+ width: "100%",
145
+ aspectRatio: 3 / 4,
146
+ position: "relative",
147
+ },
148
+ badgeImage: {
149
+ width: "100%",
150
+ height: "100%",
151
+ },
152
+ badgeImageLocked: {
153
+ opacity: 0.35,
154
+ },
155
+ lockOverlay: {
156
+ ...StyleSheet.absoluteFillObject,
157
+ alignItems: "center",
158
+ justifyContent: "center",
159
+ },
160
+ lockBtn: {
161
+ width: SPACE.iconHit,
162
+ height: SPACE.iconHit,
163
+ alignItems: "center",
164
+ justifyContent: "center",
165
+ },
166
+ });
@@ -0,0 +1,380 @@
1
+ import { useState, type ReactNode } from "react";
2
+ import {
3
+ ActivityIndicator,
4
+ Modal,
5
+ Pressable,
6
+ StyleSheet,
7
+ Text,
8
+ View,
9
+ } from "react-native";
10
+ import { Award, Percent, Play, Trophy, Zap } from "lucide-react-native";
11
+ import { useChallenges } from "../../hooks/useChallenges";
12
+ import { useBadges } from "../../hooks/useBadges";
13
+ import { useTheme } from "../../contexts/ThemeContext";
14
+ import { cardStyle, resolveTheme } from "../../helpers/themeStyles";
15
+ import { SPACE } from "../../helpers/layout";
16
+ import { Badges } from "./Badges";
17
+ import { BadgesModal } from "./BadgesModal";
18
+ import { ChallengesModal } from "./ChallengesModal";
19
+ import { ChallengeModal } from "./ChallengeModal";
20
+ import type { ChallengeWithProgress } from "../../hooks/useChallenges";
21
+
22
+ export type ChallengeProps = {};
23
+
24
+ function isChallengeComplete(c: ChallengeWithProgress) {
25
+ return !!c.progression?.badge;
26
+ }
27
+
28
+ function progressLabel(c: ChallengeWithProgress) {
29
+ const series = c.series ?? [];
30
+ if (series.length === 0) {
31
+ return isChallengeComplete(c) ? "1/1" : "0/1";
32
+ }
33
+ const done = series.filter((s) => s.progression).length;
34
+ return `${done}/${series.length}`;
35
+ }
36
+
37
+ function progressRatio(c: ChallengeWithProgress) {
38
+ const series = c.series ?? [];
39
+ if (series.length === 0) return isChallengeComplete(c) ? 1 : 0;
40
+ return series.filter((s) => s.progression).length / series.length;
41
+ }
42
+
43
+ export function Challenge({}: ChallengeProps): ReactNode {
44
+ const { theme } = useTheme();
45
+ const t = resolveTheme(theme);
46
+ const { challenges, acquiredBadges, isLoading, error } = useChallenges();
47
+ const { badges } = useBadges();
48
+ const [displayChallenges, setDisplayChallenges] = useState(false);
49
+ const [displayAllBadges, setDisplayAllBadges] = useState(false);
50
+ const [activeChallenge, setActiveChallenge] = useState<ChallengeWithProgress | null>(null);
51
+
52
+ const completedCount = challenges.filter(isChallengeComplete).length;
53
+ const totalChallenges = challenges.length;
54
+ const totalBadges = badges.length > 0 ? badges.length : acquiredBadges.length;
55
+ const challengeScores = challenges
56
+ .map((c) => c.progression?.score)
57
+ .filter((s): s is number => typeof s === "number");
58
+ const successPct =
59
+ challengeScores.length > 0
60
+ ? Math.round(
61
+ challengeScores.reduce((sum, score) => sum + score, 0) /
62
+ challengeScores.length,
63
+ )
64
+ : 0;
65
+
66
+ if (isLoading && !challenges.length) {
67
+ return (
68
+ <View style={styles.stateBox}>
69
+ <ActivityIndicator color={theme.colors.primary} />
70
+ </View>
71
+ );
72
+ }
73
+
74
+ if (error) {
75
+ return (
76
+ <View style={styles.stateBox}>
77
+ <Text style={{ color: theme.colors.error }}>{error}</Text>
78
+ </View>
79
+ );
80
+ }
81
+
82
+ return (
83
+ <>
84
+ <View style={styles.section}>
85
+ <Text style={[styles.sectionLabel, { color: t.colors.muted }]}>STATISTIQUES</Text>
86
+ <View style={[styles.statsCard, cardStyle(theme), { borderRadius: t.radii.lg }]}>
87
+ <StatCell
88
+ icon={<Zap size={16} color="#fff" />}
89
+ value={String(completedCount)}
90
+ suffix={`/${totalChallenges}`}
91
+ label="Défis terminés"
92
+ color={theme.colors.primary}
93
+ />
94
+ <StatCell
95
+ icon={<Award size={16} color="#fff" />}
96
+ value={String(acquiredBadges.length)}
97
+ suffix={`/${totalBadges}`}
98
+ label="badge obtenue"
99
+ color={theme.colors.primary}
100
+ />
101
+ <StatCell
102
+ icon={<Percent size={16} color="#fff" />}
103
+ value={String(successPct)}
104
+ suffix="%"
105
+ label="pourcent de réussite"
106
+ color={theme.colors.primary}
107
+ />
108
+ </View>
109
+ </View>
110
+
111
+ <View style={styles.section}>
112
+ <View style={styles.sectionHeader}>
113
+ <Text style={[styles.sectionLabel, { color: t.colors.muted }]}>LES BADGES</Text>
114
+ <Pressable onPress={() => setDisplayAllBadges(true)}>
115
+ <Text style={{ color: theme.colors.primary, fontWeight: "700" }}>
116
+ Tous les badges ›
117
+ </Text>
118
+ </Pressable>
119
+ </View>
120
+ <Badges badges={acquiredBadges} />
121
+ </View>
122
+
123
+ <View style={styles.section}>
124
+ <View style={styles.sectionHeader}>
125
+ <Text style={[styles.sectionLabel, { color: t.colors.muted }]}>
126
+ TOUS LES DÉFIS
127
+ </Text>
128
+ <Pressable onPress={() => setDisplayChallenges(true)}>
129
+ <Text style={{ color: theme.colors.primary, fontWeight: "700" }}>
130
+ Voir tout ›
131
+ </Text>
132
+ </Pressable>
133
+ </View>
134
+ <View style={[styles.listCard, cardStyle(theme), { borderRadius: t.radii.lg }]}>
135
+ {challenges.length === 0 ? (
136
+ <Text style={[styles.empty, { color: t.colors.muted }]}>
137
+ Aucun défi disponible
138
+ </Text>
139
+ ) : (
140
+ challenges.map((c, index) => {
141
+ const done = isChallengeComplete(c);
142
+ const ratio = progressRatio(c);
143
+ return (
144
+ <Pressable
145
+ key={c.id}
146
+ onPress={() => setActiveChallenge(c)}
147
+ style={[
148
+ styles.challengeRow,
149
+ index < challenges.length - 1 && {
150
+ borderBottomWidth: StyleSheet.hairlineWidth,
151
+ borderBottomColor: theme.colors.border,
152
+ },
153
+ ]}
154
+ >
155
+ <View style={styles.challengeMain}>
156
+ <Text
157
+ style={[
158
+ styles.challengeTitle,
159
+ { color: theme.colors.onSurface },
160
+ ]}
161
+ numberOfLines={2}
162
+ >
163
+ {c.name}
164
+ </Text>
165
+ <View
166
+ style={[
167
+ styles.track,
168
+ {
169
+ backgroundColor: theme.colors.border,
170
+ borderRadius: t.radii.pill,
171
+ },
172
+ ]}
173
+ >
174
+ <View
175
+ style={[
176
+ styles.fill,
177
+ {
178
+ width: `${Math.round(ratio * 100)}%`,
179
+ backgroundColor: done
180
+ ? theme.colors.success
181
+ : theme.colors.primary,
182
+ borderRadius: t.radii.pill,
183
+ },
184
+ ]}
185
+ >
186
+ <Text
187
+ style={[
188
+ styles.fillLabel,
189
+ {
190
+ color: done
191
+ ? theme.colors.onSuccess
192
+ : theme.colors.onPrimary,
193
+ },
194
+ ]}
195
+ >
196
+ {progressLabel(c)}
197
+ </Text>
198
+ </View>
199
+ </View>
200
+ </View>
201
+ <View
202
+ style={[
203
+ styles.actionBtn,
204
+ {
205
+ backgroundColor: done
206
+ ? theme.colors.success
207
+ : theme.colors.primary,
208
+ borderRadius: t.radii.pill,
209
+ },
210
+ ]}
211
+ >
212
+ {done ? (
213
+ <Trophy size={18} color={theme.colors.onSuccess} />
214
+ ) : (
215
+ <Play size={18} color={theme.colors.onPrimary} fill={theme.colors.onPrimary} />
216
+ )}
217
+ </View>
218
+ </Pressable>
219
+ );
220
+ })
221
+ )}
222
+ </View>
223
+ </View>
224
+
225
+ <ChallengesModal
226
+ visible={displayChallenges}
227
+ challenges={challenges}
228
+ onClose={() => setDisplayChallenges(false)}
229
+ />
230
+ <BadgesModal
231
+ visible={displayAllBadges}
232
+ acquiredBadges={acquiredBadges}
233
+ onClose={() => setDisplayAllBadges(false)}
234
+ />
235
+ <Modal
236
+ visible={!!activeChallenge}
237
+ animationType="slide"
238
+ onRequestClose={() => setActiveChallenge(null)}
239
+ >
240
+ {activeChallenge ? (
241
+ <ChallengeModal
242
+ challenge={activeChallenge}
243
+ onClose={() => setActiveChallenge(null)}
244
+ />
245
+ ) : null}
246
+ </Modal>
247
+ </>
248
+ );
249
+ }
250
+
251
+ function StatCell({
252
+ icon,
253
+ value,
254
+ suffix,
255
+ label,
256
+ color,
257
+ }: {
258
+ icon: React.ReactNode;
259
+ value: string;
260
+ suffix?: string;
261
+ label: string;
262
+ color: string;
263
+ }) {
264
+ return (
265
+ <View style={styles.statCell}>
266
+ <View style={[styles.statIcon, { backgroundColor: color }]}>{icon}</View>
267
+ <View style={styles.statValueRow}>
268
+ <Text style={styles.statValue}>{value}</Text>
269
+ {suffix ? <Text style={styles.statSuffix}>{suffix}</Text> : null}
270
+ </View>
271
+ <Text style={styles.statLabel}>{label}</Text>
272
+ </View>
273
+ );
274
+ }
275
+
276
+ const styles = StyleSheet.create({
277
+ stateBox: {
278
+ minHeight: 120,
279
+ alignItems: "center",
280
+ justifyContent: "center",
281
+ },
282
+ section: {
283
+ gap: 10,
284
+ marginBottom: 20,
285
+ },
286
+ sectionHeader: {
287
+ flexDirection: "row",
288
+ justifyContent: "space-between",
289
+ alignItems: "center",
290
+ },
291
+ sectionLabel: {
292
+ fontSize: 12,
293
+ fontWeight: "700",
294
+ letterSpacing: 0.6,
295
+ },
296
+ statsCard: {
297
+ flexDirection: "row",
298
+ padding: SPACE.stack,
299
+ gap: 8,
300
+ },
301
+ statCell: {
302
+ flex: 1,
303
+ alignItems: "center",
304
+ gap: 6,
305
+ padding: 8,
306
+ },
307
+ statIcon: {
308
+ width: SPACE.iconHit,
309
+ height: SPACE.iconHit,
310
+ borderRadius: SPACE.iconHit / 2,
311
+ alignItems: "center",
312
+ justifyContent: "center",
313
+ },
314
+ statValue: {
315
+ fontSize: 22,
316
+ fontWeight: "800",
317
+ },
318
+ statValueRow: {
319
+ flexDirection: "row",
320
+ alignItems: "baseline",
321
+ justifyContent: "center",
322
+ flexWrap: "wrap",
323
+ },
324
+ statSuffix: {
325
+ fontSize: 11,
326
+ fontWeight: "700",
327
+ opacity: 0.65,
328
+ marginLeft: 1,
329
+ },
330
+ statLabel: {
331
+ fontSize: 11,
332
+ textAlign: "center",
333
+ opacity: 0.65,
334
+ },
335
+ listCard: {
336
+ overflow: "hidden",
337
+ paddingHorizontal: SPACE.cardPad,
338
+ gap: 6,
339
+ },
340
+ empty: {
341
+ padding: 24,
342
+ textAlign: "center",
343
+ },
344
+ challengeRow: {
345
+ flexDirection: "row",
346
+ alignItems: "center",
347
+ gap: 16,
348
+ paddingVertical: 14,
349
+ },
350
+ challengeMain: {
351
+ flex: 1,
352
+ gap: 12,
353
+ },
354
+ challengeTitle: {
355
+ fontSize: 15,
356
+ fontWeight: "700",
357
+ },
358
+ track: {
359
+ height: 16,
360
+ overflow: "hidden",
361
+ justifyContent: "center",
362
+ },
363
+ fill: {
364
+ height: "100%",
365
+ minWidth: 40,
366
+ justifyContent: "center",
367
+ paddingHorizontal: 8,
368
+ },
369
+ fillLabel: {
370
+ fontSize: 11,
371
+ fontWeight: "700",
372
+ textAlign: "center",
373
+ },
374
+ actionBtn: {
375
+ width: SPACE.iconHit,
376
+ height: SPACE.iconHit,
377
+ alignItems: "center",
378
+ justifyContent: "center",
379
+ },
380
+ });