react-native-srschat 0.1.7 → 0.1.9

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 (62) hide show
  1. package/README.md +70 -0
  2. package/lib/commonjs/assets/heritage.png +0 -0
  3. package/lib/commonjs/components/header.js +105 -0
  4. package/lib/commonjs/components/header.js.map +1 -0
  5. package/lib/commonjs/components/testing.js +61 -0
  6. package/lib/commonjs/components/testing.js.map +1 -0
  7. package/lib/commonjs/contexts/AppContext.js +99 -0
  8. package/lib/commonjs/contexts/AppContext.js.map +1 -0
  9. package/lib/commonjs/hooks/Stream.js +202 -0
  10. package/lib/commonjs/hooks/Stream.js.map +1 -0
  11. package/lib/commonjs/index.js +13 -262
  12. package/lib/commonjs/index.js.map +1 -1
  13. package/lib/commonjs/layout/chatIcon.js +53 -0
  14. package/lib/commonjs/layout/chatIcon.js.map +1 -0
  15. package/lib/commonjs/layout/chatWindow.js +208 -0
  16. package/lib/commonjs/layout/chatWindow.js.map +1 -0
  17. package/lib/commonjs/layout/layout.js +35 -0
  18. package/lib/commonjs/layout/layout.js.map +1 -0
  19. package/lib/module/assets/heritage.png +0 -0
  20. package/lib/module/components/header.js +96 -0
  21. package/lib/module/components/header.js.map +1 -0
  22. package/lib/module/components/testing.js +53 -0
  23. package/lib/module/components/testing.js.map +1 -0
  24. package/lib/module/contexts/AppContext.js +90 -0
  25. package/lib/module/contexts/AppContext.js.map +1 -0
  26. package/lib/module/hooks/Stream.js +194 -0
  27. package/lib/module/hooks/Stream.js.map +1 -0
  28. package/lib/module/index.js +11 -261
  29. package/lib/module/index.js.map +1 -1
  30. package/lib/module/layout/chatIcon.js +44 -0
  31. package/lib/module/layout/chatIcon.js.map +1 -0
  32. package/lib/module/layout/chatWindow.js +199 -0
  33. package/lib/module/layout/chatWindow.js.map +1 -0
  34. package/lib/module/layout/layout.js +26 -0
  35. package/lib/module/layout/layout.js.map +1 -0
  36. package/lib/typescript/components/header.d.ts +3 -0
  37. package/lib/typescript/components/header.d.ts.map +1 -0
  38. package/lib/typescript/components/testing.d.ts +6 -0
  39. package/lib/typescript/components/testing.d.ts.map +1 -0
  40. package/lib/typescript/contexts/AppContext.d.ts +6 -0
  41. package/lib/typescript/contexts/AppContext.d.ts.map +1 -0
  42. package/lib/typescript/hooks/Stream.d.ts +2 -0
  43. package/lib/typescript/hooks/Stream.d.ts.map +1 -0
  44. package/lib/typescript/index.d.ts +5 -12
  45. package/lib/typescript/index.d.ts.map +1 -1
  46. package/lib/typescript/layout/chatIcon.d.ts +3 -0
  47. package/lib/typescript/layout/chatIcon.d.ts.map +1 -0
  48. package/lib/typescript/layout/chatWindow.d.ts +6 -0
  49. package/lib/typescript/layout/chatWindow.d.ts.map +1 -0
  50. package/lib/typescript/layout/layout.d.ts +6 -0
  51. package/lib/typescript/layout/layout.d.ts.map +1 -0
  52. package/package.json +1 -1
  53. package/src/assets/heritage.png +0 -0
  54. package/src/components/header.js +89 -0
  55. package/src/components/testing.js +47 -0
  56. package/src/contexts/AppContext.js +83 -0
  57. package/src/hooks/Stream.js +198 -0
  58. package/src/index.js +18 -0
  59. package/src/layout/chatIcon.js +38 -0
  60. package/src/layout/chatWindow.js +200 -0
  61. package/src/layout/layout.js +23 -0
  62. package/src/index.tsx +0 -323
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAe1B,UAAU,SAAS;IACjB,aAAa,EAAE,CACb,OAAO,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,KAC/B,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE;QACN,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,oBAAoB,CAAC,EAAE,MAAM,CAAC;KAC/B,CAAC;CACH;AASD,eAAO,MAAM,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,SAAS,CA+KpC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.js"],"names":[],"mappings":"AAOO;;;;sBAON;kBAd4C,OAAO"}
@@ -0,0 +1,3 @@
1
+ export function ChatIcon(): React.JSX.Element;
2
+ import React from 'react';
3
+ //# sourceMappingURL=chatIcon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chatIcon.d.ts","sourceRoot":"","sources":["../../../src/layout/chatIcon.js"],"names":[],"mappings":"AAKO,8CAUN;kBAfiC,OAAO"}
@@ -0,0 +1,6 @@
1
+ export function ChatWindow({ onProductCardClick, onAddToCartClick }: {
2
+ onProductCardClick: any;
3
+ onAddToCartClick: any;
4
+ }): React.JSX.Element;
5
+ import React from 'react';
6
+ //# sourceMappingURL=chatWindow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chatWindow.d.ts","sourceRoot":"","sources":["../../../src/layout/chatWindow.js"],"names":[],"mappings":"AAeO;;;sBA0FN;kBAzGwD,OAAO"}
@@ -0,0 +1,6 @@
1
+ export function Layout({ onProductCardClick, onAddToCartClick }: {
2
+ onProductCardClick: any;
3
+ onAddToCartClick: any;
4
+ }): React.JSX.Element;
5
+ import React from 'react';
6
+ //# sourceMappingURL=layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../../src/layout/layout.js"],"names":[],"mappings":"AAQO;;;sBAQN;kBAhBwD,OAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-srschat",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "private": false,
5
5
  "description": "A modern, sophisticated chat interface for React Native",
