@uselingu/react-native-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +279 -0
- package/lib/commonjs/components/LinguChat.js +283 -0
- package/lib/commonjs/components/LinguChat.js.map +1 -0
- package/lib/commonjs/components/LinguWebView.js +203 -0
- package/lib/commonjs/components/LinguWebView.js.map +1 -0
- package/lib/commonjs/components/MessageBubble.js +91 -0
- package/lib/commonjs/components/MessageBubble.js.map +1 -0
- package/lib/commonjs/components/NativeChatUI.js +192 -0
- package/lib/commonjs/components/NativeChatUI.js.map +1 -0
- package/lib/commonjs/components/TypingIndicator.js +92 -0
- package/lib/commonjs/components/TypingIndicator.js.map +1 -0
- package/lib/commonjs/context/LinguProvider.js +47 -0
- package/lib/commonjs/context/LinguProvider.js.map +1 -0
- package/lib/commonjs/context/index.js +17 -0
- package/lib/commonjs/context/index.js.map +1 -0
- package/lib/commonjs/hooks/useLinguChat.js +295 -0
- package/lib/commonjs/hooks/useLinguChat.js.map +1 -0
- package/lib/commonjs/index.js +129 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/services/LinguAPI.js +196 -0
- package/lib/commonjs/services/LinguAPI.js.map +1 -0
- package/lib/commonjs/services/SessionManager.js +131 -0
- package/lib/commonjs/services/SessionManager.js.map +1 -0
- package/lib/commonjs/services/StorageManager.js +116 -0
- package/lib/commonjs/services/StorageManager.js.map +1 -0
- package/lib/commonjs/theme/ThemeProvider.js +49 -0
- package/lib/commonjs/theme/ThemeProvider.js.map +1 -0
- package/lib/commonjs/theme/index.js +28 -0
- package/lib/commonjs/theme/index.js.map +1 -0
- package/lib/commonjs/theme/theme.js +150 -0
- package/lib/commonjs/theme/theme.js.map +1 -0
- package/lib/commonjs/types/index.js +2 -0
- package/lib/commonjs/types/index.js.map +1 -0
- package/lib/commonjs/utils/animations.js +118 -0
- package/lib/commonjs/utils/animations.js.map +1 -0
- package/lib/commonjs/utils/formatters.js +89 -0
- package/lib/commonjs/utils/formatters.js.map +1 -0
- package/lib/commonjs/utils/index.js +28 -0
- package/lib/commonjs/utils/index.js.map +1 -0
- package/lib/module/components/LinguChat.js +277 -0
- package/lib/module/components/LinguChat.js.map +1 -0
- package/lib/module/components/LinguWebView.js +195 -0
- package/lib/module/components/LinguWebView.js.map +1 -0
- package/lib/module/components/MessageBubble.js +83 -0
- package/lib/module/components/MessageBubble.js.map +1 -0
- package/lib/module/components/NativeChatUI.js +184 -0
- package/lib/module/components/NativeChatUI.js.map +1 -0
- package/lib/module/components/TypingIndicator.js +84 -0
- package/lib/module/components/TypingIndicator.js.map +1 -0
- package/lib/module/context/LinguProvider.js +38 -0
- package/lib/module/context/LinguProvider.js.map +1 -0
- package/lib/module/context/index.js +6 -0
- package/lib/module/context/index.js.map +1 -0
- package/lib/module/hooks/useLinguChat.js +288 -0
- package/lib/module/hooks/useLinguChat.js.map +1 -0
- package/lib/module/index.js +30 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/services/LinguAPI.js +190 -0
- package/lib/module/services/LinguAPI.js.map +1 -0
- package/lib/module/services/SessionManager.js +124 -0
- package/lib/module/services/SessionManager.js.map +1 -0
- package/lib/module/services/StorageManager.js +108 -0
- package/lib/module/services/StorageManager.js.map +1 -0
- package/lib/module/theme/ThemeProvider.js +40 -0
- package/lib/module/theme/ThemeProvider.js.map +1 -0
- package/lib/module/theme/index.js +7 -0
- package/lib/module/theme/index.js.map +1 -0
- package/lib/module/theme/theme.js +142 -0
- package/lib/module/theme/theme.js.map +1 -0
- package/lib/module/types/index.js +2 -0
- package/lib/module/types/index.js.map +1 -0
- package/lib/module/utils/animations.js +106 -0
- package/lib/module/utils/animations.js.map +1 -0
- package/lib/module/utils/formatters.js +78 -0
- package/lib/module/utils/formatters.js.map +1 -0
- package/lib/module/utils/index.js +7 -0
- package/lib/module/utils/index.js.map +1 -0
- package/lib/typescript/components/LinguChat.d.ts +18 -0
- package/lib/typescript/components/LinguChat.d.ts.map +1 -0
- package/lib/typescript/components/LinguWebView.d.ts +16 -0
- package/lib/typescript/components/LinguWebView.d.ts.map +1 -0
- package/lib/typescript/components/MessageBubble.d.ts +13 -0
- package/lib/typescript/components/MessageBubble.d.ts.map +1 -0
- package/lib/typescript/components/NativeChatUI.d.ts +16 -0
- package/lib/typescript/components/NativeChatUI.d.ts.map +1 -0
- package/lib/typescript/components/TypingIndicator.d.ts +7 -0
- package/lib/typescript/components/TypingIndicator.d.ts.map +1 -0
- package/lib/typescript/context/LinguProvider.d.ts +32 -0
- package/lib/typescript/context/LinguProvider.d.ts.map +1 -0
- package/lib/typescript/context/index.d.ts +5 -0
- package/lib/typescript/context/index.d.ts.map +1 -0
- package/lib/typescript/hooks/useLinguChat.d.ts +18 -0
- package/lib/typescript/hooks/useLinguChat.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +19 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/services/LinguAPI.d.ts +39 -0
- package/lib/typescript/services/LinguAPI.d.ts.map +1 -0
- package/lib/typescript/services/SessionManager.d.ts +46 -0
- package/lib/typescript/services/SessionManager.d.ts.map +1 -0
- package/lib/typescript/services/StorageManager.d.ts +36 -0
- package/lib/typescript/services/StorageManager.d.ts.map +1 -0
- package/lib/typescript/theme/ThemeProvider.d.ts +23 -0
- package/lib/typescript/theme/ThemeProvider.d.ts.map +1 -0
- package/lib/typescript/theme/index.d.ts +6 -0
- package/lib/typescript/theme/index.d.ts.map +1 -0
- package/lib/typescript/theme/theme.d.ts +130 -0
- package/lib/typescript/theme/theme.d.ts.map +1 -0
- package/lib/typescript/types/index.d.ts +50 -0
- package/lib/typescript/types/index.d.ts.map +1 -0
- package/lib/typescript/utils/animations.d.ts +34 -0
- package/lib/typescript/utils/animations.d.ts.map +1 -0
- package/lib/typescript/utils/formatters.d.ts +25 -0
- package/lib/typescript/utils/formatters.d.ts.map +1 -0
- package/lib/typescript/utils/index.d.ts +6 -0
- package/lib/typescript/utils/index.d.ts.map +1 -0
- package/package.json +98 -0
- package/src/components/LinguChat.tsx +309 -0
- package/src/components/LinguWebView.tsx +216 -0
- package/src/components/MessageBubble.tsx +120 -0
- package/src/components/NativeChatUI.tsx +247 -0
- package/src/components/TypingIndicator.tsx +84 -0
- package/src/context/LinguProvider.tsx +53 -0
- package/src/context/index.ts +5 -0
- package/src/hooks/useLinguChat.ts +314 -0
- package/src/index.ts +43 -0
- package/src/services/LinguAPI.ts +214 -0
- package/src/services/SessionManager.ts +131 -0
- package/src/services/StorageManager.ts +111 -0
- package/src/theme/ThemeProvider.tsx +58 -0
- package/src/theme/index.ts +6 -0
- package/src/theme/theme.ts +254 -0
- package/src/types/index.ts +59 -0
- package/src/utils/animations.ts +141 -0
- package/src/utils/formatters.ts +80 -0
- package/src/utils/index.ts +6 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
2
|
+
/**
|
|
3
|
+
* LinguChat Component
|
|
4
|
+
* Full-page chat interface with back navigation
|
|
5
|
+
* Now with theme support and improved styling
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React, { useState, useEffect, useRef } from 'react';
|
|
9
|
+
import { View, TouchableOpacity, StyleSheet, Animated, SafeAreaView, Text } from 'react-native';
|
|
10
|
+
import { NativeChatUI } from './NativeChatUI';
|
|
11
|
+
import { LinguProvider, useLinguContext } from '../context';
|
|
12
|
+
import { useTheme } from '../theme';
|
|
13
|
+
import { scaleIn, pulse } from '../utils/animations';
|
|
14
|
+
/**
|
|
15
|
+
* Internal component that uses context
|
|
16
|
+
*/
|
|
17
|
+
const LinguChatInner = props => {
|
|
18
|
+
var _props$showBackButton;
|
|
19
|
+
const {
|
|
20
|
+
theme
|
|
21
|
+
} = useTheme();
|
|
22
|
+
const [isOpen, setIsOpen] = useState(props.autoOpen || false);
|
|
23
|
+
const [hasUnread, setHasUnread] = useState(false);
|
|
24
|
+
const scaleAnim = useRef(new Animated.Value(0)).current;
|
|
25
|
+
const badgePulse = useRef(new Animated.Value(1)).current;
|
|
26
|
+
const {
|
|
27
|
+
messages,
|
|
28
|
+
isLoading,
|
|
29
|
+
config,
|
|
30
|
+
sendMessage,
|
|
31
|
+
startSession,
|
|
32
|
+
endSession,
|
|
33
|
+
isSessionActive
|
|
34
|
+
} = useLinguContext();
|
|
35
|
+
|
|
36
|
+
// Entrance animation for FAB
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
scaleIn(scaleAnim).start();
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
// Pulse animation for unread badge
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (hasUnread) {
|
|
44
|
+
pulse(badgePulse).start();
|
|
45
|
+
} else {
|
|
46
|
+
badgePulse.setValue(1);
|
|
47
|
+
}
|
|
48
|
+
}, [hasUnread]);
|
|
49
|
+
|
|
50
|
+
// Auto-open if configured
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (props.autoOpen && !isSessionActive) {
|
|
53
|
+
handleOpen();
|
|
54
|
+
}
|
|
55
|
+
}, [props.autoOpen]);
|
|
56
|
+
|
|
57
|
+
// Mark unread when new message arrives and chat is closed
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (!isOpen && messages.length > 0) {
|
|
60
|
+
const lastMessage = messages[messages.length - 1];
|
|
61
|
+
if (lastMessage.sender === 'bot') {
|
|
62
|
+
setHasUnread(true);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}, [messages, isOpen]);
|
|
66
|
+
const handleOpen = async () => {
|
|
67
|
+
setIsOpen(true);
|
|
68
|
+
setHasUnread(false);
|
|
69
|
+
if (!isSessionActive) {
|
|
70
|
+
await startSession();
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
const handleClose = async () => {
|
|
74
|
+
var _props$onBackPress;
|
|
75
|
+
setIsOpen(false);
|
|
76
|
+
await endSession();
|
|
77
|
+
(_props$onBackPress = props.onBackPress) === null || _props$onBackPress === void 0 || _props$onBackPress.call(props);
|
|
78
|
+
};
|
|
79
|
+
const handleSendMessage = async text => {
|
|
80
|
+
await sendMessage(text);
|
|
81
|
+
};
|
|
82
|
+
const getPositionStyle = (position = 'bottom-right') => {
|
|
83
|
+
const base = {
|
|
84
|
+
position: 'absolute'
|
|
85
|
+
};
|
|
86
|
+
switch (position) {
|
|
87
|
+
case 'bottom-right':
|
|
88
|
+
return {
|
|
89
|
+
...base,
|
|
90
|
+
bottom: 20,
|
|
91
|
+
right: 20
|
|
92
|
+
};
|
|
93
|
+
case 'bottom-left':
|
|
94
|
+
return {
|
|
95
|
+
...base,
|
|
96
|
+
bottom: 20,
|
|
97
|
+
left: 20
|
|
98
|
+
};
|
|
99
|
+
case 'top-right':
|
|
100
|
+
return {
|
|
101
|
+
...base,
|
|
102
|
+
top: 20,
|
|
103
|
+
right: 20
|
|
104
|
+
};
|
|
105
|
+
case 'top-left':
|
|
106
|
+
return {
|
|
107
|
+
...base,
|
|
108
|
+
top: 20,
|
|
109
|
+
left: 20
|
|
110
|
+
};
|
|
111
|
+
default:
|
|
112
|
+
return {
|
|
113
|
+
...base,
|
|
114
|
+
bottom: 20,
|
|
115
|
+
right: 20
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
const primaryColor = theme.colors.primary;
|
|
120
|
+
const position = props.position || (config === null || config === void 0 ? void 0 : config.widgetPosition) || 'bottom-right';
|
|
121
|
+
const showBackButton = (_props$showBackButton = props.showBackButton) !== null && _props$showBackButton !== void 0 ? _props$showBackButton : true;
|
|
122
|
+
|
|
123
|
+
// Full-page chat view
|
|
124
|
+
if (isOpen) {
|
|
125
|
+
return /*#__PURE__*/React.createElement(View, {
|
|
126
|
+
style: styles.fullScreenOverlay
|
|
127
|
+
}, /*#__PURE__*/React.createElement(SafeAreaView, {
|
|
128
|
+
style: [styles.fullPageContainer, {
|
|
129
|
+
backgroundColor: theme.colors.background
|
|
130
|
+
}]
|
|
131
|
+
}, showBackButton && /*#__PURE__*/React.createElement(View, {
|
|
132
|
+
style: [styles.header, {
|
|
133
|
+
backgroundColor: primaryColor,
|
|
134
|
+
paddingHorizontal: theme.spacing.lg,
|
|
135
|
+
paddingVertical: theme.spacing.md
|
|
136
|
+
}, theme.shadows.md]
|
|
137
|
+
}, /*#__PURE__*/React.createElement(TouchableOpacity, {
|
|
138
|
+
style: styles.backButton,
|
|
139
|
+
onPress: handleClose,
|
|
140
|
+
activeOpacity: 0.7
|
|
141
|
+
}, /*#__PURE__*/React.createElement(Text, {
|
|
142
|
+
style: styles.backButtonText
|
|
143
|
+
}, "\u2190")), /*#__PURE__*/React.createElement(Text, {
|
|
144
|
+
style: [styles.headerTitle, {
|
|
145
|
+
fontSize: theme.typography.fontSizes.lg,
|
|
146
|
+
fontWeight: theme.typography.fontWeights.semibold
|
|
147
|
+
}]
|
|
148
|
+
}, (config === null || config === void 0 ? void 0 : config.brandName) || 'Lingu Chat'), /*#__PURE__*/React.createElement(View, {
|
|
149
|
+
style: styles.headerSpacer
|
|
150
|
+
})), /*#__PURE__*/React.createElement(NativeChatUI, {
|
|
151
|
+
config: config,
|
|
152
|
+
messages: messages,
|
|
153
|
+
isLoading: isLoading,
|
|
154
|
+
onSendMessage: handleSendMessage
|
|
155
|
+
})));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Floating action button
|
|
159
|
+
return /*#__PURE__*/React.createElement(View, {
|
|
160
|
+
style: [styles.fabContainer, getPositionStyle(position)],
|
|
161
|
+
pointerEvents: "box-none"
|
|
162
|
+
}, /*#__PURE__*/React.createElement(Animated.View, {
|
|
163
|
+
style: {
|
|
164
|
+
transform: [{
|
|
165
|
+
scale: scaleAnim
|
|
166
|
+
}]
|
|
167
|
+
}
|
|
168
|
+
}, /*#__PURE__*/React.createElement(TouchableOpacity, {
|
|
169
|
+
style: [styles.fab, {
|
|
170
|
+
backgroundColor: primaryColor,
|
|
171
|
+
width: 60,
|
|
172
|
+
height: 60,
|
|
173
|
+
borderRadius: 30
|
|
174
|
+
}, theme.shadows.lg],
|
|
175
|
+
onPress: handleOpen,
|
|
176
|
+
activeOpacity: 0.8
|
|
177
|
+
}, hasUnread && /*#__PURE__*/React.createElement(Animated.View, {
|
|
178
|
+
style: [styles.badge, {
|
|
179
|
+
backgroundColor: theme.colors.badge,
|
|
180
|
+
transform: [{
|
|
181
|
+
scale: badgePulse
|
|
182
|
+
}]
|
|
183
|
+
}]
|
|
184
|
+
}), /*#__PURE__*/React.createElement(View, {
|
|
185
|
+
style: styles.icon
|
|
186
|
+
}, /*#__PURE__*/React.createElement(View, {
|
|
187
|
+
style: styles.iconBubble
|
|
188
|
+
})))));
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* LinguChat - Main exported component
|
|
193
|
+
* Wraps everything in LinguProvider for standalone state management
|
|
194
|
+
*/
|
|
195
|
+
export const LinguChat = ({
|
|
196
|
+
onBackPress,
|
|
197
|
+
showBackButton,
|
|
198
|
+
autoOpen,
|
|
199
|
+
position,
|
|
200
|
+
...linguConfig
|
|
201
|
+
}) => {
|
|
202
|
+
return /*#__PURE__*/React.createElement(LinguProvider, _extends({}, linguConfig, {
|
|
203
|
+
autoOpen: autoOpen
|
|
204
|
+
}), /*#__PURE__*/React.createElement(LinguChatInner, {
|
|
205
|
+
onBackPress: onBackPress,
|
|
206
|
+
showBackButton: showBackButton,
|
|
207
|
+
autoOpen: autoOpen,
|
|
208
|
+
position: position
|
|
209
|
+
}));
|
|
210
|
+
};
|
|
211
|
+
const styles = StyleSheet.create({
|
|
212
|
+
fullScreenOverlay: {
|
|
213
|
+
position: 'absolute',
|
|
214
|
+
top: 0,
|
|
215
|
+
left: 0,
|
|
216
|
+
right: 0,
|
|
217
|
+
bottom: 0,
|
|
218
|
+
zIndex: 10000
|
|
219
|
+
},
|
|
220
|
+
fullPageContainer: {
|
|
221
|
+
flex: 1
|
|
222
|
+
},
|
|
223
|
+
header: {
|
|
224
|
+
flexDirection: 'row',
|
|
225
|
+
alignItems: 'center',
|
|
226
|
+
justifyContent: 'space-between'
|
|
227
|
+
},
|
|
228
|
+
backButton: {
|
|
229
|
+
width: 40,
|
|
230
|
+
height: 40,
|
|
231
|
+
justifyContent: 'center',
|
|
232
|
+
alignItems: 'center'
|
|
233
|
+
},
|
|
234
|
+
backButtonText: {
|
|
235
|
+
fontSize: 28,
|
|
236
|
+
color: '#fff',
|
|
237
|
+
fontWeight: '600'
|
|
238
|
+
},
|
|
239
|
+
headerTitle: {
|
|
240
|
+
color: '#fff',
|
|
241
|
+
flex: 1,
|
|
242
|
+
textAlign: 'center'
|
|
243
|
+
},
|
|
244
|
+
headerSpacer: {
|
|
245
|
+
width: 40
|
|
246
|
+
},
|
|
247
|
+
fabContainer: {
|
|
248
|
+
zIndex: 9999
|
|
249
|
+
},
|
|
250
|
+
fab: {
|
|
251
|
+
justifyContent: 'center',
|
|
252
|
+
alignItems: 'center'
|
|
253
|
+
},
|
|
254
|
+
badge: {
|
|
255
|
+
position: 'absolute',
|
|
256
|
+
top: 8,
|
|
257
|
+
right: 8,
|
|
258
|
+
width: 12,
|
|
259
|
+
height: 12,
|
|
260
|
+
borderRadius: 6,
|
|
261
|
+
borderWidth: 2,
|
|
262
|
+
borderColor: '#fff'
|
|
263
|
+
},
|
|
264
|
+
icon: {
|
|
265
|
+
width: 30,
|
|
266
|
+
height: 30,
|
|
267
|
+
justifyContent: 'center',
|
|
268
|
+
alignItems: 'center'
|
|
269
|
+
},
|
|
270
|
+
iconBubble: {
|
|
271
|
+
width: 24,
|
|
272
|
+
height: 20,
|
|
273
|
+
backgroundColor: '#fff',
|
|
274
|
+
borderRadius: 12
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
//# sourceMappingURL=LinguChat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["React","useState","useEffect","useRef","View","TouchableOpacity","StyleSheet","Animated","SafeAreaView","Text","NativeChatUI","LinguProvider","useLinguContext","useTheme","scaleIn","pulse","LinguChatInner","props","_props$showBackButton","theme","isOpen","setIsOpen","autoOpen","hasUnread","setHasUnread","scaleAnim","Value","current","badgePulse","messages","isLoading","config","sendMessage","startSession","endSession","isSessionActive","start","setValue","handleOpen","length","lastMessage","sender","handleClose","_props$onBackPress","onBackPress","call","handleSendMessage","text","getPositionStyle","position","base","bottom","right","left","top","primaryColor","colors","primary","widgetPosition","showBackButton","createElement","style","styles","fullScreenOverlay","fullPageContainer","backgroundColor","background","header","paddingHorizontal","spacing","lg","paddingVertical","md","shadows","backButton","onPress","activeOpacity","backButtonText","headerTitle","fontSize","typography","fontSizes","fontWeight","fontWeights","semibold","brandName","headerSpacer","onSendMessage","fabContainer","pointerEvents","transform","scale","fab","width","height","borderRadius","badge","icon","iconBubble","LinguChat","linguConfig","_extends","create","zIndex","flex","flexDirection","alignItems","justifyContent","color","textAlign","borderWidth","borderColor"],"sources":["LinguChat.tsx"],"sourcesContent":["/**\n * LinguChat Component\n * Full-page chat interface with back navigation\n * Now with theme support and improved styling\n */\n\nimport React, { useState, useEffect, useRef } from 'react';\nimport {\n View,\n TouchableOpacity,\n StyleSheet,\n Animated,\n SafeAreaView,\n Text,\n} from 'react-native';\nimport { NativeChatUI } from './NativeChatUI';\nimport { LinguProvider, useLinguContext } from '../context';\nimport { useTheme } from '../theme';\nimport { scaleIn, pulse } from '../utils/animations';\nimport type { LinguConfig, ChatPosition } from '../types';\n\nexport interface LinguChatProps extends LinguConfig {\n onBackPress?: () => void;\n showBackButton?: boolean;\n position?: ChatPosition; // UI preference for FAB position\n}\n\ninterface LinguChatInnerProps {\n onBackPress?: () => void;\n showBackButton?: boolean;\n autoOpen?: boolean;\n position?: ChatPosition;\n}\n\n/**\n * Internal component that uses context\n */\nconst LinguChatInner: React.FC<LinguChatInnerProps> = (props) => {\n const { theme } = useTheme();\n const [isOpen, setIsOpen] = useState(props.autoOpen || false);\n const [hasUnread, setHasUnread] = useState(false);\n const scaleAnim = useRef(new Animated.Value(0)).current;\n const badgePulse = useRef(new Animated.Value(1)).current;\n\n const {\n messages,\n isLoading,\n config,\n sendMessage,\n startSession,\n endSession,\n isSessionActive,\n } = useLinguContext();\n\n // Entrance animation for FAB\n useEffect(() => {\n scaleIn(scaleAnim).start();\n }, []);\n\n // Pulse animation for unread badge\n useEffect(() => {\n if (hasUnread) {\n pulse(badgePulse).start();\n } else {\n badgePulse.setValue(1);\n }\n }, [hasUnread]);\n\n // Auto-open if configured\n useEffect(() => {\n if (props.autoOpen && !isSessionActive) {\n handleOpen();\n }\n }, [props.autoOpen]);\n\n // Mark unread when new message arrives and chat is closed\n useEffect(() => {\n if (!isOpen && messages.length > 0) {\n const lastMessage = messages[messages.length - 1];\n if (lastMessage.sender === 'bot') {\n setHasUnread(true);\n }\n }\n }, [messages, isOpen]);\n\n const handleOpen = async () => {\n setIsOpen(true);\n setHasUnread(false);\n\n if (!isSessionActive) {\n await startSession();\n }\n };\n\n const handleClose = async () => {\n setIsOpen(false);\n await endSession();\n props.onBackPress?.();\n };\n\n const handleSendMessage = async (text: string) => {\n await sendMessage(text);\n };\n\n const getPositionStyle = (position: ChatPosition = 'bottom-right') => {\n const base = { position: 'absolute' as const };\n switch (position) {\n case 'bottom-right':\n return { ...base, bottom: 20, right: 20 };\n case 'bottom-left':\n return { ...base, bottom: 20, left: 20 };\n case 'top-right':\n return { ...base, top: 20, right: 20 };\n case 'top-left':\n return { ...base, top: 20, left: 20 };\n default:\n return { ...base, bottom: 20, right: 20 };\n }\n };\n\n const primaryColor = theme.colors.primary;\n const position = props.position || config?.widgetPosition || 'bottom-right';\n const showBackButton = props.showBackButton ?? true;\n\n // Full-page chat view\n if (isOpen) {\n return (\n <View style={styles.fullScreenOverlay}>\n <SafeAreaView\n style={[\n styles.fullPageContainer,\n { backgroundColor: theme.colors.background },\n ]}\n >\n {/* Header with back button */}\n {showBackButton && (\n <View\n style={[\n styles.header,\n {\n backgroundColor: primaryColor,\n paddingHorizontal: theme.spacing.lg,\n paddingVertical: theme.spacing.md,\n },\n theme.shadows.md,\n ]}\n >\n <TouchableOpacity\n style={styles.backButton}\n onPress={handleClose}\n activeOpacity={0.7}\n >\n <Text style={styles.backButtonText}>←</Text>\n </TouchableOpacity>\n <Text\n style={[\n styles.headerTitle,\n {\n fontSize: theme.typography.fontSizes.lg,\n fontWeight: theme.typography.fontWeights.semibold,\n },\n ]}\n >\n {config?.brandName || 'Lingu Chat'}\n </Text>\n <View style={styles.headerSpacer} />\n </View>\n )}\n\n {/* Native Chat UI */}\n <NativeChatUI\n config={config}\n messages={messages}\n isLoading={isLoading}\n onSendMessage={handleSendMessage}\n />\n </SafeAreaView>\n </View>\n );\n }\n\n // Floating action button\n return (\n <View style={[styles.fabContainer, getPositionStyle(position)]} pointerEvents=\"box-none\">\n <Animated.View style={{ transform: [{ scale: scaleAnim }] }}>\n <TouchableOpacity\n style={[\n styles.fab,\n {\n backgroundColor: primaryColor,\n width: 60,\n height: 60,\n borderRadius: 30,\n },\n theme.shadows.lg,\n ]}\n onPress={handleOpen}\n activeOpacity={0.8}\n >\n {hasUnread && (\n <Animated.View\n style={[\n styles.badge,\n {\n backgroundColor: theme.colors.badge,\n transform: [{ scale: badgePulse }],\n },\n ]}\n />\n )}\n <View style={styles.icon}>\n {/* Chat icon */}\n <View style={styles.iconBubble} />\n </View>\n </TouchableOpacity>\n </Animated.View>\n </View>\n );\n};\n\n/**\n * LinguChat - Main exported component\n * Wraps everything in LinguProvider for standalone state management\n */\nexport const LinguChat: React.FC<LinguChatProps> = ({\n onBackPress,\n showBackButton,\n autoOpen,\n position,\n ...linguConfig\n}) => {\n return (\n <LinguProvider {...linguConfig} autoOpen={autoOpen}>\n <LinguChatInner\n onBackPress={onBackPress}\n showBackButton={showBackButton}\n autoOpen={autoOpen}\n position={position}\n />\n </LinguProvider>\n );\n};\n\nconst styles = StyleSheet.create({\n fullScreenOverlay: {\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n zIndex: 10000,\n },\n fullPageContainer: {\n flex: 1,\n },\n header: {\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'space-between',\n },\n backButton: {\n width: 40,\n height: 40,\n justifyContent: 'center',\n alignItems: 'center',\n },\n backButtonText: {\n fontSize: 28,\n color: '#fff',\n fontWeight: '600',\n },\n headerTitle: {\n color: '#fff',\n flex: 1,\n textAlign: 'center',\n },\n headerSpacer: {\n width: 40,\n },\n fabContainer: {\n zIndex: 9999,\n },\n fab: {\n justifyContent: 'center',\n alignItems: 'center',\n },\n badge: {\n position: 'absolute',\n top: 8,\n right: 8,\n width: 12,\n height: 12,\n borderRadius: 6,\n borderWidth: 2,\n borderColor: '#fff',\n },\n icon: {\n width: 30,\n height: 30,\n justifyContent: 'center',\n alignItems: 'center',\n },\n iconBubble: {\n width: 24,\n height: 20,\n backgroundColor: '#fff',\n borderRadius: 12,\n },\n});\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LinguWebView Component
|
|
3
|
+
* Wraps the existing web widget in a WebView with native bridge
|
|
4
|
+
* Now with comprehensive logging and message display
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { useRef, useEffect } from 'react';
|
|
8
|
+
import { WebView } from 'react-native-webview';
|
|
9
|
+
import { StyleSheet } from 'react-native';
|
|
10
|
+
const LOG_PREFIX = '[LinguWebView]';
|
|
11
|
+
export const LinguWebView = ({
|
|
12
|
+
config,
|
|
13
|
+
messages,
|
|
14
|
+
onSendMessage,
|
|
15
|
+
onClose
|
|
16
|
+
}) => {
|
|
17
|
+
const webViewRef = useRef(null);
|
|
18
|
+
console.log(`${LOG_PREFIX} Rendering with ${messages.length} messages`);
|
|
19
|
+
|
|
20
|
+
// Inject messages into WebView when they change
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (webViewRef.current && messages.length > 0) {
|
|
23
|
+
console.log(`${LOG_PREFIX} Injecting ${messages.length} messages into WebView`);
|
|
24
|
+
const script = `
|
|
25
|
+
(function() {
|
|
26
|
+
const messagesContainer = document.getElementById('messages');
|
|
27
|
+
if (messagesContainer) {
|
|
28
|
+
messagesContainer.innerHTML = ${JSON.stringify(messages.map(msg => `
|
|
29
|
+
<div style="margin-bottom: 12px; display: flex; justify-content: ${msg.sender === 'user' ? 'flex-end' : 'flex-start'};">
|
|
30
|
+
<div style="
|
|
31
|
+
max-width: 70%;
|
|
32
|
+
padding: 12px;
|
|
33
|
+
border-radius: 12px;
|
|
34
|
+
background: ${msg.sender === 'user' ? '#4ade80' : '#f3f4f6'};
|
|
35
|
+
color: ${msg.sender === 'user' ? 'white' : 'black'};
|
|
36
|
+
">
|
|
37
|
+
${msg.text}
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
`).join(''))};
|
|
41
|
+
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
|
42
|
+
console.log('Messages updated in WebView');
|
|
43
|
+
}
|
|
44
|
+
})();
|
|
45
|
+
true;
|
|
46
|
+
`;
|
|
47
|
+
webViewRef.current.injectJavaScript(script);
|
|
48
|
+
}
|
|
49
|
+
}, [messages]);
|
|
50
|
+
|
|
51
|
+
// Handle messages from WebView
|
|
52
|
+
const handleMessage = event => {
|
|
53
|
+
try {
|
|
54
|
+
const data = JSON.parse(event.nativeEvent.data);
|
|
55
|
+
console.log(`${LOG_PREFIX} Received message from WebView:`, data.type);
|
|
56
|
+
switch (data.type) {
|
|
57
|
+
case 'SEND_MESSAGE':
|
|
58
|
+
console.log(`${LOG_PREFIX} User sent message:`, data.payload.message);
|
|
59
|
+
onSendMessage(data.payload.message);
|
|
60
|
+
break;
|
|
61
|
+
case 'CLOSE_CHAT':
|
|
62
|
+
console.log(`${LOG_PREFIX} User closed chat`);
|
|
63
|
+
onClose();
|
|
64
|
+
break;
|
|
65
|
+
case 'WEBVIEW_LOG':
|
|
66
|
+
console.log(`${LOG_PREFIX} WebView log:`, data.payload);
|
|
67
|
+
break;
|
|
68
|
+
default:
|
|
69
|
+
console.log(`${LOG_PREFIX} Unknown message type:`, data.type);
|
|
70
|
+
}
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error(`${LOG_PREFIX} Failed to parse message:`, error);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Generate HTML for the widget
|
|
77
|
+
const generateHTML = () => {
|
|
78
|
+
const configJSON = JSON.stringify(config || {});
|
|
79
|
+
console.log(`${LOG_PREFIX} Generating HTML with config:`, config === null || config === void 0 ? void 0 : config.brandName);
|
|
80
|
+
return `
|
|
81
|
+
<!DOCTYPE html>
|
|
82
|
+
<html>
|
|
83
|
+
<head>
|
|
84
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
|
|
85
|
+
<style>
|
|
86
|
+
* {
|
|
87
|
+
margin: 0;
|
|
88
|
+
padding: 0;
|
|
89
|
+
box-sizing: border-box;
|
|
90
|
+
}
|
|
91
|
+
body {
|
|
92
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
93
|
+
overflow: hidden;
|
|
94
|
+
}
|
|
95
|
+
#widget-container {
|
|
96
|
+
width: 100vw;
|
|
97
|
+
height: 100vh;
|
|
98
|
+
display: flex;
|
|
99
|
+
flex-direction: column;
|
|
100
|
+
}
|
|
101
|
+
#messages {
|
|
102
|
+
flex: 1;
|
|
103
|
+
overflow-y: auto;
|
|
104
|
+
padding: 16px;
|
|
105
|
+
background: white;
|
|
106
|
+
}
|
|
107
|
+
#input-container {
|
|
108
|
+
padding: 16px;
|
|
109
|
+
border-top: 1px solid #e5e7eb;
|
|
110
|
+
background: white;
|
|
111
|
+
}
|
|
112
|
+
#message-input {
|
|
113
|
+
width: 100%;
|
|
114
|
+
padding: 12px;
|
|
115
|
+
border: 1px solid #d1d5db;
|
|
116
|
+
border-radius: 8px;
|
|
117
|
+
font-size: 16px;
|
|
118
|
+
}
|
|
119
|
+
</style>
|
|
120
|
+
</head>
|
|
121
|
+
<body>
|
|
122
|
+
<div id="widget-container">
|
|
123
|
+
<div id="messages">
|
|
124
|
+
<div style="text-align: center; color: #9ca3af; padding: 20px;">
|
|
125
|
+
${(config === null || config === void 0 ? void 0 : config.greetingMessage) || 'Start chatting...'}
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
<div id="input-container">
|
|
129
|
+
<input
|
|
130
|
+
type="text"
|
|
131
|
+
id="message-input"
|
|
132
|
+
placeholder="Type a message..."
|
|
133
|
+
/>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<script>
|
|
138
|
+
console.log('WebView initialized');
|
|
139
|
+
|
|
140
|
+
// Configuration from native
|
|
141
|
+
window.linguConfig = ${configJSON};
|
|
142
|
+
|
|
143
|
+
// Bridge to React Native
|
|
144
|
+
window.sendToNative = function(type, payload) {
|
|
145
|
+
console.log('Sending to native:', type, payload);
|
|
146
|
+
window.ReactNativeWebView.postMessage(JSON.stringify({
|
|
147
|
+
type: type,
|
|
148
|
+
payload: payload
|
|
149
|
+
}));
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// Log to native
|
|
153
|
+
window.logToNative = function(message) {
|
|
154
|
+
window.sendToNative('WEBVIEW_LOG', message);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Handle input
|
|
158
|
+
const input = document.getElementById('message-input');
|
|
159
|
+
input.addEventListener('keypress', function(e) {
|
|
160
|
+
if (e.key === 'Enter' && input.value.trim()) {
|
|
161
|
+
console.log('User pressed enter:', input.value);
|
|
162
|
+
window.sendToNative('SEND_MESSAGE', { message: input.value });
|
|
163
|
+
input.value = '';
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
console.log('Event listeners attached');
|
|
168
|
+
window.logToNative('WebView ready');
|
|
169
|
+
</script>
|
|
170
|
+
</body>
|
|
171
|
+
</html>
|
|
172
|
+
`;
|
|
173
|
+
};
|
|
174
|
+
return /*#__PURE__*/React.createElement(WebView, {
|
|
175
|
+
ref: webViewRef,
|
|
176
|
+
source: {
|
|
177
|
+
html: generateHTML()
|
|
178
|
+
},
|
|
179
|
+
style: styles.webview,
|
|
180
|
+
onMessage: handleMessage,
|
|
181
|
+
javaScriptEnabled: true,
|
|
182
|
+
domStorageEnabled: true,
|
|
183
|
+
startInLoadingState: true,
|
|
184
|
+
scalesPageToFit: true,
|
|
185
|
+
onLoad: () => console.log(`${LOG_PREFIX} WebView loaded`),
|
|
186
|
+
onError: error => console.error(`${LOG_PREFIX} WebView error:`, error)
|
|
187
|
+
});
|
|
188
|
+
};
|
|
189
|
+
const styles = StyleSheet.create({
|
|
190
|
+
webview: {
|
|
191
|
+
flex: 1,
|
|
192
|
+
backgroundColor: 'white'
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
//# sourceMappingURL=LinguWebView.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["React","useRef","useEffect","WebView","StyleSheet","LOG_PREFIX","LinguWebView","config","messages","onSendMessage","onClose","webViewRef","console","log","length","current","script","JSON","stringify","map","msg","sender","text","join","injectJavaScript","handleMessage","event","data","parse","nativeEvent","type","payload","message","error","generateHTML","configJSON","brandName","greetingMessage","createElement","ref","source","html","style","styles","webview","onMessage","javaScriptEnabled","domStorageEnabled","startInLoadingState","scalesPageToFit","onLoad","onError","create","flex","backgroundColor"],"sources":["LinguWebView.tsx"],"sourcesContent":["/**\n * LinguWebView Component\n * Wraps the existing web widget in a WebView with native bridge\n * Now with comprehensive logging and message display\n */\n\nimport React, { useRef, useEffect } from 'react';\nimport { WebView, WebViewMessageEvent } from 'react-native-webview';\nimport { StyleSheet } from 'react-native';\nimport type { Message, WidgetConfig } from '../types';\n\nconst LOG_PREFIX = '[LinguWebView]';\n\ninterface LinguWebViewProps {\n config: WidgetConfig | null;\n messages: Message[];\n onSendMessage: (message: string) => void;\n onClose: () => void;\n}\n\nexport const LinguWebView: React.FC<LinguWebViewProps> = ({\n config,\n messages,\n onSendMessage,\n onClose,\n}) => {\n const webViewRef = useRef<WebView>(null);\n\n console.log(`${LOG_PREFIX} Rendering with ${messages.length} messages`);\n\n // Inject messages into WebView when they change\n useEffect(() => {\n if (webViewRef.current && messages.length > 0) {\n console.log(`${LOG_PREFIX} Injecting ${messages.length} messages into WebView`);\n const script = `\n (function() {\n const messagesContainer = document.getElementById('messages');\n if (messagesContainer) {\n messagesContainer.innerHTML = ${JSON.stringify(\n messages\n .map(\n (msg) => `\n <div style=\"margin-bottom: 12px; display: flex; justify-content: ${msg.sender === 'user' ? 'flex-end' : 'flex-start'\n };\">\n <div style=\"\n max-width: 70%;\n padding: 12px;\n border-radius: 12px;\n background: ${msg.sender === 'user' ? '#4ade80' : '#f3f4f6'};\n color: ${msg.sender === 'user' ? 'white' : 'black'};\n \">\n ${msg.text}\n </div>\n </div>\n `\n )\n .join('')\n )};\n messagesContainer.scrollTop = messagesContainer.scrollHeight;\n console.log('Messages updated in WebView');\n }\n })();\n true;\n `;\n webViewRef.current.injectJavaScript(script);\n }\n }, [messages]);\n\n // Handle messages from WebView\n const handleMessage = (event: WebViewMessageEvent) => {\n try {\n const data = JSON.parse(event.nativeEvent.data);\n console.log(`${LOG_PREFIX} Received message from WebView:`, data.type);\n\n switch (data.type) {\n case 'SEND_MESSAGE':\n console.log(`${LOG_PREFIX} User sent message:`, data.payload.message);\n onSendMessage(data.payload.message);\n break;\n case 'CLOSE_CHAT':\n console.log(`${LOG_PREFIX} User closed chat`);\n onClose();\n break;\n case 'WEBVIEW_LOG':\n console.log(`${LOG_PREFIX} WebView log:`, data.payload);\n break;\n default:\n console.log(`${LOG_PREFIX} Unknown message type:`, data.type);\n }\n } catch (error) {\n console.error(`${LOG_PREFIX} Failed to parse message:`, error);\n }\n };\n\n // Generate HTML for the widget\n const generateHTML = () => {\n const configJSON = JSON.stringify(config || {});\n console.log(`${LOG_PREFIX} Generating HTML with config:`, config?.brandName);\n\n return `\n <!DOCTYPE html>\n <html>\n <head>\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0\">\n <style>\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n overflow: hidden;\n }\n #widget-container {\n width: 100vw;\n height: 100vh;\n display: flex;\n flex-direction: column;\n }\n #messages {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n background: white;\n }\n #input-container {\n padding: 16px;\n border-top: 1px solid #e5e7eb;\n background: white;\n }\n #message-input {\n width: 100%;\n padding: 12px;\n border: 1px solid #d1d5db;\n border-radius: 8px;\n font-size: 16px;\n }\n </style>\n </head>\n <body>\n <div id=\"widget-container\">\n <div id=\"messages\">\n <div style=\"text-align: center; color: #9ca3af; padding: 20px;\">\n ${config?.greetingMessage || 'Start chatting...'}\n </div>\n </div>\n <div id=\"input-container\">\n <input \n type=\"text\" \n id=\"message-input\"\n placeholder=\"Type a message...\"\n />\n </div>\n </div>\n \n <script>\n console.log('WebView initialized');\n \n // Configuration from native\n window.linguConfig = ${configJSON};\n \n // Bridge to React Native\n window.sendToNative = function(type, payload) {\n console.log('Sending to native:', type, payload);\n window.ReactNativeWebView.postMessage(JSON.stringify({\n type: type,\n payload: payload\n }));\n };\n \n // Log to native\n window.logToNative = function(message) {\n window.sendToNative('WEBVIEW_LOG', message);\n };\n \n // Handle input\n const input = document.getElementById('message-input');\n input.addEventListener('keypress', function(e) {\n if (e.key === 'Enter' && input.value.trim()) {\n console.log('User pressed enter:', input.value);\n window.sendToNative('SEND_MESSAGE', { message: input.value });\n input.value = '';\n }\n });\n \n console.log('Event listeners attached');\n window.logToNative('WebView ready');\n </script>\n </body>\n </html>\n `;\n };\n\n return (\n <WebView\n ref={webViewRef}\n source={{ html: generateHTML() }}\n style={styles.webview}\n onMessage={handleMessage}\n javaScriptEnabled\n domStorageEnabled\n startInLoadingState\n scalesPageToFit\n onLoad={() => console.log(`${LOG_PREFIX} WebView loaded`)}\n onError={(error) => console.error(`${LOG_PREFIX} WebView error:`, error)}\n />\n );\n};\n\nconst styles = StyleSheet.create({\n webview: {\n flex: 1,\n backgroundColor: 'white',\n },\n});\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message Bubble Component
|
|
3
|
+
* Displays a single message with animations and styling
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useEffect, useRef } from 'react';
|
|
7
|
+
import { View, Text, StyleSheet, Animated } from 'react-native';
|
|
8
|
+
import { useTheme } from '../theme';
|
|
9
|
+
import { formatRelativeTime } from '../utils/formatters';
|
|
10
|
+
import { messageEntrance } from '../utils/animations';
|
|
11
|
+
export const MessageBubble = ({
|
|
12
|
+
message,
|
|
13
|
+
showTimestamp = false
|
|
14
|
+
}) => {
|
|
15
|
+
const {
|
|
16
|
+
theme
|
|
17
|
+
} = useTheme();
|
|
18
|
+
const isUser = message.sender === 'user';
|
|
19
|
+
|
|
20
|
+
// Animation values
|
|
21
|
+
const translateY = useRef(new Animated.Value(20)).current;
|
|
22
|
+
const opacity = useRef(new Animated.Value(0)).current;
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
// Animate message entrance
|
|
25
|
+
messageEntrance(translateY, opacity, theme.animations.normal).start();
|
|
26
|
+
}, []);
|
|
27
|
+
const bubbleStyle = isUser ? {
|
|
28
|
+
backgroundColor: theme.colors.userBubble
|
|
29
|
+
} : {
|
|
30
|
+
backgroundColor: theme.colors.botBubble,
|
|
31
|
+
...theme.shadows.sm
|
|
32
|
+
};
|
|
33
|
+
const textStyle = {
|
|
34
|
+
color: isUser ? theme.colors.userText : theme.colors.botText
|
|
35
|
+
};
|
|
36
|
+
return /*#__PURE__*/React.createElement(Animated.View, {
|
|
37
|
+
style: [styles.container, isUser ? styles.userContainer : styles.botContainer, {
|
|
38
|
+
opacity,
|
|
39
|
+
transform: [{
|
|
40
|
+
translateY
|
|
41
|
+
}]
|
|
42
|
+
}]
|
|
43
|
+
}, /*#__PURE__*/React.createElement(View, {
|
|
44
|
+
style: [styles.bubble, bubbleStyle, {
|
|
45
|
+
borderRadius: theme.borderRadius.lg,
|
|
46
|
+
borderBottomRightRadius: isUser ? theme.borderRadius.sm : theme.borderRadius.lg,
|
|
47
|
+
borderBottomLeftRadius: isUser ? theme.borderRadius.lg : theme.borderRadius.sm,
|
|
48
|
+
paddingHorizontal: theme.spacing.md,
|
|
49
|
+
paddingVertical: theme.spacing.sm + 2
|
|
50
|
+
}]
|
|
51
|
+
}, /*#__PURE__*/React.createElement(Text, {
|
|
52
|
+
style: [styles.text, textStyle, {
|
|
53
|
+
fontSize: theme.typography.fontSizes.base,
|
|
54
|
+
lineHeight: theme.typography.fontSizes.base * theme.typography.lineHeights.normal
|
|
55
|
+
}]
|
|
56
|
+
}, message.text), showTimestamp && /*#__PURE__*/React.createElement(Text, {
|
|
57
|
+
style: [styles.timestamp, {
|
|
58
|
+
color: isUser ? theme.colors.gray100 : theme.colors.gray500,
|
|
59
|
+
fontSize: theme.typography.fontSizes.xs,
|
|
60
|
+
marginTop: theme.spacing.xs
|
|
61
|
+
}]
|
|
62
|
+
}, formatRelativeTime(message.timestamp))));
|
|
63
|
+
};
|
|
64
|
+
const styles = StyleSheet.create({
|
|
65
|
+
container: {
|
|
66
|
+
marginBottom: 12,
|
|
67
|
+
flexDirection: 'row'
|
|
68
|
+
},
|
|
69
|
+
userContainer: {
|
|
70
|
+
justifyContent: 'flex-end'
|
|
71
|
+
},
|
|
72
|
+
botContainer: {
|
|
73
|
+
justifyContent: 'flex-start'
|
|
74
|
+
},
|
|
75
|
+
bubble: {
|
|
76
|
+
maxWidth: '75%'
|
|
77
|
+
},
|
|
78
|
+
text: {},
|
|
79
|
+
timestamp: {
|
|
80
|
+
fontSize: 11
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
//# sourceMappingURL=MessageBubble.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["React","useEffect","useRef","View","Text","StyleSheet","Animated","useTheme","formatRelativeTime","messageEntrance","MessageBubble","message","showTimestamp","theme","isUser","sender","translateY","Value","current","opacity","animations","normal","start","bubbleStyle","backgroundColor","colors","userBubble","botBubble","shadows","sm","textStyle","color","userText","botText","createElement","style","styles","container","userContainer","botContainer","transform","bubble","borderRadius","lg","borderBottomRightRadius","borderBottomLeftRadius","paddingHorizontal","spacing","md","paddingVertical","text","fontSize","typography","fontSizes","base","lineHeight","lineHeights","timestamp","gray100","gray500","xs","marginTop","create","marginBottom","flexDirection","justifyContent","maxWidth"],"sources":["MessageBubble.tsx"],"sourcesContent":["/**\n * Message Bubble Component\n * Displays a single message with animations and styling\n */\n\nimport React, { useEffect, useRef } from 'react';\nimport { View, Text, StyleSheet, Animated } from 'react-native';\nimport { useTheme } from '../theme';\nimport { formatRelativeTime } from '../utils/formatters';\nimport { messageEntrance } from '../utils/animations';\nimport type { Message } from '../types';\n\ninterface MessageBubbleProps {\n message: Message;\n showTimestamp?: boolean;\n}\n\nexport const MessageBubble: React.FC<MessageBubbleProps> = ({\n message,\n showTimestamp = false,\n}) => {\n const { theme } = useTheme();\n const isUser = message.sender === 'user';\n\n // Animation values\n const translateY = useRef(new Animated.Value(20)).current;\n const opacity = useRef(new Animated.Value(0)).current;\n\n useEffect(() => {\n // Animate message entrance\n messageEntrance(translateY, opacity, theme.animations.normal).start();\n }, []);\n\n const bubbleStyle = isUser\n ? {\n backgroundColor: theme.colors.userBubble,\n }\n : {\n backgroundColor: theme.colors.botBubble,\n ...theme.shadows.sm,\n };\n\n const textStyle = {\n color: isUser ? theme.colors.userText : theme.colors.botText,\n };\n\n return (\n <Animated.View\n style={[\n styles.container,\n isUser ? styles.userContainer : styles.botContainer,\n {\n opacity,\n transform: [{ translateY }],\n },\n ]}\n >\n <View\n style={[\n styles.bubble,\n bubbleStyle,\n {\n borderRadius: theme.borderRadius.lg,\n borderBottomRightRadius: isUser ? theme.borderRadius.sm : theme.borderRadius.lg,\n borderBottomLeftRadius: isUser ? theme.borderRadius.lg : theme.borderRadius.sm,\n paddingHorizontal: theme.spacing.md,\n paddingVertical: theme.spacing.sm + 2,\n },\n ]}\n >\n <Text\n style={[\n styles.text,\n textStyle,\n {\n fontSize: theme.typography.fontSizes.base,\n lineHeight: theme.typography.fontSizes.base * theme.typography.lineHeights.normal,\n },\n ]}\n >\n {message.text}\n </Text>\n {showTimestamp && (\n <Text\n style={[\n styles.timestamp,\n {\n color: isUser ? theme.colors.gray100 : theme.colors.gray500,\n fontSize: theme.typography.fontSizes.xs,\n marginTop: theme.spacing.xs,\n },\n ]}\n >\n {formatRelativeTime(message.timestamp)}\n </Text>\n )}\n </View>\n </Animated.View>\n );\n};\n\nconst styles = StyleSheet.create({\n container: {\n marginBottom: 12,\n flexDirection: 'row',\n },\n userContainer: {\n justifyContent: 'flex-end',\n },\n botContainer: {\n justifyContent: 'flex-start',\n },\n bubble: {\n maxWidth: '75%',\n },\n text: {},\n timestamp: {\n fontSize: 11,\n },\n});\n"],"mappings":"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","ignoreList":[]}
|