@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,170 @@
1
+ import { useMemo, type ReactNode } from "react";
2
+ import { Linking, ScrollView, StyleSheet, Text, View } from "react-native";
3
+ import Markdown, { MarkdownIt } from "react-native-markdown-display";
4
+ import { useTheme } from "../../contexts/ThemeContext";
5
+ import { htmlContentToMarkdown } from "../../helpers/htmlContentToMarkdown";
6
+ import { markdownUnderlinePlugin } from "../../helpers/markdownUnderlinePlugin";
7
+
8
+ export type ArticleMarkdownProps = {
9
+ children: string;
10
+ };
11
+
12
+ const markdownIt = MarkdownIt({ typographer: true }).use(markdownUnderlinePlugin);
13
+
14
+ export function ArticleMarkdown({ children }: ArticleMarkdownProps): ReactNode {
15
+ const { theme } = useTheme();
16
+ const border = theme.colors.border;
17
+ const headerBg = theme.colors.primarySoft ?? `${theme.colors.primary}22`;
18
+
19
+ const markdownStyles = useMemo(
20
+ () =>
21
+ StyleSheet.create({
22
+ body: {
23
+ color: theme.colors.onBackground,
24
+ fontSize: 15,
25
+ lineHeight: 22,
26
+ },
27
+ heading1: {
28
+ color: theme.colors.onBackground,
29
+ fontSize: 22,
30
+ fontWeight: "700",
31
+ marginTop: 12,
32
+ marginBottom: 8,
33
+ },
34
+ heading2: {
35
+ color: theme.colors.onBackground,
36
+ fontSize: 19,
37
+ fontWeight: "700",
38
+ marginTop: 10,
39
+ marginBottom: 6,
40
+ },
41
+ heading3: {
42
+ color: theme.colors.onBackground,
43
+ fontSize: 17,
44
+ fontWeight: "600",
45
+ marginTop: 8,
46
+ marginBottom: 4,
47
+ },
48
+ paragraph: {
49
+ marginTop: 0,
50
+ marginBottom: 10,
51
+ },
52
+ link: {
53
+ color: theme.colors.primary,
54
+ textDecorationLine: "underline",
55
+ },
56
+ blockquote: {
57
+ borderLeftWidth: 3,
58
+ borderLeftColor: border,
59
+ paddingLeft: 10,
60
+ marginVertical: 8,
61
+ opacity: 0.9,
62
+ },
63
+ code_inline: {
64
+ backgroundColor: headerBg,
65
+ color: theme.colors.onBackground,
66
+ paddingHorizontal: 4,
67
+ borderRadius: Math.max(4, Math.round(theme.radius / 2)),
68
+ fontSize: 14,
69
+ },
70
+ fence: {
71
+ backgroundColor: theme.colors.surface,
72
+ color: theme.colors.onSurface,
73
+ padding: 10,
74
+ borderRadius: theme.radius,
75
+ marginVertical: 8,
76
+ fontSize: 13,
77
+ },
78
+ bullet_list: {
79
+ marginVertical: 4,
80
+ },
81
+ ordered_list: {
82
+ marginVertical: 4,
83
+ },
84
+ list_item: {
85
+ marginVertical: 2,
86
+ },
87
+ hr: {
88
+ backgroundColor: border,
89
+ height: StyleSheet.hairlineWidth,
90
+ marginVertical: 12,
91
+ },
92
+ table: {
93
+ borderWidth: StyleSheet.hairlineWidth,
94
+ borderColor: border,
95
+ borderRadius: theme.radius,
96
+ overflow: "hidden",
97
+ minWidth: "100%",
98
+ },
99
+ thead: {
100
+ backgroundColor: headerBg,
101
+ },
102
+ tbody: {},
103
+ tr: {
104
+ flexDirection: "row",
105
+ borderBottomWidth: StyleSheet.hairlineWidth,
106
+ borderColor: border,
107
+ },
108
+ th: {
109
+ flex: 1,
110
+ minWidth: 88,
111
+ paddingVertical: 8,
112
+ paddingHorizontal: 10,
113
+ fontWeight: "700",
114
+ color: theme.colors.onBackground,
115
+ },
116
+ td: {
117
+ flex: 1,
118
+ minWidth: 88,
119
+ paddingVertical: 8,
120
+ paddingHorizontal: 10,
121
+ },
122
+ ins: {
123
+ textDecorationLine: "underline",
124
+ color: theme.colors.onBackground,
125
+ },
126
+ }),
127
+ [theme, border, headerBg],
128
+ );
129
+
130
+ const source = useMemo(
131
+ () => htmlContentToMarkdown((children ?? "").trim()),
132
+ [children],
133
+ );
134
+
135
+ if (!source) return null;
136
+
137
+ return (
138
+ <Markdown
139
+ markdownit={markdownIt}
140
+ style={markdownStyles}
141
+ rules={{
142
+ table: (node, children, _parent, styles) => (
143
+ <ScrollView
144
+ key={node.key}
145
+ horizontal
146
+ nestedScrollEnabled
147
+ bounces={false}
148
+ showsHorizontalScrollIndicator
149
+ >
150
+ <View style={styles._VIEW_SAFE_table}>{children}</View>
151
+ </ScrollView>
152
+ ),
153
+ ins: (node, children, _parent, styles) => (
154
+ <Text key={node.key} style={styles.ins}>
155
+ {children}
156
+ </Text>
157
+ ),
158
+ }}
159
+ onLinkPress={(url: string) => {
160
+ const trimmed = url.trim();
161
+ if (trimmed) {
162
+ void Linking.openURL(trimmed);
163
+ }
164
+ return false;
165
+ }}
166
+ >
167
+ {source}
168
+ </Markdown>
169
+ );
170
+ }
@@ -0,0 +1,93 @@
1
+ import type { ReactNode } from "react";
2
+ import { Modal, ScrollView, StyleSheet, Text, View } from "react-native";
3
+ import { Image } from "expo-image";
4
+ import { XIcon } from "lucide-react-native";
5
+ import { useTheme } from "../../contexts/ThemeContext";
6
+ import { Theme } from "../../types/Theme";
7
+ import { Title } from "../title/Title";
8
+ import { NavButton } from "../nav-button/NavButton";
9
+ import { formatArticleDate, type BlogPost } from "../../helpers/blogPost";
10
+ import { publicMediaUrl } from "../../helpers/publicMediaUrl";
11
+ import { useWasto } from "../../contexts/WastoContext";
12
+ import { useSafeAreaPadding } from "../../helpers/useSafeAreaPadding";
13
+ import { ArticleMarkdown } from "./ArticleMarkdown";
14
+
15
+ export type ArticleReaderModalProps = {
16
+ visible: boolean;
17
+ post: BlogPost | null;
18
+ onClose: () => void;
19
+ };
20
+
21
+ export function ArticleReaderModal({
22
+ visible,
23
+ post,
24
+ onClose,
25
+ }: ArticleReaderModalProps): ReactNode {
26
+ const { theme } = useTheme();
27
+ const { apiUrl } = useWasto();
28
+ const themedStyles = createThemedStyles(theme);
29
+ const topInset = useSafeAreaPadding(["top"]);
30
+ const cover = publicMediaUrl(apiUrl, post?.coverMediaId);
31
+ const date = formatArticleDate(post?.publishedAt || post?.createdAt);
32
+
33
+ return (
34
+ <Modal visible={visible} animationType="slide" onRequestClose={onClose}>
35
+ <View style={[styles.modal, themedStyles.modal]}>
36
+ <View style={topInset}>
37
+ <View style={styles.header}>
38
+ <Title text={post?.title ?? "Article"} level="2" numberOfLines={2} />
39
+ <NavButton icon={XIcon} onPress={onClose} />
40
+ </View>
41
+ </View>
42
+ <ScrollView bounces={false} contentContainerStyle={styles.body}>
43
+ {cover ? (
44
+ <Image
45
+ source={cover}
46
+ style={[styles.cover, themedStyles.cover]}
47
+ contentFit="cover"
48
+ />
49
+ ) : null}
50
+ {date ? <Text style={themedStyles.meta}>{date}</Text> : null}
51
+ <ArticleMarkdown>{post?.content ?? ""}</ArticleMarkdown>
52
+ </ScrollView>
53
+ </View>
54
+ </Modal>
55
+ );
56
+ }
57
+
58
+ const createThemedStyles = (theme: Theme) =>
59
+ StyleSheet.create({
60
+ modal: {
61
+ backgroundColor: theme.colors.background,
62
+ },
63
+ cover: {
64
+ borderRadius: theme.radius,
65
+ },
66
+ meta: {
67
+ color: theme.colors.onBackground,
68
+ opacity: 0.6,
69
+ fontSize: 13,
70
+ },
71
+ });
72
+
73
+ const styles = StyleSheet.create({
74
+ modal: {
75
+ flex: 1,
76
+ },
77
+ header: {
78
+ padding: 20,
79
+ flexDirection: "row",
80
+ alignItems: "flex-start",
81
+ justifyContent: "space-between",
82
+ gap: 12,
83
+ },
84
+ body: {
85
+ padding: 20,
86
+ gap: 16,
87
+ paddingBottom: 40,
88
+ },
89
+ cover: {
90
+ width: "100%",
91
+ height: 200,
92
+ },
93
+ });
@@ -0,0 +1,57 @@
1
+ import { ActivityIndicator, StyleSheet, Text, View } from "react-native";
2
+ import { useState, type ReactNode } from "react";
3
+ import { useTheme } from "../../contexts/ThemeContext";
4
+ import { useBlogPost } from "../../hooks/useBlogPosts";
5
+ import { ArticleReaderModal } from "./ArticleReaderModal";
6
+ import { ArticleCard } from "./ArticleCard";
7
+
8
+ export type ArticlePropsSingle = {
9
+ displayMode: "single";
10
+ articleId: string;
11
+ };
12
+
13
+ export function ArticleSingle({
14
+ articleId,
15
+ }: ArticlePropsSingle): ReactNode {
16
+ const { theme } = useTheme();
17
+ const { post, isLoading, error } = useBlogPost(articleId);
18
+ const [open, setOpen] = useState(false);
19
+
20
+ if (isLoading) {
21
+ return (
22
+ <View style={styles.state}>
23
+ <ActivityIndicator color={theme.colors.primary} />
24
+ <Text style={{ color: theme.colors.onBackground, opacity: 0.7 }}>
25
+ Chargement de l'article…
26
+ </Text>
27
+ </View>
28
+ );
29
+ }
30
+
31
+ if (error || !post) {
32
+ return (
33
+ <View style={styles.state}>
34
+ <Text style={{ color: theme.colors.error, textAlign: "center" }}>
35
+ {error || "Article introuvable"}
36
+ </Text>
37
+ </View>
38
+ );
39
+ }
40
+
41
+ return (
42
+ <>
43
+ <ArticleCard post={post} onRead={() => setOpen(true)} />
44
+ <ArticleReaderModal visible={open} post={post} onClose={() => setOpen(false)} />
45
+ </>
46
+ );
47
+ }
48
+
49
+ const styles = StyleSheet.create({
50
+ state: {
51
+ minHeight: 80,
52
+ alignItems: "center",
53
+ justifyContent: "center",
54
+ gap: 8,
55
+ padding: 16,
56
+ },
57
+ });