@repzo/widget-react-native 1.0.0 → 1.0.4

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 (153) hide show
  1. package/README.md +167 -74
  2. package/lib/commonjs/api/client.js +3 -1
  3. package/lib/commonjs/api/client.js.map +1 -1
  4. package/lib/commonjs/api/realtime.js +2 -0
  5. package/lib/commonjs/api/realtime.js.map +1 -1
  6. package/lib/commonjs/attachments/pickers.js +493 -0
  7. package/lib/commonjs/attachments/pickers.js.map +1 -0
  8. package/lib/commonjs/i18n.js +45 -0
  9. package/lib/commonjs/i18n.js.map +1 -1
  10. package/lib/commonjs/index.js.map +1 -1
  11. package/lib/commonjs/locales.js +21 -0
  12. package/lib/commonjs/locales.js.map +1 -1
  13. package/lib/commonjs/state/widget-store.js +256 -18
  14. package/lib/commonjs/state/widget-store.js.map +1 -1
  15. package/lib/commonjs/types.js +16 -0
  16. package/lib/commonjs/types.js.map +1 -1
  17. package/lib/commonjs/ui/WidgetProvider.js +70 -10
  18. package/lib/commonjs/ui/WidgetProvider.js.map +1 -1
  19. package/lib/commonjs/ui/components/AttachmentChips.js +135 -0
  20. package/lib/commonjs/ui/components/AttachmentChips.js.map +1 -0
  21. package/lib/commonjs/ui/components/AttachmentMenu.js +125 -0
  22. package/lib/commonjs/ui/components/AttachmentMenu.js.map +1 -0
  23. package/lib/commonjs/ui/components/ImageViewer.js +181 -0
  24. package/lib/commonjs/ui/components/ImageViewer.js.map +1 -0
  25. package/lib/commonjs/ui/components/InAppNotification.js +210 -0
  26. package/lib/commonjs/ui/components/InAppNotification.js.map +1 -0
  27. package/lib/commonjs/ui/components/TeamAvatars.js +63 -0
  28. package/lib/commonjs/ui/components/TeamAvatars.js.map +1 -0
  29. package/lib/commonjs/ui/components/icons.js +50 -0
  30. package/lib/commonjs/ui/components/icons.js.map +1 -1
  31. package/lib/commonjs/ui/screens/chat/ChatView.js +303 -24
  32. package/lib/commonjs/ui/screens/chat/ChatView.js.map +1 -1
  33. package/lib/commonjs/ui/screens/chat/ChatView.styles.js +70 -0
  34. package/lib/commonjs/ui/screens/chat/ChatView.styles.js.map +1 -1
  35. package/lib/module/api/client.js +3 -1
  36. package/lib/module/api/client.js.map +1 -1
  37. package/lib/module/api/realtime.js +2 -0
  38. package/lib/module/api/realtime.js.map +1 -1
  39. package/lib/module/attachments/pickers.js +479 -0
  40. package/lib/module/attachments/pickers.js.map +1 -0
  41. package/lib/module/i18n.js +44 -0
  42. package/lib/module/i18n.js.map +1 -1
  43. package/lib/module/index.js.map +1 -1
  44. package/lib/module/locales.js +21 -0
  45. package/lib/module/locales.js.map +1 -1
  46. package/lib/module/state/widget-store.js +256 -18
  47. package/lib/module/state/widget-store.js.map +1 -1
  48. package/lib/module/types.js +16 -0
  49. package/lib/module/types.js.map +1 -1
  50. package/lib/module/ui/WidgetProvider.js +71 -11
  51. package/lib/module/ui/WidgetProvider.js.map +1 -1
  52. package/lib/module/ui/components/AttachmentChips.js +131 -0
  53. package/lib/module/ui/components/AttachmentChips.js.map +1 -0
  54. package/lib/module/ui/components/AttachmentMenu.js +120 -0
  55. package/lib/module/ui/components/AttachmentMenu.js.map +1 -0
  56. package/lib/module/ui/components/ImageViewer.js +177 -0
  57. package/lib/module/ui/components/ImageViewer.js.map +1 -0
  58. package/lib/module/ui/components/InAppNotification.js +208 -0
  59. package/lib/module/ui/components/InAppNotification.js.map +1 -0
  60. package/lib/module/ui/components/TeamAvatars.js +59 -0
  61. package/lib/module/ui/components/TeamAvatars.js.map +1 -0
  62. package/lib/module/ui/components/icons.js +49 -0
  63. package/lib/module/ui/components/icons.js.map +1 -1
  64. package/lib/module/ui/screens/chat/ChatView.js +306 -27
  65. package/lib/module/ui/screens/chat/ChatView.js.map +1 -1
  66. package/lib/module/ui/screens/chat/ChatView.styles.js +70 -0
  67. package/lib/module/ui/screens/chat/ChatView.styles.js.map +1 -1
  68. package/lib/typescript/commonjs/api/client.d.ts +8 -2
  69. package/lib/typescript/commonjs/api/client.d.ts.map +1 -1
  70. package/lib/typescript/commonjs/api/realtime.d.ts.map +1 -1
  71. package/lib/typescript/commonjs/attachments/pickers.d.ts +81 -0
  72. package/lib/typescript/commonjs/attachments/pickers.d.ts.map +1 -0
  73. package/lib/typescript/commonjs/i18n.d.ts +23 -0
  74. package/lib/typescript/commonjs/i18n.d.ts.map +1 -1
  75. package/lib/typescript/commonjs/index.d.ts +1 -1
  76. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  77. package/lib/typescript/commonjs/locales.d.ts.map +1 -1
  78. package/lib/typescript/commonjs/state/widget-store.d.ts +40 -2
  79. package/lib/typescript/commonjs/state/widget-store.d.ts.map +1 -1
  80. package/lib/typescript/commonjs/types.d.ts +88 -1
  81. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  82. package/lib/typescript/commonjs/ui/WidgetProvider.d.ts.map +1 -1
  83. package/lib/typescript/commonjs/ui/components/AttachmentChips.d.ts +16 -0
  84. package/lib/typescript/commonjs/ui/components/AttachmentChips.d.ts.map +1 -0
  85. package/lib/typescript/commonjs/ui/components/AttachmentMenu.d.ts +28 -0
  86. package/lib/typescript/commonjs/ui/components/AttachmentMenu.d.ts.map +1 -0
  87. package/lib/typescript/commonjs/ui/components/ImageViewer.d.ts +23 -0
  88. package/lib/typescript/commonjs/ui/components/ImageViewer.d.ts.map +1 -0
  89. package/lib/typescript/commonjs/ui/components/InAppNotification.d.ts +28 -0
  90. package/lib/typescript/commonjs/ui/components/InAppNotification.d.ts.map +1 -0
  91. package/lib/typescript/commonjs/ui/components/TeamAvatars.d.ts +17 -0
  92. package/lib/typescript/commonjs/ui/components/TeamAvatars.d.ts.map +1 -0
  93. package/lib/typescript/commonjs/ui/components/icons.d.ts +2 -0
  94. package/lib/typescript/commonjs/ui/components/icons.d.ts.map +1 -1
  95. package/lib/typescript/commonjs/ui/screens/chat/ChatView.d.ts +3 -2
  96. package/lib/typescript/commonjs/ui/screens/chat/ChatView.d.ts.map +1 -1
  97. package/lib/typescript/commonjs/ui/screens/chat/ChatView.styles.d.ts +69 -0
  98. package/lib/typescript/commonjs/ui/screens/chat/ChatView.styles.d.ts.map +1 -1
  99. package/lib/typescript/commonjs/ui/screens/chat/ChatView.types.d.ts +41 -2
  100. package/lib/typescript/commonjs/ui/screens/chat/ChatView.types.d.ts.map +1 -1
  101. package/lib/typescript/module/api/client.d.ts +8 -2
  102. package/lib/typescript/module/api/client.d.ts.map +1 -1
  103. package/lib/typescript/module/api/realtime.d.ts.map +1 -1
  104. package/lib/typescript/module/attachments/pickers.d.ts +81 -0
  105. package/lib/typescript/module/attachments/pickers.d.ts.map +1 -0
  106. package/lib/typescript/module/i18n.d.ts +23 -0
  107. package/lib/typescript/module/i18n.d.ts.map +1 -1
  108. package/lib/typescript/module/index.d.ts +1 -1
  109. package/lib/typescript/module/index.d.ts.map +1 -1
  110. package/lib/typescript/module/locales.d.ts.map +1 -1
  111. package/lib/typescript/module/state/widget-store.d.ts +40 -2
  112. package/lib/typescript/module/state/widget-store.d.ts.map +1 -1
  113. package/lib/typescript/module/types.d.ts +88 -1
  114. package/lib/typescript/module/types.d.ts.map +1 -1
  115. package/lib/typescript/module/ui/WidgetProvider.d.ts.map +1 -1
  116. package/lib/typescript/module/ui/components/AttachmentChips.d.ts +16 -0
  117. package/lib/typescript/module/ui/components/AttachmentChips.d.ts.map +1 -0
  118. package/lib/typescript/module/ui/components/AttachmentMenu.d.ts +28 -0
  119. package/lib/typescript/module/ui/components/AttachmentMenu.d.ts.map +1 -0
  120. package/lib/typescript/module/ui/components/ImageViewer.d.ts +23 -0
  121. package/lib/typescript/module/ui/components/ImageViewer.d.ts.map +1 -0
  122. package/lib/typescript/module/ui/components/InAppNotification.d.ts +28 -0
  123. package/lib/typescript/module/ui/components/InAppNotification.d.ts.map +1 -0
  124. package/lib/typescript/module/ui/components/TeamAvatars.d.ts +17 -0
  125. package/lib/typescript/module/ui/components/TeamAvatars.d.ts.map +1 -0
  126. package/lib/typescript/module/ui/components/icons.d.ts +2 -0
  127. package/lib/typescript/module/ui/components/icons.d.ts.map +1 -1
  128. package/lib/typescript/module/ui/screens/chat/ChatView.d.ts +3 -2
  129. package/lib/typescript/module/ui/screens/chat/ChatView.d.ts.map +1 -1
  130. package/lib/typescript/module/ui/screens/chat/ChatView.styles.d.ts +69 -0
  131. package/lib/typescript/module/ui/screens/chat/ChatView.styles.d.ts.map +1 -1
  132. package/lib/typescript/module/ui/screens/chat/ChatView.types.d.ts +41 -2
  133. package/lib/typescript/module/ui/screens/chat/ChatView.types.d.ts.map +1 -1
  134. package/metro.js +17 -32
  135. package/package.json +18 -1
  136. package/src/api/client.ts +5 -3
  137. package/src/api/realtime.ts +2 -0
  138. package/src/attachments/pickers.ts +515 -0
  139. package/src/i18n.ts +48 -0
  140. package/src/index.tsx +4 -0
  141. package/src/locales.ts +21 -0
  142. package/src/state/widget-store.ts +256 -19
  143. package/src/types.ts +95 -0
  144. package/src/ui/WidgetProvider.tsx +90 -9
  145. package/src/ui/components/AttachmentChips.tsx +135 -0
  146. package/src/ui/components/AttachmentMenu.tsx +132 -0
  147. package/src/ui/components/ImageViewer.tsx +178 -0
  148. package/src/ui/components/InAppNotification.tsx +225 -0
  149. package/src/ui/components/TeamAvatars.tsx +58 -0
  150. package/src/ui/components/icons.tsx +37 -0
  151. package/src/ui/screens/chat/ChatView.styles.ts +70 -0
  152. package/src/ui/screens/chat/ChatView.tsx +345 -35
  153. package/src/ui/screens/chat/ChatView.types.ts +51 -2
