@poolse/react-native 0.1.1

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.js ADDED
@@ -0,0 +1,2470 @@
1
+ import { useAttachmentUpload, useMembers, useAttachmentUrl, useUser, useMe, useThread, useMessages, useTyping, useConversations, usePresence, useReactions, usePoolse } from '@poolse/react';
2
+ import { createContext, forwardRef, useRef, useState, useImperativeHandle, useContext, useMemo, useCallback, useEffect } from 'react';
3
+ import { StyleSheet, KeyboardAvoidingView, View, Pressable, TextInput, Platform, Text, Modal, Image, FlatList, ActivityIndicator, Animated, AppState, Easing } from 'react-native';
4
+ import { SvgXml, Svg, Path, Circle, G } from 'react-native-svg';
5
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
+
7
+ // src/ConversationView.tsx
8
+
9
+ // src/theme/default.ts
10
+ var lightColors = {
11
+ brand: "#ff5436",
12
+ brandStrong: "#d63415",
13
+ brandSoft: "#ffe9e2",
14
+ onBrand: "#fbf7f4",
15
+ paper: "#fbf7f4",
16
+ surface: "#ffffff",
17
+ surface2: "#f4eee9",
18
+ ink: "#1c1a19",
19
+ ink2: "#6b6560",
20
+ ink3: "#9a938c",
21
+ border: "#eae3dc",
22
+ presence: "#16c172",
23
+ success: "#16a34a",
24
+ warning: "#e08c16",
25
+ error: "#e5484d",
26
+ info: "#3b82f6",
27
+ selfBubble: "#ff5436",
28
+ selfBubbleText: "#fbf7f4",
29
+ otherBubble: "#ffffff",
30
+ otherBubbleText: "#1c1a19",
31
+ unreadPill: "#ff5436",
32
+ unreadPillText: "#fbf7f4"
33
+ };
34
+ var darkColors = {
35
+ brand: "#ff6a4d",
36
+ brandStrong: "#ff8366",
37
+ brandSoft: "#2c1712",
38
+ onBrand: "#fbf7f4",
39
+ paper: "#0e0d10",
40
+ surface: "#16141a",
41
+ surface2: "#201d25",
42
+ ink: "#f6f2ee",
43
+ ink2: "#a8a29b",
44
+ ink3: "#766f68",
45
+ border: "#2a2630",
46
+ presence: "#22d37a",
47
+ success: "#34d399",
48
+ warning: "#fbbf24",
49
+ error: "#f87171",
50
+ info: "#60a5fa",
51
+ selfBubble: "#ff6a4d",
52
+ selfBubbleText: "#fbf7f4",
53
+ otherBubble: "#16141a",
54
+ otherBubbleText: "#f6f2ee",
55
+ unreadPill: "#ff6a4d",
56
+ unreadPillText: "#fbf7f4"
57
+ };
58
+ var type = {
59
+ // RN can't use Bricolage/Hanken/JetBrains without expo-font setup,
60
+ // so we default to the platform sans-serif. Devs override via the
61
+ // theme prop if they ship custom fonts.
62
+ fontDisplay: "System",
63
+ fontBody: "System",
64
+ fontMono: "Menlo",
65
+ bodySize: 15,
66
+ lineHeight: 22,
67
+ weightRegular: "400",
68
+ weightMedium: "500",
69
+ weightSemibold: "600",
70
+ weightBold: "700"
71
+ };
72
+ var radii = {
73
+ sm: 8,
74
+ md: 12,
75
+ lg: 16,
76
+ xl: 24,
77
+ pill: 999,
78
+ bubble: 16
79
+ };
80
+ var spacing = {
81
+ xs: 4,
82
+ sm: 8,
83
+ md: 12,
84
+ lg: 16,
85
+ xl: 24
86
+ };
87
+ var lightShadows = {
88
+ sm: {
89
+ shadowColor: "#1c1a19",
90
+ shadowOpacity: 0.08,
91
+ shadowOffset: { width: 0, height: 1 },
92
+ shadowRadius: 2,
93
+ elevation: 1
94
+ },
95
+ md: {
96
+ shadowColor: "#1c1a19",
97
+ shadowOpacity: 0.18,
98
+ shadowOffset: { width: 0, height: 10 },
99
+ shadowRadius: 30,
100
+ elevation: 6
101
+ },
102
+ lg: {
103
+ shadowColor: "#1c1a19",
104
+ shadowOpacity: 0.28,
105
+ shadowOffset: { width: 0, height: 24 },
106
+ shadowRadius: 60,
107
+ elevation: 12
108
+ }
109
+ };
110
+ var darkShadows = {
111
+ sm: {
112
+ shadowColor: "#000000",
113
+ shadowOpacity: 0.4,
114
+ shadowOffset: { width: 0, height: 1 },
115
+ shadowRadius: 2,
116
+ elevation: 1
117
+ },
118
+ md: {
119
+ shadowColor: "#000000",
120
+ shadowOpacity: 0.55,
121
+ shadowOffset: { width: 0, height: 10 },
122
+ shadowRadius: 30,
123
+ elevation: 6
124
+ },
125
+ lg: {
126
+ shadowColor: "#000000",
127
+ shadowOpacity: 0.7,
128
+ shadowOffset: { width: 0, height: 24 },
129
+ shadowRadius: 60,
130
+ elevation: 12
131
+ }
132
+ };
133
+ var defaultLightTheme = {
134
+ mode: "light",
135
+ colors: lightColors,
136
+ type,
137
+ radii,
138
+ spacing,
139
+ shadows: lightShadows
140
+ };
141
+ var defaultDarkTheme = {
142
+ mode: "dark",
143
+ colors: darkColors,
144
+ type,
145
+ radii,
146
+ spacing,
147
+ shadows: darkShadows
148
+ };
149
+ var ThemeContext = createContext(null);
150
+ function PoolseTheme({ mode, theme, children }) {
151
+ const resolved = useMemo(() => {
152
+ const base = (mode ?? theme?.mode) === "dark" ? defaultDarkTheme : defaultLightTheme;
153
+ if (!theme) return base;
154
+ return {
155
+ mode: theme.mode ?? base.mode,
156
+ colors: { ...base.colors, ...theme.colors },
157
+ type: { ...base.type, ...theme.type },
158
+ radii: { ...base.radii, ...theme.radii },
159
+ spacing: { ...base.spacing, ...theme.spacing },
160
+ shadows: {
161
+ sm: { ...base.shadows.sm, ...theme.shadows?.sm },
162
+ md: { ...base.shadows.md, ...theme.shadows?.md },
163
+ lg: { ...base.shadows.lg, ...theme.shadows?.lg }
164
+ }
165
+ };
166
+ }, [mode, theme]);
167
+ return /* @__PURE__ */ jsx(ThemeContext.Provider, { value: resolved, children });
168
+ }
169
+ function usePoolseTheme() {
170
+ return useContext(ThemeContext) ?? defaultLightTheme;
171
+ }
172
+
173
+ // src/primitives/icons-data.ts
174
+ var ICONS = {
175
+ attachment: {
176
+ vb: "0 0 24 24",
177
+ d: '<path d="M20 11.6 11.4 20.2a5 5 0 0 1-7.1-7.1l8.7-8.7a3.3 3.3 0 0 1 4.7 4.7l-8.6 8.6a1.65 1.65 0 0 1-2.36-2.32l7.86-7.86"/>'
178
+ },
179
+ bell: {
180
+ vb: "0 0 24 24",
181
+ d: '<path d="M6 9.2a6 6 0 0 1 12 0c0 4.8 1.4 6.3 1.9 6.8H4.1C4.6 15.5 6 14 6 9.2Z"/><path d="M9.6 19.8a2.5 2.5 0 0 0 4.8 0"/>'
182
+ },
183
+ "check-double": {
184
+ vb: "0 0 24 24",
185
+ d: '<path d="M2.5 12.8 6.9 17 15.4 7.6"/><path d="M11.4 16.4 12.3 17.2 21 7.6"/>'
186
+ },
187
+ check: {
188
+ vb: "0 0 24 24",
189
+ d: '<path d="M4.5 12.6 9.4 17.4 19.5 6.6"/>'
190
+ },
191
+ "chevron-down": {
192
+ vb: "0 0 24 24",
193
+ d: '<path d="M5 9 12 16 19 9"/>'
194
+ },
195
+ "chevron-right": {
196
+ vb: "0 0 24 24",
197
+ d: '<path d="M9 5 16 12 9 19"/>'
198
+ },
199
+ close: {
200
+ vb: "0 0 24 24",
201
+ d: '<path d="M6 6 18 18M18 6 6 18"/>'
202
+ },
203
+ code: {
204
+ vb: "0 0 24 24",
205
+ d: '<path d="M9 7.5 4.5 12 9 16.5"/><path d="M15 7.5 19.5 12 15 16.5"/><path d="M13.2 5 10.8 19"/>'
206
+ },
207
+ compose: {
208
+ vb: "0 0 24 24",
209
+ d: '<path d="M11.5 4H6a2.5 2.5 0 0 0-2.5 2.5V18A2.5 2.5 0 0 0 6 20.5h11.5A2.5 2.5 0 0 0 20 18v-5.5"/><path d="M18.4 2.7a2.05 2.05 0 0 1 2.9 2.9L12 15l-4 1 1-4 9.4-9.3Z"/>'
210
+ },
211
+ copy: {
212
+ vb: "0 0 24 24",
213
+ d: '<rect x="8.4" y="8.4" width="11.2" height="11.2" rx="2.3"/><path d="M5.6 15.6H5A1.6 1.6 0 0 1 3.4 14V5A1.6 1.6 0 0 1 5 3.4h9A1.6 1.6 0 0 1 15.6 5v.6"/>'
214
+ },
215
+ download: {
216
+ vb: "0 0 24 24",
217
+ d: '<path d="M12 3.2v11.6"/><path d="M7.4 10.2 12 14.8 16.6 10.2"/><path d="M4 19.6h16"/>'
218
+ },
219
+ edit: {
220
+ vb: "0 0 24 24",
221
+ d: '<path d="M16.7 3.3a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4 12.7-12.7Z"/>'
222
+ },
223
+ emoji: {
224
+ vb: "0 0 24 24",
225
+ d: '<circle cx="12" cy="12" r="8.6"/><circle cx="9" cy="10" r="1" fill="currentColor" stroke="none"/><circle cx="15" cy="10" r="1" fill="currentColor" stroke="none"/><path d="M8.4 14.2a4.6 4.6 0 0 0 7.2 0"/>'
226
+ },
227
+ heart: {
228
+ vb: "0 0 24 24",
229
+ d: '<path d="M12 20.3 4.4 12.9a4.6 4.6 0 1 1 6.5-6.5l1.1 1.1 1.1-1.1a4.6 4.6 0 1 1 6.5 6.5L12 20.3Z"/>'
230
+ },
231
+ image: {
232
+ vb: "0 0 24 24",
233
+ d: '<rect x="3.2" y="4.2" width="17.6" height="15.6" rx="2.6"/><circle cx="8.6" cy="9.4" r="1.7" fill="currentColor" stroke="none"/><path d="M20.8 15.8 16 11 6 19.8"/>'
234
+ },
235
+ info: {
236
+ vb: "0 0 24 24",
237
+ d: '<circle cx="12" cy="12" r="8.6"/><path d="M12 11v5.2"/><circle cx="12" cy="7.7" r="1" fill="currentColor" stroke="none"/>'
238
+ },
239
+ key: {
240
+ vb: "0 0 24 24",
241
+ d: '<circle cx="8" cy="8" r="4.4"/><path d="M11.1 11.1 20 20"/><path d="M16.6 16.6 18.6 14.6M14 14l2-2"/>'
242
+ },
243
+ link: {
244
+ vb: "0 0 24 24",
245
+ d: '<path d="M10.2 13.8a3.5 3.5 0 0 0 5 0l3-3a3.5 3.5 0 0 0-5-5l-1.2 1.2"/><path d="M13.8 10.2a3.5 3.5 0 0 0-5 0l-3 3a3.5 3.5 0 0 0 5 5l1.2-1.2"/>'
246
+ },
247
+ lock: {
248
+ vb: "0 0 24 24",
249
+ d: '<rect x="4.6" y="10.4" width="14.8" height="9.8" rx="2.2"/><path d="M8 10.4V7a4 4 0 0 1 8 0v3.4"/>'
250
+ },
251
+ message: {
252
+ vb: "0 0 24 24",
253
+ d: '<path d="M20.5 11.4c0 4.5-3.9 8.1-8.5 8.1a9 9 0 0 1-3.7-.78L3.5 20.5l1.8-4.6A7.9 7.9 0 0 1 3.5 11.4C3.5 6.9 7.4 3.3 12 3.3s8.5 3.6 8.5 8.1Z"/>'
254
+ },
255
+ messages: {
256
+ vb: "0 0 24 24",
257
+ d: '<path d="M14 9.2a4.7 4.7 0 0 1-4.7 4.7H6.8L3.5 16.4v-2.9A4.7 4.7 0 0 1 6.8 4.5h2.5A4.7 4.7 0 0 1 14 9.2Z"/><path d="M9.4 13.6A4.7 4.7 0 0 0 13.6 16h3.6l3.3 2.5v-2.9a4.7 4.7 0 0 0-2.4-4.1"/>'
258
+ },
259
+ mic: {
260
+ vb: "0 0 24 24",
261
+ d: '<rect x="9" y="2.6" width="6" height="11.2" rx="3"/><path d="M5.6 11.2a6.4 6.4 0 0 0 12.8 0"/><path d="M12 17.6v3.4"/><path d="M8.6 21h6.8"/>'
262
+ },
263
+ "more-h": {
264
+ vb: "0 0 24 24",
265
+ d: '<circle cx="5.4" cy="12" r="1.6" fill="currentColor" stroke="none"/><circle cx="12" cy="12" r="1.6" fill="currentColor" stroke="none"/><circle cx="18.6" cy="12" r="1.6" fill="currentColor" stroke="none"/>'
266
+ },
267
+ "more-v": {
268
+ vb: "0 0 24 24",
269
+ d: '<circle cx="12" cy="5.4" r="1.6" fill="currentColor" stroke="none"/><circle cx="12" cy="12" r="1.6" fill="currentColor" stroke="none"/><circle cx="12" cy="18.6" r="1.6" fill="currentColor" stroke="none"/>'
270
+ },
271
+ pin: {
272
+ vb: "0 0 24 24",
273
+ d: '<path d="M9 3.4h6v1.8l-1 5.6 3 3.2H7l3-3.2-1-5.6V3.4Z"/><path d="M12 17v3.6"/>'
274
+ },
275
+ plus: {
276
+ vb: "0 0 24 24",
277
+ d: '<path d="M12 5v14M5 12h14"/>'
278
+ },
279
+ presence: {
280
+ vb: "0 0 24 24",
281
+ d: '<circle cx="12" cy="12" r="9" fill="currentColor" opacity="0.18" stroke="none"/><circle cx="12" cy="12" r="5" fill="currentColor" stroke="none"/>'
282
+ },
283
+ pulse: {
284
+ vb: "0 0 24 24",
285
+ d: '<path d="M3 13h4.6l2.2-4.6 3.4 9 2.2-4.4H21"/>'
286
+ },
287
+ reply: {
288
+ vb: "0 0 24 24",
289
+ d: '<path d="M9 7 4 12l5 5"/><path d="M4 12h9.5a6.5 6.5 0 0 1 6.5 6.5V20"/>'
290
+ },
291
+ search: {
292
+ vb: "0 0 24 24",
293
+ d: '<circle cx="11" cy="11" r="6.8"/><path d="M20.8 20.8 16 16"/>'
294
+ },
295
+ "send-fill": {
296
+ vb: "0 0 24 24",
297
+ d: '<path fill="currentColor" stroke="none" d="M21.7 2.3a1 1 0 0 1 .25 1L15.3 21.8a1 1 0 0 1-1.86.06L10.6 14.2 3 11.4a1 1 0 0 1 .06-1.9L20.7 2.05a1 1 0 0 1 1 .25Z"/>'
298
+ },
299
+ send: {
300
+ vb: "0 0 24 24",
301
+ d: '<path d="M21.4 2.6 11 13"/><path d="M21.4 2.6 14.8 21.5a.6.6 0 0 1-1.1.05L11 14l-7.5-2.7a.6.6 0 0 1 .05-1.1L21.4 2.6Z"/>'
302
+ },
303
+ settings: {
304
+ vb: "0 0 24 24",
305
+ d: '<circle cx="12" cy="12" r="3.1"/><path d="M12 2.6V5M12 19v2.4M4.2 4.2 5.9 5.9M18.1 18.1l1.7 1.7M2.6 12H5M19 12h2.4M4.2 19.8 5.9 18.1M18.1 5.9l1.7-1.7"/>'
306
+ },
307
+ sliders: {
308
+ vb: "0 0 24 24",
309
+ d: '<path d="M4 7h9M17 7h3"/><circle cx="15" cy="7" r="2.1"/><path d="M4 17h3M11 17h9"/><circle cx="9" cy="17" r="2.1"/>'
310
+ },
311
+ "thumbs-up": {
312
+ vb: "0 0 24 24",
313
+ d: '<path d="M7 10.4V20H4.2V10.4Z"/><path d="M7 10.4 11 3.2a2 2 0 0 1 1.9 2.2L12.4 9H18a2 2 0 0 1 2 2.4l-1.1 6A2 2 0 0 1 16.9 20H7"/>'
314
+ },
315
+ trash: {
316
+ vb: "0 0 24 24",
317
+ d: '<path d="M4 7h16"/><path d="M9.2 7V5.2A1.8 1.8 0 0 1 11 3.4h2a1.8 1.8 0 0 1 1.8 1.8V7"/><path d="M6.2 7 7.2 19A1.9 1.9 0 0 0 9.1 20.7h5.8A1.9 1.9 0 0 0 16.8 19L17.8 7"/><path d="M10.3 11v5M13.7 11v5"/>'
318
+ },
319
+ typing: {
320
+ vb: "0 0 24 24",
321
+ d: '<circle cx="6" cy="12" r="1.7" fill="currentColor" stroke="none"/><circle cx="12" cy="12" r="1.7" fill="currentColor" stroke="none"/><circle cx="18" cy="12" r="1.7" fill="currentColor" stroke="none"/>'
322
+ },
323
+ "user-plus": {
324
+ vb: "0 0 24 24",
325
+ d: '<circle cx="9" cy="8" r="3.8"/><path d="M3 20a6 6 0 0 1 12 0"/><path d="M18 8v6M15 11h6"/>'
326
+ },
327
+ user: {
328
+ vb: "0 0 24 24",
329
+ d: '<circle cx="12" cy="8" r="4"/><path d="M4.6 20a7.4 7.4 0 0 1 14.8 0"/>'
330
+ },
331
+ users: {
332
+ vb: "0 0 24 24",
333
+ d: '<circle cx="9" cy="8" r="3.6"/><path d="M2.6 20a6.4 6.4 0 0 1 12.8 0"/><path d="M15.8 4.7a3.6 3.6 0 0 1 0 6.6"/><path d="M17.6 20a6.4 6.4 0 0 0-3.1-5.5"/>'
334
+ },
335
+ webhook: {
336
+ vb: "0 0 24 24",
337
+ d: '<circle cx="6" cy="6" r="2.6"/><circle cx="18" cy="9" r="2.6"/><circle cx="9.5" cy="18" r="2.6"/><path d="M7.9 7.9 12 12l3.7-1.5"/><path d="M11.6 12.7 9.8 16"/>'
338
+ }
339
+ };
340
+ function PoolseIcon({ name, size = 20, color, label }) {
341
+ const theme = usePoolseTheme();
342
+ const def = ICONS[name];
343
+ if (!def) return null;
344
+ const stroke = color ?? theme.colors.ink;
345
+ const xml = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${def.vb}" fill="none" stroke="${stroke}" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">${def.d}</svg>`;
346
+ const a11y = label === null ? { accessibilityRole: "none" } : { accessibilityRole: "image", accessibilityLabel: label ?? name };
347
+ return /* @__PURE__ */ jsx(SvgXml, { xml, width: size, height: size, ...a11y });
348
+ }
349
+ function AttachmentPicker({ visible, onClose, onPickInputs }) {
350
+ const theme = usePoolseTheme();
351
+ const upload = useAttachmentUpload();
352
+ const pickFromKind = async (kind) => {
353
+ try {
354
+ const adapter = onPickInputs ?? defaultAdapter;
355
+ const inputs = await adapter(kind);
356
+ for (const input of inputs) {
357
+ void upload.upload(input);
358
+ }
359
+ } catch (err) {
360
+ console.warn("[poolse/picker]", err);
361
+ } finally {
362
+ onClose();
363
+ }
364
+ };
365
+ return /* @__PURE__ */ jsx(Modal, { visible, transparent: true, animationType: "slide", onRequestClose: onClose, children: /* @__PURE__ */ jsx(Pressable, { style: styles.backdrop, onPress: onClose, children: /* @__PURE__ */ jsxs(
366
+ View,
367
+ {
368
+ style: [
369
+ styles.sheet,
370
+ {
371
+ backgroundColor: theme.colors.surface,
372
+ borderTopLeftRadius: theme.radii.lg,
373
+ borderTopRightRadius: theme.radii.lg
374
+ },
375
+ theme.shadows.lg
376
+ ],
377
+ children: [
378
+ /* @__PURE__ */ jsxs(Pressable, { style: styles.item, onPress: () => pickFromKind("image"), children: [
379
+ /* @__PURE__ */ jsx(PoolseIcon, { name: "attachment", size: 20, color: theme.colors.ink2 }),
380
+ /* @__PURE__ */ jsx(Text, { style: [styles.itemText, { color: theme.colors.ink }], children: "Photo or video" })
381
+ ] }),
382
+ /* @__PURE__ */ jsxs(Pressable, { style: styles.item, onPress: () => pickFromKind("document"), children: [
383
+ /* @__PURE__ */ jsx(PoolseIcon, { name: "attachment", size: 20, color: theme.colors.ink2 }),
384
+ /* @__PURE__ */ jsx(Text, { style: [styles.itemText, { color: theme.colors.ink }], children: "File" })
385
+ ] }),
386
+ /* @__PURE__ */ jsx(Pressable, { style: styles.cancel, onPress: onClose, children: /* @__PURE__ */ jsx(Text, { style: [styles.itemText, { color: theme.colors.brand }], children: "Cancel" }) })
387
+ ]
388
+ }
389
+ ) }) });
390
+ }
391
+ async function defaultAdapter(kind) {
392
+ if (kind === "image") {
393
+ const mod2 = await tryImport("expo-image-picker");
394
+ if (!mod2)
395
+ throw new Error("expo-image-picker not installed \u2014 `npx expo install expo-image-picker`");
396
+ const res2 = await mod2.launchImageLibraryAsync({ mediaTypes: "images", quality: 0.85 });
397
+ if (res2.canceled) return [];
398
+ return res2.assets.map(
399
+ (asset) => ({
400
+ body: {
401
+ uri: asset.uri,
402
+ name: asset.fileName ?? "image.jpg",
403
+ type: asset.mimeType ?? "image/jpeg"
404
+ },
405
+ contentType: asset.mimeType ?? "image/jpeg",
406
+ byteSize: asset.fileSize ?? 0,
407
+ filename: asset.fileName ?? "image.jpg"
408
+ })
409
+ );
410
+ }
411
+ const mod = await tryImport("expo-document-picker");
412
+ if (!mod)
413
+ throw new Error("expo-document-picker not installed \u2014 `npx expo install expo-document-picker`");
414
+ const res = await mod.getDocumentAsync({ copyToCacheDirectory: true, multiple: true });
415
+ if (res.canceled) return [];
416
+ return res.assets.map(
417
+ (asset) => ({
418
+ body: {
419
+ uri: asset.uri,
420
+ name: asset.name,
421
+ type: asset.mimeType ?? "application/octet-stream"
422
+ },
423
+ contentType: asset.mimeType ?? "application/octet-stream",
424
+ byteSize: asset.size ?? 0,
425
+ filename: asset.name
426
+ })
427
+ );
428
+ }
429
+ async function tryImport(name) {
430
+ try {
431
+ return await new Function("m", "return import(m)")(name);
432
+ } catch {
433
+ return null;
434
+ }
435
+ }
436
+ var styles = StyleSheet.create({
437
+ backdrop: {
438
+ flex: 1,
439
+ backgroundColor: "rgba(0,0,0,0.35)",
440
+ justifyContent: "flex-end"
441
+ },
442
+ sheet: {
443
+ paddingVertical: 8,
444
+ paddingBottom: 24
445
+ },
446
+ item: {
447
+ flexDirection: "row",
448
+ alignItems: "center",
449
+ paddingHorizontal: 20,
450
+ paddingVertical: 14,
451
+ gap: 14
452
+ },
453
+ cancel: {
454
+ paddingHorizontal: 20,
455
+ paddingVertical: 14,
456
+ alignItems: "center",
457
+ marginTop: 4
458
+ },
459
+ itemText: {
460
+ fontSize: 15,
461
+ fontWeight: "500"
462
+ }
463
+ });
464
+
465
+ // src/primitives/userColor.ts
466
+ var PALETTE = [
467
+ "#6366f1",
468
+ // indigo
469
+ "#10b981",
470
+ // emerald
471
+ "#f59e0b",
472
+ // amber
473
+ "#ec4899",
474
+ // rose
475
+ "#0ea5e9",
476
+ // sky
477
+ "#8b5cf6",
478
+ // violet
479
+ "#14b8a6",
480
+ // teal
481
+ "#d946ef"
482
+ // fuchsia
483
+ ];
484
+ function userColor(id) {
485
+ let h = 0;
486
+ for (let i = 0; i < id.length; i++) {
487
+ h = h * 31 + id.charCodeAt(i) | 0;
488
+ }
489
+ return PALETTE[Math.abs(h) % PALETTE.length];
490
+ }
491
+ var SIZES = { sm: 28, md: 40, lg: 56 };
492
+ function Avatar({ src, name, seed, online, size = "md" }) {
493
+ const theme = usePoolseTheme();
494
+ const dim = SIZES[size];
495
+ const initials = computeInitials(name);
496
+ const tint = seed ? userColor(seed) : theme.colors.brand;
497
+ const fontSize = size === "sm" ? 11 : size === "lg" ? 22 : 15;
498
+ return /* @__PURE__ */ jsxs(
499
+ View,
500
+ {
501
+ style: [
502
+ styles2.root,
503
+ {
504
+ width: dim,
505
+ height: dim,
506
+ borderRadius: dim / 2,
507
+ backgroundColor: tint
508
+ }
509
+ ],
510
+ accessibilityRole: "image",
511
+ accessibilityLabel: name ?? "avatar",
512
+ children: [
513
+ src ? /* @__PURE__ */ jsx(
514
+ Image,
515
+ {
516
+ source: { uri: src },
517
+ style: { width: dim, height: dim, borderRadius: dim / 2 },
518
+ accessible: false
519
+ }
520
+ ) : /* @__PURE__ */ jsx(
521
+ Text,
522
+ {
523
+ style: {
524
+ color: theme.colors.onBrand,
525
+ fontSize,
526
+ fontWeight: "600",
527
+ fontFamily: theme.type.fontBody
528
+ },
529
+ children: initials
530
+ }
531
+ ),
532
+ online ? /* @__PURE__ */ jsx(
533
+ View,
534
+ {
535
+ style: [
536
+ styles2.presence,
537
+ {
538
+ backgroundColor: theme.colors.presence,
539
+ borderColor: theme.colors.surface,
540
+ width: dim * 0.32,
541
+ height: dim * 0.32,
542
+ borderRadius: dim * 0.16
543
+ }
544
+ ]
545
+ }
546
+ ) : null
547
+ ]
548
+ }
549
+ );
550
+ }
551
+ function computeInitials(name) {
552
+ if (!name) return "?";
553
+ const parts = name.trim().split(/\s+/).slice(0, 2);
554
+ return parts.map((p) => p.charAt(0).toUpperCase()).join("").slice(0, 2) || "?";
555
+ }
556
+ var styles2 = StyleSheet.create({
557
+ root: {
558
+ alignItems: "center",
559
+ justifyContent: "center",
560
+ overflow: "visible"
561
+ },
562
+ presence: {
563
+ position: "absolute",
564
+ bottom: 0,
565
+ right: 0,
566
+ borderWidth: 2
567
+ }
568
+ });
569
+ function UploadQueueStrip() {
570
+ const theme = usePoolseTheme();
571
+ const upload = useAttachmentUpload();
572
+ if (upload.queue.length === 0) return null;
573
+ return /* @__PURE__ */ jsx(View, { style: styles3.strip, children: upload.queue.map((item) => {
574
+ const isError = item.status === "error";
575
+ const isReady = item.status === "ready";
576
+ return /* @__PURE__ */ jsxs(
577
+ View,
578
+ {
579
+ style: [
580
+ styles3.chip,
581
+ {
582
+ backgroundColor: theme.colors.surface2,
583
+ borderColor: isError ? theme.colors.error : theme.colors.border,
584
+ borderRadius: theme.radii.sm
585
+ }
586
+ ],
587
+ children: [
588
+ /* @__PURE__ */ jsx(
589
+ Text,
590
+ {
591
+ style: [styles3.name, { color: isError ? theme.colors.error : theme.colors.ink }],
592
+ numberOfLines: 1,
593
+ children: item.filename
594
+ }
595
+ ),
596
+ /* @__PURE__ */ jsx(Text, { style: [styles3.status, { color: theme.colors.ink3 }], children: isReady ? "ready" : isError ? "failed" : item.status }),
597
+ /* @__PURE__ */ jsx(
598
+ Pressable,
599
+ {
600
+ hitSlop: 6,
601
+ onPress: () => isReady || isError ? upload.remove(item.localId) : upload.cancel(item.localId),
602
+ style: styles3.btn,
603
+ accessibilityLabel: "Remove upload",
604
+ children: /* @__PURE__ */ jsx(PoolseIcon, { name: "close", size: 12, color: theme.colors.ink2 })
605
+ }
606
+ )
607
+ ]
608
+ },
609
+ item.localId
610
+ );
611
+ }) });
612
+ }
613
+ var styles3 = StyleSheet.create({
614
+ strip: {
615
+ flexDirection: "row",
616
+ flexWrap: "wrap",
617
+ gap: 6,
618
+ marginBottom: 6
619
+ },
620
+ chip: {
621
+ flexDirection: "row",
622
+ alignItems: "center",
623
+ paddingHorizontal: 8,
624
+ paddingVertical: 4,
625
+ borderWidth: StyleSheet.hairlineWidth,
626
+ gap: 6,
627
+ maxWidth: 200
628
+ },
629
+ name: {
630
+ fontSize: 12,
631
+ flex: 1
632
+ },
633
+ status: {
634
+ fontSize: 10,
635
+ textTransform: "capitalize"
636
+ },
637
+ btn: {
638
+ padding: 2
639
+ }
640
+ });
641
+ var MessageComposer = forwardRef(
642
+ function MessageComposer2({
643
+ onSend,
644
+ onTyping,
645
+ placeholder = "Type a message\u2026",
646
+ disabled = false,
647
+ replyingTo,
648
+ onCancelReply,
649
+ labelFor,
650
+ attachments = true,
651
+ onAttachPress
652
+ }, ref) {
653
+ const theme = usePoolseTheme();
654
+ const inputRef = useRef(null);
655
+ const [value, setValue] = useState("");
656
+ const [sending, setSending] = useState(false);
657
+ const upload = useAttachmentUpload();
658
+ useImperativeHandle(
659
+ ref,
660
+ () => ({
661
+ addFiles: (inputs) => {
662
+ if (!attachments) return;
663
+ for (const input of inputs) {
664
+ void upload.upload(input);
665
+ }
666
+ },
667
+ focus: () => inputRef.current?.focus(),
668
+ clear: () => setValue("")
669
+ }),
670
+ [attachments, upload]
671
+ );
672
+ const readyAttachmentIds = upload.queue.filter((item) => item.status === "ready" && item.attachment).map((item) => item.attachment.id);
673
+ const isUploading = upload.queue.some(
674
+ (item) => item.status === "uploading" || item.status === "pending"
675
+ );
676
+ const submitDisabled = disabled || sending || isUploading || value.trim().length === 0 && readyAttachmentIds.length === 0;
677
+ const handleSend = async () => {
678
+ if (submitDisabled) return;
679
+ setSending(true);
680
+ try {
681
+ await onSend(value.trim(), {
682
+ ...replyingTo ? { quoted_message_id: replyingTo.id } : {},
683
+ ...readyAttachmentIds.length > 0 ? { attachment_ids: readyAttachmentIds } : {}
684
+ });
685
+ setValue("");
686
+ upload.reset();
687
+ } finally {
688
+ setSending(false);
689
+ }
690
+ };
691
+ return /* @__PURE__ */ jsx(
692
+ KeyboardAvoidingView,
693
+ {
694
+ behavior: Platform.OS === "ios" ? "padding" : void 0,
695
+ keyboardVerticalOffset: Platform.OS === "ios" ? 80 : 0,
696
+ children: /* @__PURE__ */ jsxs(
697
+ View,
698
+ {
699
+ style: [
700
+ styles4.wrap,
701
+ {
702
+ backgroundColor: theme.colors.surface,
703
+ borderTopColor: theme.colors.border
704
+ }
705
+ ],
706
+ children: [
707
+ replyingTo ? /* @__PURE__ */ jsx(
708
+ ReplyChip,
709
+ {
710
+ message: replyingTo,
711
+ ...labelFor ? { labelFor } : {},
712
+ ...onCancelReply ? { onCancel: onCancelReply } : {}
713
+ }
714
+ ) : null,
715
+ attachments ? /* @__PURE__ */ jsx(UploadQueueStrip, {}) : null,
716
+ /* @__PURE__ */ jsxs(View, { style: styles4.row, children: [
717
+ attachments && onAttachPress ? /* @__PURE__ */ jsx(
718
+ Pressable,
719
+ {
720
+ onPress: onAttachPress,
721
+ hitSlop: 8,
722
+ style: styles4.iconBtn,
723
+ accessibilityLabel: "Attach file",
724
+ children: /* @__PURE__ */ jsx(PoolseIcon, { name: "attachment", size: 22, color: theme.colors.ink2 })
725
+ }
726
+ ) : null,
727
+ /* @__PURE__ */ jsx(
728
+ TextInput,
729
+ {
730
+ ref: inputRef,
731
+ value,
732
+ onChangeText: (text) => {
733
+ setValue(text);
734
+ onTyping?.();
735
+ },
736
+ placeholder,
737
+ placeholderTextColor: theme.colors.ink3,
738
+ multiline: true,
739
+ editable: !disabled,
740
+ style: [
741
+ styles4.input,
742
+ {
743
+ color: theme.colors.ink,
744
+ backgroundColor: theme.colors.surface2,
745
+ borderRadius: theme.radii.lg,
746
+ fontFamily: theme.type.fontBody,
747
+ fontSize: theme.type.bodySize
748
+ }
749
+ ]
750
+ }
751
+ ),
752
+ /* @__PURE__ */ jsx(
753
+ Pressable,
754
+ {
755
+ onPress: handleSend,
756
+ disabled: submitDisabled,
757
+ style: [
758
+ styles4.sendBtn,
759
+ {
760
+ backgroundColor: submitDisabled ? theme.colors.ink3 : theme.colors.brand,
761
+ opacity: submitDisabled ? 0.6 : 1
762
+ }
763
+ ],
764
+ accessibilityLabel: "Send message",
765
+ children: /* @__PURE__ */ jsx(PoolseIcon, { name: "send", size: 18, color: theme.colors.onBrand })
766
+ }
767
+ )
768
+ ] })
769
+ ]
770
+ }
771
+ )
772
+ }
773
+ );
774
+ }
775
+ );
776
+ function ReplyChip({
777
+ message,
778
+ labelFor,
779
+ onCancel
780
+ }) {
781
+ const theme = usePoolseTheme();
782
+ const senderName = message.sender_external_id ? labelFor?.(message.sender_external_id) ?? message.sender_external_id : "Unknown";
783
+ return /* @__PURE__ */ jsxs(
784
+ View,
785
+ {
786
+ style: [
787
+ styles4.replyChip,
788
+ {
789
+ backgroundColor: theme.colors.surface2,
790
+ borderLeftColor: theme.colors.brand
791
+ }
792
+ ],
793
+ children: [
794
+ /* @__PURE__ */ jsxs(View, { style: { flex: 1 }, children: [
795
+ /* @__PURE__ */ jsxs(Text, { style: [styles4.replyName, { color: theme.colors.brand }], children: [
796
+ "Replying to ",
797
+ senderName
798
+ ] }),
799
+ /* @__PURE__ */ jsx(Text, { style: [styles4.replyBody, { color: theme.colors.ink2 }], numberOfLines: 1, children: message.body ?? "\u2026" })
800
+ ] }),
801
+ onCancel ? /* @__PURE__ */ jsx(Pressable, { onPress: onCancel, hitSlop: 8, style: styles4.replyCancel, children: /* @__PURE__ */ jsx(PoolseIcon, { name: "close", size: 16, color: theme.colors.ink2 }) }) : null
802
+ ]
803
+ }
804
+ );
805
+ }
806
+ var styles4 = StyleSheet.create({
807
+ wrap: {
808
+ borderTopWidth: StyleSheet.hairlineWidth,
809
+ paddingHorizontal: 12,
810
+ paddingVertical: 8
811
+ },
812
+ row: {
813
+ flexDirection: "row",
814
+ alignItems: "flex-end",
815
+ gap: 8
816
+ },
817
+ iconBtn: {
818
+ padding: 8,
819
+ justifyContent: "center",
820
+ alignItems: "center"
821
+ },
822
+ input: {
823
+ flex: 1,
824
+ minHeight: 40,
825
+ maxHeight: 120,
826
+ paddingHorizontal: 12,
827
+ paddingTop: 10,
828
+ paddingBottom: 10
829
+ },
830
+ sendBtn: {
831
+ width: 40,
832
+ height: 40,
833
+ borderRadius: 20,
834
+ justifyContent: "center",
835
+ alignItems: "center"
836
+ },
837
+ replyChip: {
838
+ flexDirection: "row",
839
+ alignItems: "center",
840
+ borderLeftWidth: 3,
841
+ paddingLeft: 10,
842
+ paddingRight: 6,
843
+ paddingVertical: 6,
844
+ marginBottom: 8,
845
+ borderRadius: 6
846
+ },
847
+ replyName: {
848
+ fontSize: 11,
849
+ fontWeight: "600",
850
+ marginBottom: 2
851
+ },
852
+ replyBody: {
853
+ fontSize: 12
854
+ },
855
+ replyCancel: {
856
+ padding: 6
857
+ }
858
+ });
859
+ function MentionInput({ conversationId, onSend, ...composerProps }) {
860
+ const theme = usePoolseTheme();
861
+ const { members } = useMembers(conversationId);
862
+ const [pickerOpen, setPickerOpen] = useState(false);
863
+ const [pickerQuery, setPickerQuery] = useState("");
864
+ const [pendingMentionUuids, setPendingMentionUuids] = useState([]);
865
+ const [text, setText] = useState("");
866
+ const filteredMembers = useMemo(() => {
867
+ if (!pickerQuery) return members;
868
+ const q = pickerQuery.toLowerCase();
869
+ return members.filter((m) => m.external_id.toLowerCase().includes(q));
870
+ }, [members, pickerQuery]);
871
+ const handleSend = async (body, opts) => {
872
+ await onSend(body, {
873
+ ...opts,
874
+ ...pendingMentionUuids.length > 0 ? { mentions: pendingMentionUuids } : {}
875
+ });
876
+ setPendingMentionUuids([]);
877
+ setText("");
878
+ };
879
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
880
+ /* @__PURE__ */ jsx(
881
+ MessageComposer,
882
+ {
883
+ ...composerProps,
884
+ onSend: handleSend,
885
+ onTyping: () => {
886
+ composerProps.onTyping?.();
887
+ const lastChar = text.slice(-1);
888
+ if (lastChar === "@") setPickerOpen(true);
889
+ }
890
+ }
891
+ ),
892
+ /* @__PURE__ */ jsx(
893
+ Modal,
894
+ {
895
+ visible: pickerOpen,
896
+ transparent: true,
897
+ animationType: "slide",
898
+ onRequestClose: () => setPickerOpen(false),
899
+ children: /* @__PURE__ */ jsx(Pressable, { style: styles5.backdrop, onPress: () => setPickerOpen(false), children: /* @__PURE__ */ jsxs(
900
+ View,
901
+ {
902
+ style: [
903
+ styles5.sheet,
904
+ {
905
+ backgroundColor: theme.colors.surface,
906
+ borderTopLeftRadius: theme.radii.lg,
907
+ borderTopRightRadius: theme.radii.lg
908
+ }
909
+ ],
910
+ children: [
911
+ /* @__PURE__ */ jsx(
912
+ Text,
913
+ {
914
+ style: [styles5.title, { color: theme.colors.ink, fontFamily: theme.type.fontBody }],
915
+ children: "Mention a member"
916
+ }
917
+ ),
918
+ /* @__PURE__ */ jsx(
919
+ FlatList,
920
+ {
921
+ data: filteredMembers,
922
+ keyExtractor: (m) => m.user_id,
923
+ renderItem: ({ item }) => /* @__PURE__ */ jsx(
924
+ MentionRow,
925
+ {
926
+ member: item,
927
+ onPress: () => {
928
+ setPendingMentionUuids((prev) => [...prev, item.user_id]);
929
+ setText((prev) => `${prev}${item.external_id} `);
930
+ setPickerOpen(false);
931
+ }
932
+ }
933
+ )
934
+ }
935
+ )
936
+ ]
937
+ }
938
+ ) })
939
+ }
940
+ )
941
+ ] });
942
+ }
943
+ function MentionRow({ member, onPress }) {
944
+ const theme = usePoolseTheme();
945
+ return /* @__PURE__ */ jsxs(Pressable, { onPress, style: styles5.row, children: [
946
+ /* @__PURE__ */ jsx(Avatar, { src: null, name: member.external_id, seed: member.external_id, size: "sm" }),
947
+ /* @__PURE__ */ jsx(Text, { style: [styles5.rowText, { color: theme.colors.ink, fontFamily: theme.type.fontBody }], children: member.external_id })
948
+ ] });
949
+ }
950
+ var styles5 = StyleSheet.create({
951
+ backdrop: {
952
+ flex: 1,
953
+ backgroundColor: "rgba(0,0,0,0.35)",
954
+ justifyContent: "flex-end"
955
+ },
956
+ sheet: {
957
+ maxHeight: "60%",
958
+ paddingVertical: 12
959
+ },
960
+ title: {
961
+ fontSize: 14,
962
+ fontWeight: "600",
963
+ paddingHorizontal: 16,
964
+ paddingBottom: 8
965
+ },
966
+ row: {
967
+ flexDirection: "row",
968
+ alignItems: "center",
969
+ paddingHorizontal: 16,
970
+ paddingVertical: 10,
971
+ gap: 10
972
+ },
973
+ rowText: {
974
+ fontSize: 14
975
+ }
976
+ });
977
+ function MessageList({
978
+ messages,
979
+ loading = false,
980
+ hasMore = false,
981
+ onLoadMore,
982
+ renderItem,
983
+ ListHeader = null,
984
+ ListFooter = null,
985
+ emptyState
986
+ }) {
987
+ const theme = usePoolseTheme();
988
+ const data = [...messages].reverse();
989
+ const handleRender = useCallback(
990
+ ({ item, index }) => renderItem(item, messages.length - 1 - index),
991
+ [renderItem, messages.length]
992
+ );
993
+ if (loading && messages.length === 0) {
994
+ return /* @__PURE__ */ jsx(View, { style: [styles6.placeholder, { backgroundColor: theme.colors.paper }], children: /* @__PURE__ */ jsx(ActivityIndicator, { color: theme.colors.brand }) });
995
+ }
996
+ if (messages.length === 0) {
997
+ return /* @__PURE__ */ jsx(View, { style: [styles6.placeholder, { backgroundColor: theme.colors.paper }], children: typeof emptyState === "string" ? /* @__PURE__ */ jsx(Text, { style: { color: theme.colors.ink3, fontFamily: theme.type.fontBody }, children: emptyState }) : emptyState ?? null });
998
+ }
999
+ return /* @__PURE__ */ jsx(
1000
+ FlatList,
1001
+ {
1002
+ inverted: true,
1003
+ data,
1004
+ keyExtractor: (item) => item.id,
1005
+ renderItem: handleRender,
1006
+ onEndReached: hasMore ? onLoadMore : void 0,
1007
+ onEndReachedThreshold: 0.3,
1008
+ ListHeaderComponent: ListFooter,
1009
+ ListFooterComponent: /* @__PURE__ */ jsxs(View, { children: [
1010
+ hasMore && loading ? /* @__PURE__ */ jsx(ActivityIndicator, { color: theme.colors.ink3, style: { marginVertical: 8 } }) : null,
1011
+ ListHeader
1012
+ ] }),
1013
+ style: { backgroundColor: theme.colors.paper },
1014
+ contentContainerStyle: { paddingVertical: 8 }
1015
+ }
1016
+ );
1017
+ }
1018
+ var styles6 = StyleSheet.create({
1019
+ placeholder: {
1020
+ flex: 1,
1021
+ alignItems: "center",
1022
+ justifyContent: "center",
1023
+ padding: 24
1024
+ }
1025
+ });
1026
+ function EditableMessageBubble({
1027
+ message,
1028
+ currentUserId,
1029
+ onCancel,
1030
+ onSave
1031
+ }) {
1032
+ const theme = usePoolseTheme();
1033
+ const isSelf = currentUserId !== null && message.sender_id === currentUserId;
1034
+ const [value, setValue] = useState(message.body ?? "");
1035
+ const [saving, setSaving] = useState(false);
1036
+ const submit = async () => {
1037
+ if (saving || !value.trim()) return;
1038
+ setSaving(true);
1039
+ try {
1040
+ await onSave(value.trim());
1041
+ } finally {
1042
+ setSaving(false);
1043
+ }
1044
+ };
1045
+ const bg = isSelf ? theme.colors.selfBubble : theme.colors.otherBubble;
1046
+ const ink = isSelf ? theme.colors.selfBubbleText : theme.colors.otherBubbleText;
1047
+ return /* @__PURE__ */ jsx(
1048
+ View,
1049
+ {
1050
+ style: [
1051
+ styles7.wrap,
1052
+ {
1053
+ alignSelf: isSelf ? "flex-end" : "flex-start",
1054
+ maxWidth: "80%"
1055
+ }
1056
+ ],
1057
+ children: /* @__PURE__ */ jsxs(
1058
+ View,
1059
+ {
1060
+ style: [
1061
+ styles7.bubble,
1062
+ {
1063
+ backgroundColor: bg,
1064
+ borderRadius: theme.radii.bubble,
1065
+ paddingHorizontal: theme.spacing.md,
1066
+ paddingVertical: theme.spacing.sm
1067
+ },
1068
+ theme.shadows.sm
1069
+ ],
1070
+ children: [
1071
+ /* @__PURE__ */ jsx(
1072
+ TextInput,
1073
+ {
1074
+ value,
1075
+ onChangeText: setValue,
1076
+ multiline: true,
1077
+ autoFocus: true,
1078
+ style: {
1079
+ color: ink,
1080
+ fontFamily: theme.type.fontBody,
1081
+ fontSize: theme.type.bodySize,
1082
+ minHeight: 24
1083
+ }
1084
+ }
1085
+ ),
1086
+ /* @__PURE__ */ jsxs(View, { style: styles7.actions, children: [
1087
+ /* @__PURE__ */ jsx(Pressable, { onPress: onCancel, hitSlop: 6, children: /* @__PURE__ */ jsx(Text, { style: [styles7.btn, { color: ink, opacity: 0.7 }], children: "Cancel" }) }),
1088
+ /* @__PURE__ */ jsx(Pressable, { onPress: submit, disabled: saving, hitSlop: 6, children: /* @__PURE__ */ jsx(Text, { style: [styles7.btn, { color: ink, fontWeight: "700" }], children: saving ? "Saving\u2026" : "Save" }) })
1089
+ ] })
1090
+ ]
1091
+ }
1092
+ )
1093
+ }
1094
+ );
1095
+ }
1096
+ var styles7 = StyleSheet.create({
1097
+ wrap: {
1098
+ marginBottom: 4
1099
+ },
1100
+ bubble: {},
1101
+ actions: {
1102
+ flexDirection: "row",
1103
+ justifyContent: "flex-end",
1104
+ gap: 12,
1105
+ marginTop: 6
1106
+ },
1107
+ btn: {
1108
+ fontSize: 13
1109
+ }
1110
+ });
1111
+ function MessageActions({
1112
+ visible,
1113
+ onClose,
1114
+ isSelf,
1115
+ onReply,
1116
+ onReplyInThread,
1117
+ onQuote,
1118
+ onEdit,
1119
+ onDelete,
1120
+ onCopy,
1121
+ onReact
1122
+ }) {
1123
+ const theme = usePoolseTheme();
1124
+ const items = [];
1125
+ if (onReact) items.push({ label: "React", icon: "emoji", onPress: onReact });
1126
+ if (onReply) items.push({ label: "Reply", icon: "reply", onPress: onReply });
1127
+ if (onQuote) items.push({ label: "Quote reply", icon: "reply", onPress: onQuote });
1128
+ if (onReplyInThread)
1129
+ items.push({ label: "Reply in thread", icon: "messages", onPress: onReplyInThread });
1130
+ if (onCopy) items.push({ label: "Copy text", icon: "copy", onPress: onCopy });
1131
+ if (isSelf && onEdit) items.push({ label: "Edit", icon: "edit", onPress: onEdit });
1132
+ if (isSelf && onDelete)
1133
+ items.push({ label: "Delete", icon: "trash", onPress: onDelete, danger: true });
1134
+ return /* @__PURE__ */ jsx(Modal, { visible, transparent: true, animationType: "fade", onRequestClose: onClose, children: /* @__PURE__ */ jsx(Pressable, { style: styles8.backdrop, onPress: onClose, children: /* @__PURE__ */ jsx(
1135
+ View,
1136
+ {
1137
+ style: [
1138
+ styles8.sheet,
1139
+ {
1140
+ backgroundColor: theme.colors.surface,
1141
+ borderTopLeftRadius: theme.radii.lg,
1142
+ borderTopRightRadius: theme.radii.lg
1143
+ },
1144
+ theme.shadows.lg
1145
+ ],
1146
+ children: items.map((item) => /* @__PURE__ */ jsxs(Pressable, { onPress: item.onPress, style: styles8.item, children: [
1147
+ /* @__PURE__ */ jsx(
1148
+ PoolseIcon,
1149
+ {
1150
+ name: item.icon,
1151
+ size: 18,
1152
+ color: item.danger ? theme.colors.error : theme.colors.ink2
1153
+ }
1154
+ ),
1155
+ /* @__PURE__ */ jsx(
1156
+ Text,
1157
+ {
1158
+ style: [
1159
+ styles8.itemText,
1160
+ {
1161
+ color: item.danger ? theme.colors.error : theme.colors.ink,
1162
+ fontFamily: theme.type.fontBody
1163
+ }
1164
+ ],
1165
+ children: item.label
1166
+ }
1167
+ )
1168
+ ] }, item.label))
1169
+ }
1170
+ ) }) });
1171
+ }
1172
+ var styles8 = StyleSheet.create({
1173
+ backdrop: {
1174
+ flex: 1,
1175
+ backgroundColor: "rgba(0,0,0,0.35)",
1176
+ justifyContent: "flex-end"
1177
+ },
1178
+ sheet: {
1179
+ paddingVertical: 8,
1180
+ paddingBottom: 24
1181
+ },
1182
+ item: {
1183
+ flexDirection: "row",
1184
+ alignItems: "center",
1185
+ paddingHorizontal: 20,
1186
+ paddingVertical: 14,
1187
+ gap: 14
1188
+ },
1189
+ itemText: {
1190
+ fontSize: 15,
1191
+ fontWeight: "500"
1192
+ }
1193
+ });
1194
+ function AttachmentPreview({ attachment, onPress, maxWidth = 280 }) {
1195
+ const theme = usePoolseTheme();
1196
+ const isImage = (attachment.content_type ?? "").startsWith("image/");
1197
+ const { url } = useAttachmentUrl(attachment.id);
1198
+ if (isImage) {
1199
+ return /* @__PURE__ */ jsx(Pressable, { onPress, disabled: !onPress, children: url ? /* @__PURE__ */ jsx(
1200
+ Image,
1201
+ {
1202
+ source: { uri: url },
1203
+ style: {
1204
+ width: maxWidth,
1205
+ height: 200,
1206
+ borderRadius: theme.radii.md,
1207
+ backgroundColor: theme.colors.surface2
1208
+ },
1209
+ resizeMode: "cover",
1210
+ accessible: true,
1211
+ accessibilityLabel: attachment.original_filename ?? "attachment image"
1212
+ }
1213
+ ) : /* @__PURE__ */ jsx(
1214
+ View,
1215
+ {
1216
+ style: [
1217
+ styles9.imagePlaceholder,
1218
+ {
1219
+ width: maxWidth,
1220
+ height: 200,
1221
+ borderRadius: theme.radii.md,
1222
+ backgroundColor: theme.colors.surface2
1223
+ }
1224
+ ]
1225
+ }
1226
+ ) });
1227
+ }
1228
+ return /* @__PURE__ */ jsxs(
1229
+ Pressable,
1230
+ {
1231
+ onPress,
1232
+ disabled: !onPress,
1233
+ style: [
1234
+ styles9.fileCard,
1235
+ {
1236
+ backgroundColor: theme.colors.surface2,
1237
+ borderColor: theme.colors.border,
1238
+ borderRadius: theme.radii.md
1239
+ }
1240
+ ],
1241
+ children: [
1242
+ /* @__PURE__ */ jsx(PoolseIcon, { name: "attachment", size: 18, color: theme.colors.ink2 }),
1243
+ /* @__PURE__ */ jsxs(View, { style: { flex: 1, marginLeft: 8 }, children: [
1244
+ /* @__PURE__ */ jsx(
1245
+ Text,
1246
+ {
1247
+ style: [styles9.fileName, { color: theme.colors.ink, fontFamily: theme.type.fontBody }],
1248
+ numberOfLines: 1,
1249
+ children: attachment.original_filename ?? "file"
1250
+ }
1251
+ ),
1252
+ /* @__PURE__ */ jsx(Text, { style: [styles9.fileSize, { color: theme.colors.ink3 }], children: formatBytes(attachment.byte_size) })
1253
+ ] })
1254
+ ]
1255
+ }
1256
+ );
1257
+ }
1258
+ function formatBytes(n) {
1259
+ if (n < 1024) return `${n} B`;
1260
+ if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
1261
+ if (n < 1024 * 1024 * 1024) return `${(n / 1024 / 1024).toFixed(1)} MB`;
1262
+ return `${(n / 1024 / 1024 / 1024).toFixed(1)} GB`;
1263
+ }
1264
+ var styles9 = StyleSheet.create({
1265
+ imagePlaceholder: {},
1266
+ fileCard: {
1267
+ flexDirection: "row",
1268
+ alignItems: "center",
1269
+ padding: 8,
1270
+ borderWidth: StyleSheet.hairlineWidth
1271
+ },
1272
+ fileName: {
1273
+ fontSize: 13,
1274
+ fontWeight: "500"
1275
+ },
1276
+ fileSize: {
1277
+ fontSize: 11
1278
+ }
1279
+ });
1280
+ function MessageBubble({
1281
+ message,
1282
+ currentUserId,
1283
+ readState,
1284
+ labelFor,
1285
+ onQuotedClick,
1286
+ groupPosition = "standalone",
1287
+ maxBodyLength = 0,
1288
+ showSenderName = false,
1289
+ actionsTrigger,
1290
+ showAttachments = true
1291
+ }) {
1292
+ const theme = usePoolseTheme();
1293
+ const isSelf = currentUserId !== null && message.sender_id === currentUserId;
1294
+ const [expanded, setExpanded] = useState(false);
1295
+ const sender = useUser(!isSelf ? message.sender_external_id : null);
1296
+ const senderFallbackName = message.sender_external_id ?? "Unknown";
1297
+ const senderLabel = sender.profile?.displayName ?? (message.sender_external_id ? labelFor?.(message.sender_external_id) ?? senderFallbackName : senderFallbackName);
1298
+ const senderTint = userColor(message.sender_external_id ?? message.sender_id ?? message.id);
1299
+ const tailCorner = groupPosition === "last" || groupPosition === "standalone";
1300
+ const bubbleColor = isSelf ? theme.colors.selfBubble : theme.colors.otherBubble;
1301
+ const textColor = isSelf ? theme.colors.selfBubbleText : theme.colors.otherBubbleText;
1302
+ const radii2 = {
1303
+ topLeft: theme.radii.bubble,
1304
+ topRight: theme.radii.bubble,
1305
+ bottomLeft: theme.radii.bubble,
1306
+ bottomRight: theme.radii.bubble
1307
+ };
1308
+ if (tailCorner) {
1309
+ if (isSelf) radii2.bottomRight = 4;
1310
+ else radii2.bottomLeft = 4;
1311
+ }
1312
+ const body = message.body ?? "";
1313
+ const truncated = maxBodyLength > 0 && body.length > maxBodyLength && !expanded ? body.slice(0, maxBodyLength) + "\u2026" : body;
1314
+ const time = formatTime(message.inserted_at);
1315
+ const isDeleted = !!message.deleted_at;
1316
+ const isEdited = !!message.edited_at && !isDeleted;
1317
+ const images = (message.attachments ?? []).filter((a) => isImageContentType(a.content_type));
1318
+ const files = (message.attachments ?? []).filter((a) => !isImageContentType(a.content_type));
1319
+ const renderAttachments = showAttachments && (images.length > 0 || files.length > 0);
1320
+ return /* @__PURE__ */ jsx(
1321
+ View,
1322
+ {
1323
+ style: [
1324
+ styles10.wrap,
1325
+ {
1326
+ alignSelf: isSelf ? "flex-end" : "flex-start",
1327
+ maxWidth: "80%"
1328
+ }
1329
+ ],
1330
+ children: /* @__PURE__ */ jsxs(
1331
+ View,
1332
+ {
1333
+ style: [
1334
+ styles10.bubble,
1335
+ {
1336
+ backgroundColor: bubbleColor,
1337
+ borderColor: isSelf ? "transparent" : theme.colors.border,
1338
+ borderWidth: isSelf ? 0 : StyleSheet.hairlineWidth,
1339
+ borderTopLeftRadius: radii2.topLeft,
1340
+ borderTopRightRadius: radii2.topRight,
1341
+ borderBottomLeftRadius: radii2.bottomLeft,
1342
+ borderBottomRightRadius: radii2.bottomRight,
1343
+ paddingHorizontal: theme.spacing.md,
1344
+ paddingVertical: theme.spacing.sm
1345
+ },
1346
+ theme.shadows.sm
1347
+ ],
1348
+ children: [
1349
+ showSenderName && !isSelf && (groupPosition === "first" || groupPosition === "standalone") ? /* @__PURE__ */ jsx(Text, { style: [styles10.senderName, { color: senderTint, fontFamily: theme.type.fontBody }], children: senderLabel }) : null,
1350
+ message.quoted_message ? /* @__PURE__ */ jsx(
1351
+ QuotedCard,
1352
+ {
1353
+ quoted: message.quoted_message,
1354
+ isSelf,
1355
+ ...labelFor ? { labelFor } : {},
1356
+ ...onQuotedClick ? { onPress: () => onQuotedClick(message.quoted_message.id) } : {}
1357
+ }
1358
+ ) : null,
1359
+ renderAttachments && images.length > 0 ? /* @__PURE__ */ jsx(View, { style: { gap: theme.spacing.xs, marginBottom: theme.spacing.xs }, children: images.map((att) => /* @__PURE__ */ jsx(AttachmentPreview, { attachment: att }, att.id)) }) : null,
1360
+ renderAttachments && files.length > 0 ? /* @__PURE__ */ jsx(View, { style: { gap: theme.spacing.xs, marginBottom: theme.spacing.xs }, children: files.map((att) => /* @__PURE__ */ jsx(AttachmentPreview, { attachment: att }, att.id)) }) : null,
1361
+ body.length > 0 ? /* @__PURE__ */ jsx(
1362
+ Text,
1363
+ {
1364
+ style: [
1365
+ styles10.body,
1366
+ {
1367
+ color: isDeleted ? theme.colors.ink3 : textColor,
1368
+ fontFamily: theme.type.fontBody,
1369
+ fontSize: theme.type.bodySize,
1370
+ lineHeight: theme.type.lineHeight,
1371
+ fontStyle: isDeleted ? "italic" : "normal"
1372
+ }
1373
+ ],
1374
+ children: isDeleted ? "Message deleted" : truncated
1375
+ }
1376
+ ) : null,
1377
+ maxBodyLength > 0 && body.length > maxBodyLength ? /* @__PURE__ */ jsx(Pressable, { onPress: () => setExpanded((e) => !e), children: /* @__PURE__ */ jsx(
1378
+ Text,
1379
+ {
1380
+ style: [
1381
+ styles10.readMore,
1382
+ { color: isSelf ? theme.colors.onBrand : theme.colors.brand }
1383
+ ],
1384
+ children: expanded ? "Show less" : "Read more"
1385
+ }
1386
+ ) }) : null,
1387
+ /* @__PURE__ */ jsxs(View, { style: styles10.metaRow, children: [
1388
+ isEdited ? /* @__PURE__ */ jsxs(
1389
+ Text,
1390
+ {
1391
+ style: [styles10.meta, { color: isSelf ? theme.colors.onBrand : theme.colors.ink3 }],
1392
+ children: [
1393
+ "edited \xB7",
1394
+ " "
1395
+ ]
1396
+ }
1397
+ ) : null,
1398
+ /* @__PURE__ */ jsx(Text, { style: [styles10.meta, { color: isSelf ? theme.colors.onBrand : theme.colors.ink3 }], children: time }),
1399
+ isSelf && readState ? /* @__PURE__ */ jsx(View, { style: { marginLeft: 4 }, children: /* @__PURE__ */ jsx(
1400
+ PoolseIcon,
1401
+ {
1402
+ name: readState === "read" ? "check-double" : "check",
1403
+ size: 12,
1404
+ color: theme.colors.onBrand
1405
+ }
1406
+ ) }) : null
1407
+ ] }),
1408
+ actionsTrigger ? /* @__PURE__ */ jsx(View, { style: styles10.actionsSlot, children: actionsTrigger }) : null
1409
+ ]
1410
+ }
1411
+ )
1412
+ }
1413
+ );
1414
+ }
1415
+ function QuotedCard({
1416
+ quoted,
1417
+ isSelf,
1418
+ labelFor,
1419
+ onPress
1420
+ }) {
1421
+ const theme = usePoolseTheme();
1422
+ const senderName = quoted.sender_external_id ? labelFor?.(quoted.sender_external_id) ?? quoted.sender_external_id : "Unknown";
1423
+ const bg = isSelf ? "rgba(255,255,255,0.18)" : theme.colors.surface2;
1424
+ const accent = isSelf ? theme.colors.onBrand : theme.colors.brand;
1425
+ const txt = isSelf ? theme.colors.onBrand : theme.colors.ink;
1426
+ return /* @__PURE__ */ jsxs(
1427
+ Pressable,
1428
+ {
1429
+ onPress,
1430
+ disabled: !onPress,
1431
+ style: [
1432
+ styles10.quoted,
1433
+ {
1434
+ backgroundColor: bg,
1435
+ borderLeftColor: accent
1436
+ }
1437
+ ],
1438
+ children: [
1439
+ /* @__PURE__ */ jsx(Text, { style: [styles10.quotedSender, { color: accent }], children: senderName }),
1440
+ /* @__PURE__ */ jsx(Text, { style: [styles10.quotedBody, { color: txt }], numberOfLines: 2, children: quoted.body ?? "\u2026" })
1441
+ ]
1442
+ }
1443
+ );
1444
+ }
1445
+ function isImageContentType(ct) {
1446
+ if (!ct) return false;
1447
+ return ct.startsWith("image/");
1448
+ }
1449
+ function formatTime(iso) {
1450
+ try {
1451
+ const d = new Date(iso);
1452
+ return d.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
1453
+ } catch {
1454
+ return "";
1455
+ }
1456
+ }
1457
+ var styles10 = StyleSheet.create({
1458
+ wrap: {
1459
+ marginBottom: 4
1460
+ },
1461
+ bubble: {
1462
+ position: "relative"
1463
+ },
1464
+ senderName: {
1465
+ fontSize: 12,
1466
+ fontWeight: "600",
1467
+ marginBottom: 2
1468
+ },
1469
+ body: {
1470
+ flexShrink: 1
1471
+ },
1472
+ readMore: {
1473
+ fontSize: 12,
1474
+ fontWeight: "600",
1475
+ marginTop: 4
1476
+ },
1477
+ metaRow: {
1478
+ flexDirection: "row",
1479
+ alignItems: "center",
1480
+ justifyContent: "flex-end",
1481
+ marginTop: 4
1482
+ },
1483
+ meta: {
1484
+ fontSize: 10,
1485
+ opacity: 0.7
1486
+ },
1487
+ actionsSlot: {
1488
+ position: "absolute",
1489
+ top: 4,
1490
+ right: 4
1491
+ },
1492
+ quoted: {
1493
+ borderLeftWidth: 3,
1494
+ paddingLeft: 8,
1495
+ paddingVertical: 4,
1496
+ paddingRight: 6,
1497
+ marginBottom: 6,
1498
+ borderRadius: 4
1499
+ },
1500
+ quotedSender: {
1501
+ fontSize: 12,
1502
+ fontWeight: "600",
1503
+ marginBottom: 2
1504
+ },
1505
+ quotedBody: {
1506
+ fontSize: 12,
1507
+ opacity: 0.85
1508
+ }
1509
+ });
1510
+ function MessageRow({
1511
+ msg,
1512
+ meId,
1513
+ reactions = true,
1514
+ attachments = true,
1515
+ actions = true,
1516
+ threads = true,
1517
+ quotations = true,
1518
+ readState,
1519
+ editing = false,
1520
+ onStartEdit,
1521
+ onCancelEdit,
1522
+ onSaveEdit,
1523
+ onDelete,
1524
+ onOpenThread,
1525
+ onQuote,
1526
+ labelFor,
1527
+ onQuotedClick,
1528
+ groupPosition = "standalone",
1529
+ maxBodyLength = 0,
1530
+ showSenderName = false,
1531
+ showAvatar = false,
1532
+ avatarFor
1533
+ }) {
1534
+ const theme = usePoolseTheme();
1535
+ const isSelf = meId !== null && msg.sender_id === meId;
1536
+ const [actionsOpen, setActionsOpen] = useState(false);
1537
+ const showAvatarSlot = showAvatar && !isSelf;
1538
+ const showAvatarRender = showAvatarSlot && (groupPosition === "last" || groupPosition === "standalone");
1539
+ const avatarUrl = avatarFor && msg.sender_external_id ? avatarFor(msg.sender_external_id) : null;
1540
+ const senderLabel = msg.sender_external_id && labelFor ? labelFor(msg.sender_external_id) : msg.sender_external_id ?? null;
1541
+ return /* @__PURE__ */ jsxs(View, { style: [styles11.row, { alignSelf: isSelf ? "flex-end" : "flex-start" }], children: [
1542
+ showAvatarSlot ? /* @__PURE__ */ jsx(View, { style: styles11.avatarSlot, children: showAvatarRender ? /* @__PURE__ */ jsx(
1543
+ Avatar,
1544
+ {
1545
+ src: avatarUrl ?? null,
1546
+ name: senderLabel,
1547
+ seed: msg.sender_external_id ?? null,
1548
+ size: "sm"
1549
+ }
1550
+ ) : null }) : null,
1551
+ /* @__PURE__ */ jsxs(View, { style: styles11.bubbleCol, children: [
1552
+ /* @__PURE__ */ jsx(
1553
+ Pressable,
1554
+ {
1555
+ onLongPress: () => actions && setActionsOpen(true),
1556
+ accessibilityRole: "button",
1557
+ accessibilityLabel: "Message actions",
1558
+ children: editing ? /* @__PURE__ */ jsx(
1559
+ EditableMessageBubble,
1560
+ {
1561
+ message: msg,
1562
+ currentUserId: meId,
1563
+ onCancel: onCancelEdit ?? (() => void 0),
1564
+ onSave: onSaveEdit ?? (() => void 0)
1565
+ }
1566
+ ) : /* @__PURE__ */ jsx(
1567
+ MessageBubble,
1568
+ {
1569
+ message: msg,
1570
+ currentUserId: meId,
1571
+ ...readState ? { readState } : {},
1572
+ ...labelFor ? { labelFor } : {},
1573
+ ...onQuotedClick ? { onQuotedClick } : {},
1574
+ groupPosition,
1575
+ maxBodyLength,
1576
+ showSenderName,
1577
+ showAttachments: attachments,
1578
+ actionsTrigger: actions ? /* @__PURE__ */ jsx(Pressable, { onPress: () => setActionsOpen(true), hitSlop: 8, children: /* @__PURE__ */ jsx(
1579
+ PoolseIcon,
1580
+ {
1581
+ name: "chevron-down",
1582
+ size: 14,
1583
+ color: isSelf ? theme.colors.onBrand : theme.colors.ink3
1584
+ }
1585
+ ) }) : null
1586
+ }
1587
+ )
1588
+ }
1589
+ ),
1590
+ threads && (msg.reply_count ?? 0) > 0 && onOpenThread ? /* @__PURE__ */ jsx(Pressable, { onPress: onOpenThread, style: styles11.threadPill, children: /* @__PURE__ */ jsxs(Text, { style: [styles11.threadPillText, { color: theme.colors.brand }], children: [
1591
+ "\u{1F4AC} ",
1592
+ msg.reply_count,
1593
+ " ",
1594
+ msg.reply_count === 1 ? "reply" : "replies"
1595
+ ] }) }) : null,
1596
+ reactions && msg.reactions && Object.keys(msg.reactions).length > 0 ? /* @__PURE__ */ jsx(View, { style: styles11.reactionsRow, children: Object.entries(msg.reactions).map(([emoji, userIds]) => /* @__PURE__ */ jsxs(
1597
+ View,
1598
+ {
1599
+ style: [
1600
+ styles11.reactionChip,
1601
+ {
1602
+ backgroundColor: theme.colors.surface2,
1603
+ borderColor: theme.colors.border
1604
+ }
1605
+ ],
1606
+ children: [
1607
+ /* @__PURE__ */ jsx(Text, { style: styles11.reactionEmoji, children: emoji }),
1608
+ /* @__PURE__ */ jsx(Text, { style: [styles11.reactionCount, { color: theme.colors.ink2 }], children: Array.isArray(userIds) ? userIds.length : 0 })
1609
+ ]
1610
+ },
1611
+ emoji
1612
+ )) }) : null
1613
+ ] }),
1614
+ actions && actionsOpen ? /* @__PURE__ */ jsx(
1615
+ MessageActions,
1616
+ {
1617
+ visible: actionsOpen,
1618
+ onClose: () => setActionsOpen(false),
1619
+ isSelf,
1620
+ ...onStartEdit ? {
1621
+ onEdit: () => {
1622
+ setActionsOpen(false);
1623
+ onStartEdit();
1624
+ }
1625
+ } : {},
1626
+ ...onDelete ? {
1627
+ onDelete: () => {
1628
+ setActionsOpen(false);
1629
+ onDelete();
1630
+ }
1631
+ } : {},
1632
+ ...onOpenThread ? {
1633
+ onReplyInThread: () => {
1634
+ setActionsOpen(false);
1635
+ onOpenThread();
1636
+ }
1637
+ } : {},
1638
+ ...quotations && onQuote ? {
1639
+ onQuote: () => {
1640
+ setActionsOpen(false);
1641
+ onQuote();
1642
+ }
1643
+ } : {}
1644
+ }
1645
+ ) : null
1646
+ ] });
1647
+ }
1648
+ var styles11 = StyleSheet.create({
1649
+ row: {
1650
+ flexDirection: "row",
1651
+ alignItems: "flex-end",
1652
+ marginVertical: 2,
1653
+ paddingHorizontal: 8,
1654
+ maxWidth: "95%"
1655
+ },
1656
+ avatarSlot: {
1657
+ width: 36,
1658
+ marginRight: 6
1659
+ },
1660
+ bubbleCol: {
1661
+ flexShrink: 1
1662
+ },
1663
+ threadPill: {
1664
+ alignSelf: "flex-start",
1665
+ marginTop: 2,
1666
+ paddingHorizontal: 8,
1667
+ paddingVertical: 3
1668
+ },
1669
+ threadPillText: {
1670
+ fontSize: 11,
1671
+ fontWeight: "600"
1672
+ },
1673
+ reactionsRow: {
1674
+ flexDirection: "row",
1675
+ flexWrap: "wrap",
1676
+ marginTop: 4,
1677
+ gap: 4
1678
+ },
1679
+ reactionChip: {
1680
+ flexDirection: "row",
1681
+ alignItems: "center",
1682
+ paddingHorizontal: 6,
1683
+ paddingVertical: 2,
1684
+ borderRadius: 12,
1685
+ borderWidth: StyleSheet.hairlineWidth
1686
+ },
1687
+ reactionEmoji: {
1688
+ fontSize: 13,
1689
+ marginRight: 3
1690
+ },
1691
+ reactionCount: {
1692
+ fontSize: 11,
1693
+ fontWeight: "600"
1694
+ }
1695
+ });
1696
+ function ThreadView({
1697
+ visible,
1698
+ onClose,
1699
+ conversationId,
1700
+ rootMessage,
1701
+ labelFor
1702
+ }) {
1703
+ const theme = usePoolseTheme();
1704
+ const { me } = useMe();
1705
+ const meId = me?.id ?? null;
1706
+ const { replies, sendReply, loadMore, hasMore } = useThread(conversationId, rootMessage.id);
1707
+ return /* @__PURE__ */ jsx(Modal, { visible, animationType: "slide", onRequestClose: onClose, children: /* @__PURE__ */ jsxs(View, { style: [styles12.root, { backgroundColor: theme.colors.paper }], children: [
1708
+ /* @__PURE__ */ jsxs(
1709
+ View,
1710
+ {
1711
+ style: [
1712
+ styles12.header,
1713
+ {
1714
+ borderBottomColor: theme.colors.border,
1715
+ backgroundColor: theme.colors.surface
1716
+ }
1717
+ ],
1718
+ children: [
1719
+ /* @__PURE__ */ jsx(Pressable, { onPress: onClose, hitSlop: 8, style: styles12.closeBtn, children: /* @__PURE__ */ jsx(PoolseIcon, { name: "close", size: 18, color: theme.colors.ink }) }),
1720
+ /* @__PURE__ */ jsx(
1721
+ Text,
1722
+ {
1723
+ style: [styles12.title, { color: theme.colors.ink, fontFamily: theme.type.fontDisplay }],
1724
+ children: "Thread"
1725
+ }
1726
+ )
1727
+ ]
1728
+ }
1729
+ ),
1730
+ /* @__PURE__ */ jsx(View, { style: [styles12.rootPreview, { backgroundColor: theme.colors.surface2 }], children: /* @__PURE__ */ jsx(
1731
+ MessageBubble,
1732
+ {
1733
+ message: rootMessage,
1734
+ currentUserId: meId,
1735
+ ...labelFor ? { labelFor } : {}
1736
+ }
1737
+ ) }),
1738
+ /* @__PURE__ */ jsx(View, { style: { flex: 1 }, children: /* @__PURE__ */ jsx(
1739
+ MessageList,
1740
+ {
1741
+ messages: replies,
1742
+ hasMore,
1743
+ onLoadMore: loadMore,
1744
+ renderItem: (msg) => /* @__PURE__ */ jsx(View, { style: { paddingHorizontal: 8 }, children: /* @__PURE__ */ jsx(
1745
+ MessageBubble,
1746
+ {
1747
+ message: msg,
1748
+ currentUserId: meId,
1749
+ ...labelFor ? { labelFor } : {}
1750
+ }
1751
+ ) }, msg.id)
1752
+ }
1753
+ ) }),
1754
+ /* @__PURE__ */ jsx(
1755
+ MessageComposer,
1756
+ {
1757
+ onSend: async (body, opts) => {
1758
+ await sendReply({ body, ...opts });
1759
+ },
1760
+ placeholder: "Reply to thread\u2026"
1761
+ }
1762
+ )
1763
+ ] }) });
1764
+ }
1765
+ var styles12 = StyleSheet.create({
1766
+ root: {
1767
+ flex: 1
1768
+ },
1769
+ header: {
1770
+ flexDirection: "row",
1771
+ alignItems: "center",
1772
+ paddingHorizontal: 12,
1773
+ paddingVertical: 12,
1774
+ borderBottomWidth: StyleSheet.hairlineWidth,
1775
+ gap: 8
1776
+ },
1777
+ closeBtn: {
1778
+ padding: 6
1779
+ },
1780
+ title: {
1781
+ fontSize: 16,
1782
+ fontWeight: "600"
1783
+ },
1784
+ rootPreview: {
1785
+ paddingVertical: 8,
1786
+ paddingHorizontal: 8
1787
+ }
1788
+ });
1789
+ function UserName({ externalId, fallback, labelFor }) {
1790
+ const theme = usePoolseTheme();
1791
+ const name = useDisplayName(externalId, labelFor) ?? fallback ?? externalId ?? "";
1792
+ return /* @__PURE__ */ jsx(Text, { style: { color: theme.colors.ink, fontFamily: theme.type.fontBody }, children: name });
1793
+ }
1794
+ function useDisplayName(externalId, labelFor) {
1795
+ const resolved = useUser(externalId ?? null);
1796
+ if (!externalId) return "";
1797
+ if (labelFor) {
1798
+ const override = labelFor(externalId);
1799
+ if (override) return override;
1800
+ }
1801
+ return resolved.profile?.displayName ?? externalId;
1802
+ }
1803
+ function TypingIndicator({ typing, labelFor }) {
1804
+ const ids = Array.from(typing);
1805
+ const isTyping = ids.length > 0;
1806
+ if (!isTyping) return /* @__PURE__ */ jsx(View, { style: { height: 0 } });
1807
+ return /* @__PURE__ */ jsxs(View, { style: styles13.row, children: [
1808
+ /* @__PURE__ */ jsx(TypingDots, {}),
1809
+ /* @__PURE__ */ jsx(TypingLabel, { ids, ...labelFor ? { labelFor } : {} })
1810
+ ] });
1811
+ }
1812
+ function TypingDots() {
1813
+ const theme = usePoolseTheme();
1814
+ const a = useRef(new Animated.Value(0)).current;
1815
+ const b = useRef(new Animated.Value(0)).current;
1816
+ const c = useRef(new Animated.Value(0)).current;
1817
+ useEffect(() => {
1818
+ const loop = (val, delay) => Animated.loop(
1819
+ Animated.sequence([
1820
+ Animated.delay(delay),
1821
+ Animated.timing(val, {
1822
+ toValue: 1,
1823
+ duration: 400,
1824
+ easing: Easing.inOut(Easing.ease),
1825
+ useNativeDriver: true
1826
+ }),
1827
+ Animated.timing(val, {
1828
+ toValue: 0,
1829
+ duration: 400,
1830
+ easing: Easing.inOut(Easing.ease),
1831
+ useNativeDriver: true
1832
+ })
1833
+ ])
1834
+ );
1835
+ const animations = [loop(a, 0), loop(b, 150), loop(c, 300)];
1836
+ animations.forEach((anim) => anim.start());
1837
+ return () => animations.forEach((anim) => anim.stop());
1838
+ }, [a, b, c]);
1839
+ const dot = (val) => ({
1840
+ width: 6,
1841
+ height: 6,
1842
+ borderRadius: 3,
1843
+ backgroundColor: theme.colors.ink3,
1844
+ marginHorizontal: 2,
1845
+ transform: [
1846
+ {
1847
+ translateY: val.interpolate({ inputRange: [0, 1], outputRange: [0, -3] })
1848
+ }
1849
+ ],
1850
+ opacity: val.interpolate({ inputRange: [0, 1], outputRange: [0.5, 1] })
1851
+ });
1852
+ return /* @__PURE__ */ jsxs(View, { style: styles13.dotsWrap, children: [
1853
+ /* @__PURE__ */ jsx(Animated.View, { style: dot(a) }),
1854
+ /* @__PURE__ */ jsx(Animated.View, { style: dot(b) }),
1855
+ /* @__PURE__ */ jsx(Animated.View, { style: dot(c) })
1856
+ ] });
1857
+ }
1858
+ function TypingLabel({
1859
+ ids,
1860
+ labelFor
1861
+ }) {
1862
+ const theme = usePoolseTheme();
1863
+ const id1 = ids[0] ?? null;
1864
+ const id2 = ids[1] ?? null;
1865
+ const name1 = useDisplayName(id1, labelFor);
1866
+ const name2 = useDisplayName(id2, labelFor);
1867
+ const text = ids.length === 1 ? `${name1} is typing` : ids.length === 2 ? `${name1} and ${name2} are typing` : `${ids.length} people are typing`;
1868
+ return /* @__PURE__ */ jsx(Text, { style: [styles13.label, { color: theme.colors.ink2, fontFamily: theme.type.fontBody }], children: text });
1869
+ }
1870
+ var styles13 = StyleSheet.create({
1871
+ row: {
1872
+ flexDirection: "row",
1873
+ alignItems: "center",
1874
+ paddingHorizontal: 12,
1875
+ paddingVertical: 6
1876
+ },
1877
+ dotsWrap: {
1878
+ flexDirection: "row",
1879
+ alignItems: "flex-end",
1880
+ marginRight: 8,
1881
+ height: 12
1882
+ },
1883
+ label: {
1884
+ fontSize: 12
1885
+ }
1886
+ });
1887
+ function ConversationView({
1888
+ conversationId,
1889
+ emptyState,
1890
+ renderMessage,
1891
+ labelFor,
1892
+ avatarFor,
1893
+ reactions = true,
1894
+ mentions = true,
1895
+ attachments = true,
1896
+ actions = true,
1897
+ threads = true,
1898
+ quotations = true,
1899
+ readReceipts = true,
1900
+ maxBodyLength = 200,
1901
+ senderLabels = "auto",
1902
+ avatars = "auto",
1903
+ onMarkedRead
1904
+ }) {
1905
+ const theme = usePoolseTheme();
1906
+ const { me } = useMe();
1907
+ const meId = me?.id ?? null;
1908
+ const {
1909
+ messages,
1910
+ loading,
1911
+ hasMore,
1912
+ loadMore,
1913
+ send,
1914
+ edit,
1915
+ delete: deleteMsg
1916
+ } = useMessages(conversationId);
1917
+ const { typing, signalTyping } = useTyping(conversationId);
1918
+ const wantsMembers = mentions || senderLabels === "auto" || avatars === "auto";
1919
+ const { members } = useMembers(wantsMembers ? conversationId : "");
1920
+ const isGroupChat = members.length > 2;
1921
+ const showSenderLabels = senderLabels === "always" || senderLabels === "auto" && isGroupChat;
1922
+ const showAvatars = avatars === "always" || avatars === "auto" && isGroupChat;
1923
+ const [replyingTo, setReplyingTo] = useState(null);
1924
+ const [editingId, setEditingId] = useState(null);
1925
+ const [threadRoot, setThreadRoot] = useState(null);
1926
+ const [pickerOpen, setPickerOpen] = useState(false);
1927
+ const renderItem = useCallback(
1928
+ (msg) => {
1929
+ if (renderMessage) return /* @__PURE__ */ jsx(Fragment, { children: renderMessage(msg, meId) });
1930
+ const readState = readReceipts && meId !== null && msg.sender_id === meId ? readStateForMessage(msg, members, meId) : void 0;
1931
+ return /* @__PURE__ */ jsx(
1932
+ MessageRow,
1933
+ {
1934
+ msg,
1935
+ meId,
1936
+ reactions,
1937
+ attachments,
1938
+ actions,
1939
+ threads,
1940
+ quotations,
1941
+ ...readState ? { readState } : {},
1942
+ editing: editingId === msg.id,
1943
+ onStartEdit: () => setEditingId(msg.id),
1944
+ onCancelEdit: () => setEditingId(null),
1945
+ onSaveEdit: async (body) => {
1946
+ await edit(msg.id, body);
1947
+ setEditingId(null);
1948
+ },
1949
+ onDelete: () => deleteMsg(msg.id),
1950
+ ...threads ? { onOpenThread: () => setThreadRoot(msg) } : {},
1951
+ ...quotations ? { onQuote: () => setReplyingTo(msg) } : {},
1952
+ ...labelFor ? { labelFor } : {},
1953
+ ...avatarFor ? { avatarFor } : {},
1954
+ showSenderName: showSenderLabels,
1955
+ showAvatar: showAvatars,
1956
+ maxBodyLength
1957
+ }
1958
+ );
1959
+ },
1960
+ [
1961
+ renderMessage,
1962
+ meId,
1963
+ readReceipts,
1964
+ members,
1965
+ reactions,
1966
+ attachments,
1967
+ actions,
1968
+ threads,
1969
+ quotations,
1970
+ editingId,
1971
+ edit,
1972
+ deleteMsg,
1973
+ labelFor,
1974
+ avatarFor,
1975
+ showSenderLabels,
1976
+ showAvatars,
1977
+ maxBodyLength
1978
+ ]
1979
+ );
1980
+ const composerProps = useMemo(
1981
+ () => ({
1982
+ onSend: async (body, opts) => {
1983
+ await send({ body, ...opts });
1984
+ setReplyingTo(null);
1985
+ },
1986
+ onTyping: signalTyping,
1987
+ ...attachments ? { onAttachPress: () => setPickerOpen(true) } : {},
1988
+ ...replyingTo ? { replyingTo } : {},
1989
+ onCancelReply: () => setReplyingTo(null),
1990
+ ...labelFor ? { labelFor } : {},
1991
+ attachments
1992
+ }),
1993
+ [send, signalTyping, attachments, replyingTo, labelFor]
1994
+ );
1995
+ return /* @__PURE__ */ jsxs(View, { style: [styles14.root, { backgroundColor: theme.colors.paper }], children: [
1996
+ /* @__PURE__ */ jsx(View, { style: styles14.listWrap, children: /* @__PURE__ */ jsx(
1997
+ MessageList,
1998
+ {
1999
+ messages,
2000
+ loading,
2001
+ hasMore,
2002
+ onLoadMore: loadMore,
2003
+ renderItem,
2004
+ ListHeader: /* @__PURE__ */ jsx(TypingIndicator, { typing, ...labelFor ? { labelFor } : {} }),
2005
+ ...emptyState !== void 0 ? { emptyState } : {}
2006
+ }
2007
+ ) }),
2008
+ mentions ? /* @__PURE__ */ jsx(MentionInput, { conversationId, ...composerProps }) : /* @__PURE__ */ jsx(MessageComposer, { ...composerProps }),
2009
+ attachments ? /* @__PURE__ */ jsx(AttachmentPicker, { visible: pickerOpen, onClose: () => setPickerOpen(false) }) : null,
2010
+ threads && threadRoot ? /* @__PURE__ */ jsx(
2011
+ ThreadView,
2012
+ {
2013
+ visible: true,
2014
+ onClose: () => setThreadRoot(null),
2015
+ conversationId,
2016
+ rootMessage: threadRoot,
2017
+ ...labelFor ? { labelFor } : {}
2018
+ }
2019
+ ) : null
2020
+ ] });
2021
+ }
2022
+ function readStateForMessage(msg, members, meId) {
2023
+ const otherMembers = members.filter((m) => m.user_id !== meId);
2024
+ if (otherMembers.length === 0) return "sent";
2025
+ const anyRead = otherMembers.some(
2026
+ (m) => m.last_read_message_id !== null && msg.id !== null && m.last_read_message_id >= msg.id
2027
+ );
2028
+ return anyRead ? "read" : "sent";
2029
+ }
2030
+ var styles14 = StyleSheet.create({
2031
+ root: {
2032
+ flex: 1
2033
+ },
2034
+ listWrap: {
2035
+ flex: 1
2036
+ }
2037
+ });
2038
+ function ConversationList({
2039
+ selectedId,
2040
+ onSelect,
2041
+ renderItem,
2042
+ emptyState,
2043
+ conversations: controlled,
2044
+ loading: controlledLoading,
2045
+ error: controlledError,
2046
+ unreadCounts
2047
+ }) {
2048
+ const theme = usePoolseTheme();
2049
+ const auto = useConversations();
2050
+ const isControlled = controlled !== void 0;
2051
+ const conversations = isControlled ? controlled : auto.conversations;
2052
+ const loading = isControlled ? controlledLoading ?? false : auto.loading;
2053
+ const error = isControlled ? controlledError ?? null : auto.error;
2054
+ const counts = unreadCounts ?? (isControlled ? void 0 : auto.unreadCounts);
2055
+ if (loading && conversations.length === 0) {
2056
+ return /* @__PURE__ */ jsx(View, { style: [styles15.placeholder, { backgroundColor: theme.colors.paper }], children: /* @__PURE__ */ jsx(ActivityIndicator, { color: theme.colors.brand }) });
2057
+ }
2058
+ if (error) {
2059
+ return /* @__PURE__ */ jsx(View, { style: [styles15.placeholder, { backgroundColor: theme.colors.paper }], children: /* @__PURE__ */ jsx(Text, { style: { color: theme.colors.error, fontFamily: theme.type.fontBody }, children: "Failed to load conversations." }) });
2060
+ }
2061
+ if (conversations.length === 0) {
2062
+ return /* @__PURE__ */ jsx(View, { style: [styles15.placeholder, { backgroundColor: theme.colors.paper }], children: typeof emptyState === "string" || emptyState == null ? /* @__PURE__ */ jsx(Text, { style: { color: theme.colors.ink3, fontFamily: theme.type.fontBody }, children: emptyState ?? "No conversations yet." }) : emptyState });
2063
+ }
2064
+ return /* @__PURE__ */ jsx(
2065
+ FlatList,
2066
+ {
2067
+ data: conversations,
2068
+ keyExtractor: (c) => c.id,
2069
+ style: { backgroundColor: theme.colors.paper },
2070
+ renderItem: ({ item }) => {
2071
+ const selected = selectedId === item.id;
2072
+ if (renderItem) {
2073
+ return /* @__PURE__ */ jsx(Fragment, { children: renderItem(item, selected) });
2074
+ }
2075
+ const unread = counts?.[item.id] ?? 0;
2076
+ return /* @__PURE__ */ jsxs(
2077
+ Pressable,
2078
+ {
2079
+ onPress: () => onSelect?.(item),
2080
+ style: [
2081
+ styles15.row,
2082
+ {
2083
+ backgroundColor: selected ? theme.colors.surface2 : "transparent"
2084
+ }
2085
+ ],
2086
+ children: [
2087
+ /* @__PURE__ */ jsx(Avatar, { src: null, name: item.name ?? null, seed: item.id, size: "md" }),
2088
+ /* @__PURE__ */ jsxs(View, { style: styles15.body, children: [
2089
+ /* @__PURE__ */ jsx(
2090
+ Text,
2091
+ {
2092
+ style: [styles15.name, { color: theme.colors.ink, fontFamily: theme.type.fontBody }],
2093
+ numberOfLines: 1,
2094
+ children: item.name ?? "Untitled conversation"
2095
+ }
2096
+ ),
2097
+ item.last_message_at ? /* @__PURE__ */ jsx(Text, { style: [styles15.preview, { color: theme.colors.ink3 }], numberOfLines: 1, children: formatRelative(item.last_message_at) }) : null
2098
+ ] }),
2099
+ unread > 0 ? /* @__PURE__ */ jsx(View, { style: [styles15.unreadPill, { backgroundColor: theme.colors.unreadPill }], children: /* @__PURE__ */ jsx(Text, { style: [styles15.unreadText, { color: theme.colors.unreadPillText }], children: unread > 99 ? "99+" : String(unread) }) }) : null
2100
+ ]
2101
+ }
2102
+ );
2103
+ }
2104
+ }
2105
+ );
2106
+ }
2107
+ function formatRelative(iso) {
2108
+ try {
2109
+ const d = new Date(iso);
2110
+ const now = Date.now();
2111
+ const diff = now - d.getTime();
2112
+ if (diff < 6e4) return "just now";
2113
+ if (diff < 36e5) return `${Math.floor(diff / 6e4)}m`;
2114
+ if (diff < 864e5) return `${Math.floor(diff / 36e5)}h`;
2115
+ return `${Math.floor(diff / 864e5)}d`;
2116
+ } catch {
2117
+ return "";
2118
+ }
2119
+ }
2120
+ var styles15 = StyleSheet.create({
2121
+ placeholder: {
2122
+ flex: 1,
2123
+ alignItems: "center",
2124
+ justifyContent: "center",
2125
+ padding: 24
2126
+ },
2127
+ row: {
2128
+ flexDirection: "row",
2129
+ alignItems: "center",
2130
+ paddingHorizontal: 12,
2131
+ paddingVertical: 10,
2132
+ gap: 10
2133
+ },
2134
+ body: {
2135
+ flex: 1,
2136
+ gap: 2
2137
+ },
2138
+ name: {
2139
+ fontSize: 15,
2140
+ fontWeight: "600"
2141
+ },
2142
+ preview: {
2143
+ fontSize: 12
2144
+ },
2145
+ unreadPill: {
2146
+ minWidth: 22,
2147
+ height: 22,
2148
+ borderRadius: 11,
2149
+ paddingHorizontal: 6,
2150
+ alignItems: "center",
2151
+ justifyContent: "center"
2152
+ },
2153
+ unreadText: {
2154
+ fontSize: 11,
2155
+ fontWeight: "700"
2156
+ }
2157
+ });
2158
+ function MemberList({
2159
+ conversationId,
2160
+ labelFor,
2161
+ avatarFor,
2162
+ onlineExternalIds,
2163
+ canRemove,
2164
+ renderItem,
2165
+ emptyState
2166
+ }) {
2167
+ const theme = usePoolseTheme();
2168
+ const { members, loading, error, removeMember } = useMembers(conversationId);
2169
+ const { online: presenceOnline } = usePresence(
2170
+ onlineExternalIds === void 0 ? conversationId : ""
2171
+ );
2172
+ const effectiveOnline = onlineExternalIds ?? presenceOnline;
2173
+ if (loading && members.length === 0) {
2174
+ return /* @__PURE__ */ jsx(View, { style: [styles16.placeholder, { backgroundColor: theme.colors.paper }], children: /* @__PURE__ */ jsx(ActivityIndicator, { color: theme.colors.brand }) });
2175
+ }
2176
+ if (error) {
2177
+ return /* @__PURE__ */ jsxs(View, { style: [styles16.placeholder, { backgroundColor: theme.colors.paper }], children: [
2178
+ /* @__PURE__ */ jsx(Text, { style: { color: theme.colors.error, fontFamily: theme.type.fontBody }, children: "Failed to load members." }),
2179
+ error.message ? /* @__PURE__ */ jsx(Text, { style: [styles16.errorDetail, { color: theme.colors.ink3 }], children: error.message }) : null
2180
+ ] });
2181
+ }
2182
+ if (members.length === 0) {
2183
+ return /* @__PURE__ */ jsx(View, { style: [styles16.placeholder, { backgroundColor: theme.colors.paper }], children: typeof emptyState === "string" || emptyState == null ? /* @__PURE__ */ jsx(Text, { style: { color: theme.colors.ink3, fontFamily: theme.type.fontBody }, children: emptyState ?? "No members." }) : emptyState });
2184
+ }
2185
+ return /* @__PURE__ */ jsx(
2186
+ FlatList,
2187
+ {
2188
+ data: members,
2189
+ keyExtractor: (m) => m.user_id,
2190
+ style: { backgroundColor: theme.colors.paper },
2191
+ renderItem: ({ item }) => {
2192
+ if (renderItem) return /* @__PURE__ */ jsx(Fragment, { children: renderItem(item) });
2193
+ const name = labelFor?.(item.external_id) ?? item.external_id;
2194
+ const avatarUrl = avatarFor?.(item.external_id) ?? null;
2195
+ const isOnline = effectiveOnline.has(item.external_id);
2196
+ const showRemove = canRemove?.(item) ?? false;
2197
+ return /* @__PURE__ */ jsx(
2198
+ MemberRow,
2199
+ {
2200
+ name,
2201
+ externalId: item.external_id,
2202
+ avatarUrl,
2203
+ role: item.role,
2204
+ online: isOnline,
2205
+ ...showRemove ? { onRemove: () => removeMember(item.external_id) } : {}
2206
+ }
2207
+ );
2208
+ }
2209
+ }
2210
+ );
2211
+ }
2212
+ function MemberRow({
2213
+ name,
2214
+ externalId,
2215
+ avatarUrl,
2216
+ role,
2217
+ online,
2218
+ onRemove
2219
+ }) {
2220
+ const theme = usePoolseTheme();
2221
+ const displayName = useDisplayName(externalId) || name;
2222
+ return /* @__PURE__ */ jsxs(View, { style: styles16.row, children: [
2223
+ /* @__PURE__ */ jsx(Avatar, { src: avatarUrl, name: displayName, seed: externalId, online, size: "md" }),
2224
+ /* @__PURE__ */ jsxs(View, { style: styles16.body, children: [
2225
+ /* @__PURE__ */ jsx(
2226
+ Text,
2227
+ {
2228
+ style: [styles16.name, { color: theme.colors.ink, fontFamily: theme.type.fontBody }],
2229
+ numberOfLines: 1,
2230
+ children: displayName
2231
+ }
2232
+ ),
2233
+ /* @__PURE__ */ jsx(Text, { style: [styles16.role, { color: theme.colors.ink3 }], children: role })
2234
+ ] }),
2235
+ onRemove ? /* @__PURE__ */ jsx(Pressable, { onPress: onRemove, hitSlop: 8, style: styles16.removeBtn, children: /* @__PURE__ */ jsx(PoolseIcon, { name: "close", size: 16, color: theme.colors.ink2 }) }) : null
2236
+ ] });
2237
+ }
2238
+ var styles16 = StyleSheet.create({
2239
+ placeholder: {
2240
+ flex: 1,
2241
+ alignItems: "center",
2242
+ justifyContent: "center",
2243
+ padding: 24
2244
+ },
2245
+ errorDetail: {
2246
+ marginTop: 6,
2247
+ fontSize: 11
2248
+ },
2249
+ row: {
2250
+ flexDirection: "row",
2251
+ alignItems: "center",
2252
+ paddingHorizontal: 12,
2253
+ paddingVertical: 8,
2254
+ gap: 10
2255
+ },
2256
+ body: {
2257
+ flex: 1,
2258
+ gap: 1
2259
+ },
2260
+ name: {
2261
+ fontSize: 14,
2262
+ fontWeight: "500"
2263
+ },
2264
+ role: {
2265
+ fontSize: 11,
2266
+ textTransform: "capitalize"
2267
+ },
2268
+ removeBtn: {
2269
+ padding: 6
2270
+ }
2271
+ });
2272
+ var DEFAULT_EMOJIS = ["\u{1F44D}", "\u2764\uFE0F", "\u{1F602}", "\u{1F389}", "\u{1F64F}", "\u{1F525}"];
2273
+ function ReactionStrip({
2274
+ messageId,
2275
+ conversationId,
2276
+ meId,
2277
+ initialReactions
2278
+ }) {
2279
+ const theme = usePoolseTheme();
2280
+ const { reactions, addReaction, removeReaction } = useReactions(messageId, {
2281
+ conversationId,
2282
+ currentUserId: meId,
2283
+ ...initialReactions ? { initialReactions } : {}
2284
+ });
2285
+ const entries = Object.entries(reactions);
2286
+ if (entries.length === 0) return null;
2287
+ return /* @__PURE__ */ jsx(View, { style: styles17.strip, children: entries.map(([emoji, userIds]) => {
2288
+ const userArr = Array.isArray(userIds) ? userIds : [];
2289
+ const meReacted = meId !== null && userArr.includes(meId);
2290
+ return /* @__PURE__ */ jsxs(
2291
+ Pressable,
2292
+ {
2293
+ onPress: () => meReacted ? removeReaction(emoji) : addReaction(emoji),
2294
+ style: [
2295
+ styles17.chip,
2296
+ {
2297
+ backgroundColor: meReacted ? theme.colors.brandSoft : theme.colors.surface2,
2298
+ borderColor: meReacted ? theme.colors.brand : theme.colors.border
2299
+ }
2300
+ ],
2301
+ children: [
2302
+ /* @__PURE__ */ jsx(Text, { style: styles17.emoji, children: emoji }),
2303
+ /* @__PURE__ */ jsx(Text, { style: [styles17.count, { color: theme.colors.ink2 }], children: userArr.length })
2304
+ ]
2305
+ },
2306
+ emoji
2307
+ );
2308
+ }) });
2309
+ }
2310
+ function ReactionPicker({
2311
+ visible,
2312
+ onClose,
2313
+ onPick,
2314
+ emojis = DEFAULT_EMOJIS
2315
+ }) {
2316
+ const theme = usePoolseTheme();
2317
+ return /* @__PURE__ */ jsx(Modal, { visible, transparent: true, animationType: "fade", onRequestClose: onClose, children: /* @__PURE__ */ jsx(Pressable, { style: styles17.backdrop, onPress: onClose, children: /* @__PURE__ */ jsx(
2318
+ View,
2319
+ {
2320
+ style: [
2321
+ styles17.sheet,
2322
+ {
2323
+ backgroundColor: theme.colors.surface,
2324
+ borderRadius: theme.radii.lg
2325
+ },
2326
+ theme.shadows.lg
2327
+ ],
2328
+ children: emojis.map((emoji) => /* @__PURE__ */ jsx(
2329
+ Pressable,
2330
+ {
2331
+ style: styles17.emojiBtn,
2332
+ onPress: () => {
2333
+ onPick(emoji);
2334
+ onClose();
2335
+ },
2336
+ children: /* @__PURE__ */ jsx(Text, { style: styles17.bigEmoji, children: emoji })
2337
+ },
2338
+ emoji
2339
+ ))
2340
+ }
2341
+ ) }) });
2342
+ }
2343
+ var styles17 = StyleSheet.create({
2344
+ strip: {
2345
+ flexDirection: "row",
2346
+ flexWrap: "wrap",
2347
+ gap: 4,
2348
+ marginTop: 4
2349
+ },
2350
+ chip: {
2351
+ flexDirection: "row",
2352
+ alignItems: "center",
2353
+ paddingHorizontal: 8,
2354
+ paddingVertical: 3,
2355
+ borderRadius: 12,
2356
+ borderWidth: StyleSheet.hairlineWidth,
2357
+ gap: 4
2358
+ },
2359
+ emoji: {
2360
+ fontSize: 14
2361
+ },
2362
+ count: {
2363
+ fontSize: 11,
2364
+ fontWeight: "600"
2365
+ },
2366
+ backdrop: {
2367
+ flex: 1,
2368
+ backgroundColor: "rgba(0,0,0,0.35)",
2369
+ justifyContent: "center",
2370
+ alignItems: "center"
2371
+ },
2372
+ sheet: {
2373
+ flexDirection: "row",
2374
+ padding: 8,
2375
+ gap: 4
2376
+ },
2377
+ emojiBtn: {
2378
+ paddingHorizontal: 10,
2379
+ paddingVertical: 8
2380
+ },
2381
+ bigEmoji: {
2382
+ fontSize: 28
2383
+ }
2384
+ });
2385
+ var TILE_PATH = "M50 4 C20 4 4 20 4 50 C4 80 20 96 50 96 C80 96 96 80 96 50 C96 20 80 4 50 4 Z";
2386
+ var PULSE_PATH = "M16 55 H34 L40 55 L47 30 L54 73 L60 55 H72";
2387
+ var WORDMARK_P_PATH = "M62 -169V258V528H194L196 378L214 376Q223 432 246.0 469.0Q269 506 305.5 524.0Q342 542 389 542Q459 542 509.5 508.0Q560 474 587.0 411.0Q614 348 614 260Q614 182 590.5 120.0Q567 58 519.5 22.0Q472 -14 399 -14Q350 -14 316.0 3.5Q282 21 258.5 55.0Q235 89 219 140H200Q206 113 211.0 85.0Q216 57 219.5 31.0Q223 5 223 -19V-169ZM340 113Q373 113 396.5 131.0Q420 149 433.0 182.0Q446 215 446 259Q446 306 432.5 339.5Q419 373 394.0 391.5Q369 410 336 410Q305 410 283.5 396.5Q262 383 248.5 361.5Q235 340 229.0 316.0Q223 292 223 271V249Q223 230 228.0 211.0Q233 192 243.0 174.5Q253 157 267.0 143.0Q281 129 299.5 121.0Q318 113 340 113Z";
2388
+ var WORDMARK_O_PATH = "M305 -14Q225 -14 163.5 17.0Q102 48 67.0 110.0Q32 172 32 265Q32 358 67.0 419.5Q102 481 164.0 511.5Q226 542 306 542Q386 542 448.0 511.0Q510 480 545.0 418.5Q580 357 580 264Q580 169 544.0 107.0Q508 45 445.5 15.5Q383 -14 305 -14ZM310 103Q346 103 370.0 119.5Q394 136 406.0 170.0Q418 204 418 254Q418 307 405.0 343.5Q392 380 366.5 399.5Q341 419 301 419Q267 419 242.5 402.5Q218 386 206.0 352.0Q194 318 194 267Q194 185 224.5 144.0Q255 103 310 103Z";
2389
+ var WORDMARK_L_PATH = "M62 0V720H220V0Z";
2390
+ var WORDMARK_S_PATH = "M278 -14Q225 -14 182.0 -4.5Q139 5 107.0 23.0Q75 41 54.5 66.5Q34 92 26 123L146 176Q153 159 170.5 141.5Q188 124 217.5 113.0Q247 102 289 102Q329 102 351.5 113.5Q374 125 374 147Q374 163 361.5 172.5Q349 182 324.0 189.5Q299 197 262 204Q224 212 185.0 222.5Q146 233 112.0 251.5Q78 270 57.5 300.5Q37 331 37 378Q37 427 63.0 463.5Q89 500 140.5 521.0Q192 542 266 542Q332 542 382.0 525.0Q432 508 465.5 476.5Q499 445 512 401L383 355Q378 377 362.5 393.0Q347 409 323.5 417.5Q300 426 268 426Q229 426 207.5 414.0Q186 402 186 382Q186 366 200.5 355.5Q215 345 242.0 338.0Q269 331 306 323Q345 315 383.5 304.5Q422 294 453.5 276.5Q485 259 503.5 230.5Q522 202 522 157Q522 104 494.0 65.5Q466 27 411.5 6.5Q357 -14 278 -14Z";
2391
+ var WORDMARK_E_PATH = "M313 -14Q247 -14 195.0 3.5Q143 21 106.5 55.0Q70 89 51.0 139.0Q32 189 32 254Q32 317 50.5 370.0Q69 423 103.5 461.5Q138 500 188.5 521.0Q239 542 302 542Q366 542 414.5 521.5Q463 501 494.5 461.0Q526 421 541.0 363.5Q556 306 551 232L138 229V313L459 316L402 277Q409 327 396.0 358.5Q383 390 358.0 404.5Q333 419 304 419Q269 419 242.5 400.0Q216 381 201.0 345.0Q186 309 186 257Q186 175 221.5 138.0Q257 101 312 101Q339 101 357.0 108.5Q375 116 386.5 127.5Q398 139 404.5 153.0Q411 167 415 180L555 150Q547 113 529.5 83.0Q512 53 482.5 31.0Q453 9 411.5 -2.5Q370 -14 313 -14Z";
2392
+ var VARIANT_VB = {
2393
+ lockup: { vb: [0, 0, 454, 100], aspect: 4.54 },
2394
+ mark: { vb: [0, 0, 100, 100], aspect: 1 },
2395
+ wordmark: { vb: [0, 0, 350, 100], aspect: 3.5 },
2396
+ mono: { vb: [0, 0, 454, 100], aspect: 4.54 }
2397
+ };
2398
+ function PoolseLogo({ variant = "lockup", size = 28, color, label }) {
2399
+ const theme = usePoolseTheme();
2400
+ const mono = variant === "mono";
2401
+ const monoColor = color ?? theme.colors.ink;
2402
+ const tileFill = mono ? "none" : theme.colors.brand;
2403
+ const tileStroke = mono ? monoColor : "transparent";
2404
+ const tileStrokeWidth = mono ? 5 : 0;
2405
+ const pulseStroke = mono ? monoColor : theme.colors.onBrand;
2406
+ const pulseDotFill = mono ? monoColor : theme.colors.onBrand;
2407
+ const inkFill = mono ? monoColor : theme.colors.ink;
2408
+ const oFill = mono ? monoColor : theme.colors.brand;
2409
+ const { vb, aspect } = VARIANT_VB[variant];
2410
+ const width = size * aspect;
2411
+ const showMark = variant === "lockup" || variant === "mark" || variant === "mono";
2412
+ const showWordmark = variant === "lockup" || variant === "wordmark" || variant === "mono";
2413
+ const wordmarkOffsetX = variant === "wordmark" ? 0 : 126;
2414
+ const a11y = label === null ? {} : { accessibilityLabel: label ?? "poolse" };
2415
+ return /* @__PURE__ */ jsxs(Svg, { viewBox: vb.join(" "), width, height: size, ...a11y, children: [
2416
+ showMark ? /* @__PURE__ */ jsxs(Fragment, { children: [
2417
+ /* @__PURE__ */ jsx(Path, { d: TILE_PATH, fill: tileFill, stroke: tileStroke, strokeWidth: tileStrokeWidth }),
2418
+ /* @__PURE__ */ jsx(
2419
+ Path,
2420
+ {
2421
+ d: PULSE_PATH,
2422
+ fill: "none",
2423
+ stroke: pulseStroke,
2424
+ strokeWidth: 9,
2425
+ strokeLinecap: "round",
2426
+ strokeLinejoin: "round"
2427
+ }
2428
+ ),
2429
+ /* @__PURE__ */ jsx(Circle, { cx: 72, cy: 55, r: 6.2, fill: pulseDotFill })
2430
+ ] }) : null,
2431
+ showWordmark ? /* @__PURE__ */ jsxs(G, { transform: `translate(${wordmarkOffsetX},85.0) scale(0.097222 -0.097222)`, children: [
2432
+ /* @__PURE__ */ jsx(Path, { d: WORDMARK_P_PATH, fill: inkFill, transform: "translate(0,0)" }),
2433
+ /* @__PURE__ */ jsx(Path, { d: WORDMARK_O_PATH, fill: oFill, transform: "translate(658,0)" }),
2434
+ /* @__PURE__ */ jsx(Path, { d: WORDMARK_O_PATH, fill: oFill, transform: "translate(1280,0)" }),
2435
+ /* @__PURE__ */ jsx(Path, { d: WORDMARK_L_PATH, fill: inkFill, transform: "translate(1902,0)" }),
2436
+ /* @__PURE__ */ jsx(Path, { d: WORDMARK_S_PATH, fill: inkFill, transform: "translate(2195,0)" }),
2437
+ /* @__PURE__ */ jsx(Path, { d: WORDMARK_E_PATH, fill: inkFill, transform: "translate(2754,0)" })
2438
+ ] }) : null
2439
+ ] });
2440
+ }
2441
+
2442
+ // src/primitives/index.ts
2443
+ var primitives = { PoolseIcon, PoolseLogo, Avatar };
2444
+ function useAppStateLifecycle() {
2445
+ const poolse = usePoolse();
2446
+ useEffect(() => {
2447
+ let prev = AppState.currentState;
2448
+ const sub = AppState.addEventListener("change", (next) => {
2449
+ const wasActive = prev === "active";
2450
+ const isActive = next === "active";
2451
+ if (wasActive && !isActive) {
2452
+ try {
2453
+ poolse.realtime?.disconnect?.();
2454
+ } catch {
2455
+ }
2456
+ } else if (!wasActive && isActive) {
2457
+ try {
2458
+ poolse.realtime?.connect?.();
2459
+ } catch {
2460
+ }
2461
+ }
2462
+ prev = next;
2463
+ });
2464
+ return () => sub.remove();
2465
+ }, [poolse]);
2466
+ }
2467
+
2468
+ export { AttachmentPicker, AttachmentPreview, Avatar, ConversationList, ConversationView, EditableMessageBubble, MemberList, MentionInput, MessageActions, MessageBubble, MessageComposer, MessageList, MessageRow, PoolseIcon, PoolseLogo, PoolseTheme, primitives as Primitives, ReactionPicker, ReactionStrip, ThreadView, TypingIndicator, UploadQueueStrip, UserName, defaultDarkTheme, defaultLightTheme, useAppStateLifecycle, useDisplayName, usePoolseTheme };
2469
+ //# sourceMappingURL=index.js.map
2470
+ //# sourceMappingURL=index.js.map