6
6
  "main": "lib/commonjs/index",
Binary file
@@ -0,0 +1,89 @@
1
+ import React, { useContext } from 'react';
2
+ import { View, Text, TouchableOpacity, StyleSheet, Image } from 'react-native';
3
+ import { Ionicons as Icon } from '@expo/vector-icons';
4
+ import { AppContext } from '../contexts/AppContext';
5
+
6
+ export const Header = () => {
7
+ const { setShowModal } = useContext(AppContext);
8
+
9
+ return (
10
+ <View style={styles.header}>
11
+ {/* Logo on the left */}
12
+ <View style={styles.section}>
13
+ <Image source={require('../assets/heritage.png')} style={styles.logo} />
14
+ </View>
15
+
16
+ {/* Title in the center */}
17
+ <View style={[styles.section, styles.titleContainer]}>
18
+ <View style={styles.titleWrapper}>
19
+ <Text style={styles.title}>
20
+ Poseidon
21
+ </Text>
22
+ <Text style={styles.betaText}><Icon style={styles.icon} name="sparkles-outline" size={10} color="#007AFF" />beta</Text>
23
+ </View>
24
+ </View>
25
+
26
+ {/* Close button on the right */}
27
+ <View style={styles.iconsSection}>
28
+ <TouchableOpacity onPress={() => setShowModal("Icon")}>
29
+ <Icon name="trash" size={24} color="gray" />
30
+ </TouchableOpacity>
31
+ <TouchableOpacity onPress={() => setShowModal("Icon")}>
32
+ <Icon name="chevron-down" size={24} color="gray" />
33
+ </TouchableOpacity>
34
+ </View>
35
+ </View>
36
+ );
37
+ };
38
+
39
+ const styles = StyleSheet.create({
40
+ header: {
41
+ flexDirection: 'row',
42
+ alignItems: 'center',
43
+ backgroundColor: '#f6f6f6',
44
+ padding: 16,
45
+ borderBottomWidth: 1,
46
+ borderBottomColor: '#ddd',
47
+ },
48
+ section: {
49
+ flex: 1,
50
+ alignItems: 'center',
51
+ },
52
+ iconsSection: {
53
+ flex: 1,
54
+ display: 'flex',
55
+ flexDirection: 'row',
56
+ alignItems: 'center',
57
+ justifyContent: 'flex-end'
58
+ },
59
+ logo: {
60
+ width: 100,
61
+ height: 40,
62
+ resizeMode: 'contain',
63
+ },
64
+ icon: {
65
+ paddingLeft: 2,
66
+ paddingRight: 2
67
+ },
68
+ titleContainer: {
69
+ flex: 2, // Slightly larger space for the title
70
+ },
71
+ titleWrapper: {
72
+ alignItems: 'center',
73
+ justifyContent: 'center',
74
+ },
75
+ title: {
76
+ fontSize: 20,
77
+ fontWeight: 'medium',
78
+ color: '#000',
79
+ textAlign: 'center',
80
+ flexDirection: 'row',
81
+ },
82
+ betaText: {
83
+ fontSize: 12,
84
+ color: 'gray',
85
+ position: 'absolute',
86
+ top: -10,
87
+ right: -35,
88
+ },
89
+ });
@@ -0,0 +1,47 @@
1
+ import React from 'react';
2
+ import { View, Button, StyleSheet } from 'react-native';
3
+
4
+ export const Testing = ({ onProductCardClick, onAddToCartClick }) => {
5
+
6
+ const product = {
7
+ "part_number": "AEQO177",
8
+ "inventory_info": {
9
+ "default_uom": "EA",
10
+ "is_valid": true,
11
+ "info_by_uom": {
12
+ "EA": {
13
+ "gross_price": 7.83,
14
+ "net_price": 7.83,
15
+ "is_on_sale": false,
16
+ "quantity_available": 0,
17
+ "discounts": null
18
+ }
19
+ }
20
+ },
21
+ "product_details": {
22
+ "product_name": "Aladdin Hayward SPX1600S SuperPump Lid Gasket | O-177-2",
23
+ "part_number": "AEQO177",
24
+ "manufacturer_id": "O-177-2",
25
+ "heritage_link": "https://www.heritagepoolplus.com/aeqo177-aladdin-o-ring-o-177-o-177-2-o-177-2",
26
+ "image_url": "https://media.heritageplus.com/image/upload/v1668157956/image/AEQO177_0.jpg"
27
+ }
28
+ }
29
+
30
+ return (
31
+ <View style={styles.container}>
32
+ <Button title="Product Card Click" onPress={() => onProductCardClick(product)} />
33
+ <Button title="Add to Cart" onPress={() => onAddToCartClick({"quantity":1,"product":product})} />
34
+ </View>
35
+ );
36
+ };
37
+
38
+ const styles = StyleSheet.create({
39
+ container: {
40
+ flexDirection: 'row',
41
+ justifyContent: 'space-evenly',
42
+ marginVertical: 10,
43
+ paddingHorizontal: 16,
44
+ borderTopWidth: 1,
45
+ borderTopColor: '#DDD',
46
+ },
47
+ });
@@ -0,0 +1,83 @@
1
+ import React, {createContext, useContext, useState, useEffect } from "react";
2
+
3
+ export const AppContext = createContext();
4
+
5
+ export const AppProvider = ({ children }) => {
6
+
7
+ const [showModal, setShowModal] = useState("Icon");
8
+
9
+ const [input, setInput] = useState('');
10
+ const [messages, setMessages] = useState([
11
+ {type: "ai", text: "Hi there 👋 I’m Poseidon, your Heritage Pool+ AI Agent. I can help you during your online visit with Product and Account information. How can I help you today?"},
12
+ {type: "user", text: "Do you carry Hayward super pumps?"},
13
+ {type: "ai", text: "Could you provide more details on the specific model or horsepower you are interested in for the Hayward Super Pump? We have various models with different features, such as variable speed settings and different horsepower ratings. Alternatively, I can offer you some recommendations right away to help you decide."},
14
+ ]);
15
+
16
+ const handleSend = async () => {
17
+ if (!input.trim()) return;
18
+
19
+ const userMessage = input;
20
+ setInput('');
21
+
22
+ // Add user message to the list
23
+ setMessages((prev) => [...prev, { type: "user", text: userMessage }]);
24
+
25
+ try {
26
+ // Add placeholder for AI message
27
+ setMessages((prev) => [...prev, { type: "ai", text: '' }]);
28
+
29
+ // Simulate response with streaming
30
+ const response = await onSendMessage(userMessage, (chunk) => {
31
+ setMessages((prev) => {
32
+ const newMessages = [...prev];
33
+ const lastMessage = newMessages[newMessages.length - 1];
34
+ if (lastMessage && lastMessage.type === "ai") {
35
+ lastMessage.text += chunk;
36
+ }
37
+ return newMessages;
38
+ });
39
+ });
40
+
41
+ // Finalize AI response
42
+ setMessages((prev) => {
43
+ const newMessages = [...prev];
44
+ const lastMessage = newMessages[newMessages.length - 1];
45
+ if (lastMessage && lastMessage.type === "ai" && lastMessage.text !== response) {
46
+ lastMessage.text = response;
47
+ }
48
+ return newMessages;
49
+ });
50
+ } catch (error) {
51
+ console.error('Error sending message:', error);
52
+ }
53
+ };
54
+
55
+ const onSendMessage = async (message, onStream) => {
56
+ try {
57
+ if (onStream) {
58
+ const words = `You said: ${message}`.split(' ');
59
+ for (const word of words) {
60
+ await new Promise((resolve) => setTimeout(resolve, 100));
61
+ onStream(word + ' ');
62
+ }
63
+ return `You said: ${message}`;
64
+ }
65
+
66
+ await new Promise((resolve) => setTimeout(resolve, 1000));
67
+ return `You said: ${message}`;
68
+ } catch (error) {
69
+ console.error('Error handling message:', error);
70
+ return 'Error processing message';
71
+ }
72
+ };
73
+
74
+
75
+ return (
76
+ <AppContext.Provider
77
+ value={{ showModal, setShowModal, messages, setMessages, handleSend, onSendMessage, input, setInput
78
+ }}
79
+ >
80
+ {children}
81
+ </AppContext.Provider>
82
+ );
83
+ };
@@ -0,0 +1,198 @@
1
+ import React, { useState, useEffect, useRef, useContext } from 'react';
2
+ import { AppContext } from "../contexts/AppContext";
3
+
4
+ export function useWebSocketMessage() {
5
+ const { setIsComplete, startStreaming, setMessages, messages, setGhostMessage, isMobile,
6
+ setTypingIndicator, setStartStreaming, lastUserMessage, stopActivated, custEmail, custName, data,
7
+ setGhostCard, BASE_URL, setStopActivated, setLastMessageId } = useContext(AppContext);
8
+
9
+ const wsProtocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
10
+ const wsUrl = BASE_URL.replace(/^http(s)?:\/\//, '');
11
+ const ENDPOINT = '/send/event';
12
+
13
+ const payload = {
14
+ ...data,
15
+ user_query: lastUserMessage,
16
+ customer_name: data.customer_name
17
+ };
18
+
19
+ const wsRef = useRef(null);
20
+
21
+ useEffect(() => {
22
+ if (startStreaming) {
23
+ console.log(payload)
24
+ const socketUrl = `${wsProtocol}${wsUrl}${ENDPOINT}`;
25
+ const ws = new WebSocket(socketUrl);
26
+ wsRef.current = ws;
27
+
28
+ ws.onopen = () => {
29
+ console.log("WebSocket connection established.");
30
+ ws.send(JSON.stringify(payload));
31
+ };
32
+
33
+ ws.onmessage = (event) => {
34
+ const response = JSON.parse(event.data);
35
+ console.log(response)
36
+ switch (response.type) {
37
+ case 'middle_message':
38
+ const middleMessage = {
39
+ type: "middle",
40
+ text: response.message,
41
+ products: [],
42
+ product_cards: "False",
43
+ }
44
+ setMessages([...messages, middleMessage])
45
+ break;
46
+ case 'message':
47
+ if (response.product_cards == "False" || response.product_cards == false ) {
48
+ setGhostMessage(false);
49
+ setTypingIndicator(false)
50
+ } else {
51
+ setGhostMessage(false)
52
+ setGhostCard(true)
53
+ }
54
+ const newMessage = {
55
+ type: "ai",
56
+ message_id: response.message_id || '',
57
+ text: [response.message],
58
+ feedback: "True",
59
+ products: [],
60
+ product_cards: response.product_cards || "False",
61
+ resource: response?.resource_details?.[0]?.link ?? "",
62
+ resource_type: response?.resource_details?.[0]?.type ?? ""
63
+ };
64
+ setMessages((prevMessages) => [...prevMessages, newMessage]);
65
+ setLastMessageId(response.message_id)
66
+ break;
67
+ case 'chunk':
68
+ const newContent = response.chunk;
69
+ const newMessageId = response.message_id;
70
+ setMessages(prevMessages => {
71
+ const lastMessageIndex = prevMessages.length - 1;
72
+ if (prevMessages[lastMessageIndex] && prevMessages[lastMessageIndex].type === "ai") {
73
+ const updatedLastMessage = {
74
+ ...prevMessages[lastMessageIndex],
75
+ text: [prevMessages[lastMessageIndex].text[0] + newContent],
76
+ message_id: newMessageId || prevMessages[lastMessageIndex].message_id,
77
+ feedback: "True"
78
+ };
79
+ return [
80
+ ...prevMessages.slice(0, lastMessageIndex),
81
+ updatedLastMessage
82
+ ];
83
+ } else {
84
+ setGhostMessage(false);
85
+ return [...prevMessages, {
86
+ type: "ai",
87
+ text: [newContent],
88
+ message_id: newMessageId,
89
+ feedback: "True"
90
+ }];
91
+ }
92
+ });
93
+ if (newMessageId) {
94
+ setLastMessageId(newMessageId);
95
+ }
96
+ break;
97
+ case 'product_cards':
98
+ setMessages(prevMessages => {
99
+ const lastMessageIndex = prevMessages.length - 1;
100
+ if (prevMessages[lastMessageIndex] && prevMessages[lastMessageIndex].type === "ai") {
101
+ const updatedLastMessage = {
102
+ ...prevMessages[lastMessageIndex],
103
+ products: response.products || [],
104
+ product_cards: "True"
105
+ };
106
+ return [
107
+ ...prevMessages.slice(0, lastMessageIndex),
108
+ updatedLastMessage
109
+ ];
110
+ }
111
+ });
112
+ setGhostMessage(false);
113
+ setTypingIndicator(false)
114
+ setGhostCard(false)
115
+ break;
116
+ case 'product_document':
117
+ setMessages(prevMessages => {
118
+ const lastMessageIndex = prevMessages.length - 1;
119
+ if (prevMessages[lastMessageIndex] && prevMessages[lastMessageIndex].type === "ai") {
120
+ const updatedLastMessage = {
121
+ ...prevMessages[lastMessageIndex],
122
+ resource: response?.resource_details?.[0]?.link ?? "",
123
+ resource_type: response?.resource_details?.[0]?.type ?? ""
124
+ };
125
+ return [
126
+ ...prevMessages.slice(0, lastMessageIndex),
127
+ updatedLastMessage
128
+ ];
129
+ }
130
+ });
131
+ setGhostMessage(false);
132
+ setTypingIndicator(false)
133
+ setGhostCard(false)
134
+ break;
135
+ case 'suggested_questions':
136
+ /* const questions = {
137
+ type: "questions",
138
+ message_id: response.message_id || '',
139
+ suggested_questions: response.suggested_questions || [],
140
+ }; */
141
+ setMessages(prevMessages => {
142
+ const lastMessageIndex = prevMessages.length - 1;
143
+ if (prevMessages[lastMessageIndex] && prevMessages[lastMessageIndex].type === "ai") {
144
+ const updatedLastMessage = {
145
+ ...prevMessages[lastMessageIndex],
146
+ suggested_questions: true,
147
+ questions: response.suggested_questions
148
+ };
149
+ return [
150
+ ...prevMessages.slice(0, lastMessageIndex),
151
+ updatedLastMessage
152
+ ];
153
+ }
154
+ });
155
+ //setMessages((prevMessages) => [...prevMessages, questions]);
156
+ break;
157
+ case 'setComplete':
158
+ setIsComplete(true);
159
+ setTypingIndicator(false);
160
+ setStartStreaming(false)
161
+ setGhostCard(false)
162
+ break;
163
+ }
164
+ };
165
+
166
+ ws.onerror = (error) => {
167
+ setStartStreaming(false)
168
+ setGhostMessage(false);
169
+ setTypingIndicator(false)
170
+ setGhostCard(false)
171
+ };
172
+
173
+ ws.onclose = (event) => {
174
+ console.log("WebSocket connection closed:", event.reason);
175
+ setStartStreaming(false)
176
+ setGhostMessage(false);
177
+ setTypingIndicator(false)
178
+ setGhostCard(false)
179
+ };
180
+
181
+ return () => {
182
+ ws.close();
183
+ };
184
+ };
185
+ }, [startStreaming]);
186
+
187
+ useEffect(() => {
188
+ if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
189
+ wsRef.current.close();
190
+ setStartStreaming(false);
191
+ setGhostMessage(false)
192
+ setGhostCard(false)
193
+ setTypingIndicator(false)
194
+ setStopActivated(false)
195
+ }
196
+ }, [stopActivated, setStartStreaming]);
197
+
198
+ }
package/src/index.js ADDED
@@ -0,0 +1,18 @@
1
+ import React, { useState, useCallback } from 'react';
2
+ import { SafeAreaView, Text, StyleSheet, View, TextInput, ScrollView, KeyboardAvoidingView,
3
+ Platform, TouchableOpacity, RefreshControl, } from 'react-native';
4
+ import { Ionicons as Icon } from '@expo/vector-icons';
5
+ import { AppProvider } from './contexts/AppContext';
6
+ import { Layout } from './layout/layout'
7
+
8
+ export const Chat = ({data, onProductCardClick, onAddToCartClick }) => {
9
+
10
+ return (
11
+ <AppProvider>
12
+ <Layout onProductCardClick={onProductCardClick} onAddToCartClick={onAddToCartClick}/>
13
+ </AppProvider>
14
+ );
15
+ };
16
+
17
+ const styles = StyleSheet.create({
18
+ });
@@ -0,0 +1,38 @@
1
+ import React, { useContext } from 'react';
2
+ import { TouchableOpacity, View, StyleSheet } from 'react-native';
3
+ import { Ionicons as Icon } from '@expo/vector-icons';
4
+ import { AppContext } from '../contexts/AppContext';
5
+
6
+ export const ChatIcon = () => {
7
+ const { setShowModal } = useContext(AppContext);
8
+
9
+ return (
10
+ <TouchableOpacity style={styles.iconContainer} onPress={() => setShowModal("ChatWindow")}>
11
+ <View style={styles.icon}>
12
+ <Icon name="chatbubble-ellipses" size={28} color="white" />
13
+ </View>
14
+ </TouchableOpacity>
15
+ );
16
+ };
17
+
18
+ const styles = StyleSheet.create({
19
+ iconContainer: {
20
+ position: 'absolute',
21
+ bottom: 80,
22
+ right: 20,
23
+ zIndex: 10,
24
+ },
25
+ icon: {
26
+ backgroundColor: '#FFA500',
27
+ width: 60,
28
+ height: 60,
29
+ borderRadius: 30,
30
+ alignItems: 'center',
31
+ justifyContent: 'center',
32
+ shadowColor: '#000',
33
+ shadowOffset: { width: 0, height: 2 },
34
+ shadowOpacity: 0.2,
35
+ shadowRadius: 3,
36
+ elevation: 5,
37
+ },
38
+ });