@codewithvincent/react-native-love-chat 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.
Files changed (50) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/LICENSE +21 -0
  3. package/README.md +152 -0
  4. package/dist/Chat.d.ts +3 -0
  5. package/dist/Chat.js +78 -0
  6. package/dist/components/Bubble.d.ts +21 -0
  7. package/dist/components/Bubble.js +55 -0
  8. package/dist/components/Composer.d.ts +14 -0
  9. package/dist/components/Composer.js +44 -0
  10. package/dist/components/FooterReplyPreview.d.ts +7 -0
  11. package/dist/components/FooterReplyPreview.js +130 -0
  12. package/dist/components/Icons.d.ts +21 -0
  13. package/dist/components/Icons.js +71 -0
  14. package/dist/components/InputToolbar.d.ts +17 -0
  15. package/dist/components/InputToolbar.js +110 -0
  16. package/dist/components/Message.d.ts +21 -0
  17. package/dist/components/Message.js +131 -0
  18. package/dist/components/MessageList.d.ts +6 -0
  19. package/dist/components/MessageList.js +111 -0
  20. package/dist/components/MessageStatus.d.ts +6 -0
  21. package/dist/components/MessageStatus.js +59 -0
  22. package/dist/components/ReactionBubble.d.ts +55 -0
  23. package/dist/components/ReactionBubble.js +262 -0
  24. package/dist/components/ReplyPreview.d.ts +7 -0
  25. package/dist/components/ReplyPreview.js +102 -0
  26. package/dist/components/UploadFooter.d.ts +4 -0
  27. package/dist/components/UploadFooter.js +33 -0
  28. package/dist/components/adapters/UniversalAudio.d.ts +8 -0
  29. package/dist/components/adapters/UniversalAudio.js +23 -0
  30. package/dist/components/adapters/UniversalBlurView.d.ts +3 -0
  31. package/dist/components/adapters/UniversalBlurView.js +26 -0
  32. package/dist/components/adapters/UniversalVideo.d.ts +8 -0
  33. package/dist/components/adapters/UniversalVideo.js +20 -0
  34. package/dist/components/media/AudioCard.d.ts +7 -0
  35. package/dist/components/media/AudioCard.js +187 -0
  36. package/dist/components/media/FileCard.d.ts +8 -0
  37. package/dist/components/media/FileCard.js +68 -0
  38. package/dist/components/media/ImageCard.d.ts +9 -0
  39. package/dist/components/media/ImageCard.js +76 -0
  40. package/dist/components/media/VideoCard.d.ts +10 -0
  41. package/dist/components/media/VideoCard.js +232 -0
  42. package/dist/index.d.ts +9 -0
  43. package/dist/index.js +36 -0
  44. package/dist/types.d.ts +71 -0
  45. package/dist/types.js +2 -0
  46. package/dist/utils/index.d.ts +14 -0
  47. package/dist/utils/index.js +44 -0
  48. package/dist/utils/theme.d.ts +77 -0
  49. package/dist/utils/theme.js +55 -0
  50. package/package.json +67 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## 0.1.0
