@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.
- package/README.md +167 -74
- package/lib/commonjs/api/client.js +3 -1
- package/lib/commonjs/api/client.js.map +1 -1
- package/lib/commonjs/api/realtime.js +2 -0
- package/lib/commonjs/api/realtime.js.map +1 -1
- package/lib/commonjs/attachments/pickers.js +493 -0
- package/lib/commonjs/attachments/pickers.js.map +1 -0
- package/lib/commonjs/i18n.js +45 -0
- package/lib/commonjs/i18n.js.map +1 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/locales.js +21 -0
- package/lib/commonjs/locales.js.map +1 -1
- package/lib/commonjs/state/widget-store.js +256 -18
- package/lib/commonjs/state/widget-store.js.map +1 -1
- package/lib/commonjs/types.js +16 -0
- package/lib/commonjs/types.js.map +1 -1
- package/lib/commonjs/ui/WidgetProvider.js +70 -10
- package/lib/commonjs/ui/WidgetProvider.js.map +1 -1
- package/lib/commonjs/ui/components/AttachmentChips.js +135 -0
- package/lib/commonjs/ui/components/AttachmentChips.js.map +1 -0
- package/lib/commonjs/ui/components/AttachmentMenu.js +125 -0
- package/lib/commonjs/ui/components/AttachmentMenu.js.map +1 -0
- package/lib/commonjs/ui/components/ImageViewer.js +181 -0
- package/lib/commonjs/ui/components/ImageViewer.js.map +1 -0
- package/lib/commonjs/ui/components/InAppNotification.js +210 -0
- package/lib/commonjs/ui/components/InAppNotification.js.map +1 -0
- package/lib/commonjs/ui/components/TeamAvatars.js +63 -0
- package/lib/commonjs/ui/components/TeamAvatars.js.map +1 -0
- package/lib/commonjs/ui/components/icons.js +50 -0
- package/lib/commonjs/ui/components/icons.js.map +1 -1
- package/lib/commonjs/ui/screens/chat/ChatView.js +303 -24
- package/lib/commonjs/ui/screens/chat/ChatView.js.map +1 -1
- package/lib/commonjs/ui/screens/chat/ChatView.styles.js +70 -0
- package/lib/commonjs/ui/screens/chat/ChatView.styles.js.map +1 -1
- package/lib/module/api/client.js +3 -1
- package/lib/module/api/client.js.map +1 -1
- package/lib/module/api/realtime.js +2 -0
- package/lib/module/api/realtime.js.map +1 -1
- package/lib/module/attachments/pickers.js +479 -0
- package/lib/module/attachments/pickers.js.map +1 -0
- package/lib/module/i18n.js +44 -0
- package/lib/module/i18n.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/locales.js +21 -0
- package/lib/module/locales.js.map +1 -1
- package/lib/module/state/widget-store.js +256 -18
- package/lib/module/state/widget-store.js.map +1 -1
- package/lib/module/types.js +16 -0
- package/lib/module/types.js.map +1 -1
- package/lib/module/ui/WidgetProvider.js +71 -11
- package/lib/module/ui/WidgetProvider.js.map +1 -1
- package/lib/module/ui/components/AttachmentChips.js +131 -0
- package/lib/module/ui/components/AttachmentChips.js.map +1 -0
- package/lib/module/ui/components/AttachmentMenu.js +120 -0
- package/lib/module/ui/components/AttachmentMenu.js.map +1 -0
- package/lib/module/ui/components/ImageViewer.js +177 -0
- package/lib/module/ui/components/ImageViewer.js.map +1 -0
- package/lib/module/ui/components/InAppNotification.js +208 -0
- package/lib/module/ui/components/InAppNotification.js.map +1 -0
- package/lib/module/ui/components/TeamAvatars.js +59 -0
- package/lib/module/ui/components/TeamAvatars.js.map +1 -0
- package/lib/module/ui/components/icons.js +49 -0
- package/lib/module/ui/components/icons.js.map +1 -1
- package/lib/module/ui/screens/chat/ChatView.js +306 -27
- package/lib/module/ui/screens/chat/ChatView.js.map +1 -1
- package/lib/module/ui/screens/chat/ChatView.styles.js +70 -0
- package/lib/module/ui/screens/chat/ChatView.styles.js.map +1 -1
- package/lib/typescript/commonjs/api/client.d.ts +8 -2
- package/lib/typescript/commonjs/api/client.d.ts.map +1 -1
- package/lib/typescript/commonjs/api/realtime.d.ts.map +1 -1
- package/lib/typescript/commonjs/attachments/pickers.d.ts +81 -0
- package/lib/typescript/commonjs/attachments/pickers.d.ts.map +1 -0
- package/lib/typescript/commonjs/i18n.d.ts +23 -0
- package/lib/typescript/commonjs/i18n.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +1 -1
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/locales.d.ts.map +1 -1
- package/lib/typescript/commonjs/state/widget-store.d.ts +40 -2
- package/lib/typescript/commonjs/state/widget-store.d.ts.map +1 -1
- package/lib/typescript/commonjs/types.d.ts +88 -1
- package/lib/typescript/commonjs/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/WidgetProvider.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/AttachmentChips.d.ts +16 -0
- package/lib/typescript/commonjs/ui/components/AttachmentChips.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/AttachmentMenu.d.ts +28 -0
- package/lib/typescript/commonjs/ui/components/AttachmentMenu.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/ImageViewer.d.ts +23 -0
- package/lib/typescript/commonjs/ui/components/ImageViewer.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/InAppNotification.d.ts +28 -0
- package/lib/typescript/commonjs/ui/components/InAppNotification.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/TeamAvatars.d.ts +17 -0
- package/lib/typescript/commonjs/ui/components/TeamAvatars.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/icons.d.ts +2 -0
- package/lib/typescript/commonjs/ui/components/icons.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.d.ts +3 -2
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.styles.d.ts +69 -0
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.styles.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.types.d.ts +41 -2
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.types.d.ts.map +1 -1
- package/lib/typescript/module/api/client.d.ts +8 -2
- package/lib/typescript/module/api/client.d.ts.map +1 -1
- package/lib/typescript/module/api/realtime.d.ts.map +1 -1
- package/lib/typescript/module/attachments/pickers.d.ts +81 -0
- package/lib/typescript/module/attachments/pickers.d.ts.map +1 -0
- package/lib/typescript/module/i18n.d.ts +23 -0
- package/lib/typescript/module/i18n.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +1 -1
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/locales.d.ts.map +1 -1
- package/lib/typescript/module/state/widget-store.d.ts +40 -2
- package/lib/typescript/module/state/widget-store.d.ts.map +1 -1
- package/lib/typescript/module/types.d.ts +88 -1
- package/lib/typescript/module/types.d.ts.map +1 -1
- package/lib/typescript/module/ui/WidgetProvider.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/AttachmentChips.d.ts +16 -0
- package/lib/typescript/module/ui/components/AttachmentChips.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/AttachmentMenu.d.ts +28 -0
- package/lib/typescript/module/ui/components/AttachmentMenu.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/ImageViewer.d.ts +23 -0
- package/lib/typescript/module/ui/components/ImageViewer.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/InAppNotification.d.ts +28 -0
- package/lib/typescript/module/ui/components/InAppNotification.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/TeamAvatars.d.ts +17 -0
- package/lib/typescript/module/ui/components/TeamAvatars.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/icons.d.ts +2 -0
- package/lib/typescript/module/ui/components/icons.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/chat/ChatView.d.ts +3 -2
- package/lib/typescript/module/ui/screens/chat/ChatView.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/chat/ChatView.styles.d.ts +69 -0
- package/lib/typescript/module/ui/screens/chat/ChatView.styles.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/chat/ChatView.types.d.ts +41 -2
- package/lib/typescript/module/ui/screens/chat/ChatView.types.d.ts.map +1 -1
- package/metro.js +17 -32
- package/package.json +18 -1
- package/src/api/client.ts +5 -3
- package/src/api/realtime.ts +2 -0
- package/src/attachments/pickers.ts +515 -0
- package/src/i18n.ts +48 -0
- package/src/index.tsx +4 -0
- package/src/locales.ts +21 -0
- package/src/state/widget-store.ts +256 -19
- package/src/types.ts +95 -0
- package/src/ui/WidgetProvider.tsx +90 -9
- package/src/ui/components/AttachmentChips.tsx +135 -0
- package/src/ui/components/AttachmentMenu.tsx +132 -0
- package/src/ui/components/ImageViewer.tsx +178 -0
- package/src/ui/components/InAppNotification.tsx +225 -0
- package/src/ui/components/TeamAvatars.tsx +58 -0
- package/src/ui/components/icons.tsx +37 -0
- package/src/ui/screens/chat/ChatView.styles.ts +70 -0
- package/src/ui/screens/chat/ChatView.tsx +345 -35
- 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,
|