@@ -0,0 +1,225 @@
1
+ /**
2
+ * The card that floats over the host app when a team reply arrives while the
3
+ * messenger is closed — Intercom's in-app notification. It never times out:
4
+ * tapping it opens the conversation, swiping it left or right dismisses it
5
+ * (the thread stays unread). The store takes it down on its own once the
6
+ * messenger opens or the thread is read.
7
+ *
8
+ * Pure JS gestures (PanResponder), so hosts need no gesture library.
9
+ */
10
+
11
+ import { useEffect, useMemo, useRef } from 'react';
12
+ import {
13
+ AccessibilityInfo,
14
+ Animated,
15
+ PanResponder,
16
+ Pressable,
17
+ StyleSheet,
18
+ Text,
19
+ View,
20
+ useWindowDimensions,
21
+ type AccessibilityActionEvent,
22
+ } from 'react-native';
23
+
24
+ import { formatLabel } from '../../i18n';
25
+ import type { InAppNotification as InAppNotificationData } from '../../types';
26
+ import { useReduceMotion } from '../hooks/use-reduce-motion';
27
+ import { fontSize, palette, radius, shadows, spacing, withAlpha } from '../styles/theme';
28
+ import { START_ALIGN } from '../utils/rtl';
29
+ import { Avatar } from './Avatar';
30
+
31
+ /** Share of the card's width a drag must travel to count as a dismiss. */
32
+ const DISMISS_DISTANCE_RATIO = 0.35;
33
+ /** A quick flick dismisses even when it travels less than that. */
34
+ const DISMISS_VELOCITY = 0.6;
35
+
36
+ interface InAppNotificationProps {
37
+ notification: InAppNotificationData;
38
+ /** Brand color — the avatar's background when the agent has no photo. */
39
+ color: string;
40
+ bottomInset: number;
41
+ labels: {
42
+ replyFrom: string;
43
+ newReply: string;
44
+ attachment: string;
45
+ openHint: string;
46
+ dismiss: string;
47
+ };
48
+ onOpen: (conversationId: string) => void;
49
+ onDismiss: () => void;
50
+ }
51
+
52
+ export function InAppNotification({
53
+ notification,
54
+ color,
55
+ bottomInset,
56
+ labels,
57
+ onOpen,
58
+ onDismiss,
59
+ }: InAppNotificationProps) {
60
+ const { width } = useWindowDimensions();
61
+ const reduceMotion = useReduceMotion();
62
+ // Read as "hold still" until the setting is known (see useReduceMotion).
63
+ const animate = reduceMotion === false;
64
+
65
+ const entry = useRef(new Animated.Value(animate ? 0 : 1)).current;
66
+ const dragX = useRef(new Animated.Value(0)).current;
67
+
68
+ const preview = notification.preview || (notification.hasAttachments ? labels.attachment : '');
69
+ const sender = notification.senderName
70
+ ? formatLabel(labels.replyFrom, { name: notification.senderName })
71
+ : labels.newReply;
72
+ const accessibilityLabel = [preview, sender].filter(Boolean).join(', ');
73
+
74
+ // Mounted once per reply (the provider keys it by message id), so this runs
75
+ // for every new card: slide it in and tell screen-reader users it arrived.
76
+ useEffect(() => {
77
+ if (animate) {
78
+ Animated.spring(entry, {
79
+ toValue: 1,
80
+ useNativeDriver: true,
81
+ speed: 16,
82
+ bounciness: 4,
83
+ }).start();
84
+ }
85
+ AccessibilityInfo.announceForAccessibility(accessibilityLabel);
86
+ // eslint-disable-next-line react-hooks/exhaustive-deps
87
+ }, []);
88
+
89
+ const panResponder = useMemo(() => {
90
+ const settle = () =>
91
+ Animated.spring(dragX, { toValue: 0, useNativeDriver: true, bounciness: 6 }).start();
92
+
93
+ return PanResponder.create({
94
+ // Capture so a horizontal drag wins over the Pressable underneath; taps
95
+ // and vertical movement still reach it.
96
+ onMoveShouldSetPanResponderCapture: (_, gesture) =>
97
+ Math.abs(gesture.dx) > 8 && Math.abs(gesture.dx) > Math.abs(gesture.dy) * 1.5,
98
+ onPanResponderMove: (_, gesture) => dragX.setValue(gesture.dx),
99
+ onPanResponderTerminationRequest: () => false,
100
+ onPanResponderRelease: (_, gesture) => {
101
+ const farEnough = Math.abs(gesture.dx) > width * DISMISS_DISTANCE_RATIO;
102
+ const fastEnough = Math.abs(gesture.vx) > DISMISS_VELOCITY;
103
+ if (!farEnough && !fastEnough) {
104
+ settle();
105
+ return;
106
+ }
107
+ const direction = (gesture.dx || gesture.vx) < 0 ? -1 : 1;
108
+ Animated.timing(dragX, {
109
+ toValue: direction * width,
110
+ duration: animate ? 180 : 0,
111
+ useNativeDriver: true,
112
+ }).start(() => onDismiss());
113
+ },
114
+ onPanResponderTerminate: settle,
115
+ });
116
+ }, [animate, dragX, onDismiss, width]);
117
+
118
+ const handleAccessibilityAction = (event: AccessibilityActionEvent) => {
119
+ if (event.nativeEvent.actionName === 'dismiss') onDismiss();
120
+ if (event.nativeEvent.actionName === 'activate') onOpen(notification.conversationId);
121
+ };
122
+
123
+ const opacity = Animated.multiply(
124
+ entry,
125
+ dragX.interpolate({
126
+ inputRange: [-width, 0, width],
127
+ outputRange: [0, 1, 0],
128
+ extrapolate: 'clamp',
129
+ })
130
+ );
131
+ const translateY = entry.interpolate({ inputRange: [0, 1], outputRange: [spacing.lg, 0] });
132
+
133
+ return (
134
+ <View
135
+ pointerEvents="box-none"
136
+ style={[styles.wrapper, { bottom: bottomInset + spacing.md }]}>
137
+ <Animated.View
138
+ {...panResponder.panHandlers}
139
+ style={[styles.card, { opacity, transform: [{ translateX: dragX }, { translateY }] }]}>
140
+ <Pressable
141
+ onPress={() => onOpen(notification.conversationId)}
142
+ accessibilityRole="button"
143
+ accessibilityLabel={accessibilityLabel}
144
+ accessibilityHint={labels.openHint}
145
+ accessibilityActions={[
146
+ { name: 'activate' },
147
+ { name: 'dismiss', label: labels.dismiss },
148
+ ]}
149
+ onAccessibilityAction={handleAccessibilityAction}
150
+ style={({ pressed }) => [styles.row, pressed && styles.pressed]}>
151
+ <Avatar
152
+ avatar={notification.senderAvatar}
153
+ name={notification.senderName}
154
+ color={color}
155
+ size={40}
156
+ style={styles.avatar}
157
+ />
158
+ <View style={styles.text}>
159
+ {preview ? (
160
+ <Text numberOfLines={2} style={styles.preview}>
161
+ {preview}
162
+ </Text>
163
+ ) : null}
164
+ <Text numberOfLines={1} style={styles.sender}>
165
+ {sender}
166
+ </Text>
167
+ </View>
168
+ </Pressable>
169
+ </Animated.View>
170
+ </View>
171
+ );
172
+ }
173
+
174
+ const styles = StyleSheet.create({
175
+ wrapper: {
176
+ position: 'absolute',
177
+ left: 0,
178
+ right: 0,
179
+ paddingHorizontal: spacing.lg,
180
+ alignItems: 'center',
181
+ // Above the host's navigator even when the provider is mounted before it.
182
+ zIndex: 1000,
183
+ elevation: 1000,
184
+ },
185
+ card: {
186
+ width: '100%',
187
+ maxWidth: 480,
188
+ borderRadius: radius.xl,
189
+ backgroundColor: palette.text,
190
+ ...shadows.floating,
191
+ },
192
+ row: {
193
+ flexDirection: 'row',
194
+ alignItems: 'center',
195
+ gap: spacing.md,
196
+ minHeight: 64,
197
+ paddingVertical: spacing.md,
198
+ paddingHorizontal: spacing.lg,
199
+ borderRadius: radius.xl,
200
+ },
201
+ pressed: {
202
+ opacity: 0.85,
203
+ },
204
+ avatar: {
205
+ borderWidth: 1,
206
+ borderColor: withAlpha(palette.white, 0.1),
207
+ },
208
+ text: {
209
+ flex: 1,
210
+ minWidth: 0,
211
+ gap: 2,
212
+ },
213
+ preview: {
214
+ color: palette.white,
215
+ fontSize: fontSize.body,
216
+ fontWeight: '600',
217
+ lineHeight: 20,
218
+ textAlign: START_ALIGN,
219
+ },
220
+ sender: {
221
+ color: withAlpha(palette.white, 0.64),
222
+ fontSize: fontSize.meta,
223
+ textAlign: START_ALIGN,
224
+ },
225
+ });
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Up to three overlapping teammate avatars — the Messenger header when no
3
+ * teammate has joined yet, and the "Waiting for a teammate" row (Intercom-style).
4
+ * Falls back to a single placeholder avatar when the workspace sent none.
5
+ */
6
+
7
+ import { StyleSheet, View } from 'react-native';
8
+
9
+ import type { TeamAvatar } from '../../types';
10
+ import { palette } from '../styles/theme';
11
+ import { Avatar } from './Avatar';
12
+
13
+ interface TeamAvatarsProps {
14
+ avatars: TeamAvatar[];
15
+ size?: number;
16
+ color?: string;
17
+ /** Ring between overlapping avatars; matches the surface behind them. */
18
+ ringColor?: string;
19
+ translucent?: boolean;
20
+ }
21
+
22
+ export function TeamAvatars({
23
+ avatars,
24
+ size = 32,
25
+ color = palette.ink,
26
+ ringColor = palette.white,
27
+ translucent = false,
28
+ }: TeamAvatarsProps) {
29
+ if (avatars.length === 0) {
30
+ return <Avatar size={size} color={color} translucent={translucent} />;
31
+ }
32
+ const ring = Math.max(1.5, size * 0.06);
33
+ return (
34
+ <View style={styles.row} accessibilityElementsHidden importantForAccessibility="no-hide-descendants">
35
+ {avatars.slice(0, 3).map((avatar, index) => (
36
+ <View
37
+ key={`${avatar.initials}-${index}`}
38
+ style={[
39
+ styles.slot,
40
+ {
41
+ marginStart: index === 0 ? 0 : -size * 0.3,
42
+ borderRadius: (size + ring * 2) / 2,
43
+ borderWidth: ring,
44
+ borderColor: ringColor,
45
+ zIndex: 3 - index,
46
+ },
47
+ ]}>
48
+ <Avatar avatar={avatar.avatarUrl} name={avatar.initials} size={size} color={color} translucent={translucent} />
49
+ </View>
50
+ ))}
51
+ </View>
52
+ );
53
+ }
54
+
55
+ const styles = StyleSheet.create({
56
+ row: { flexDirection: 'row', alignItems: 'center' },
57
+ slot: { overflow: 'hidden' },
58
+ });
@@ -313,3 +313,40 @@ const styles = StyleSheet.create({
313
313
  justifyContent: 'center',
314
314
  },
315
315
  });
