@repzo/widget-react-native 1.0.9 → 1.2.0

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 (191) hide show
  1. package/CHANGELOG.md +400 -0
  2. package/README.md +104 -5
  3. package/lib/commonjs/agent-ui-blocks.js +2 -1
  4. package/lib/commonjs/agent-ui-blocks.js.map +1 -1
  5. package/lib/commonjs/api/client.js +25 -0
  6. package/lib/commonjs/api/client.js.map +1 -1
  7. package/lib/commonjs/attachments/pickers.js +3 -6
  8. package/lib/commonjs/attachments/pickers.js.map +1 -1
  9. package/lib/commonjs/device-id.js +31 -3
  10. package/lib/commonjs/device-id.js.map +1 -1
  11. package/lib/commonjs/i18n.js +40 -4
  12. package/lib/commonjs/i18n.js.map +1 -1
  13. package/lib/commonjs/index.js.map +1 -1
  14. package/lib/commonjs/locales.js +34 -1
  15. package/lib/commonjs/locales.js.map +1 -1
  16. package/lib/commonjs/push/push-notifications.js +9 -8
  17. package/lib/commonjs/push/push-notifications.js.map +1 -1
  18. package/lib/commonjs/state/widget-store.js +507 -26
  19. package/lib/commonjs/state/widget-store.js.map +1 -1
  20. package/lib/commonjs/storage.js +13 -1
  21. package/lib/commonjs/storage.js.map +1 -1
  22. package/lib/commonjs/types.js +20 -2
  23. package/lib/commonjs/types.js.map +1 -1
  24. package/lib/commonjs/ui/WidgetProvider.js +138 -49
  25. package/lib/commonjs/ui/WidgetProvider.js.map +1 -1
  26. package/lib/commonjs/ui/components/AgentUiBlocks.js +35 -15
  27. package/lib/commonjs/ui/components/AgentUiBlocks.js.map +1 -1
  28. package/lib/commonjs/ui/components/Launcher.js +4 -1
  29. package/lib/commonjs/ui/components/Launcher.js.map +1 -1
  30. package/lib/commonjs/ui/components/Markdown.js +32 -2
  31. package/lib/commonjs/ui/components/Markdown.js.map +1 -1
  32. package/lib/commonjs/ui/components/Post.js +1241 -0
  33. package/lib/commonjs/ui/components/Post.js.map +1 -0
  34. package/lib/commonjs/ui/components/RatingCard.js +6 -3
  35. package/lib/commonjs/ui/components/RatingCard.js.map +1 -1
  36. package/lib/commonjs/ui/hooks/use-widget-insets.js +0 -1
  37. package/lib/commonjs/ui/hooks/use-widget-insets.js.map +1 -1
  38. package/lib/commonjs/ui/screens/chat/ChatView.js +67 -29
  39. package/lib/commonjs/ui/screens/chat/ChatView.js.map +1 -1
  40. package/lib/commonjs/ui/utils/html.js +9 -1
  41. package/lib/commonjs/ui/utils/html.js.map +1 -1
  42. package/lib/commonjs/ui/utils/native-gradient.js +0 -1
  43. package/lib/commonjs/ui/utils/native-gradient.js.map +1 -1
  44. package/lib/commonjs/ui/utils/post-html.js +276 -0
  45. package/lib/commonjs/ui/utils/post-html.js.map +1 -0
  46. package/lib/commonjs/ui/utils/safe-url.js +16 -0
  47. package/lib/commonjs/ui/utils/safe-url.js.map +1 -1
  48. package/lib/module/agent-ui-blocks.js +2 -1
  49. package/lib/module/agent-ui-blocks.js.map +1 -1
  50. package/lib/module/api/client.js +25 -0
  51. package/lib/module/api/client.js.map +1 -1
  52. package/lib/module/attachments/pickers.js +3 -6
  53. package/lib/module/attachments/pickers.js.map +1 -1
  54. package/lib/module/device-id.js +31 -3
  55. package/lib/module/device-id.js.map +1 -1
  56. package/lib/module/i18n.js +40 -4
  57. package/lib/module/i18n.js.map +1 -1
  58. package/lib/module/index.js.map +1 -1
  59. package/lib/module/locales.js +34 -1
  60. package/lib/module/locales.js.map +1 -1
  61. package/lib/module/push/push-notifications.js +9 -8
  62. package/lib/module/push/push-notifications.js.map +1 -1
  63. package/lib/module/state/widget-store.js +507 -25
  64. package/lib/module/state/widget-store.js.map +1 -1
  65. package/lib/module/storage.js +13 -1
  66. package/lib/module/storage.js.map +1 -1
  67. package/lib/module/types.js +20 -2
  68. package/lib/module/types.js.map +1 -1
  69. package/lib/module/ui/WidgetProvider.js +139 -50
  70. package/lib/module/ui/WidgetProvider.js.map +1 -1
  71. package/lib/module/ui/components/AgentUiBlocks.js +35 -15
  72. package/lib/module/ui/components/AgentUiBlocks.js.map +1 -1
  73. package/lib/module/ui/components/Launcher.js +4 -1
  74. package/lib/module/ui/components/Launcher.js.map +1 -1
  75. package/lib/module/ui/components/Markdown.js +32 -3
  76. package/lib/module/ui/components/Markdown.js.map +1 -1
  77. package/lib/module/ui/components/Post.js +1236 -0
  78. package/lib/module/ui/components/Post.js.map +1 -0
  79. package/lib/module/ui/components/RatingCard.js +6 -3
  80. package/lib/module/ui/components/RatingCard.js.map +1 -1
  81. package/lib/module/ui/hooks/use-widget-insets.js +0 -1
  82. package/lib/module/ui/hooks/use-widget-insets.js.map +1 -1
  83. package/lib/module/ui/screens/chat/ChatView.js +68 -30
  84. package/lib/module/ui/screens/chat/ChatView.js.map +1 -1
  85. package/lib/module/ui/utils/html.js +9 -2
  86. package/lib/module/ui/utils/html.js.map +1 -1
  87. package/lib/module/ui/utils/native-gradient.js +0 -1
  88. package/lib/module/ui/utils/native-gradient.js.map +1 -1
  89. package/lib/module/ui/utils/post-html.js +271 -0
  90. package/lib/module/ui/utils/post-html.js.map +1 -0
  91. package/lib/module/ui/utils/safe-url.js +15 -0
  92. package/lib/module/ui/utils/safe-url.js.map +1 -1
  93. package/lib/typescript/commonjs/agent-ui-blocks.d.ts.map +1 -1
  94. package/lib/typescript/commonjs/api/client.d.ts +19 -1
  95. package/lib/typescript/commonjs/api/client.d.ts.map +1 -1
  96. package/lib/typescript/commonjs/attachments/pickers.d.ts.map +1 -1
  97. package/lib/typescript/commonjs/device-id.d.ts.map +1 -1
  98. package/lib/typescript/commonjs/i18n.d.ts +35 -1
  99. package/lib/typescript/commonjs/i18n.d.ts.map +1 -1
  100. package/lib/typescript/commonjs/index.d.ts +1 -1
  101. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  102. package/lib/typescript/commonjs/locales.d.ts.map +1 -1
  103. package/lib/typescript/commonjs/push/push-notifications.d.ts +6 -2
  104. package/lib/typescript/commonjs/push/push-notifications.d.ts.map +1 -1
  105. package/lib/typescript/commonjs/state/widget-store.d.ts +97 -8
  106. package/lib/typescript/commonjs/state/widget-store.d.ts.map +1 -1
  107. package/lib/typescript/commonjs/storage.d.ts.map +1 -1
  108. package/lib/typescript/commonjs/types.d.ts +91 -8
  109. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  110. package/lib/typescript/commonjs/ui/WidgetProvider.d.ts.map +1 -1
  111. package/lib/typescript/commonjs/ui/components/AgentUiBlocks.d.ts +14 -0
  112. package/lib/typescript/commonjs/ui/components/AgentUiBlocks.d.ts.map +1 -1
  113. package/lib/typescript/commonjs/ui/components/Launcher.d.ts.map +1 -1
  114. package/lib/typescript/commonjs/ui/components/Markdown.d.ts +8 -0
  115. package/lib/typescript/commonjs/ui/components/Markdown.d.ts.map +1 -1
  116. package/lib/typescript/commonjs/ui/components/Post.d.ts +69 -0
  117. package/lib/typescript/commonjs/ui/components/Post.d.ts.map +1 -0
  118. package/lib/typescript/commonjs/ui/components/RatingCard.d.ts.map +1 -1
  119. package/lib/typescript/commonjs/ui/hooks/use-widget-insets.d.ts.map +1 -1
  120. package/lib/typescript/commonjs/ui/screens/chat/ChatView.d.ts.map +1 -1
  121. package/lib/typescript/commonjs/ui/screens/chat/ChatView.types.d.ts +10 -0
  122. package/lib/typescript/commonjs/ui/screens/chat/ChatView.types.d.ts.map +1 -1
  123. package/lib/typescript/commonjs/ui/utils/html.d.ts +1 -0
  124. package/lib/typescript/commonjs/ui/utils/html.d.ts.map +1 -1
  125. package/lib/typescript/commonjs/ui/utils/native-gradient.d.ts.map +1 -1
  126. package/lib/typescript/commonjs/ui/utils/post-html.d.ts +54 -0
  127. package/lib/typescript/commonjs/ui/utils/post-html.d.ts.map +1 -0
  128. package/lib/typescript/commonjs/ui/utils/safe-url.d.ts +6 -0
  129. package/lib/typescript/commonjs/ui/utils/safe-url.d.ts.map +1 -1
  130. package/lib/typescript/module/agent-ui-blocks.d.ts.map +1 -1
  131. package/lib/typescript/module/api/client.d.ts +19 -1
  132. package/lib/typescript/module/api/client.d.ts.map +1 -1
  133. package/lib/typescript/module/attachments/pickers.d.ts.map +1 -1
  134. package/lib/typescript/module/device-id.d.ts.map +1 -1
  135. package/lib/typescript/module/i18n.d.ts +35 -1
  136. package/lib/typescript/module/i18n.d.ts.map +1 -1
  137. package/lib/typescript/module/index.d.ts +1 -1
  138. package/lib/typescript/module/index.d.ts.map +1 -1
  139. package/lib/typescript/module/locales.d.ts.map +1 -1
  140. package/lib/typescript/module/push/push-notifications.d.ts +6 -2
  141. package/lib/typescript/module/push/push-notifications.d.ts.map +1 -1
  142. package/lib/typescript/module/state/widget-store.d.ts +97 -8
  143. package/lib/typescript/module/state/widget-store.d.ts.map +1 -1
  144. package/lib/typescript/module/storage.d.ts.map +1 -1
  145. package/lib/typescript/module/types.d.ts +91 -8
  146. package/lib/typescript/module/types.d.ts.map +1 -1
  147. package/lib/typescript/module/ui/WidgetProvider.d.ts.map +1 -1
  148. package/lib/typescript/module/ui/components/AgentUiBlocks.d.ts +14 -0
  149. package/lib/typescript/module/ui/components/AgentUiBlocks.d.ts.map +1 -1
  150. package/lib/typescript/module/ui/components/Launcher.d.ts.map +1 -1
  151. package/lib/typescript/module/ui/components/Markdown.d.ts +8 -0
  152. package/lib/typescript/module/ui/components/Markdown.d.ts.map +1 -1
  153. package/lib/typescript/module/ui/components/Post.d.ts +69 -0
  154. package/lib/typescript/module/ui/components/Post.d.ts.map +1 -0
  155. package/lib/typescript/module/ui/components/RatingCard.d.ts.map +1 -1
  156. package/lib/typescript/module/ui/hooks/use-widget-insets.d.ts.map +1 -1
  157. package/lib/typescript/module/ui/screens/chat/ChatView.d.ts.map +1 -1
  158. package/lib/typescript/module/ui/screens/chat/ChatView.types.d.ts +10 -0
  159. package/lib/typescript/module/ui/screens/chat/ChatView.types.d.ts.map +1 -1
  160. package/lib/typescript/module/ui/utils/html.d.ts +1 -0
  161. package/lib/typescript/module/ui/utils/html.d.ts.map +1 -1
  162. package/lib/typescript/module/ui/utils/native-gradient.d.ts.map +1 -1
  163. package/lib/typescript/module/ui/utils/post-html.d.ts +54 -0
  164. package/lib/typescript/module/ui/utils/post-html.d.ts.map +1 -0
  165. package/lib/typescript/module/ui/utils/safe-url.d.ts +6 -0
  166. package/lib/typescript/module/ui/utils/safe-url.d.ts.map +1 -1
  167. package/package.json +14 -4
  168. package/src/agent-ui-blocks.ts +2 -1
  169. package/src/api/client.ts +27 -0
  170. package/src/attachments/pickers.ts +3 -6
  171. package/src/device-id.ts +34 -3
  172. package/src/i18n.ts +39 -2
  173. package/src/index.tsx +4 -0
  174. package/src/locales.ts +33 -0
  175. package/src/push/push-notifications.ts +10 -8
  176. package/src/state/widget-store.ts +480 -24
  177. package/src/storage.ts +18 -1
  178. package/src/types.ts +91 -8
  179. package/src/ui/WidgetProvider.tsx +166 -48
  180. package/src/ui/components/AgentUiBlocks.tsx +40 -12
  181. package/src/ui/components/Launcher.tsx +6 -1
  182. package/src/ui/components/Markdown.tsx +34 -3
  183. package/src/ui/components/Post.tsx +943 -0
  184. package/src/ui/components/RatingCard.tsx +6 -2
  185. package/src/ui/hooks/use-widget-insets.ts +0 -1
  186. package/src/ui/screens/chat/ChatView.tsx +77 -42
  187. package/src/ui/screens/chat/ChatView.types.ts +10 -0
  188. package/src/ui/utils/html.ts +10 -2
  189. package/src/ui/utils/native-gradient.ts +0 -1
  190. package/src/ui/utils/post-html.ts +256 -0
  191. package/src/ui/utils/safe-url.ts +15 -0