6
+ - Initial release of react-native-standalone-chat
7
+ - Replies, reactions, media attachments, load earlier, theming
8
+ - TypeScript definitions and build output
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,152 @@
1
+ # React Native Standalone Chat
2
+
3
+ A customizable chat UI component for React Native with replies, reactions, media attachments, theming, and performance optimizations.
4
+
5
+ ## Features
6
+
7
+ - Replies and swipe-to-reply interactions
8
+ - Reactions with overlay UI
9
+ - Image, video, audio, and generic file attachments
10
+ - Load earlier messages header
11
+ - Theming via theme prop override
12
+ - FlashList-backed message rendering
13
+ - TypeScript types included
14
+
15
+ ## Installation
16
+
17
+ Install the package and required peers:
18
+
19
+ ```bash
20
+ npm install @codewithvincent/react-native-love-chat
21
+ # or
22
+ yarn add @codewithvincent/react-native-love-chat
23
+ ```
24
+
25
+ Peer dependencies (install as needed):
26
+
27
+ - react
28
+ - react-native
29
+ - react-native-gesture-handler
30
+ - react-native-reanimated
31
+ - react-native-safe-area-context
32
+ - react-native-root-siblings
33
+ - @shopify/flash-list
34
+ - react-native-svg
35
+ - Optional blur providers: @react-native-community/blur or expo-blur
36
+
37
+ ## Usage
38
+
39
+ ```tsx
40
+ import React, { useState, useCallback, useEffect } from 'react';
41
+ import { Chat, IMessage } from '@codewithvincent/react-native-love-chat';
42
+
43
+ export default function ChatScreen() {
44
+ const [messages, setMessages] = useState<IMessage[]>([]);
45
+ const user = { _id: 1, name: 'Developer' };
46
+
47
+ useEffect(() => {
48
+ setMessages([
49
+ {
50
+ _id: 1,
51
+ text: 'Hello developer',
52
+ createdAt: new Date(),
53
+ user: {
54
+ _id: 2,
55
+ name: 'React Native',
56
+ avatar: 'https://placeimg.com/140/140/any',
57
+ },
58
+ },
59
+ ]);
60
+ }, []);
61
+
62
+ const onSend = useCallback((newMessages: IMessage[] = []) => {
63
+ setMessages((previousMessages) => [...newMessages, ...previousMessages]);
64
+ }, []);
65
+
66
+ return (
67
+ <Chat messages={messages} onSend={onSend} user={user} placeholder="Type your message here..." />
68
+ );
69
+ }
70
+ ```
71
+
72
+ ## API Reference
73
+
74
+ ### Essential Props
75
+
76
+ - messages: IMessage[] — array of messages (newest first if inverted)
77
+ - user: IUser — current user object
78
+ - onSend: (messages: IMessage[]) => void — send handler
79
+ - placeholder?: string
80
+ - isTyping?: boolean
81
+ - loadEarlier?: boolean
82
+ - isLoadingEarlier?: boolean
83
+ - onLoadEarlier?: () => void
84
+ - onPressAttachment?: (type: string) => void
85
+ - onReaction?: (msg: IMessage, emoji: string) => void
86
+ - onDeleteMessage?: (msg: IMessage) => void
87
+ - onDownloadFile?: (msg: IMessage) => void
88
+ - renderUploadFooter?: (props) => ReactNode
89
+ - theme?: PartialTheme — to override default theme colors
90
+
91
+ ## Data Structures
92
+
93
+ ### IMessage
94
+
95
+ ```typescript
96
+ interface IMessage {
97
+ _id: string | number;
98
+ text: string;
99
+ createdAt: Date | number;
100
+ user: IUser;
101
+ image?: string;
102
+ video?: string;
103
+ audio?: string;
104
+ system?: boolean;
105
+ sent?: boolean;
106
+ received?: boolean;
107
+ pending?: boolean;
108
+ // ... custom fields
109
+ }
110
+ ```
111
+
112
+ ### IUser
113
+
114
+ ```typescript
115
+ interface IUser {
116
+ _id: string | number;
117
+ name?: string;
118
+ avatar?: string | number;
119
+ }
120
+ ```
121
+
122
+ ## Theming
123
+
124
+ Override with the `theme` prop. Example:
125
+
126
+ ```tsx
127
+ <Chat
128
+ messages={messages}
129
+ user={user}
130
+ onSend={onSend}
131
+ theme={{
132
+ colors: {
133
+ primary: '#4fd1a5',
134
+ darkRed: '#ff5757',
135
+ },
136
+ }}
137
+ />
138
+ ```
139
+
140
+ ## Examples
141
+
142
+ See the `examples` folder for complete usage samples.
143
+
144
+ ## Contributing
145
+
146
+ - Fork the repo and create a feature branch
147
+ - Run tests and build before submitting a PR
148
+ - Follow semantic commit messages
149
+
150
+ ## License
151
+
152
+ MIT © Contributors
package/dist/Chat.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { ChatProps } from './types';
2
+ declare const Chat: (props: ChatProps) => import("react/jsx-runtime").JSX.Element;
3
+ export default Chat;
package/dist/Chat.js ADDED
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const jsx_runtime_1 = require("react/jsx-runtime");
7
+ const react_1 = require("react");
8
+ const react_native_1 = require("react-native");
9
+ const MessageList_1 = __importDefault(require("./components/MessageList"));
10
+ const InputToolbar_1 = __importDefault(require("./components/InputToolbar"));
11
+ const theme_1 = require("./utils/theme");
12
+ const InnerChat = (props) => {
13
+ const theme = (0, theme_1.useTheme)();
14
+ const { messages = [], text: propText, onInputTextChanged, onSend, renderInputToolbar, renderChatFooter, containerStyle, keyboardShouldPersistTaps = 'never', onPressAttachment, onReply, onClearReply, renderUploadFooter, onDeleteMessage, onDownloadFile, } = props;
15
+ const [text, setText] = (0, react_1.useState)(propText || '');
16
+ const [replyMessage, setReplyMessage] = (0, react_1.useState)(null);
17
+ (0, react_1.useEffect)(() => {
18
+ if (propText !== undefined && propText !== text) {
19
+ setText(propText);
20
+ }
21
+ }, [propText]);
22
+ const handleInputTextChanged = (newText) => {
23
+ setText(newText);
24
+ if (onInputTextChanged) {
25
+ onInputTextChanged(newText);
26
+ }
27
+ };
28
+ const handleSend = (newMessages) => {
29
+ if (Array.isArray(newMessages)) {
30
+ onSend(newMessages);
31
+ }
32
+ setText('');
33
+ if (onInputTextChanged) {
34
+ onInputTextChanged('');
35
+ }
36
+ setReplyMessage(null);
37
+ };
38
+ const handleReply = (message) => {
39
+ setReplyMessage(message);
40
+ onReply === null || onReply === void 0 ? void 0 : onReply(message);
41
+ };
42
+ const handleClearReply = () => {
43
+ setReplyMessage(null);
44
+ onClearReply === null || onClearReply === void 0 ? void 0 : onClearReply();
45
+ };
46
+ const renderInput = () => {
47
+ const inputProps = {
48
+ ...props,
49
+ text,
50
+ onTextChanged: handleInputTextChanged,
51
+ onSend: handleSend,
52
+ onPressAttachment,
53
+ replyMessage,
54
+ onClearReply: handleClearReply,
55
+ renderUploadFooter,
56
+ };
57
+ if (renderInputToolbar) {
58
+ return renderInputToolbar(inputProps);
59
+ }
60
+ return ((0, jsx_runtime_1.jsx)(InputToolbar_1.default, { ...inputProps }));
61
+ };
62
+ return ((0, jsx_runtime_1.jsx)(react_native_1.SafeAreaView, { style: [styles.container, { backgroundColor: theme.colors.white }, containerStyle], children: (0, jsx_runtime_1.jsx)(react_native_1.KeyboardAvoidingView, { style: styles.keyboardAvoidingView, behavior: react_native_1.Platform.OS === 'ios' ? 'padding' : undefined, keyboardVerticalOffset: react_native_1.Platform.OS === 'ios' ? 0 : 0, children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.contentContainer, children: [(0, jsx_runtime_1.jsx)(MessageList_1.default, { ...props, messages: messages, keyboardShouldPersistTaps: keyboardShouldPersistTaps, onReply: handleReply }), renderChatFooter === null || renderChatFooter === void 0 ? void 0 : renderChatFooter(), renderInput()] }) }) }));
63
+ };
64
+ const Chat = (props) => ((0, jsx_runtime_1.jsx)(theme_1.ThemeProvider, { theme: props.theme, children: (0, jsx_runtime_1.jsx)(InnerChat, { ...props }) }));
65
+ const styles = react_native_1.StyleSheet.create({
66
+ container: {
67
+ flex: 1,
68
+ backgroundColor: theme_1.defaultTheme.colors.white,
69
+ },
70
+ keyboardAvoidingView: {
71
+ flex: 1,
72
+ },
73
+ contentContainer: {
74
+ flex: 1,
75
+ overflow: 'hidden',
76
+ },
77
+ });
78
+ exports.default = Chat;
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ import { ViewStyle } from 'react-native';
3
+ interface ChatBubbleProps {
4
+ isOwnMessage: boolean;
5
+ children: React.ReactNode;
6
+ bubbleColor?: string;
7
+ tailColor?: string;
8
+ withTail?: boolean;
9
+ style?: ViewStyle;
10
+ onPress?: () => void;
11
+ hitSlop?: number | {
12
+ top: number;
13
+ bottom: number;
14
+ left: number;
15
+ right: number;
16
+ };
17
+ maxWidth?: number;
18
+ isMedia?: boolean;
19
+ }
20
+ declare const ChatBubble: React.FC<ChatBubbleProps>;
21
+ export default ChatBubble;
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const jsx_runtime_1 = require("react/jsx-runtime");
4
+ const react_native_1 = require("react-native");
5
+ const react_native_svg_1 = require("react-native-svg");
6
+ const utils_1 = require("../utils");
7
+ const theme_1 = require("../utils/theme");
8
+ const ChatBubble = ({ isOwnMessage, children, bubbleColor, tailColor, withTail = true, style, onPress, hitSlop, maxWidth, isMedia, ...rest }) => {
9
+ const theme = (0, theme_1.useTheme)();
10
+ const effectiveBubbleColor = bubbleColor || (isOwnMessage ? theme.colors.ownMessageBubble : theme.colors.otherMessageBubble);
11
+ const tailFillColor = tailColor || effectiveBubbleColor;
12
+ const styles = getStyleObj({ isOwnMessage, maxWidth, isMedia });
13
+ const SvgContainerStyle = isOwnMessage
14
+ ? styles.svgContainerOwn
15
+ : styles.svgContainerOther;
16
+ const Container = onPress ? react_native_1.Pressable : react_native_1.View;
17
+ const tailPath = isOwnMessage
18
+ ? 'M48,35c-7-4-6-8.75-6-17.5C28,17.5,29,35,48,35z'
19
+ : 'M38.484,17.5c0,8.75,1,13.5-6,17.5C51.484,35,52.484,17.5,38.484,17.5z';
20
+ const bubbleStyle = {
21
+ ...styles.bubble,
22
+ ...style,
23
+ backgroundColor: effectiveBubbleColor,
24
+ };
25
+ return ((0, jsx_runtime_1.jsxs)(Container, { ...rest, hitSlop: hitSlop, onPress: onPress, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: bubbleStyle, children: children }), withTail && ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.svgContainer, SvgContainerStyle], children: (0, jsx_runtime_1.jsx)(react_native_svg_1.Svg, { width: utils_1.appSize.width(4.5), height: utils_1.appSize.height(2.5), viewBox: "32.485 17.5 15.515 17.5", children: (0, jsx_runtime_1.jsx)(react_native_svg_1.Path, { d: tailPath, fill: tailFillColor }) }) }))] }));
26
+ };
27
+ exports.default = ChatBubble;
28
+ const getStyleObj = ({ isOwnMessage, maxWidth, isMedia, }) => ({
29
+ bubble: {
30
+ maxWidth: maxWidth || utils_1.appSize.width(80),
31
+ alignSelf: isOwnMessage ? 'flex-end' : 'flex-start',
32
+ paddingHorizontal: isMedia ? 0 : utils_1.appSize.width(3),
33
+ paddingTop: isMedia ? 0 : utils_1.appSize.height(0.5),
34
+ paddingBottom: isMedia ? 0 : utils_1.appSize.height(0.8),
35
+ borderRadius: utils_1.appSize.width(2),
36
+ marginVertical: utils_1.appSize.height(0.5),
37
+ },
38
+ svgContainer: {
39
+ position: 'absolute',
40
+ top: 0,
41
+ left: 0,
42
+ right: 0,
43
+ bottom: 0,
44
+ zIndex: -1,
45
+ flex: 1,
46
+ },
47
+ svgContainerOwn: {
48
+ justifyContent: 'flex-end',
49
+ alignItems: 'flex-end',
50
+ },
51
+ svgContainerOther: {
52
+ justifyContent: 'flex-end',
53
+ alignItems: 'flex-start',
54
+ },
55
+ });
@@ -0,0 +1,14 @@
1
+ interface ComposerProps {
2
+ text?: string;
3
+ placeholder?: string;
4
+ placeholderTextColor?: string;
5
+ textInputProps?: any;
6
+ onTextChanged?: (text: string) => void;
7
+ onSend?: () => void;
8
+ textInputStyle?: any;
9
+ multiline?: boolean;
10
+ maxComposerHeight?: number;
11
+ minComposerHeight?: number;
12
+ }
13
+ declare const Composer: (props: ComposerProps) => import("react/jsx-runtime").JSX.Element;
14
+ export default Composer;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const jsx_runtime_1 = require("react/jsx-runtime");
4
+ const react_1 = require("react");
5
+ const react_native_1 = require("react-native");
6
+ const theme_1 = require("../utils/theme");
7
+ const Composer = (props) => {
8
+ const theme = (0, theme_1.useTheme)();
9
+ const { text = '', placeholder = 'Type a message...', placeholderTextColor = theme.colors.gray, textInputProps, onTextChanged, textInputStyle, multiline = true, maxComposerHeight = 100, minComposerHeight = 40, } = props;
10
+ const [composerHeight, setComposerHeight] = (0, react_1.useState)(minComposerHeight);
11
+ const handleContentSizeChange = (e) => {
12
+ const contentSize = e.nativeEvent.contentSize;
13
+ if (!contentSize)
14
+ return;
15
+ if (!composerHeight ||
16
+ (composerHeight && composerHeight < maxComposerHeight) ||
17
+ (composerHeight === maxComposerHeight && contentSize.height < maxComposerHeight)) {
18
+ setComposerHeight(Math.max(minComposerHeight, Math.min(maxComposerHeight, contentSize.height)));
19
+ }
20
+ };
21
+ return ((0, jsx_runtime_1.jsx)(react_native_1.TextInput, { testID: placeholder, accessible: true, accessibilityLabel: placeholder, placeholder: placeholder, placeholderTextColor: placeholderTextColor, multiline: multiline, onChangeText: onTextChanged, onContentSizeChange: handleContentSizeChange, style: [
22
+ styles.textInput,
23
+ { color: theme.colors.black, backgroundColor: theme.colors.primary },
24
+ textInputStyle,
25
+ {
26
+ height: composerHeight,
27
+ },
28
+ ], autoFocus: false, value: text, enablesReturnKeyAutomatically: true, underlineColorAndroid: "transparent", blurOnSubmit: false, ...textInputProps }));
29
+ };
30
+ const styles = react_native_1.StyleSheet.create({
31
+ textInput: {
32
+ flex: 1,
33
+ fontSize: 16,
34
+ lineHeight: 22,
35
+ color: theme_1.defaultTheme.colors.black,
36
+ backgroundColor: theme_1.defaultTheme.colors.primary,
37
+ borderRadius: 20,
38
+ paddingHorizontal: 12,
39
+ paddingTop: react_native_1.Platform.OS === 'ios' ? 8 : 4,
40
+ paddingBottom: react_native_1.Platform.OS === 'ios' ? 8 : 4,
41
+ marginHorizontal: 8,
42
+ },
43
+ });
44
+ exports.default = Composer;
@@ -0,0 +1,7 @@
1
+ interface FooterReplyPreviewProps {
2
+ chatMessage: any;
3
+ clearReply?: () => void;
4
+ userId: string;
5
+ }
6
+ declare const FooterReplyPreview: ({ chatMessage, clearReply, userId }: FooterReplyPreviewProps) => import("react/jsx-runtime").JSX.Element;
7
+ export default FooterReplyPreview;
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const jsx_runtime_1 = require("react/jsx-runtime");
4
+ const react_native_1 = require("react-native");
5
+ const utils_1 = require("../utils");
6
+ const theme_1 = require("../utils/theme");
7
+ const Icons_1 = require("./Icons");
8
+ const FooterReplyPreview = ({ chatMessage, clearReply, userId }) => {
9
+ var _a, _b;
10
+ console.log('chatMessage', chatMessage);
11
+ const theme = (0, theme_1.useTheme)();
12
+ if (!chatMessage)
13
+ return null;
14
+ const isMine = ((_a = chatMessage.user) === null || _a === void 0 ? void 0 : _a._id) === userId;
15
+ const senderName = isMine ? 'You' : ((_b = chatMessage.user) === null || _b === void 0 ? void 0 : _b.name) || 'Unknown';
16
+ const getFileNameFromUrl = (url) => {
17
+ if (!url)
18
+ return undefined;
19
+ try {
20
+ const clean = url.split('?')[0];
21
+ const parts = clean.split('/');
22
+ const last = parts[parts.length - 1];
23
+ return last || undefined;
24
+ }
25
+ catch {
26
+ return undefined;
27
+ }
28
+ };
29
+ const getExtension = (name) => {
30
+ if (!name)
31
+ return undefined;
32
+ const ext = name.split('.').pop();
33
+ return ext ? ext.toLowerCase() : undefined;
34
+ };
35
+ const computedFileName = chatMessage.fileName || getFileNameFromUrl(chatMessage.fileUrl);
36
+ const extension = getExtension(computedFileName);
37
+ const isAudioByExt = extension ? ['mp3', 'wav', 'm4a', 'aac', 'ogg'].includes(extension) : false;
38
+ const rawFileType = typeof chatMessage.fileType === 'string'
39
+ ? chatMessage.fileType.toLowerCase()
40
+ : chatMessage.fileType;
41
+ const fileType = rawFileType ||
42
+ (chatMessage.image
43
+ ? 'image'
44
+ : chatMessage.video
45
+ ? 'video'
46
+ : chatMessage.audio
47
+ ? 'audio'
48
+ : computedFileName
49
+ ? isAudioByExt
50
+ ? 'audio'
51
+ : 'file'
52
+ : null);
53
+ const renderContent = () => {
54
+ switch (fileType) {
55
+ case 'image':
56
+ return ((0, jsx_runtime_1.jsx)(react_native_1.Image, { source: { uri: chatMessage.image || chatMessage.fileUrl }, style: styles.replyImage, resizeMode: "cover" }));
57
+ case 'video':
58
+ return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.replyMediaContainer, children: (0, jsx_runtime_1.jsx)(Icons_1.VideoIcon, { size: 20, color: theme.colors.darkBrown }) }));
59
+ case 'audio':
60
+ return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.replyFileContainer, children: [(0, jsx_runtime_1.jsx)(Icons_1.AudioIcon, { size: 18, color: theme.colors.darkBrown }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { numberOfLines: 1, style: [styles.replyFileName, { color: theme.colors.darkBrown }], children: computedFileName || 'Audio' })] }));
61
+ case 'file':
62
+ return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.replyFileContainer, children: [(0, jsx_runtime_1.jsx)(Icons_1.FileIcon, { size: 18, color: theme.colors.darkBrown }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { numberOfLines: 1, style: [styles.replyFileName, { color: theme.colors.darkBrown }], children: computedFileName || (extension ? extension.toUpperCase() : 'File') })] }));
63
+ default:
64
+ if (computedFileName) {
65
+ return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.replyFileContainer, children: [isAudioByExt ? ((0, jsx_runtime_1.jsx)(Icons_1.AudioIcon, { size: 18, color: theme.colors.darkBrown })) : ((0, jsx_runtime_1.jsx)(Icons_1.FileIcon, { size: 18, color: theme.colors.darkBrown })), (0, jsx_runtime_1.jsx)(react_native_1.Text, { numberOfLines: 1, style: [styles.replyFileName, { color: theme.colors.darkBrown }], children: computedFileName })] }));
66
+ }
67
+ return ((0, jsx_runtime_1.jsx)(react_native_1.Text, { numberOfLines: 1, style: [styles.replySnippet, { color: theme.colors.darkBrown }], children: chatMessage.text || chatMessage.caption || '' }));
68
+ }
69
+ };
70
+ return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
71
+ styles.replyPreviewContainer,
72
+ { backgroundColor: theme.colors.softRed, borderLeftColor: theme.colors.primary },
73
+ ], children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.replyLine, { backgroundColor: theme.colors.primary }] }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.repContainer, children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: { flex: 1 }, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.replyTitle, { color: theme.colors.darkBrown }], children: senderName }), renderContent()] }), clearReply && ((0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { onPress: clearReply, children: (0, jsx_runtime_1.jsx)(Icons_1.XCircleIcon, { size: 16, color: theme.colors.darkRed }) }))] })] }));
74
+ };
75
+ const styles = react_native_1.StyleSheet.create({
76
+ replyPreviewContainer: {
77
+ flexDirection: 'row',
78
+ alignItems: 'center',
79
+ backgroundColor: theme_1.defaultTheme.colors.softRed,
80
+ padding: 10,
81
+ borderLeftWidth: 4,
82
+ borderLeftColor: theme_1.defaultTheme.colors.primary,
83
+ borderBottomWidth: 0.3,
84
+ borderBottomColor: '#ddd',
85
+ },
86
+ replyLine: {
87
+ width: 4,
88
+ height: '100%',
89
+ backgroundColor: theme_1.defaultTheme.colors.primary,
90
+ marginRight: 8,
91
+ },
92
+ repContainer: {
93
+ flex: 1,
94
+ flexDirection: 'row',
95
+ justifyContent: 'space-between',
96
+ alignItems: 'center',
97
+ },
98
+ replyTitle: {
99
+ fontSize: utils_1.appSize.font(11),
100
+ fontWeight: '600',
101
+ color: theme_1.defaultTheme.colors.darkBrown,
102
+ },
103
+ replySnippet: {
104
+ fontSize: utils_1.appSize.font(13.5),
105
+ color: theme_1.defaultTheme.colors.darkBrown,
106
+ marginTop: 2,
107
+ },
108
+ replyImage: {
109
+ width: 40,
110
+ height: 40,
111
+ borderRadius: 4,
112
+ },
113
+ replyMediaContainer: {
114
+ width: 40,
115
+ height: 40,
116
+ justifyContent: 'center',
117
+ alignItems: 'center',
118
+ backgroundColor: '#eee',
119
+ borderRadius: 4,
120
+ },
121
+ replyFileContainer: {
122
+ flexDirection: 'row',
123
+ alignItems: 'center',
124
+ },
125
+ replyFileName: {
126
+ marginLeft: 6,
127
+ color: theme_1.defaultTheme.colors.darkBrown,
128
+ },
129
+ });
130
+ exports.default = FooterReplyPreview;
@@ -0,0 +1,21 @@
1
+ interface IconProps {
2
+ color?: string;
3
+ size?: number;
4
+ }
5
+ export declare const PlusIcon: ({ color, size }: IconProps) => import("react/jsx-runtime").JSX.Element;
6
+ export declare const SendIcon: ({ color, size }: IconProps) => import("react/jsx-runtime").JSX.Element;
7
+ export declare const MicIcon: ({ color, size }: IconProps) => import("react/jsx-runtime").JSX.Element;
8
+ export declare const CloseIcon: ({ color, size }: IconProps) => import("react/jsx-runtime").JSX.Element;
9
+ export declare const PlayIcon: ({ color, size }: IconProps) => import("react/jsx-runtime").JSX.Element;
10
+ export declare const PauseIcon: ({ color, size }: IconProps) => import("react/jsx-runtime").JSX.Element;
11
+ export declare const FileIcon: ({ color, size }: IconProps) => import("react/jsx-runtime").JSX.Element;
12
+ export declare const ImageIcon: ({ color, size }: IconProps) => import("react/jsx-runtime").JSX.Element;
13
+ export declare const VideoIcon: ({ color, size }: IconProps) => import("react/jsx-runtime").JSX.Element;
14
+ export declare const AudioIcon: ({ color, size }: IconProps) => import("react/jsx-runtime").JSX.Element;
15
+ export declare const ChevronRightIcon: ({ color, size }: IconProps) => import("react/jsx-runtime").JSX.Element;
16
+ export declare const LockIcon: ({ color, size }: IconProps) => import("react/jsx-runtime").JSX.Element;
17
+ export declare const XCircleIcon: ({ color, size }: IconProps) => import("react/jsx-runtime").JSX.Element;
18
+ export declare const ReplyIcon: ({ color, size }: IconProps) => import("react/jsx-runtime").JSX.Element;
19
+ export declare const TrashIcon: ({ color, size }: IconProps) => import("react/jsx-runtime").JSX.Element;
20
+ export declare const DownloadIcon: ({ color, size }: IconProps) => import("react/jsx-runtime").JSX.Element;
21
+ export {};
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.DownloadIcon = exports.TrashIcon = exports.ReplyIcon = exports.XCircleIcon = exports.LockIcon = exports.ChevronRightIcon = exports.AudioIcon = exports.VideoIcon = exports.ImageIcon = exports.FileIcon = exports.PauseIcon = exports.PlayIcon = exports.CloseIcon = exports.MicIcon = exports.SendIcon = exports.PlusIcon = void 0;
37
+ const jsx_runtime_1 = require("react/jsx-runtime");
38
+ const react_native_svg_1 = __importStar(require("react-native-svg"));
39
+ const theme_1 = require("../utils/theme");
40
+ const PlusIcon = ({ color = theme_1.defaultTheme.colors.darkRed, size = 24 }) => ((0, jsx_runtime_1.jsxs)(react_native_svg_1.default, { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [(0, jsx_runtime_1.jsx)(react_native_svg_1.Line, { x1: "12", y1: "5", x2: "12", y2: "19" }), (0, jsx_runtime_1.jsx)(react_native_svg_1.Line, { x1: "5", y1: "12", x2: "19", y2: "12" })] }));
41
+ exports.PlusIcon = PlusIcon;
42
+ const SendIcon = ({ color = theme_1.defaultTheme.colors.white, size = 24 }) => ((0, jsx_runtime_1.jsxs)(react_native_svg_1.default, { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [(0, jsx_runtime_1.jsx)(react_native_svg_1.Line, { x1: "22", y1: "2", x2: "11", y2: "13" }), (0, jsx_runtime_1.jsx)(react_native_svg_1.Polygon, { points: "22 2 15 22 11 13 2 9 22 2" })] }));
43
+ exports.SendIcon = SendIcon;
44
+ const MicIcon = ({ color = theme_1.defaultTheme.colors.darkRed, size = 24 }) => ((0, jsx_runtime_1.jsxs)(react_native_svg_1.default, { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [(0, jsx_runtime_1.jsx)(react_native_svg_1.Path, { d: "M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" }), (0, jsx_runtime_1.jsx)(react_native_svg_1.Path, { d: "M19 10v2a7 7 0 0 1-14 0v-2" }), (0, jsx_runtime_1.jsx)(react_native_svg_1.Line, { x1: "12", y1: "19", x2: "12", y2: "23" }), (0, jsx_runtime_1.jsx)(react_native_svg_1.Line, { x1: "8", y1: "23", x2: "16", y2: "23" })] }));
45
+ exports.MicIcon = MicIcon;
46
+ const CloseIcon = ({ color = theme_1.defaultTheme.colors.gray, size = 24 }) => ((0, jsx_runtime_1.jsxs)(react_native_svg_1.default, { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [(0, jsx_runtime_1.jsx)(react_native_svg_1.Line, { x1: "18", y1: "6", x2: "6", y2: "18" }), (0, jsx_runtime_1.jsx)(react_native_svg_1.Line, { x1: "6", y1: "6", x2: "18", y2: "18" })] }));
47
+ exports.CloseIcon = CloseIcon;
48
+ const PlayIcon = ({ color = theme_1.defaultTheme.colors.white, size = 24 }) => ((0, jsx_runtime_1.jsx)(react_native_svg_1.default, { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: (0, jsx_runtime_1.jsx)(react_native_svg_1.Polygon, { points: "5 3 19 12 5 21 5 3" }) }));
49
+ exports.PlayIcon = PlayIcon;
50
+ const PauseIcon = ({ color = theme_1.defaultTheme.colors.white, size = 24 }) => ((0, jsx_runtime_1.jsxs)(react_native_svg_1.default, { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [(0, jsx_runtime_1.jsx)(react_native_svg_1.Rect, { x: "6", y: "4", width: "4", height: "16" }), (0, jsx_runtime_1.jsx)(react_native_svg_1.Rect, { x: "14", y: "4", width: "4", height: "16" })] }));
51
+ exports.PauseIcon = PauseIcon;
52
+ const FileIcon = ({ color = theme_1.defaultTheme.colors.gray, size = 24 }) => ((0, jsx_runtime_1.jsxs)(react_native_svg_1.default, { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [(0, jsx_runtime_1.jsx)(react_native_svg_1.Path, { d: "M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z" }), (0, jsx_runtime_1.jsx)(react_native_svg_1.Polyline, { points: "13 2 13 9 20 9" })] }));
53
+ exports.FileIcon = FileIcon;
54
+ const ImageIcon = ({ color = theme_1.defaultTheme.colors.gray, size = 24 }) => ((0, jsx_runtime_1.jsxs)(react_native_svg_1.default, { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [(0, jsx_runtime_1.jsx)(react_native_svg_1.Rect, { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }), (0, jsx_runtime_1.jsx)(react_native_svg_1.Circle, { cx: "8.5", cy: "8.5", r: "1.5" }), (0, jsx_runtime_1.jsx)(react_native_svg_1.Polyline, { points: "21 15 16 10 5 21" })] }));
55
+ exports.ImageIcon = ImageIcon;
56
+ const VideoIcon = ({ color = theme_1.defaultTheme.colors.gray, size = 24 }) => ((0, jsx_runtime_1.jsxs)(react_native_svg_1.default, { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [(0, jsx_runtime_1.jsx)(react_native_svg_1.Polygon, { points: "23 7 16 12 23 17 23 7" }), (0, jsx_runtime_1.jsx)(react_native_svg_1.Rect, { x: "1", y: "5", width: "15", height: "14", rx: "2", ry: "2" })] }));
57
+ exports.VideoIcon = VideoIcon;
58
+ const AudioIcon = ({ color = theme_1.defaultTheme.colors.gray, size = 24 }) => ((0, jsx_runtime_1.jsxs)(react_native_svg_1.default, { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [(0, jsx_runtime_1.jsx)(react_native_svg_1.Path, { d: "M9 18V5l12-2v13" }), (0, jsx_runtime_1.jsx)(react_native_svg_1.Circle, { cx: "6", cy: "18", r: "3" }), (0, jsx_runtime_1.jsx)(react_native_svg_1.Circle, { cx: "18", cy: "16", r: "3" })] }));
59
+ exports.AudioIcon = AudioIcon;
60
+ const ChevronRightIcon = ({ color = theme_1.defaultTheme.colors.gray, size = 24 }) => ((0, jsx_runtime_1.jsx)(react_native_svg_1.default, { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: (0, jsx_runtime_1.jsx)(react_native_svg_1.Polyline, { points: "9 18 15 12 9 6" }) }));
61
+ exports.ChevronRightIcon = ChevronRightIcon;
62
+ const LockIcon = ({ color = theme_1.defaultTheme.colors.white, size = 24 }) => ((0, jsx_runtime_1.jsxs)(react_native_svg_1.default, { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [(0, jsx_runtime_1.jsx)(react_native_svg_1.Rect, { x: "3", y: "11", width: "18", height: "11", rx: "2", ry: "2" }), (0, jsx_runtime_1.jsx)(react_native_svg_1.Path, { d: "M7 11V7a5 5 0 0 1 10 0v4" })] }));
63
+ exports.LockIcon = LockIcon;
64
+ const XCircleIcon = ({ color = theme_1.defaultTheme.colors.darkRed, size = 24 }) => ((0, jsx_runtime_1.jsxs)(react_native_svg_1.default, { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [(0, jsx_runtime_1.jsx)(react_native_svg_1.Circle, { cx: "12", cy: "12", r: "10" }), (0, jsx_runtime_1.jsx)(react_native_svg_1.Line, { x1: "15", y1: "9", x2: "9", y2: "15" }), (0, jsx_runtime_1.jsx)(react_native_svg_1.Line, { x1: "9", y1: "9", x2: "15", y2: "15" })] }));
65
+ exports.XCircleIcon = XCircleIcon;
66
+ const ReplyIcon = ({ color = theme_1.defaultTheme.colors.white, size = 24 }) => ((0, jsx_runtime_1.jsxs)(react_native_svg_1.default, { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [(0, jsx_runtime_1.jsx)(react_native_svg_1.Polyline, { points: "9 10 4 15 9 20" }), (0, jsx_runtime_1.jsx)(react_native_svg_1.Path, { d: "M20 4v7a4 4 0 0 1-4 4H4" })] }));
67
+ exports.ReplyIcon = ReplyIcon;
68
+ const TrashIcon = ({ color = theme_1.defaultTheme.colors.darkRed, size = 24 }) => ((0, jsx_runtime_1.jsxs)(react_native_svg_1.default, { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [(0, jsx_runtime_1.jsx)(react_native_svg_1.Polyline, { points: "3 6 5 6 21 6" }), (0, jsx_runtime_1.jsx)(react_native_svg_1.Path, { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" })] }));
69
+ exports.TrashIcon = TrashIcon;
70
+ const DownloadIcon = ({ color = theme_1.defaultTheme.colors.white, size = 24 }) => ((0, jsx_runtime_1.jsxs)(react_native_svg_1.default, { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [(0, jsx_runtime_1.jsx)(react_native_svg_1.Path, { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }), (0, jsx_runtime_1.jsx)(react_native_svg_1.Polyline, { points: "7 10 12 15 17 10" }), (0, jsx_runtime_1.jsx)(react_native_svg_1.Line, { x1: "12", y1: "15", x2: "12", y2: "3" })] }));
71
+ exports.DownloadIcon = DownloadIcon;