316
+
317
+ /** A ring with two hands — the reply-time hint beside the team name. */
318
+ export function ClockIcon({ size = 14, color = palette.textMuted, style }: IconProps) {
319
+ const thickness = Math.max(1.25, size * 0.1);
320
+ const hand: ViewStyle = {
321
+ position: 'absolute',
322
+ width: thickness,
323
+ borderRadius: thickness,
324
+ backgroundColor: color,
325
+ bottom: size / 2 - thickness / 2,
326
+ };
327
+ return (
328
+ <View style={[{ width: size, height: size }, styles.center, style]}>
329
+ <View
330
+ style={{
331
+ position: 'absolute',
332
+ width: size,
333
+ height: size,
334
+ borderRadius: size / 2,
335
+ borderWidth: thickness,
336
+ borderColor: color,
337
+ }}
338
+ />
339
+ <View style={[hand, { height: size * 0.32, left: size / 2 - thickness / 2 }]} />
340
+ <View
341
+ style={[
342
+ hand,
343
+ {
344
+ height: size * 0.24,
345
+ left: size / 2 - thickness / 2,
346
+ transform: [{ translateX: size * 0.1 }, { translateY: size * 0.04 }, { rotate: '-90deg' }],
347
+ },
348
+ ]}
349
+ />
350
+ </View>
351
+ );
352
+ }
@@ -50,6 +50,36 @@ export const styles = StyleSheet.create({
50
50
  fontSize: fontSize.caption,
51
51
  marginTop: 1,
52
52
  },
53
+ headerReplyTime: {
54
+ flexDirection: 'row',
55
+ alignItems: 'center',
56
+ gap: 4,
57
+ marginTop: 1,
58
+ },
59
+ headerReplyTimeText: {
60
+ flexShrink: 1,
61
+ marginTop: 0,
62
+ },
63
+ waitingRow: {
64
+ flexDirection: 'row',
65
+ alignItems: 'center',
66
+ justifyContent: 'center',
67
+ gap: spacing.sm,
68
+ paddingVertical: spacing.sm,
69
+ },
70
+ waitingText: {
71
+ color: palette.textMuted,
72
+ fontSize: fontSize.caption,
73
+ },
74
+ joinedRow: {
75
+ flexDirection: 'row',
76
+ justifyContent: 'center',
77
+ gap: spacing.xs,
78
+ },
79
+ joinedName: {
80
+ fontWeight: '700',
81
+ color: palette.ink,
82
+ },
53
83
  messagesContentEmpty: {
54
84
  flexGrow: 1,
55
85
  justifyContent: 'center',
@@ -108,6 +138,13 @@ export const styles = StyleSheet.create({
108
138
  marginBottom: 3,
109
139
  marginStart: spacing.xs,
110
140
  },
141
+ receiptText: {
142
+ alignSelf: 'flex-end',
143
+ marginTop: 3,
144
+ marginEnd: spacing.xs,
145
+ fontSize: 11,
146
+ color: palette.textSubtle,
147
+ },
111
148
  messageTime: {
112
149
  fontSize: 11,
113
150
  alignSelf: 'flex-end',
@@ -190,6 +227,20 @@ export const styles = StyleSheet.create({
190
227
  fontSize: fontSize.caption,
191
228
  marginTop: spacing.xs,
192
229
  },
230
+ deletedBubble: {
231
+ maxWidth: '80%',
232
+ paddingHorizontal: spacing.md,
233
+ paddingVertical: spacing.sm,
234
+ borderRadius: radius.lg + 4,
235
+ borderWidth: 1,
236
+ borderStyle: 'dashed',
237
+ borderColor: palette.border,
238
+ },
239
+ deletedText: {
240
+ color: palette.textMuted,
241
+ fontSize: fontSize.caption,
242
+ fontStyle: 'italic',
243
+ },
193
244
  systemRow: {
194
245
  alignItems: 'center',
195
246
  paddingVertical: spacing.sm,
@@ -261,9 +312,28 @@ export const styles = StyleSheet.create({
261
312
  paddingRight: spacing.xs + 2,
262
313
  paddingVertical: spacing.xs + 2,
263
314
  },
315
+ // The paperclip sits where the text inset was; both ends match the send side.
316
+ composerWithAttach: {
317
+ paddingLeft: spacing.xs + 2,
318
+ },
264
319
  composerFocused: {
265
320
  backgroundColor: palette.white,
266
321
  },
322
+ attachButton: {
323
+ width: 36,
324
+ height: 36,
325
+ borderRadius: 18,
326
+ alignItems: 'center',
327
+ justifyContent: 'center',
328
+ },
329
+ composerNotice: {
330
+ textAlign: START_ALIGN,
331
+ fontSize: fontSize.caption,
332
+ lineHeight: fontSize.caption * 1.4,
333
+ color: palette.danger,
334
+ marginBottom: spacing.sm,
335
+ paddingHorizontal: spacing.xs,
336
+ },
267
337
  input: {
268
338
  textAlign: START_ALIGN,
269
339
  flex: 1,