@@ -0,0 +1,1236 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * A Send post over the host app — the mobile side of Send → Post.
5
+ *
6
+ * Sent as a badge or a snippet, it waits at the bottom of the screen (a pill
7
+ * or a card); tapping opens it in full. Sent as "full" it opens right away. A
8
+ * small post opens as a sheet over the app, a large one as a full screen.
9
+ * The full post has the body (paragraphs, lists, code, a button, social
10
+ * links), what the visitor already replied, and a reply box or reactions.
11
+ * Replies stay under the post and go to the inbox, where the team answers.
12
+ * A reaction answers it: the emoji pops, it says thanks, and goes.
13
+ *
14
+ * "Not now" — the ⌄ button, swiping the top down, tapping outside a sheet, or
15
+ * Android's back — minimizes it back to its pill (or snippet card), where it
16
+ * stays, on later launches too. × closes it for good (the next post loads).
17
+ */
18
+
19
+ import { useEffect, useMemo, useRef, useState } from 'react';
20
+ import { AccessibilityInfo, Animated, Modal, PanResponder, Platform, Pressable, ScrollView, StyleSheet, Text, TextInput, View, useWindowDimensions } from 'react-native';
21
+ import { formatLabel } from "../../i18n.js";
22
+ import { useKeyboardOverlap } from "../hooks/use-keyboard-overlap.js";
23
+ import { useReduceMotion } from "../hooks/use-reduce-motion.js";
24
+ import { contrastingTextColor, fontSize, palette, radius, shadows, spacing, withAlpha } from "../styles/theme.js";
25
+ import { openExternalUrl } from "../utils/safe-url.js";
26
+ import { parsePostHtml } from "../utils/post-html.js";
27
+ import { START_ALIGN } from "../utils/rtl.js";
28
+ import { Avatar } from "./Avatar.js";
29
+ import { ArrowUpIcon, ChevronIcon, CloseIcon } from "./icons.js";
30
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
31
+ export function PostOverlay(props) {
32
+ const {
33
+ post,
34
+ expanded,
35
+ onShown
36
+ } = props;
37
+ // Reported once per post per session (the store dedupes).
38
+ useEffect(() => {
39
+ onShown(post.id);
40
+ }, [onShown, post.id]);
41
+ if (expanded) return /*#__PURE__*/_jsx(PostFull, {
42
+ ...props
43
+ });
44
+ // A snippet waits as its card; a badge — or a full post put away for later — as the pill.
45
+ return post.sentAs === 'snippet' ? /*#__PURE__*/_jsx(PostCard, {
46
+ ...props
47
+ }) : /*#__PURE__*/_jsx(PostBadge, {
48
+ ...props
49
+ });
50
+ }
51
+ function senderLine(post, labels) {
52
+ if (post.sender && post.workspaceName) {
53
+ return formatLabel(labels.from, {
54
+ name: post.sender.name,
55
+ workspace: post.workspaceName
56
+ });
57
+ }
58
+ return post.sender?.name || post.workspaceName || labels.newMessage;
59
+ }
60
+
61
+ /**
62
+ * Slides a card in from below and tells screen-reader users it arrived — the
63
+ * way the in-app notification card does. Holds still under Reduce Motion.
64
+ */
65
+ function useArrival(accessibilityLabel) {
66
+ const reduceMotion = useReduceMotion();
67
+ // Read as "hold still" until the setting is known (see useReduceMotion).
68
+ const animate = reduceMotion === false;
69
+ const entry = useRef(new Animated.Value(animate ? 0 : 1)).current;
70
+
71
+ // Runs per mount: a new post, or the card coming back after the messenger.
72
+ useEffect(() => {
73
+ if (animate) {
74
+ Animated.spring(entry, {
75
+ toValue: 1,
76
+ useNativeDriver: true,
77
+ speed: 16,
78
+ bounciness: 4
79
+ }).start();
80
+ }
81
+ AccessibilityInfo.announceForAccessibility(accessibilityLabel);
82
+ // eslint-disable-next-line react-hooks/exhaustive-deps
83
+ }, []);
84
+ return {
85
+ opacity: entry,
86
+ transform: [{
87
+ translateY: entry.interpolate({
88
+ inputRange: [0, 1],
89
+ outputRange: [spacing.lg, 0]
90
+ })
91
+ }]
92
+ };
93
+ }
94
+ function CloseButton({
95
+ label,
96
+ onPress,
97
+ color = palette.textMuted
98
+ }) {
99
+ return /*#__PURE__*/_jsx(Pressable, {
100
+ onPress: onPress,
101
+ accessibilityRole: "button",
102
+ accessibilityLabel: label,
103
+ hitSlop: 10,
104
+ style: ({
105
+ pressed
106
+ }) => [styles.close, pressed && styles.pressed],
107
+ children: /*#__PURE__*/_jsx(CloseIcon, {
108
+ size: 14,
109
+ color: color
110
+ })
111
+ });
112
+ }
113
+ function MinimizeButton({
114
+ label,
115
+ onPress
116
+ }) {
117
+ return /*#__PURE__*/_jsx(Pressable, {
118
+ onPress: onPress,
119
+ accessibilityRole: "button",
120
+ accessibilityLabel: label,
121
+ hitSlop: 10,
122
+ style: ({
123
+ pressed
124
+ }) => [styles.close, pressed && styles.pressed],
125
+ children: /*#__PURE__*/_jsx(ChevronIcon, {
126
+ size: 16,
127
+ direction: "down",
128
+ color: palette.textMuted
129
+ })
130
+ });
131
+ }
132
+
133
+ /** How far the top must be dragged down to minimize — or a quick flick. */
134
+ const MINIMIZE_DISTANCE = 120;
135
+ const MINIMIZE_VELOCITY = 0.8;
136
+
137
+ /**
138
+ * Dragging the post's top edge down minimizes it, the way a sheet slides away.
139
+ * Pure JS (PanResponder), like the in-app notification's swipe.
140
+ */
141
+ function useSwipeToMinimize(onMinimize) {
142
+ const {
143
+ height
144
+ } = useWindowDimensions();
145
+ const reduceMotion = useReduceMotion();
146
+ const drag = useRef(new Animated.Value(0)).current;
147
+ // Read through a ref: the provider re-renders on every store change, and a
148
+ // PanResponder rebuilt mid-drag would restart the gesture.
149
+ const minimizeRef = useRef(onMinimize);
150
+ minimizeRef.current = onMinimize;
151
+ const handlers = useMemo(() => {
152
+ const settle = () => Animated.spring(drag, {
153
+ toValue: 0,
154
+ useNativeDriver: true,
155
+ bounciness: 4
156
+ }).start();
157
+ return PanResponder.create({
158
+ onMoveShouldSetPanResponder: (_, gesture) => gesture.dy > 6 && gesture.dy > Math.abs(gesture.dx) * 1.5,
159
+ onPanResponderMove: (_, gesture) => drag.setValue(Math.max(0, gesture.dy)),
160
+ onPanResponderRelease: (_, gesture) => {
161
+ if (gesture.dy < MINIMIZE_DISTANCE && gesture.vy < MINIMIZE_VELOCITY) {
162
+ settle();
163
+ return;
164
+ }
165
+ Animated.timing(drag, {
166
+ toValue: height,
167
+ duration: reduceMotion === false ? 200 : 0,
168
+ useNativeDriver: true
169
+ }).start(() => minimizeRef.current());
170
+ },
171
+ onPanResponderTerminate: settle
172
+ });
173
+ }, [drag, height, reduceMotion]);
174
+ return {
175
+ panHandlers: handlers.panHandlers,
176
+ // Never above where it started.
177
+ translateY: drag.interpolate({
178
+ inputRange: [0, 1],
179
+ outputRange: [0, 1],
180
+ extrapolateLeft: 'clamp'
181
+ }),
182
+ // The dimmed backdrop lightens as the sheet goes.
183
+ backdropOpacity: drag.interpolate({
184
+ inputRange: [0, 400],
185
+ outputRange: [1, 0],
186
+ extrapolate: 'clamp'
187
+ })
188
+ };
189
+ }
190
+
191
+ /** Snippet: a card at the bottom with who it's from and the first words. */
192
+ function PostCard({
193
+ post,
194
+ color,
195
+ insets,
196
+ labels,
197
+ onExpand,
198
+ onInteract
199
+ }) {
200
+ const accessibilityLabel = [senderLine(post, labels), post.snippet].filter(Boolean).join(': ');
201
+ const arrival = useArrival(accessibilityLabel);
202
+ return /*#__PURE__*/_jsx(View, {
203
+ pointerEvents: "box-none",
204
+ style: [styles.bottomWrap, {
205
+ bottom: insets.bottom + spacing.md
206
+ }],
207
+ children: /*#__PURE__*/_jsxs(Animated.View, {
208
+ style: [styles.card, arrival],
209
+ children: [/*#__PURE__*/_jsxs(Pressable, {
210
+ onPress: () => onExpand(post.id),
211
+ accessibilityRole: "button",
212
+ accessibilityLabel: accessibilityLabel,
213
+ accessibilityHint: labels.openHint,
214
+ style: ({
215
+ pressed
216
+ }) => [styles.cardBody, pressed && styles.pressed],
217
+ children: [/*#__PURE__*/_jsxs(View, {
218
+ style: styles.cardHead,
219
+ children: [/*#__PURE__*/_jsx(Avatar, {
220
+ avatar: post.sender?.avatarUrl,
221
+ name: post.sender?.name ?? post.workspaceName,
222
+ color: color,
223
+ size: 28
224
+ }), /*#__PURE__*/_jsx(Text, {
225
+ numberOfLines: 1,
226
+ style: styles.senderText,
227
+ children: senderLine(post, labels)
228
+ })]
229
+ }), /*#__PURE__*/_jsx(Text, {
230
+ numberOfLines: 3,
231
+ style: styles.snippet,
232
+ children: post.snippet || labels.newMessage
233
+ })]
234
+ }), /*#__PURE__*/_jsx(View, {
235
+ style: styles.cardClose,
236
+ children: /*#__PURE__*/_jsx(CloseButton, {
237
+ label: labels.close,
238
+ onPress: () => void onInteract(post.id, 'dismiss')
239
+ })
240
+ })]
241
+ })
242
+ });
243
+ }
244
+
245
+ /** Badge: a small pill — the sender and a dot — that opens the post. */
246
+ function PostBadge({
247
+ post,
248
+ color,
249
+ insets,
250
+ labels,
251
+ onExpand,
252
+ onInteract
253
+ }) {
254
+ const accessibilityLabel = [post.sender?.name || labels.newMessage, post.snippet].filter(Boolean).join(': ');
255
+ const arrival = useArrival(accessibilityLabel);
256
+ return /*#__PURE__*/_jsxs(Animated.View, {
257
+ pointerEvents: "box-none",
258
+ style: [styles.badgeWrap, {
259
+ bottom: insets.bottom + spacing.md
260
+ }, arrival],
261
+ children: [/*#__PURE__*/_jsxs(Pressable, {
262
+ onPress: () => onExpand(post.id),
263
+ accessibilityRole: "button",
264
+ accessibilityLabel: accessibilityLabel,
265
+ accessibilityHint: labels.openHint,
266
+ style: ({
267
+ pressed
268
+ }) => [styles.pill, pressed && styles.pressed],
269
+ children: [/*#__PURE__*/_jsx(Avatar, {
270
+ avatar: post.sender?.avatarUrl,
271
+ name: post.sender?.name ?? post.workspaceName,
272
+ color: color,
273
+ size: 28
274
+ }), /*#__PURE__*/_jsx(Text, {
275
+ numberOfLines: 1,
276
+ style: styles.pillText,
277
+ children: post.sender?.name ?? labels.newMessage
278
+ }), /*#__PURE__*/_jsx(View, {
279
+ style: styles.dot
280
+ })]
281
+ }), /*#__PURE__*/_jsx(View, {
282
+ style: styles.pillClose,
283
+ children: /*#__PURE__*/_jsx(CloseButton, {
284
+ label: labels.close,
285
+ onPress: () => void onInteract(post.id, 'dismiss')
286
+ })
287
+ })]
288
+ });
289
+ }
290
+
291
+ /** The post in full: a sheet (small) or a full screen (large). */
292
+ /** How long iOS gets to present the post before it's treated as blocked (another modal was up). */
293
+ const PRESENT_TIMEOUT_MS = 2500;
294
+ function PostFull({
295
+ post,
296
+ color,
297
+ insets,
298
+ labels,
299
+ onMinimize,
300
+ onCollapse,
301
+ onOpenConversation,
302
+ onInteract
303
+ }) {
304
+ const large = post.format === 'large';
305
+ // KeyboardAvoidingView misplaces the composer inside a modal (see the hook);
306
+ // this is the same lift the messenger's panel uses.
307
+ const keyboard = useKeyboardOverlap(true, insets.bottom);
308
+ const dismiss = () => void onInteract(post.id, 'dismiss');
309
+ const minimize = () => onMinimize(post.id);
310
+ const swipe = useSwipeToMinimize(minimize);
311
+ const click = href => {
312
+ void onInteract(post.id, 'click', href);
313
+ void openExternalUrl(href);
314
+ // A link the app routes in-app would otherwise open behind the post.
315
+ onCollapse(post.id);
316
+ };
317
+
318
+ // iOS presents one modal at a time: if the app had its own up (a Modal, an
319
+ // alert, a modal screen), this one never shows and onShow never fires. Fall
320
+ // back to the pill instead of leaving an invisible post.
321
+ const shown = useRef(false);
322
+ const collapseRef = useRef(onCollapse);
323
+ collapseRef.current = onCollapse;
324
+ useEffect(() => {
325
+ if (Platform.OS !== 'ios') return undefined;
326
+ const timer = setTimeout(() => {
327
+ if (!shown.current) collapseRef.current(post.id);
328
+ }, PRESENT_TIMEOUT_MS);
329
+ return () => clearTimeout(timer);
330
+ }, [post.id]);
331
+ const content = /*#__PURE__*/_jsxs(_Fragment, {
332
+ children: [/*#__PURE__*/_jsxs(View, {
333
+ ...swipe.panHandlers,
334
+ children: [/*#__PURE__*/_jsx(View, {
335
+ style: styles.grabber
336
+ }), /*#__PURE__*/_jsxs(View, {
337
+ style: styles.fullHead,
338
+ children: [/*#__PURE__*/_jsx(Avatar, {
339
+ avatar: post.sender?.avatarUrl,
340
+ name: post.sender?.name ?? post.workspaceName,
341
+ color: color,
342
+ size: 32
343
+ }), /*#__PURE__*/_jsx(Text, {
344
+ numberOfLines: 1,
345
+ style: [styles.senderText, styles.fullSender],
346
+ children: senderLine(post, labels)
347
+ }), /*#__PURE__*/_jsx(MinimizeButton, {
348
+ label: labels.minimize,
349
+ onPress: minimize
350
+ }), /*#__PURE__*/_jsx(CloseButton, {
351
+ label: labels.close,
352
+ onPress: dismiss
353
+ })]
354
+ })]
355
+ }), /*#__PURE__*/_jsxs(ScrollView, {
356
+ style: large ? styles.flex : styles.fullScroll,
357
+ contentContainerStyle: styles.fullBody,
358
+ keyboardShouldPersistTaps: "handled",
359
+ children: [/*#__PURE__*/_jsx(PostBody, {
360
+ html: post.bodyHtml,
361
+ color: color,
362
+ onLink: click
363
+ }), post.thread.length > 0 ? /*#__PURE__*/_jsxs(View, {
364
+ style: styles.replies,
365
+ accessibilityLabel: labels.conversation,
366
+ children: [post.thread.map((message, index) => {
367
+ const team = message.from === 'team';
368
+ const previous = post.thread[index - 1];
369
+ // The teammate's name over the first of their answers in a row.
370
+ const showName = team && !!message.senderName && !(previous?.from === 'team' && previous.senderName === message.senderName);
371
+ return /*#__PURE__*/_jsxs(View, {
372
+ style: team ? styles.answerWrap : styles.replyWrap,
373
+ children: [showName ? /*#__PURE__*/_jsx(Text, {
374
+ style: styles.replyMeta,
375
+ children: message.senderName
376
+ }) : null, /*#__PURE__*/_jsx(View, {
377
+ style: team ? styles.answer : [styles.reply, {
378
+ backgroundColor: color
379
+ }],
380
+ children: /*#__PURE__*/_jsx(Text, {
381
+ style: [styles.replyText, {
382
+ color: team ? palette.text : contrastingTextColor(color)
383
+ }],
384
+ children: message.text
385
+ })
386
+ }), team ? null : /*#__PURE__*/_jsx(Text, {
387
+ style: styles.replyMeta,
388
+ children: labels.sent
389
+ })]
390
+ }, `${message.at}-${index}`);
391
+ }), post.conversationId ? /*#__PURE__*/_jsx(Pressable, {
392
+ onPress: () => onOpenConversation(post.id, post.conversationId),
393
+ accessibilityRole: "button",
394
+ accessibilityLabel: labels.continueChat,
395
+ hitSlop: 8,
396
+ style: ({
397
+ pressed
398
+ }) => [styles.continue, pressed && styles.pressed],
399
+ children: /*#__PURE__*/_jsx(Text, {
400
+ style: [styles.continueText, {
401
+ color: contrastingTextColor(color) === palette.ink ? palette.text : color
402
+ }],
403
+ children: labels.continueChat
404
+ })
405
+ }) : null]
406
+ }) : null]
407
+ }), /*#__PURE__*/_jsx(PostFooter, {
408
+ post: post,
409
+ color: color,
410
+ labels: labels,
411
+ onInteract: onInteract
412
+ })]
413
+ });
414
+ return /*#__PURE__*/_jsx(Modal, {
415
+ visible: true
416
+ // Transparent either way, so swiping a large post down shows the app under it.
417
+ ,
418
+ transparent: true,
419
+ animationType: large ? 'slide' : 'fade',
420
+ presentationStyle: "overFullScreen",
421
+ statusBarTranslucent: true,
422
+ onShow: () => {
423
+ shown.current = true;
424
+ }
425
+ // Back is "not now", not ×.
426
+ ,
427
+ onRequestClose: minimize,
428
+ children: large ? /*#__PURE__*/_jsx(Animated.View, {
429
+ accessibilityViewIsModal: true
430
+ // VoiceOver's two-finger scrub: "not now".
431
+ ,
432
+ onAccessibilityEscape: minimize,
433
+ style: [styles.flex, styles.page, {
434
+ paddingTop: insets.top,
435
+ paddingBottom: insets.bottom,
436
+ transform: [{
437
+ translateY: swipe.translateY
438
+ }]
439
+ }],
440
+ children: /*#__PURE__*/_jsx(View, {
441
+ onLayout: keyboard.onLayout,
442
+ style: [styles.flex, {
443
+ paddingBottom: keyboard.overlap
444
+ }],
445
+ children: content
446
+ })
447
+ }) : /*#__PURE__*/_jsxs(View, {
448
+ style: styles.flex,
449
+ children: [/*#__PURE__*/_jsx(Animated.View, {
450
+ style: [StyleSheet.absoluteFill, styles.backdropTint, {
451
+ opacity: swipe.backdropOpacity
452
+ }]
453
+ }), /*#__PURE__*/_jsxs(View, {
454
+ style: [styles.backdrop, {
455
+ paddingTop: insets.top + spacing.xl,
456
+ paddingBottom: insets.bottom + spacing.md
457
+ }],
458
+ children: [/*#__PURE__*/_jsx(Pressable, {
459
+ style: StyleSheet.absoluteFill,
460
+ onPress: minimize,
461
+ accessibilityRole: "button",
462
+ accessibilityLabel: labels.minimize
463
+ }), /*#__PURE__*/_jsx(View, {
464
+ onLayout: keyboard.onLayout,
465
+ pointerEvents: "box-none",
466
+ style: [styles.sheetArea, {
467
+ paddingBottom: keyboard.overlap
468
+ }],
469
+ children: /*#__PURE__*/_jsx(Animated.View, {
470
+ accessibilityViewIsModal: true,
471
+ onAccessibilityEscape: minimize,
472
+ style: [styles.sheet, {
473
+ transform: [{
474
+ translateY: swipe.translateY
475
+ }]
476
+ }],
477
+ children: content
478
+ })
479
+ })]
480
+ })]
481
+ })
482
+ });
483
+ }
484
+ function PostFooter({
485
+ post,
486
+ color,
487
+ labels,
488
+ onInteract
489
+ }) {
490
+ const [text, setText] = useState('');
491
+ const [sending, setSending] = useState(false);
492
+ const [failed, setFailed] = useState(false);
493
+ const [focused, setFocused] = useState(false);
494
+ if (post.replyType === 'reaction' && post.reactions.length > 0) {
495
+ return /*#__PURE__*/_jsx(PostReactions, {
496
+ post: post,
497
+ color: color,
498
+ labels: labels,
499
+ onInteract: onInteract
500
+ });
501
+ }
502
+ if (post.replyType !== 'text') return null;
503
+ const value = text.trim();
504
+ const canSend = !!value && !sending;
505
+ const send = async () => {
506
+ if (!canSend) return;
507
+ setSending(true);
508
+ setFailed(false);
509
+ try {
510
+ await onInteract(post.id, 'reply', value);
511
+ setText('');
512
+ } catch {
513
+ setFailed(true);
514
+ } finally {
515
+ setSending(false);
516
+ }
517
+ };
518
+ return /*#__PURE__*/_jsxs(View, {
519
+ style: styles.footer,
520
+ children: [/*#__PURE__*/_jsxs(View, {
521
+ style: [styles.composer, focused && styles.composerFocused],
522
+ children: [/*#__PURE__*/_jsx(TextInput, {
523
+ value: text,
524
+ onChangeText: setText,
525
+ onFocus: () => setFocused(true),
526
+ onBlur: () => setFocused(false),
527
+ placeholder: labels.replyPlaceholder,
528
+ placeholderTextColor: palette.textSubtle,
529
+ accessibilityLabel: labels.replyPlaceholder,
530
+ maxLength: 2000,
531
+ multiline: true,
532
+ editable: !sending,
533
+ style: styles.input
534
+ }), /*#__PURE__*/_jsx(Pressable, {
535
+ onPress: send,
536
+ disabled: !canSend,
537
+ accessibilityRole: "button",
538
+ accessibilityLabel: labels.send,
539
+ accessibilityState: {
540
+ disabled: !canSend
541
+ }
542
+ // 36pt circle + 4pt on every side = a 44pt target.
543
+ ,
544
+ hitSlop: 4,
545
+ style: ({
546
+ pressed
547
+ }) => [styles.send, canSend ? {
548
+ backgroundColor: color
549
+ } : styles.sendDisabled, pressed && canSend && styles.pressed],
550
+ children: /*#__PURE__*/_jsx(ArrowUpIcon, {
551
+ size: 18,
552
+ color: canSend ? contrastingTextColor(color) : palette.textSubtle
553
+ })
554
+ })]
555
+ }), failed ? /*#__PURE__*/_jsx(Text, {
556
+ accessibilityRole: "alert",
557
+ style: styles.error,
558
+ children: labels.replyFailed
559
+ }) : null]
560
+ });
561
+ }
562
+
563
+ /**
564
+ * The reaction row. A tap pops the emoji and throws a few copies of it (the
565
+ * shared reaction burst), then thanks them; the store takes the post away a
566
+ * moment later. Another tap in that moment changes their pick.
567
+ */
568
+ function PostReactions({
569
+ post,
570
+ color,
571
+ labels,
572
+ onInteract
573
+ }) {
574
+ const [thanked, setThanked] = useState(false);
575
+ const react = emoji => {
576
+ if (!thanked) {
577
+ setThanked(true);
578
+ AccessibilityInfo.announceForAccessibility(labels.thanks);
579
+ }
580
+ void onInteract(post.id, 'reaction', emoji);
581
+ };
582
+ return /*#__PURE__*/_jsxs(View, {
583
+ style: styles.footer,
584
+ children: [/*#__PURE__*/_jsx(View, {
585
+ style: styles.reactions,
586
+ accessibilityRole: "radiogroup",
587
+ accessibilityLabel: labels.reactions,
588
+ children: post.reactions.map(emoji => /*#__PURE__*/_jsx(ReactionChip, {
589
+ emoji: emoji,
590
+ color: color,
591
+ picked: post.reacted === emoji,
592
+ dimmed: !!post.reacted && post.reacted !== emoji,
593
+ onPress: () => react(emoji)
594
+ }, emoji))
595
+ }), thanked && post.reacted ? /*#__PURE__*/_jsx(Text, {
596
+ accessibilityLiveRegion: "polite",
597
+ style: styles.thanks,
598
+ children: labels.thanks
599
+ }) : null]
600
+ });
601
+ }
602
+
603
+ /** Where the burst's copies fly: a fan above the chip. */
604
+ const SPARKS = [-70, -40, -12, 12, 40, 70].map((degrees, index) => {
605
+ const angle = (degrees - 90) * Math.PI / 180;
606
+ const distance = index % 2 ? 54 : 44;
607
+ return {
608
+ x: Math.cos(angle) * distance,
609
+ y: Math.sin(angle) * distance
610
+ };
611
+ });
612
+ function ReactionChip({
613
+ emoji,
614
+ color,
615
+ picked,
616
+ dimmed,
617
+ onPress
618
+ }) {
619
+ const reduceMotion = useReduceMotion();
620
+ const scale = useRef(new Animated.Value(1)).current;
621
+ const spark = useRef(new Animated.Value(0)).current;
622
+ const [bursting, setBursting] = useState(false);
623
+ const press = () => {
624
+ onPress();
625
+ if (reduceMotion !== false) return;
626
+ scale.setValue(1);
627
+ Animated.sequence([Animated.timing(scale, {
628
+ toValue: 1.35,
629
+ duration: 120,
630
+ useNativeDriver: true
631
+ }), Animated.spring(scale, {
632
+ toValue: 1,
633
+ useNativeDriver: true,
634
+ speed: 14,
635
+ bounciness: 12
636
+ })]).start();
637
+ spark.setValue(0);
638
+ setBursting(true);
639
+ Animated.timing(spark, {
640
+ toValue: 1,
641
+ duration: 650,
642
+ useNativeDriver: true
643
+ }).start(({
644
+ finished
645
+ }) => {
646
+ if (finished) setBursting(false);
647
+ });
648
+ };
649
+ return /*#__PURE__*/_jsxs(Pressable, {
650
+ onPress: press,
651
+ accessibilityRole: "radio",
652
+ accessibilityState: {
653
+ checked: picked
654
+ },
655
+ accessibilityLabel: emoji,
656
+ style: ({
657
+ pressed
658
+ }) => [styles.reaction, picked && {
659
+ borderColor: color,
660
+ backgroundColor: withAlpha(color, 0.12)
661
+ }, dimmed ? styles.reactionDim : null, pressed && styles.pressed],
662
+ children: [/*#__PURE__*/_jsx(Animated.Text, {
663
+ style: [styles.reactionEmoji, {
664
+ transform: [{
665
+ scale
666
+ }]
667
+ }],
668
+ children: emoji
669
+ }), bursting ? SPARKS.map((to, index) => /*#__PURE__*/_jsx(Animated.Text, {
670
+ pointerEvents: "none",
671
+ importantForAccessibility: "no-hide-descendants",
672
+ accessibilityElementsHidden: true,
673
+ style: [styles.spark, {
674
+ opacity: spark.interpolate({
675
+ inputRange: [0, 0.6, 1],
676
+ outputRange: [1, 0.9, 0]
677
+ }),
678
+ transform: [{
679
+ translateX: spark.interpolate({
680
+ inputRange: [0, 1],
681
+ outputRange: [0, to.x]
682
+ })
683
+ }, {
684
+ translateY: spark.interpolate({
685
+ inputRange: [0, 1],
686
+ outputRange: [0, to.y]
687
+ })
688
+ }, {
689
+ scale: spark.interpolate({
690
+ inputRange: [0, 1],
691
+ outputRange: [0.6, 1.1]
692
+ })
693
+ }]
694
+ }],
695
+ children: emoji
696
+ }, index)) : null]
697
+ });
698
+ }
699
+
700
+ // ─── Body ──────────────────────────────────────────────────────────────────
701
+
702
+ const ALIGN_ITEMS = {
703
+ left: 'flex-start',
704
+ center: 'center',
705
+ right: 'flex-end'
706
+ };
707
+
708
+ /** Brand marks drawn as a colored circle and letters — the SDK ships no SVG renderer. */
709
+ const SOCIAL = {
710
+ facebook: {
711
+ label: 'Facebook',
712
+ glyph: 'f',
713
+ color: '#1877F2'
714
+ },
715
+ // A plain X: the math-letter 𝕏 is missing from some Android fonts.
716
+ x: {
717
+ label: 'X',
718
+ glyph: 'X',
719
+ color: '#000000'
720
+ },
721
+ linkedin: {
722
+ label: 'LinkedIn',
723
+ glyph: 'in',
724
+ color: '#0A66C2'
725
+ },
726
+ instagram: {
727
+ label: 'Instagram',
728
+ glyph: 'IG',
729
+ color: '#E4405F'
730
+ },
731
+ youtube: {
732
+ label: 'YouTube',
733
+ glyph: '▶',
734
+ color: '#FF0000'
735
+ }
736
+ };
737
+ function Runs({
738
+ runs,
739
+ color,
740
+ onLink
741
+ }) {
742
+ return /*#__PURE__*/_jsx(_Fragment, {
743
+ children: runs.map((run, index) => /*#__PURE__*/_jsx(Text, {
744
+ onPress: run.href ? () => onLink(run.href) : undefined,
745
+ accessibilityRole: run.href ? 'link' : undefined,
746
+ style: [run.bold && styles.bold, run.italic && styles.italic, run.code && styles.inlineCode, run.href ? [styles.link, {
747
+ color: contrastingTextColor(color) === palette.ink ? palette.text : color
748
+ }] : null],
749
+ children: run.text
750
+ }, index))
751
+ });
752
+ }
753
+
754
+ /** "1." / "•" per item, numbering each nested list on its own. */
755
+ function listMarkers(levels) {
756
+ const counts = [];
757
+ return levels.map(({
758
+ depth,
759
+ ordered
760
+ }) => {
761
+ counts.length = depth + 1;
762
+ counts[depth] = (counts[depth] ?? 0) + 1;
763
+ if (ordered) return `${counts[depth]}.`;
764
+ return depth === 0 ? '•' : '◦';
765
+ });
766
+ }
767
+ export function PostBody({
768
+ html,
769
+ color,
770
+ onLink
771
+ }) {
772
+ // The provider re-renders on every store change (typing, new messages).
773
+ const blocks = useMemo(() => parsePostHtml(html), [html]);
774
+ return /*#__PURE__*/_jsx(View, {
775
+ style: styles.body,
776
+ children: blocks.map((block, index) => {
777
+ if (block.type === 'paragraph') {
778
+ return /*#__PURE__*/_jsx(Text, {
779
+ style: styles.paragraph,
780
+ children: /*#__PURE__*/_jsx(Runs, {
781
+ runs: block.runs,
782
+ color: color,
783
+ onLink: onLink
784
+ })
785
+ }, index);
786
+ }
787
+ if (block.type === 'list') {
788
+ const markers = listMarkers(block.levels);
789
+ return /*#__PURE__*/_jsx(View, {
790
+ style: styles.list,
791
+ children: block.items.map((item, itemIndex) => /*#__PURE__*/_jsxs(View, {
792
+ style: [styles.listItem, {
793
+ paddingStart: (block.levels[itemIndex]?.depth ?? 0) * spacing.lg
794
+ }],
795
+ children: [/*#__PURE__*/_jsx(Text, {
796
+ style: styles.marker,
797
+ children: markers[itemIndex]
798
+ }), /*#__PURE__*/_jsx(Text, {
799
+ style: [styles.paragraph, styles.listText],
800
+ children: /*#__PURE__*/_jsx(Runs, {
801
+ runs: item,
802
+ color: color,
803
+ onLink: onLink
804
+ })
805
+ })]
806
+ }, itemIndex))
807
+ }, index);
808
+ }
809
+ if (block.type === 'code') {
810
+ return /*#__PURE__*/_jsx(View, {
811
+ style: styles.codeBlock,
812
+ children: /*#__PURE__*/_jsx(Text, {
813
+ style: styles.codeText,
814
+ children: block.text
815
+ })
816
+ }, index);
817
+ }
818
+ if (block.type === 'button') {
819
+ return /*#__PURE__*/_jsx(View, {
820
+ style: {
821
+ alignItems: ALIGN_ITEMS[block.align]
822
+ },
823
+ children: /*#__PURE__*/_jsx(Pressable, {
824
+ onPress: () => onLink(block.href),
825
+ accessibilityRole: "link",
826
+ style: ({
827
+ pressed
828
+ }) => [styles.button, {
829
+ backgroundColor: color
830
+ }, pressed && styles.pressed],
831
+ children: /*#__PURE__*/_jsx(Text, {
832
+ style: [styles.buttonText, {
833
+ color: contrastingTextColor(color)
834
+ }],
835
+ children: block.label
836
+ })
837
+ })
838
+ }, index);
839
+ }
840
+ return /*#__PURE__*/_jsx(View, {
841
+ style: [styles.social, {
842
+ justifyContent: ALIGN_ITEMS[block.align]
843
+ }],
844
+ children: block.links.map(link => {
845
+ const mark = SOCIAL[link.network];
846
+ return /*#__PURE__*/_jsx(Pressable, {
847
+ onPress: () => onLink(link.href),
848
+ accessibilityRole: "link",
849
+ accessibilityLabel: mark.label,
850
+ hitSlop: 6,
851
+ style: ({
852
+ pressed
853
+ }) => [styles.socialMark, {
854
+ backgroundColor: mark.color
855
+ }, pressed && styles.pressed],
856
+ children: /*#__PURE__*/_jsx(Text, {
857
+ style: styles.socialGlyph,
858
+ children: mark.glyph
859
+ })
860
+ }, `${link.network}-${link.href}`);
861
+ })
862
+ }, index);
863
+ })
864
+ });
865
+ }
866
+ const CLOSE_SIZE = 32;
867
+ const LINE_HEIGHT = 1.45;
868
+ const styles = StyleSheet.create({
869
+ flex: {
870
+ flex: 1
871
+ },
872
+ pressed: {
873
+ opacity: 0.85
874
+ },
875
+ close: {
876
+ width: CLOSE_SIZE,
877
+ height: CLOSE_SIZE,
878
+ borderRadius: radius.sm,
879
+ alignItems: 'center',
880
+ justifyContent: 'center'
881
+ },
882
+ bottomWrap: {
883
+ position: 'absolute',
884
+ left: 0,
885
+ right: 0,
886
+ paddingHorizontal: spacing.lg,
887
+ alignItems: 'center',
888
+ zIndex: 1000,
889
+ elevation: 1000
890
+ },
891
+ card: {
892
+ width: '100%',
893
+ maxWidth: 480,
894
+ borderRadius: radius.xl,
895
+ backgroundColor: palette.white,
896
+ borderWidth: StyleSheet.hairlineWidth,
897
+ borderColor: palette.border,
898
+ ...shadows.floating
899
+ },
900
+ cardBody: {
901
+ paddingVertical: spacing.md,
902
+ paddingStart: spacing.lg,
903
+ // Room for the × in the corner.
904
+ paddingEnd: CLOSE_SIZE + spacing.lg,
905
+ gap: spacing.sm,
906
+ borderRadius: radius.xl
907
+ },
908
+ cardHead: {
909
+ flexDirection: 'row',
910
+ alignItems: 'center',
911
+ gap: spacing.sm
912
+ },
913
+ cardClose: {
914
+ position: 'absolute',
915
+ top: spacing.sm,
916
+ end: spacing.sm
917
+ },
918
+ senderText: {
919
+ flexShrink: 1,
920
+ color: palette.textMuted,
921
+ fontSize: fontSize.meta,
922
+ fontWeight: '600',
923
+ textAlign: START_ALIGN
924
+ },
925
+ snippet: {
926
+ color: palette.text,
927
+ fontSize: fontSize.body,
928
+ lineHeight: fontSize.body * LINE_HEIGHT,
929
+ textAlign: START_ALIGN
930
+ },
931
+ badgeWrap: {
932
+ position: 'absolute',
933
+ end: spacing.lg,
934
+ flexDirection: 'row',
935
+ alignItems: 'center',
936
+ gap: spacing.xs,
937
+ zIndex: 1000,
938
+ elevation: 1000
939
+ },
940
+ pill: {
941
+ flexDirection: 'row',
942
+ alignItems: 'center',
943
+ gap: spacing.sm,
944
+ minHeight: 44,
945
+ paddingVertical: spacing.xs,
946
+ paddingStart: spacing.xs,
947
+ paddingEnd: spacing.md,
948
+ borderRadius: radius.pill,
949
+ backgroundColor: palette.white,
950
+ borderWidth: StyleSheet.hairlineWidth,
951
+ borderColor: palette.border,
952
+ ...shadows.floating
953
+ },
954
+ pillText: {
955
+ maxWidth: 180,
956
+ color: palette.text,
957
+ fontSize: fontSize.meta,
958
+ fontWeight: '600'
959
+ },
960
+ dot: {
961
+ width: 8,
962
+ height: 8,
963
+ borderRadius: 4,
964
+ backgroundColor: palette.danger
965
+ },
966
+ pillClose: {
967
+ borderRadius: radius.pill,
968
+ backgroundColor: palette.white,
969
+ borderWidth: StyleSheet.hairlineWidth,
970
+ borderColor: palette.border,
971
+ ...shadows.card
972
+ },
973
+ backdrop: {
974
+ flex: 1,
975
+ paddingHorizontal: spacing.md
976
+ },
977
+ backdropTint: {
978
+ backgroundColor: 'rgba(15, 23, 42, 0.45)'
979
+ },
980
+ sheetArea: {
981
+ flex: 1,
982
+ justifyContent: 'flex-end'
983
+ },
984
+ sheet: {
985
+ maxHeight: '100%',
986
+ borderRadius: radius.xl,
987
+ backgroundColor: palette.white,
988
+ overflow: 'hidden',
989
+ ...shadows.floating
990
+ },
991
+ page: {
992
+ backgroundColor: palette.white
993
+ },
994
+ grabber: {
995
+ alignSelf: 'center',
996
+ width: 36,
997
+ height: 5,
998
+ borderRadius: 3,
999
+ marginTop: spacing.sm,
1000
+ backgroundColor: palette.border
1001
+ },
1002
+ fullHead: {
1003
+ flexDirection: 'row',
1004
+ alignItems: 'center',
1005
+ gap: spacing.sm,
1006
+ paddingStart: spacing.lg,
1007
+ paddingEnd: spacing.sm,
1008
+ paddingTop: spacing.xs,
1009
+ paddingBottom: spacing.md
1010
+ },
1011
+ fullSender: {
1012
+ flex: 1
1013
+ },
1014
+ fullScroll: {
1015
+ flexGrow: 0,
1016
+ flexShrink: 1
1017
+ },
1018
+ fullBody: {
1019
+ paddingHorizontal: spacing.lg,
1020
+ paddingBottom: spacing.lg,
1021
+ gap: spacing.lg
1022
+ },
1023
+ body: {
1024
+ gap: spacing.md
1025
+ },
1026
+ paragraph: {
1027
+ color: palette.text,
1028
+ fontSize: fontSize.body,
1029
+ lineHeight: fontSize.body * LINE_HEIGHT,
1030
+ textAlign: START_ALIGN
1031
+ },
1032
+ bold: {
1033
+ fontWeight: '700'
1034
+ },
1035
+ italic: {
1036
+ fontStyle: 'italic'
1037
+ },
1038
+ inlineCode: {
1039
+ fontFamily: Platform.select({
1040
+ ios: 'Menlo',
1041
+ default: 'monospace'
1042
+ }),
1043
+ fontSize: fontSize.meta,
1044
+ backgroundColor: palette.surfaceAlt
1045
+ },
1046
+ link: {
1047
+ textDecorationLine: 'underline',
1048
+ fontWeight: '600'
1049
+ },
1050
+ list: {
1051
+ gap: spacing.xs
1052
+ },
1053
+ listItem: {
1054
+ flexDirection: 'row',
1055
+ gap: spacing.sm
1056
+ },
1057
+ marker: {
1058
+ minWidth: spacing.lg,
1059
+ color: palette.textMuted,
1060
+ fontSize: fontSize.body,
1061
+ lineHeight: fontSize.body * LINE_HEIGHT
1062
+ },
1063
+ listText: {
1064
+ flex: 1
1065
+ },
1066
+ codeBlock: {
1067
+ borderRadius: radius.md,
1068
+ backgroundColor: palette.surfaceAlt,
1069
+ padding: spacing.md
1070
+ },
1071
+ codeText: {
1072
+ color: palette.text,
1073
+ fontFamily: Platform.select({
1074
+ ios: 'Menlo',
1075
+ default: 'monospace'
1076
+ }),
1077
+ fontSize: fontSize.meta,
1078
+ lineHeight: fontSize.meta * LINE_HEIGHT
1079
+ },
1080
+ button: {
1081
+ minHeight: 44,
1082
+ paddingHorizontal: spacing.lg,
1083
+ borderRadius: radius.md,
1084
+ alignItems: 'center',
1085
+ justifyContent: 'center'
1086
+ },
1087
+ buttonText: {
1088
+ fontSize: fontSize.body,
1089
+ fontWeight: '700'
1090
+ },
1091
+ social: {
1092
+ flexDirection: 'row',
1093
+ flexWrap: 'wrap',
1094
+ gap: spacing.sm
1095
+ },
1096
+ socialMark: {
1097
+ width: 36,
1098
+ height: 36,
1099
+ borderRadius: 18,
1100
+ alignItems: 'center',
1101
+ justifyContent: 'center'
1102
+ },
1103
+ socialGlyph: {
1104
+ color: palette.white,
1105
+ fontSize: fontSize.meta,
1106
+ fontWeight: '800'
1107
+ },
1108
+ replies: {
1109
+ gap: spacing.sm
1110
+ },
1111
+ replyWrap: {
1112
+ alignItems: 'flex-end',
1113
+ gap: spacing.xs / 2
1114
+ },
1115
+ answerWrap: {
1116
+ alignItems: 'flex-start',
1117
+ gap: spacing.xs / 2
1118
+ },
1119
+ answer: {
1120
+ maxWidth: '85%',
1121
+ paddingHorizontal: spacing.md,
1122
+ paddingVertical: spacing.sm,
1123
+ borderRadius: radius.xl,
1124
+ borderBottomStartRadius: spacing.xs,
1125
+ backgroundColor: palette.surfaceAlt
1126
+ },
1127
+ continue: {
1128
+ alignSelf: 'center',
1129
+ paddingVertical: spacing.xs,
1130
+ paddingHorizontal: spacing.sm,
1131
+ marginTop: spacing.xs
1132
+ },
1133
+ continueText: {
1134
+ fontSize: fontSize.meta,
1135
+ fontWeight: '600'
1136
+ },
1137
+ reply: {
1138
+ maxWidth: '85%',
1139
+ paddingHorizontal: spacing.md,
1140
+ paddingVertical: spacing.sm,
1141
+ borderRadius: radius.xl,
1142
+ borderBottomEndRadius: spacing.xs
1143
+ },
1144
+ replyText: {
1145
+ fontSize: fontSize.body,
1146
+ lineHeight: fontSize.body * LINE_HEIGHT
1147
+ },
1148
+ replyMeta: {
1149
+ color: palette.textSubtle,
1150
+ fontSize: fontSize.caption
1151
+ },
1152
+ footer: {
1153
+ borderTopWidth: StyleSheet.hairlineWidth,
1154
+ borderTopColor: palette.border,
1155
+ backgroundColor: palette.white,
1156
+ paddingHorizontal: spacing.md,
1157
+ paddingVertical: spacing.sm,
1158
+ gap: spacing.xs
1159
+ },
1160
+ // Matches the messenger's composer (ChatView.styles.ts).
1161
+ composer: {
1162
+ flexDirection: 'row',
1163
+ alignItems: 'flex-end',
1164
+ gap: spacing.sm,
1165
+ minHeight: 48,
1166
+ backgroundColor: palette.surfaceAlt,
1167
+ borderRadius: 24,
1168
+ borderWidth: 1,
1169
+ borderColor: palette.border,
1170
+ paddingStart: spacing.lg,
1171
+ paddingEnd: spacing.xs + 2,
1172
+ paddingVertical: spacing.xs + 2
1173
+ },
1174
+ composerFocused: {
1175
+ backgroundColor: palette.white
1176
+ },
1177
+ input: {
1178
+ flex: 1,
1179
+ maxHeight: 120,
1180
+ padding: 0,
1181
+ paddingTop: 6,
1182
+ paddingBottom: 6,
1183
+ color: palette.ink,
1184
+ fontSize: fontSize.input,
1185
+ lineHeight: fontSize.input * 1.4,
1186
+ textAlign: START_ALIGN
1187
+ },
1188
+ send: {
1189
+ width: 36,
1190
+ height: 36,
1191
+ borderRadius: 18,
1192
+ alignItems: 'center',
1193
+ justifyContent: 'center'
1194
+ },
1195
+ sendDisabled: {
1196
+ backgroundColor: palette.surfaceAlt
1197
+ },
1198
+ error: {
1199
+ color: palette.danger,
1200
+ fontSize: fontSize.caption
1201
+ },
1202
+ reactions: {
1203
+ flexDirection: 'row',
1204
+ justifyContent: 'center',
1205
+ gap: spacing.sm,
1206
+ paddingVertical: spacing.xs
1207
+ },
1208
+ reaction: {
1209
+ width: 48,
1210
+ height: 48,
1211
+ borderRadius: 24,
1212
+ borderWidth: 1,
1213
+ borderColor: palette.border,
1214
+ backgroundColor: palette.white,
1215
+ alignItems: 'center',
1216
+ justifyContent: 'center'
1217
+ },
1218
+ reactionDim: {
1219
+ opacity: 0.5
1220
+ },
1221
+ reactionEmoji: {
1222
+ fontSize: 24
1223
+ },
1224
+ spark: {
1225
+ position: 'absolute',
1226
+ fontSize: 16
1227
+ },
1228
+ thanks: {
1229
+ textAlign: 'center',
1230
+ color: palette.text,
1231
+ fontSize: fontSize.meta,
1232
+ fontWeight: '600',
1233
+ paddingBottom: spacing.xs
1234
+ }
1235
+ });
1236
+ //# sourceMappingURL=Post.js.map